summaryrefslogtreecommitdiffstats
path: root/lib/dns
diff options
context:
space:
mode:
Diffstat (limited to 'lib/dns')
-rw-r--r--lib/dns/Makefile.am325
-rw-r--r--lib/dns/Makefile.in2238
-rw-r--r--lib/dns/acl.c863
-rw-r--r--lib/dns/adb.c4749
-rw-r--r--lib/dns/badcache.c522
-rw-r--r--lib/dns/byaddr.c282
-rw-r--r--lib/dns/cache.c1444
-rw-r--r--lib/dns/callbacks.c107
-rw-r--r--lib/dns/catz.c2698
-rw-r--r--lib/dns/client.c1326
-rw-r--r--lib/dns/clientinfo.c42
-rw-r--r--lib/dns/compress.c580
-rw-r--r--lib/dns/db.c1121
-rw-r--r--lib/dns/dbiterator.c135
-rw-r--r--lib/dns/diff.c686
-rw-r--r--lib/dns/dispatch.c2296
-rw-r--r--lib/dns/dlz.c537
-rw-r--r--lib/dns/dns64.c484
-rw-r--r--lib/dns/dnsrps.c1004
-rw-r--r--lib/dns/dnssec.c2533
-rw-r--r--lib/dns/dnstap.c1386
-rw-r--r--lib/dns/dnstap.proto289
-rw-r--r--lib/dns/ds.c135
-rw-r--r--lib/dns/dst_api.c2831
-rw-r--r--lib/dns/dst_internal.h254
-rw-r--r--lib/dns/dst_openssl.h67
-rw-r--r--lib/dns/dst_parse.c798
-rw-r--r--lib/dns/dst_parse.h131
-rw-r--r--lib/dns/dyndb.c325
-rw-r--r--lib/dns/ecs.c113
-rw-r--r--lib/dns/fixedname.c39
-rw-r--r--lib/dns/forward.c217
-rw-r--r--lib/dns/gen.c1062
-rw-r--r--lib/dns/geoip2.c382
-rw-r--r--lib/dns/gssapi_link.c364
-rw-r--r--lib/dns/gssapictx.c962
-rw-r--r--lib/dns/hmac_link.c527
-rw-r--r--lib/dns/include/dns/acl.h337
-rw-r--r--lib/dns/include/dns/adb.h805
-rw-r--r--lib/dns/include/dns/badcache.h149
-rw-r--r--lib/dns/include/dns/bit.h25
-rw-r--r--lib/dns/include/dns/byaddr.h149
-rw-r--r--lib/dns/include/dns/cache.h302
-rw-r--r--lib/dns/include/dns/callbacks.h93
-rw-r--r--lib/dns/include/dns/catz.h461
-rw-r--r--lib/dns/include/dns/cert.h60
-rw-r--r--lib/dns/include/dns/client.h293
-rw-r--r--lib/dns/include/dns/clientinfo.h102
-rw-r--r--lib/dns/include/dns/compress.h299
-rw-r--r--lib/dns/include/dns/db.h1759
-rw-r--r--lib/dns/include/dns/dbiterator.h290
-rw-r--r--lib/dns/include/dns/diff.h279
-rw-r--r--lib/dns/include/dns/dispatch.h390
-rw-r--r--lib/dns/include/dns/dlz.h333
-rw-r--r--lib/dns/include/dns/dlz_dlopen.h156
-rw-r--r--lib/dns/include/dns/dns64.h192
-rw-r--r--lib/dns/include/dns/dnsrps.h112
-rw-r--r--lib/dns/include/dns/dnssec.h399
-rw-r--r--lib/dns/include/dns/dnstap.h393
-rw-r--r--lib/dns/include/dns/ds.h65
-rw-r--r--lib/dns/include/dns/dsdigest.h69
-rw-r--r--lib/dns/include/dns/dyndb.h159
-rw-r--r--lib/dns/include/dns/ecs.h81
-rw-r--r--lib/dns/include/dns/edns.h26
-rw-r--r--lib/dns/include/dns/events.h87
-rw-r--r--lib/dns/include/dns/fixedname.h82
-rw-r--r--lib/dns/include/dns/forward.h123
-rw-r--r--lib/dns/include/dns/geoip.h111
-rw-r--r--lib/dns/include/dns/ipkeylist.h87
-rw-r--r--lib/dns/include/dns/iptable.h67
-rw-r--r--lib/dns/include/dns/journal.h338
-rw-r--r--lib/dns/include/dns/kasp.h716
-rw-r--r--lib/dns/include/dns/keydata.h48
-rw-r--r--lib/dns/include/dns/keyflags.h45
-rw-r--r--lib/dns/include/dns/keymgr.h131
-rw-r--r--lib/dns/include/dns/keytable.h351
-rw-r--r--lib/dns/include/dns/keyvalues.h104
-rw-r--r--lib/dns/include/dns/librpz.h945
-rw-r--r--lib/dns/include/dns/log.h112
-rw-r--r--lib/dns/include/dns/lookup.h128
-rw-r--r--lib/dns/include/dns/master.h259
-rw-r--r--lib/dns/include/dns/masterdump.h359
-rw-r--r--lib/dns/include/dns/message.h1567
-rw-r--r--lib/dns/include/dns/name.h1381
-rw-r--r--lib/dns/include/dns/ncache.h184
-rw-r--r--lib/dns/include/dns/nsec.h126
-rw-r--r--lib/dns/include/dns/nsec3.h269
-rw-r--r--lib/dns/include/dns/nta.h209
-rw-r--r--lib/dns/include/dns/opcode.h43
-rw-r--r--lib/dns/include/dns/order.h90
-rw-r--r--lib/dns/include/dns/peer.h208
-rw-r--r--lib/dns/include/dns/private.h66
-rw-r--r--lib/dns/include/dns/rbt.h995
-rw-r--r--lib/dns/include/dns/rcode.h107
-rw-r--r--lib/dns/include/dns/rdata.h809
-rw-r--r--lib/dns/include/dns/rdataclass.h91
-rw-r--r--lib/dns/include/dns/rdatalist.h120
-rw-r--r--lib/dns/include/dns/rdataset.h621
-rw-r--r--lib/dns/include/dns/rdatasetiter.h161
-rw-r--r--lib/dns/include/dns/rdataslab.h170
-rw-r--r--lib/dns/include/dns/rdatatype.h94
-rw-r--r--lib/dns/include/dns/request.h321
-rw-r--r--lib/dns/include/dns/resolver.h709
-rw-r--r--lib/dns/include/dns/result.h31
-rw-r--r--lib/dns/include/dns/rootns.h36
-rw-r--r--lib/dns/include/dns/rpz.h441
-rw-r--r--lib/dns/include/dns/rriterator.h179
-rw-r--r--lib/dns/include/dns/rrl.h269
-rw-r--r--lib/dns/include/dns/sdb.h211
-rw-r--r--lib/dns/include/dns/sdlz.h356
-rw-r--r--lib/dns/include/dns/secalg.h69
-rw-r--r--lib/dns/include/dns/secproto.h62
-rw-r--r--lib/dns/include/dns/soa.h93
-rw-r--r--lib/dns/include/dns/ssu.h261
-rw-r--r--lib/dns/include/dns/stats.h825
-rw-r--r--lib/dns/include/dns/time.h70
-rw-r--r--lib/dns/include/dns/tkey.h243
-rw-r--r--lib/dns/include/dns/transport.h168
-rw-r--r--lib/dns/include/dns/tsec.h129
-rw-r--r--lib/dns/include/dns/tsig.h295
-rw-r--r--lib/dns/include/dns/ttl.h77
-rw-r--r--lib/dns/include/dns/types.h430
-rw-r--r--lib/dns/include/dns/update.h72
-rw-r--r--lib/dns/include/dns/validator.h240
-rw-r--r--lib/dns/include/dns/view.h1416
-rw-r--r--lib/dns/include/dns/xfrin.h96
-rw-r--r--lib/dns/include/dns/zone.h2657
-rw-r--r--lib/dns/include/dns/zonekey.h35
-rw-r--r--lib/dns/include/dns/zoneverify.h50
-rw-r--r--lib/dns/include/dns/zt.h220
-rw-r--r--lib/dns/include/dst/dst.h1245
-rw-r--r--lib/dns/include/dst/gssapi.h192
-rw-r--r--lib/dns/ipkeylist.c226
-rw-r--r--lib/dns/iptable.c174
-rw-r--r--lib/dns/journal.c2856
-rw-r--r--lib/dns/kasp.c515
-rw-r--r--lib/dns/key.c192
-rw-r--r--lib/dns/keydata.c76
-rw-r--r--lib/dns/keymgr.c2647
-rw-r--r--lib/dns/keytable.c956
-rw-r--r--lib/dns/log.c71
-rw-r--r--lib/dns/lookup.c445
-rw-r--r--lib/dns/master.c3200
-rw-r--r--lib/dns/masterdump.c2094
-rw-r--r--lib/dns/message.c4849
-rw-r--r--lib/dns/name.c2652
-rw-r--r--lib/dns/ncache.c777
-rw-r--r--lib/dns/nsec.c533
-rw-r--r--lib/dns/nsec3.c2195
-rw-r--r--lib/dns/nta.c703
-rw-r--r--lib/dns/openssl_link.c223
-rw-r--r--lib/dns/openssl_shim.c252
-rw-r--r--lib/dns/openssl_shim.h87
-rw-r--r--lib/dns/openssldh_link.c1336
-rw-r--r--lib/dns/opensslecdsa_link.c1453
-rw-r--r--lib/dns/openssleddsa_link.c702
-rw-r--r--lib/dns/opensslrsa_link.c1816
-rw-r--r--lib/dns/order.c150
-rw-r--r--lib/dns/peer.c898
-rw-r--r--lib/dns/private.c417
-rw-r--r--lib/dns/rbt.c3124
-rw-r--r--lib/dns/rbtdb.c10241
-rw-r--r--lib/dns/rbtdb.h49
-rw-r--r--lib/dns/rcode.c585
-rw-r--r--lib/dns/rdata.c2367
-rw-r--r--lib/dns/rdata/any_255/tsig_250.c622
-rw-r--r--lib/dns/rdata/any_255/tsig_250.h29
-rw-r--r--lib/dns/rdata/ch_3/a_1.c326
-rw-r--r--lib/dns/rdata/ch_3/a_1.h28
-rw-r--r--lib/dns/rdata/generic/afsdb_18.c318
-rw-r--r--lib/dns/rdata/generic/afsdb_18.h24
-rw-r--r--lib/dns/rdata/generic/amtrelay_260.c472
-rw-r--r--lib/dns/rdata/generic/amtrelay_260.h27
-rw-r--r--lib/dns/rdata/generic/avc_258.c145
-rw-r--r--lib/dns/rdata/generic/avc_258.h30
-rw-r--r--lib/dns/rdata/generic/caa_257.c628
-rw-r--r--lib/dns/rdata/generic/caa_257.h24
-rw-r--r--lib/dns/rdata/generic/cdnskey_60.c164
-rw-r--r--lib/dns/rdata/generic/cdnskey_60.h17
-rw-r--r--lib/dns/rdata/generic/cds_59.c167
-rw-r--r--lib/dns/rdata/generic/cds_59.h17
-rw-r--r--lib/dns/rdata/generic/cert_37.c285
-rw-r--r--lib/dns/rdata/generic/cert_37.h25
-rw-r--r--lib/dns/rdata/generic/cname_5.c231
-rw-r--r--lib/dns/rdata/generic/cname_5.h20
-rw-r--r--lib/dns/rdata/generic/csync_62.c274
-rw-r--r--lib/dns/rdata/generic/csync_62.h27
-rw-r--r--lib/dns/rdata/generic/dlv_32769.c163
-rw-r--r--lib/dns/rdata/generic/dlv_32769.h17
-rw-r--r--lib/dns/rdata/generic/dname_39.c231
-rw-r--r--lib/dns/rdata/generic/dname_39.h23
-rw-r--r--lib/dns/rdata/generic/dnskey_48.c165
-rw-r--r--lib/dns/rdata/generic/dnskey_48.h20
-rw-r--r--lib/dns/rdata/generic/doa_259.c362
-rw-r--r--lib/dns/rdata/generic/doa_259.h26
-rw-r--r--lib/dns/rdata/generic/ds_43.c386
-rw-r--r--lib/dns/rdata/generic/ds_43.h26
-rw-r--r--lib/dns/rdata/generic/eui48_108.c212
-rw-r--r--lib/dns/rdata/generic/eui48_108.h20
-rw-r--r--lib/dns/rdata/generic/eui64_109.c215
-rw-r--r--lib/dns/rdata/generic/eui64_109.h20
-rw-r--r--lib/dns/rdata/generic/gpos_27.c257
-rw-r--r--lib/dns/rdata/generic/gpos_27.h28
-rw-r--r--lib/dns/rdata/generic/hinfo_13.c218
-rw-r--r--lib/dns/rdata/generic/hinfo_13.h23
-rw-r--r--lib/dns/rdata/generic/hip_55.c523
-rw-r--r--lib/dns/rdata/generic/hip_55.h39
-rw-r--r--lib/dns/rdata/generic/ipseckey_45.c527
-rw-r--r--lib/dns/rdata/generic/ipseckey_45.h27
-rw-r--r--lib/dns/rdata/generic/isdn_20.c248
-rw-r--r--lib/dns/rdata/generic/isdn_20.h26
-rw-r--r--lib/dns/rdata/generic/key_25.c469
-rw-r--r--lib/dns/rdata/generic/key_25.h27
-rw-r--r--lib/dns/rdata/generic/keydata_65533.c463
-rw-r--r--lib/dns/rdata/generic/keydata_65533.h27
-rw-r--r--lib/dns/rdata/generic/l32_105.c231
-rw-r--r--lib/dns/rdata/generic/l32_105.h21
-rw-r--r--lib/dns/rdata/generic/l64_106.c225
-rw-r--r--lib/dns/rdata/generic/l64_106.h21
-rw-r--r--lib/dns/rdata/generic/loc_29.c839
-rw-r--r--lib/dns/rdata/generic/loc_29.h34
-rw-r--r--lib/dns/rdata/generic/lp_107.c278
-rw-r--r--lib/dns/rdata/generic/lp_107.h22
-rw-r--r--lib/dns/rdata/generic/mb_7.c234
-rw-r--r--lib/dns/rdata/generic/mb_7.h21
-rw-r--r--lib/dns/rdata/generic/md_3.c236
-rw-r--r--lib/dns/rdata/generic/md_3.h21
-rw-r--r--lib/dns/rdata/generic/mf_4.c235
-rw-r--r--lib/dns/rdata/generic/mf_4.h21
-rw-r--r--lib/dns/rdata/generic/mg_8.c229
-rw-r--r--lib/dns/rdata/generic/mg_8.h21
-rw-r--r--lib/dns/rdata/generic/minfo_14.c323
-rw-r--r--lib/dns/rdata/generic/minfo_14.h22
-rw-r--r--lib/dns/rdata/generic/mr_9.c230
-rw-r--r--lib/dns/rdata/generic/mr_9.h21
-rw-r--r--lib/dns/rdata/generic/mx_15.c359
-rw-r--r--lib/dns/rdata/generic/mx_15.h22
-rw-r--r--lib/dns/rdata/generic/naptr_35.c738
-rw-r--r--lib/dns/rdata/generic/naptr_35.h31
-rw-r--r--lib/dns/rdata/generic/nid_104.c225
-rw-r--r--lib/dns/rdata/generic/nid_104.h21
-rw-r--r--lib/dns/rdata/generic/ninfo_56.c170
-rw-r--r--lib/dns/rdata/generic/ninfo_56.h33
-rw-r--r--lib/dns/rdata/generic/ns_2.c256
-rw-r--r--lib/dns/rdata/generic/ns_2.h21
-rw-r--r--lib/dns/rdata/generic/nsec3_50.c425
-rw-r--r--lib/dns/rdata/generic/nsec3_50.h109
-rw-r--r--lib/dns/rdata/generic/nsec3param_51.c322
-rw-r--r--lib/dns/rdata/generic/nsec3param_51.h29
-rw-r--r--lib/dns/rdata/generic/nsec_47.c291
-rw-r--r--lib/dns/rdata/generic/nsec_47.h25
-rw-r--r--lib/dns/rdata/generic/null_10.c187
-rw-r--r--lib/dns/rdata/generic/null_10.h22
-rw-r--r--lib/dns/rdata/generic/nxt_30.c351
-rw-r--r--lib/dns/rdata/generic/nxt_30.h25
-rw-r--r--lib/dns/rdata/generic/openpgpkey_61.c250
-rw-r--r--lib/dns/rdata/generic/openpgpkey_61.h21
-rw-r--r--lib/dns/rdata/generic/opt_41.c473
-rw-r--r--lib/dns/rdata/generic/opt_41.h46
-rw-r--r--lib/dns/rdata/generic/proforma.c165
-rw-r--r--lib/dns/rdata/generic/proforma.h22
-rw-r--r--lib/dns/rdata/generic/ptr_12.c279
-rw-r--r--lib/dns/rdata/generic/ptr_12.h21
-rw-r--r--lib/dns/rdata/generic/rkey_57.c161
-rw-r--r--lib/dns/rdata/generic/rkey_57.h16
-rw-r--r--lib/dns/rdata/generic/rp_17.c310
-rw-r--r--lib/dns/rdata/generic/rp_17.h24
-rw-r--r--lib/dns/rdata/generic/rrsig_46.c640
-rw-r--r--lib/dns/rdata/generic/rrsig_46.h31
-rw-r--r--lib/dns/rdata/generic/rt_21.c324
-rw-r--r--lib/dns/rdata/generic/rt_21.h24
-rw-r--r--lib/dns/rdata/generic/sig_24.c591
-rw-r--r--lib/dns/rdata/generic/sig_24.h32
-rw-r--r--lib/dns/rdata/generic/sink_40.c292
-rw-r--r--lib/dns/rdata/generic/sink_40.h24
-rw-r--r--lib/dns/rdata/generic/smimea_53.c153
-rw-r--r--lib/dns/rdata/generic/smimea_53.h16
-rw-r--r--lib/dns/rdata/generic/soa_6.c443
-rw-r--r--lib/dns/rdata/generic/soa_6.h27
-rw-r--r--lib/dns/rdata/generic/spf_99.c146
-rw-r--r--lib/dns/rdata/generic/spf_99.h33
-rw-r--r--lib/dns/rdata/generic/sshfp_44.c297
-rw-r--r--lib/dns/rdata/generic/sshfp_44.h26
-rw-r--r--lib/dns/rdata/generic/ta_32768.c163
-rw-r--r--lib/dns/rdata/generic/ta_32768.h19
-rw-r--r--lib/dns/rdata/generic/talink_58.c258
-rw-r--r--lib/dns/rdata/generic/talink_58.h25
-rw-r--r--lib/dns/rdata/generic/tkey_249.c581
-rw-r--r--lib/dns/rdata/generic/tkey_249.h31
-rw-r--r--lib/dns/rdata/generic/tlsa_52.c340
-rw-r--r--lib/dns/rdata/generic/tlsa_52.h27
-rw-r--r--lib/dns/rdata/generic/txt_16.c360
-rw-r--r--lib/dns/rdata/generic/txt_16.h43
-rw-r--r--lib/dns/rdata/generic/uri_256.c319
-rw-r--r--lib/dns/rdata/generic/uri_256.h23
-rw-r--r--lib/dns/rdata/generic/x25_19.c233
-rw-r--r--lib/dns/rdata/generic/x25_19.h24
-rw-r--r--lib/dns/rdata/generic/zonemd_63.c351
-rw-r--r--lib/dns/rdata/generic/zonemd_63.h31
-rw-r--r--lib/dns/rdata/hs_4/a_1.c233
-rw-r--r--lib/dns/rdata/hs_4/a_1.h20
-rw-r--r--lib/dns/rdata/in_1/a6_38.c487
-rw-r--r--lib/dns/rdata/in_1/a6_38.h25
-rw-r--r--lib/dns/rdata/in_1/a_1.c279
-rw-r--r--lib/dns/rdata/in_1/a_1.h20
-rw-r--r--lib/dns/rdata/in_1/aaaa_28.c266
-rw-r--r--lib/dns/rdata/in_1/aaaa_28.h22
-rw-r--r--lib/dns/rdata/in_1/apl_42.c483
-rw-r--r--lib/dns/rdata/in_1/apl_42.h50
-rw-r--r--lib/dns/rdata/in_1/atma_34.c318
-rw-r--r--lib/dns/rdata/in_1/atma_34.h25
-rw-r--r--lib/dns/rdata/in_1/dhcid_49.c236
-rw-r--r--lib/dns/rdata/in_1/dhcid_49.h22
-rw-r--r--lib/dns/rdata/in_1/eid_31.c225
-rw-r--r--lib/dns/rdata/in_1/eid_31.h25
-rw-r--r--lib/dns/rdata/in_1/https_65.c183
-rw-r--r--lib/dns/rdata/in_1/https_65.h32
-rw-r--r--lib/dns/rdata/in_1/kx_36.c291
-rw-r--r--lib/dns/rdata/in_1/kx_36.h24
-rw-r--r--lib/dns/rdata/in_1/nimloc_32.c225
-rw-r--r--lib/dns/rdata/in_1/nimloc_32.h25
-rw-r--r--lib/dns/rdata/in_1/nsap-ptr_23.c244
-rw-r--r--lib/dns/rdata/in_1/nsap-ptr_23.h23
-rw-r--r--lib/dns/rdata/in_1/nsap_22.c260
-rw-r--r--lib/dns/rdata/in_1/nsap_22.h24
-rw-r--r--lib/dns/rdata/in_1/px_26.c372
-rw-r--r--lib/dns/rdata/in_1/px_26.h25
-rw-r--r--lib/dns/rdata/in_1/srv_33.c413
-rw-r--r--lib/dns/rdata/in_1/srv_33.h26
-rw-r--r--lib/dns/rdata/in_1/svcb_64.c1338
-rw-r--r--lib/dns/rdata/in_1/svcb_64.h37
-rw-r--r--lib/dns/rdata/in_1/wks_11.c404
-rw-r--r--lib/dns/rdata/in_1/wks_11.h23
-rw-r--r--lib/dns/rdata/rdatastructpre.h35
-rw-r--r--lib/dns/rdata/rdatastructsuf.h14
-rw-r--r--lib/dns/rdatalist.c448
-rw-r--r--lib/dns/rdatalist_p.h62
-rw-r--r--lib/dns/rdataset.c749
-rw-r--r--lib/dns/rdatasetiter.c71
-rw-r--r--lib/dns/rdataslab.c1005
-rw-r--r--lib/dns/request.c1216
-rw-r--r--lib/dns/resolver.c11753
-rw-r--r--lib/dns/result.c121
-rw-r--r--lib/dns/rootns.c566
-rw-r--r--lib/dns/rpz.c2734
-rw-r--r--lib/dns/rriterator.c220
-rw-r--r--lib/dns/rrl.c1367
-rw-r--r--lib/dns/sdb.c1598
-rw-r--r--lib/dns/sdlz.c2086
-rw-r--r--lib/dns/soa.c137
-rw-r--r--lib/dns/ssu.c715
-rw-r--r--lib/dns/ssu_external.c255
-rw-r--r--lib/dns/stats.c653
-rw-r--r--lib/dns/time.c216
-rw-r--r--lib/dns/tkey.c1605
-rw-r--r--lib/dns/transport.c474
-rw-r--r--lib/dns/tsec.c149
-rw-r--r--lib/dns/tsig.c1919
-rw-r--r--lib/dns/tsig_p.h40
-rw-r--r--lib/dns/ttl.c225
-rw-r--r--lib/dns/update.c2279
-rw-r--r--lib/dns/validator.c3394
-rw-r--r--lib/dns/view.c2761
-rw-r--r--lib/dns/xfrin.c2034
-rw-r--r--lib/dns/zone.c23706
-rw-r--r--lib/dns/zone_p.h50
-rw-r--r--lib/dns/zonekey.c54
-rw-r--r--lib/dns/zoneverify.c2037
-rw-r--r--lib/dns/zt.c615
369 files changed, 210813 insertions, 0 deletions
diff --git a/lib/dns/Makefile.am b/lib/dns/Makefile.am
new file mode 100644
index 0000000..ef8e9f2
--- /dev/null
+++ b/lib/dns/Makefile.am
@@ -0,0 +1,325 @@
+include $(top_srcdir)/Makefile.top
+
+lib_LTLIBRARIES = libdns.la
+
+nodist_libdns_ladir = $(includedir)/dns
+nodist_libdns_la_HEADERS = \
+ include/dns/enumclass.h \
+ include/dns/enumtype.h \
+ include/dns/rdatastruct.h
+
+nodist_libdns_la_SOURCES = \
+ $(nodist_libdns_la_HEADERS) \
+ code.h
+
+BUILT_SOURCES = \
+ $(nodist_libdns_la_SOURCES)
+
+CLEANFILES = \
+ $(nodist_libdns_la_SOURCES) \
+ gen$(BUILD_EXEEXT)
+
+gen$(BUILD_EXEEXT): gen.c
+ $(CC_FOR_BUILD) -g -I. $(srcdir)/gen.c -o $@
+
+EXTRA_DIST = \
+ dnstap.proto \
+ gen.c \
+ rdata/*
+
+include/dns/enumtype.h: gen Makefile
+ mkdir -p include/dns
+ $(builddir)/gen -s $(srcdir) -t > $@
+
+include/dns/enumclass.h: gen Makefile
+ mkdir -p include/dns
+ $(builddir)/gen -s $(srcdir) -c > $@
+
+include/dns/rdatastruct.h: gen rdata/rdatastructpre.h rdata/rdatastructsuf.h Makefile
+ mkdir -p include/dns
+ $(builddir)/gen -s $(srcdir) -i \
+ -P $(srcdir)/rdata/rdatastructpre.h \
+ -S $(srcdir)/rdata/rdatastructsuf.h > $@
+
+code.h: gen Makefile
+ $(builddir)/gen -s $(srcdir) > $@
+
+libdns_ladir = $(includedir)/dns
+libdns_la_HEADERS = \
+ include/dns/acl.h \
+ include/dns/adb.h \
+ include/dns/badcache.h \
+ include/dns/bit.h \
+ include/dns/byaddr.h \
+ include/dns/cache.h \
+ include/dns/callbacks.h \
+ include/dns/catz.h \
+ include/dns/cert.h \
+ include/dns/client.h \
+ include/dns/clientinfo.h \
+ include/dns/compress.h \
+ include/dns/db.h \
+ include/dns/dbiterator.h \
+ include/dns/diff.h \
+ include/dns/dispatch.h \
+ include/dns/dlz.h \
+ include/dns/dlz_dlopen.h \
+ include/dns/dns64.h \
+ include/dns/dnsrps.h \
+ include/dns/dnssec.h \
+ include/dns/ds.h \
+ include/dns/dsdigest.h \
+ include/dns/dnstap.h \
+ include/dns/dyndb.h \
+ include/dns/ecs.h \
+ include/dns/edns.h \
+ include/dns/events.h \
+ include/dns/fixedname.h \
+ include/dns/forward.h \
+ include/dns/geoip.h \
+ include/dns/ipkeylist.h \
+ include/dns/iptable.h \
+ include/dns/journal.h \
+ include/dns/kasp.h \
+ include/dns/keydata.h \
+ include/dns/keyflags.h \
+ include/dns/keymgr.h \
+ include/dns/keytable.h \
+ include/dns/keyvalues.h \
+ include/dns/librpz.h \
+ include/dns/lookup.h \
+ include/dns/log.h \
+ include/dns/master.h \
+ include/dns/masterdump.h \
+ include/dns/message.h \
+ include/dns/name.h \
+ include/dns/ncache.h \
+ include/dns/nsec.h \
+ include/dns/nsec3.h \
+ include/dns/nta.h \
+ include/dns/opcode.h \
+ include/dns/order.h \
+ include/dns/peer.h \
+ include/dns/private.h \
+ include/dns/rbt.h \
+ include/dns/rcode.h \
+ include/dns/rdata.h \
+ include/dns/rdataclass.h \
+ include/dns/rdatalist.h \
+ include/dns/rdataset.h \
+ include/dns/rdatasetiter.h \
+ include/dns/rdataslab.h \
+ include/dns/rdatatype.h \
+ include/dns/request.h \
+ include/dns/resolver.h \
+ include/dns/result.h \
+ include/dns/rootns.h \
+ include/dns/rpz.h \
+ include/dns/rriterator.h \
+ include/dns/rrl.h \
+ include/dns/sdb.h \
+ include/dns/sdlz.h \
+ include/dns/secalg.h \
+ include/dns/secproto.h \
+ include/dns/soa.h \
+ include/dns/ssu.h \
+ include/dns/stats.h \
+ include/dns/time.h \
+ include/dns/transport.h \
+ include/dns/tkey.h \
+ include/dns/tsec.h \
+ include/dns/tsig.h \
+ include/dns/ttl.h \
+ include/dns/types.h \
+ include/dns/update.h \
+ include/dns/validator.h \
+ include/dns/view.h \
+ include/dns/xfrin.h \
+ include/dns/zone.h \
+ include/dns/zonekey.h \
+ include/dns/zoneverify.h \
+ include/dns/zt.h
+
+dstdir = $(includedir)/dst
+dst_HEADERS = \
+ include/dst/dst.h \
+ include/dst/gssapi.h
+
+libdns_la_SOURCES = \
+ $(libdns_la_HEADERS) \
+ $(dst_HEADERS) \
+ acl.c \
+ adb.c \
+ badcache.c \
+ byaddr.c \
+ cache.c \
+ callbacks.c \
+ catz.c \
+ clientinfo.c \
+ compress.c \
+ db.c \
+ dbiterator.c \
+ diff.c \
+ dispatch.c \
+ dlz.c \
+ dns64.c \
+ dnsrps.c \
+ dnssec.c \
+ ds.c \
+ dst_api.c \
+ dst_internal.h \
+ dst_openssl.h \
+ dst_parse.c \
+ dst_parse.h \
+ dyndb.c \
+ ecs.c \
+ fixedname.c \
+ forward.c \
+ gssapictx.c \
+ hmac_link.c \
+ ipkeylist.c \
+ iptable.c \
+ journal.c \
+ kasp.c \
+ key.c \
+ keydata.c \
+ keymgr.c \
+ keytable.c \
+ log.c \
+ lookup.c \
+ master.c \
+ masterdump.c \
+ message.c \
+ name.c \
+ ncache.c \
+ nsec.c \
+ nsec3.c \
+ nta.c \
+ openssl_link.c \
+ openssl_shim.c \
+ openssl_shim.h \
+ openssldh_link.c \
+ opensslecdsa_link.c \
+ openssleddsa_link.c \
+ opensslrsa_link.c \
+ order.c \
+ peer.c \
+ private.c \
+ rbt.c \
+ rbtdb.h \
+ rbtdb.c \
+ rcode.c \
+ rdata.c \
+ rdatalist.c \
+ rdataset.c \
+ rdatasetiter.c \
+ rdataslab.c \
+ request.c \
+ resolver.c \
+ result.c \
+ rootns.c \
+ rpz.c \
+ rrl.c \
+ rriterator.c \
+ sdb.c \
+ sdlz.c \
+ soa.c \
+ ssu.c \
+ ssu_external.c \
+ stats.c \
+ time.c \
+ transport.c \
+ tkey.c \
+ tsec.c \
+ tsig.c \
+ ttl.c \
+ update.c \
+ validator.c \
+ view.c \
+ xfrin.c \
+ zone.c \
+ zoneverify.c \
+ zonekey.c \
+ zt.c \
+ client.c \
+ rdatalist_p.h \
+ tsig_p.h \
+ zone_p.h
+
+if HAVE_GSSAPI
+libdns_la_SOURCES += \
+ gssapi_link.c
+endif
+
+if HAVE_GEOIP2
+libdns_la_SOURCES += \
+ geoip2.c
+endif
+
+libdns_la_CPPFLAGS = \
+ $(AM_CPPFLAGS) \
+ $(LIBDNS_CFLAGS) \
+ $(LIBISC_CFLAGS) \
+ $(LIBUV_CFLAGS) \
+ $(OPENSSL_CFLAGS)
+
+libdns_la_LDFLAGS = \
+ $(AM_LDFLAGS) \
+ -release "$(PACKAGE_VERSION)"
+
+libdns_la_LIBADD = \
+ $(LIBISC_LIBS) \
+ $(LIBUV_LIBS) \
+ $(OPENSSL_LIBS)
+
+if HAVE_JSON_C
+libdns_la_CPPFLAGS += \
+ $(JSON_C_CFLAGS)
+
+libdns_la_LIBADD += \
+ $(JSON_C_LIBS)
+endif HAVE_JSON_C
+
+if HAVE_LIBXML2
+libdns_la_CPPFLAGS += \
+ $(LIBXML2_CFLAGS)
+
+libdns_la_LIBADD += \
+ $(LIBXML2_LIBS)
+endif HAVE_LIBXML2
+
+if HAVE_GSSAPI
+libdns_la_CPPFLAGS += \
+ $(GSSAPI_CFLAGS) \
+ $(KRB5_CFLAGS)
+libdns_la_LIBADD += \
+ $(GSSAPI_LIBS) \
+ $(KRB5_LIBS)
+endif
+
+if HAVE_GEOIP2
+libdns_la_CPPFLAGS += \
+ $(MAXMINDDB_CFLAGS)
+libdns_la_LIBADD += \
+ $(MAXMINDDB_LIBS)
+endif
+
+if HAVE_DNSTAP
+nodist_libdns_la_SOURCES += \
+ dnstap.pb-c.h \
+ dnstap.pb-c.c
+
+libdns_la_SOURCES += \
+ dnstap.c
+
+dnstap.pb-c.h dnstap.pb-c.c: dnstap.proto
+ $(PROTOC_C) --proto_path=$(srcdir) --c_out=. dnstap.proto
+
+libdns_la_CPPFLAGS += $(DNSTAP_CFLAGS)
+libdns_la_LIBADD += $(DNSTAP_LIBS)
+endif
+
+if HAVE_LMDB
+libdns_la_CPPFLAGS += $(LMDB_CFLAGS)
+libdns_la_LIBADD += $(LMDB_LIBS)
+endif
diff --git a/lib/dns/Makefile.in b/lib/dns/Makefile.in
new file mode 100644
index 0000000..7d71f13
--- /dev/null
+++ b/lib/dns/Makefile.in
@@ -0,0 +1,2238 @@
+# Makefile.in generated by automake 1.16.5 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994-2021 Free Software Foundation, Inc.
+
+# This Makefile.in is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY, to the extent permitted by law; without
+# even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE.
+
+@SET_MAKE@
+
+# Hey Emacs, this is -*- makefile-automake -*- file!
+# vim: filetype=automake
+
+
+VPATH = @srcdir@
+am__is_gnu_make = { \
+ if test -z '$(MAKELEVEL)'; then \
+ false; \
+ elif test -n '$(MAKE_HOST)'; then \
+ true; \
+ elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \
+ true; \
+ else \
+ false; \
+ fi; \
+}
+am__make_running_with_option = \
+ case $${target_option-} in \
+ ?) ;; \
+ *) echo "am__make_running_with_option: internal error: invalid" \
+ "target option '$${target_option-}' specified" >&2; \
+ exit 1;; \
+ esac; \
+ has_opt=no; \
+ sane_makeflags=$$MAKEFLAGS; \
+ if $(am__is_gnu_make); then \
+ sane_makeflags=$$MFLAGS; \
+ else \
+ case $$MAKEFLAGS in \
+ *\\[\ \ ]*) \
+ bs=\\; \
+ sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \
+ | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \
+ esac; \
+ fi; \
+ skip_next=no; \
+ strip_trailopt () \
+ { \
+ flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \
+ }; \
+ for flg in $$sane_makeflags; do \
+ test $$skip_next = yes && { skip_next=no; continue; }; \
+ case $$flg in \
+ *=*|--*) continue;; \
+ -*I) strip_trailopt 'I'; skip_next=yes;; \
+ -*I?*) strip_trailopt 'I';; \
+ -*O) strip_trailopt 'O'; skip_next=yes;; \
+ -*O?*) strip_trailopt 'O';; \
+ -*l) strip_trailopt 'l'; skip_next=yes;; \
+ -*l?*) strip_trailopt 'l';; \
+ -[dEDm]) skip_next=yes;; \
+ -[JT]) skip_next=yes;; \
+ esac; \
+ case $$flg in \
+ *$$target_option*) has_opt=yes; break;; \
+ esac; \
+ done; \
+ test $$has_opt = yes
+am__make_dryrun = (target_option=n; $(am__make_running_with_option))
+am__make_keepgoing = (target_option=k; $(am__make_running_with_option))
+pkgdatadir = $(datadir)/@PACKAGE@
+pkgincludedir = $(includedir)/@PACKAGE@
+pkglibdir = $(libdir)/@PACKAGE@
+pkglibexecdir = $(libexecdir)/@PACKAGE@
+am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd
+install_sh_DATA = $(install_sh) -c -m 644
+install_sh_PROGRAM = $(install_sh) -c
+install_sh_SCRIPT = $(install_sh) -c
+INSTALL_HEADER = $(INSTALL_DATA)
+transform = $(program_transform_name)
+NORMAL_INSTALL = :
+PRE_INSTALL = :
+POST_INSTALL = :
+NORMAL_UNINSTALL = :
+PRE_UNINSTALL = :
+POST_UNINSTALL = :
+build_triplet = @build@
+host_triplet = @host@
+target_triplet = @target@
+@HOST_MACOS_TRUE@am__append_1 = \
+@HOST_MACOS_TRUE@ -Wl,-flat_namespace
+
+@HAVE_GSSAPI_TRUE@am__append_2 = \
+@HAVE_GSSAPI_TRUE@ gssapi_link.c
+
+@HAVE_GEOIP2_TRUE@am__append_3 = \
+@HAVE_GEOIP2_TRUE@ geoip2.c
+
+@HAVE_JSON_C_TRUE@am__append_4 = \
+@HAVE_JSON_C_TRUE@ $(JSON_C_CFLAGS)
+
+@HAVE_JSON_C_TRUE@am__append_5 = \
+@HAVE_JSON_C_TRUE@ $(JSON_C_LIBS)
+
+@HAVE_LIBXML2_TRUE@am__append_6 = \
+@HAVE_LIBXML2_TRUE@ $(LIBXML2_CFLAGS)
+
+@HAVE_LIBXML2_TRUE@am__append_7 = \
+@HAVE_LIBXML2_TRUE@ $(LIBXML2_LIBS)
+
+@HAVE_GSSAPI_TRUE@am__append_8 = \
+@HAVE_GSSAPI_TRUE@ $(GSSAPI_CFLAGS) \
+@HAVE_GSSAPI_TRUE@ $(KRB5_CFLAGS)
+
+@HAVE_GSSAPI_TRUE@am__append_9 = \
+@HAVE_GSSAPI_TRUE@ $(GSSAPI_LIBS) \
+@HAVE_GSSAPI_TRUE@ $(KRB5_LIBS)
+
+@HAVE_GEOIP2_TRUE@am__append_10 = \
+@HAVE_GEOIP2_TRUE@ $(MAXMINDDB_CFLAGS)
+
+@HAVE_GEOIP2_TRUE@am__append_11 = \
+@HAVE_GEOIP2_TRUE@ $(MAXMINDDB_LIBS)
+
+@HAVE_DNSTAP_TRUE@am__append_12 = \
+@HAVE_DNSTAP_TRUE@ dnstap.pb-c.h \
+@HAVE_DNSTAP_TRUE@ dnstap.pb-c.c
+
+@HAVE_DNSTAP_TRUE@am__append_13 = \
+@HAVE_DNSTAP_TRUE@ dnstap.c
+
+@HAVE_DNSTAP_TRUE@am__append_14 = $(DNSTAP_CFLAGS)
+@HAVE_DNSTAP_TRUE@am__append_15 = $(DNSTAP_LIBS)
+@HAVE_LMDB_TRUE@am__append_16 = $(LMDB_CFLAGS)
+@HAVE_LMDB_TRUE@am__append_17 = $(LMDB_LIBS)
+subdir = lib/dns
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/m4/ax_check_compile_flag.m4 \
+ $(top_srcdir)/m4/ax_check_link_flag.m4 \
+ $(top_srcdir)/m4/ax_check_openssl.m4 \
+ $(top_srcdir)/m4/ax_gcc_func_attribute.m4 \
+ $(top_srcdir)/m4/ax_jemalloc.m4 \
+ $(top_srcdir)/m4/ax_lib_lmdb.m4 \
+ $(top_srcdir)/m4/ax_perl_module.m4 \
+ $(top_srcdir)/m4/ax_posix_shell.m4 \
+ $(top_srcdir)/m4/ax_prog_cc_for_build.m4 \
+ $(top_srcdir)/m4/ax_pthread.m4 \
+ $(top_srcdir)/m4/ax_python_module.m4 \
+ $(top_srcdir)/m4/ax_restore_flags.m4 \
+ $(top_srcdir)/m4/ax_save_flags.m4 $(top_srcdir)/m4/ax_tls.m4 \
+ $(top_srcdir)/m4/libtool.m4 $(top_srcdir)/m4/ltoptions.m4 \
+ $(top_srcdir)/m4/ltsugar.m4 $(top_srcdir)/m4/ltversion.m4 \
+ $(top_srcdir)/m4/lt~obsolete.m4 $(top_srcdir)/configure.ac
+am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
+ $(ACLOCAL_M4)
+DIST_COMMON = $(srcdir)/Makefile.am $(dst_HEADERS) \
+ $(libdns_la_HEADERS) $(am__DIST_COMMON)
+mkinstalldirs = $(install_sh) -d
+CONFIG_HEADER = $(top_builddir)/config.h
+CONFIG_CLEAN_FILES =
+CONFIG_CLEAN_VPATH_FILES =
+am__vpath_adj_setup = srcdirstrip=`echo "$(srcdir)" | sed 's|.|.|g'`;
+am__vpath_adj = case $$p in \
+ $(srcdir)/*) f=`echo "$$p" | sed "s|^$$srcdirstrip/||"`;; \
+ *) f=$$p;; \
+ esac;
+am__strip_dir = f=`echo $$p | sed -e 's|^.*/||'`;
+am__install_max = 40
+am__nobase_strip_setup = \
+ srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*|]/\\\\&/g'`
+am__nobase_strip = \
+ for p in $$list; do echo "$$p"; done | sed -e "s|$$srcdirstrip/||"
+am__nobase_list = $(am__nobase_strip_setup); \
+ for p in $$list; do echo "$$p $$p"; done | \
+ sed "s| $$srcdirstrip/| |;"' / .*\//!s/ .*/ ./; s,\( .*\)/[^/]*$$,\1,' | \
+ $(AWK) 'BEGIN { files["."] = "" } { files[$$2] = files[$$2] " " $$1; \
+ if (++n[$$2] == $(am__install_max)) \
+ { print $$2, files[$$2]; n[$$2] = 0; files[$$2] = "" } } \
+ END { for (dir in files) print dir, files[dir] }'
+am__base_list = \
+ sed '$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;s/\n/ /g' | \
+ sed '$$!N;$$!N;$$!N;$$!N;s/\n/ /g'
+am__uninstall_files_from_dir = { \
+ test -z "$$files" \
+ || { test ! -d "$$dir" && test ! -f "$$dir" && test ! -r "$$dir"; } \
+ || { echo " ( cd '$$dir' && rm -f" $$files ")"; \
+ $(am__cd) "$$dir" && rm -f $$files; }; \
+ }
+am__installdirs = "$(DESTDIR)$(libdir)" "$(DESTDIR)$(dstdir)" \
+ "$(DESTDIR)$(libdns_ladir)" "$(DESTDIR)$(libdns_ladir)"
+LTLIBRARIES = $(lib_LTLIBRARIES)
+am__DEPENDENCIES_1 =
+@HAVE_JSON_C_TRUE@am__DEPENDENCIES_2 = $(am__DEPENDENCIES_1)
+@HAVE_LIBXML2_TRUE@am__DEPENDENCIES_3 = $(am__DEPENDENCIES_1)
+@HAVE_GSSAPI_TRUE@am__DEPENDENCIES_4 = $(am__DEPENDENCIES_1) \
+@HAVE_GSSAPI_TRUE@ $(am__DEPENDENCIES_1)
+@HAVE_GEOIP2_TRUE@am__DEPENDENCIES_5 = $(am__DEPENDENCIES_1)
+@HAVE_DNSTAP_TRUE@am__DEPENDENCIES_6 = $(am__DEPENDENCIES_1)
+@HAVE_LMDB_TRUE@am__DEPENDENCIES_7 = $(am__DEPENDENCIES_1)
+libdns_la_DEPENDENCIES = $(LIBISC_LIBS) $(am__DEPENDENCIES_1) \
+ $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_2) \
+ $(am__DEPENDENCIES_3) $(am__DEPENDENCIES_4) \
+ $(am__DEPENDENCIES_5) $(am__DEPENDENCIES_6) \
+ $(am__DEPENDENCIES_7)
+am__libdns_la_SOURCES_DIST = include/dns/acl.h include/dns/adb.h \
+ include/dns/badcache.h include/dns/bit.h include/dns/byaddr.h \
+ include/dns/cache.h include/dns/callbacks.h include/dns/catz.h \
+ include/dns/cert.h include/dns/client.h \
+ include/dns/clientinfo.h include/dns/compress.h \
+ include/dns/db.h include/dns/dbiterator.h include/dns/diff.h \
+ include/dns/dispatch.h include/dns/dlz.h \
+ include/dns/dlz_dlopen.h include/dns/dns64.h \
+ include/dns/dnsrps.h include/dns/dnssec.h include/dns/ds.h \
+ include/dns/dsdigest.h include/dns/dnstap.h \
+ include/dns/dyndb.h include/dns/ecs.h include/dns/edns.h \
+ include/dns/events.h include/dns/fixedname.h \
+ include/dns/forward.h include/dns/geoip.h \
+ include/dns/ipkeylist.h include/dns/iptable.h \
+ include/dns/journal.h include/dns/kasp.h include/dns/keydata.h \
+ include/dns/keyflags.h include/dns/keymgr.h \
+ include/dns/keytable.h include/dns/keyvalues.h \
+ include/dns/librpz.h include/dns/lookup.h include/dns/log.h \
+ include/dns/master.h include/dns/masterdump.h \
+ include/dns/message.h include/dns/name.h include/dns/ncache.h \
+ include/dns/nsec.h include/dns/nsec3.h include/dns/nta.h \
+ include/dns/opcode.h include/dns/order.h include/dns/peer.h \
+ include/dns/private.h include/dns/rbt.h include/dns/rcode.h \
+ include/dns/rdata.h include/dns/rdataclass.h \
+ include/dns/rdatalist.h include/dns/rdataset.h \
+ include/dns/rdatasetiter.h include/dns/rdataslab.h \
+ include/dns/rdatatype.h include/dns/request.h \
+ include/dns/resolver.h include/dns/result.h \
+ include/dns/rootns.h include/dns/rpz.h \
+ include/dns/rriterator.h include/dns/rrl.h include/dns/sdb.h \
+ include/dns/sdlz.h include/dns/secalg.h include/dns/secproto.h \
+ include/dns/soa.h include/dns/ssu.h include/dns/stats.h \
+ include/dns/time.h include/dns/transport.h include/dns/tkey.h \
+ include/dns/tsec.h include/dns/tsig.h include/dns/ttl.h \
+ include/dns/types.h include/dns/update.h \
+ include/dns/validator.h include/dns/view.h include/dns/xfrin.h \
+ include/dns/zone.h include/dns/zonekey.h \
+ include/dns/zoneverify.h include/dns/zt.h include/dst/dst.h \
+ include/dst/gssapi.h acl.c adb.c badcache.c byaddr.c cache.c \
+ callbacks.c catz.c clientinfo.c compress.c db.c dbiterator.c \
+ diff.c dispatch.c dlz.c dns64.c dnsrps.c dnssec.c ds.c \
+ dst_api.c dst_internal.h dst_openssl.h dst_parse.c dst_parse.h \
+ dyndb.c ecs.c fixedname.c forward.c gssapictx.c hmac_link.c \
+ ipkeylist.c iptable.c journal.c kasp.c key.c keydata.c \
+ keymgr.c keytable.c log.c lookup.c master.c masterdump.c \
+ message.c name.c ncache.c nsec.c nsec3.c nta.c openssl_link.c \
+ openssl_shim.c openssl_shim.h openssldh_link.c \
+ opensslecdsa_link.c openssleddsa_link.c opensslrsa_link.c \
+ order.c peer.c private.c rbt.c rbtdb.h rbtdb.c rcode.c rdata.c \
+ rdatalist.c rdataset.c rdatasetiter.c rdataslab.c request.c \
+ resolver.c result.c rootns.c rpz.c rrl.c rriterator.c sdb.c \
+ sdlz.c soa.c ssu.c ssu_external.c stats.c time.c transport.c \
+ tkey.c tsec.c tsig.c ttl.c update.c validator.c view.c xfrin.c \
+ zone.c zoneverify.c zonekey.c zt.c client.c rdatalist_p.h \
+ tsig_p.h zone_p.h gssapi_link.c geoip2.c dnstap.c
+am__objects_1 =
+@HAVE_GSSAPI_TRUE@am__objects_2 = libdns_la-gssapi_link.lo
+@HAVE_GEOIP2_TRUE@am__objects_3 = libdns_la-geoip2.lo
+@HAVE_DNSTAP_TRUE@am__objects_4 = libdns_la-dnstap.lo
+am_libdns_la_OBJECTS = $(am__objects_1) $(am__objects_1) \
+ libdns_la-acl.lo libdns_la-adb.lo libdns_la-badcache.lo \
+ libdns_la-byaddr.lo libdns_la-cache.lo libdns_la-callbacks.lo \
+ libdns_la-catz.lo libdns_la-clientinfo.lo \
+ libdns_la-compress.lo libdns_la-db.lo libdns_la-dbiterator.lo \
+ libdns_la-diff.lo libdns_la-dispatch.lo libdns_la-dlz.lo \
+ libdns_la-dns64.lo libdns_la-dnsrps.lo libdns_la-dnssec.lo \
+ libdns_la-ds.lo libdns_la-dst_api.lo libdns_la-dst_parse.lo \
+ libdns_la-dyndb.lo libdns_la-ecs.lo libdns_la-fixedname.lo \
+ libdns_la-forward.lo libdns_la-gssapictx.lo \
+ libdns_la-hmac_link.lo libdns_la-ipkeylist.lo \
+ libdns_la-iptable.lo libdns_la-journal.lo libdns_la-kasp.lo \
+ libdns_la-key.lo libdns_la-keydata.lo libdns_la-keymgr.lo \
+ libdns_la-keytable.lo libdns_la-log.lo libdns_la-lookup.lo \
+ libdns_la-master.lo libdns_la-masterdump.lo \
+ libdns_la-message.lo libdns_la-name.lo libdns_la-ncache.lo \
+ libdns_la-nsec.lo libdns_la-nsec3.lo libdns_la-nta.lo \
+ libdns_la-openssl_link.lo libdns_la-openssl_shim.lo \
+ libdns_la-openssldh_link.lo libdns_la-opensslecdsa_link.lo \
+ libdns_la-openssleddsa_link.lo libdns_la-opensslrsa_link.lo \
+ libdns_la-order.lo libdns_la-peer.lo libdns_la-private.lo \
+ libdns_la-rbt.lo libdns_la-rbtdb.lo libdns_la-rcode.lo \
+ libdns_la-rdata.lo libdns_la-rdatalist.lo \
+ libdns_la-rdataset.lo libdns_la-rdatasetiter.lo \
+ libdns_la-rdataslab.lo libdns_la-request.lo \
+ libdns_la-resolver.lo libdns_la-result.lo libdns_la-rootns.lo \
+ libdns_la-rpz.lo libdns_la-rrl.lo libdns_la-rriterator.lo \
+ libdns_la-sdb.lo libdns_la-sdlz.lo libdns_la-soa.lo \
+ libdns_la-ssu.lo libdns_la-ssu_external.lo libdns_la-stats.lo \
+ libdns_la-time.lo libdns_la-transport.lo libdns_la-tkey.lo \
+ libdns_la-tsec.lo libdns_la-tsig.lo libdns_la-ttl.lo \
+ libdns_la-update.lo libdns_la-validator.lo libdns_la-view.lo \
+ libdns_la-xfrin.lo libdns_la-zone.lo libdns_la-zoneverify.lo \
+ libdns_la-zonekey.lo libdns_la-zt.lo libdns_la-client.lo \
+ $(am__objects_2) $(am__objects_3) $(am__objects_4)
+@HAVE_DNSTAP_TRUE@am__objects_5 = libdns_la-dnstap.pb-c.lo
+nodist_libdns_la_OBJECTS = $(am__objects_1) $(am__objects_5)
+libdns_la_OBJECTS = $(am_libdns_la_OBJECTS) \
+ $(nodist_libdns_la_OBJECTS)
+AM_V_lt = $(am__v_lt_@AM_V@)
+am__v_lt_ = $(am__v_lt_@AM_DEFAULT_V@)
+am__v_lt_0 = --silent
+am__v_lt_1 =
+libdns_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \
+ $(libdns_la_LDFLAGS) $(LDFLAGS) -o $@
+AM_V_P = $(am__v_P_@AM_V@)
+am__v_P_ = $(am__v_P_@AM_DEFAULT_V@)
+am__v_P_0 = false
+am__v_P_1 = :
+AM_V_GEN = $(am__v_GEN_@AM_V@)
+am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@)
+am__v_GEN_0 = @echo " GEN " $@;
+am__v_GEN_1 =
+AM_V_at = $(am__v_at_@AM_V@)
+am__v_at_ = $(am__v_at_@AM_DEFAULT_V@)
+am__v_at_0 = @
+am__v_at_1 =
+DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir)
+depcomp = $(SHELL) $(top_srcdir)/depcomp
+am__maybe_remake_depfiles = depfiles
+am__depfiles_remade = ./$(DEPDIR)/libdns_la-acl.Plo \
+ ./$(DEPDIR)/libdns_la-adb.Plo \
+ ./$(DEPDIR)/libdns_la-badcache.Plo \
+ ./$(DEPDIR)/libdns_la-byaddr.Plo \
+ ./$(DEPDIR)/libdns_la-cache.Plo \
+ ./$(DEPDIR)/libdns_la-callbacks.Plo \
+ ./$(DEPDIR)/libdns_la-catz.Plo \
+ ./$(DEPDIR)/libdns_la-client.Plo \
+ ./$(DEPDIR)/libdns_la-clientinfo.Plo \
+ ./$(DEPDIR)/libdns_la-compress.Plo \
+ ./$(DEPDIR)/libdns_la-db.Plo \
+ ./$(DEPDIR)/libdns_la-dbiterator.Plo \
+ ./$(DEPDIR)/libdns_la-diff.Plo \
+ ./$(DEPDIR)/libdns_la-dispatch.Plo \
+ ./$(DEPDIR)/libdns_la-dlz.Plo ./$(DEPDIR)/libdns_la-dns64.Plo \
+ ./$(DEPDIR)/libdns_la-dnsrps.Plo \
+ ./$(DEPDIR)/libdns_la-dnssec.Plo \
+ ./$(DEPDIR)/libdns_la-dnstap.Plo \
+ ./$(DEPDIR)/libdns_la-dnstap.pb-c.Plo \
+ ./$(DEPDIR)/libdns_la-ds.Plo ./$(DEPDIR)/libdns_la-dst_api.Plo \
+ ./$(DEPDIR)/libdns_la-dst_parse.Plo \
+ ./$(DEPDIR)/libdns_la-dyndb.Plo ./$(DEPDIR)/libdns_la-ecs.Plo \
+ ./$(DEPDIR)/libdns_la-fixedname.Plo \
+ ./$(DEPDIR)/libdns_la-forward.Plo \
+ ./$(DEPDIR)/libdns_la-geoip2.Plo \
+ ./$(DEPDIR)/libdns_la-gssapi_link.Plo \
+ ./$(DEPDIR)/libdns_la-gssapictx.Plo \
+ ./$(DEPDIR)/libdns_la-hmac_link.Plo \
+ ./$(DEPDIR)/libdns_la-ipkeylist.Plo \
+ ./$(DEPDIR)/libdns_la-iptable.Plo \
+ ./$(DEPDIR)/libdns_la-journal.Plo \
+ ./$(DEPDIR)/libdns_la-kasp.Plo ./$(DEPDIR)/libdns_la-key.Plo \
+ ./$(DEPDIR)/libdns_la-keydata.Plo \
+ ./$(DEPDIR)/libdns_la-keymgr.Plo \
+ ./$(DEPDIR)/libdns_la-keytable.Plo \
+ ./$(DEPDIR)/libdns_la-log.Plo ./$(DEPDIR)/libdns_la-lookup.Plo \
+ ./$(DEPDIR)/libdns_la-master.Plo \
+ ./$(DEPDIR)/libdns_la-masterdump.Plo \
+ ./$(DEPDIR)/libdns_la-message.Plo \
+ ./$(DEPDIR)/libdns_la-name.Plo \
+ ./$(DEPDIR)/libdns_la-ncache.Plo \
+ ./$(DEPDIR)/libdns_la-nsec.Plo ./$(DEPDIR)/libdns_la-nsec3.Plo \
+ ./$(DEPDIR)/libdns_la-nta.Plo \
+ ./$(DEPDIR)/libdns_la-openssl_link.Plo \
+ ./$(DEPDIR)/libdns_la-openssl_shim.Plo \
+ ./$(DEPDIR)/libdns_la-openssldh_link.Plo \
+ ./$(DEPDIR)/libdns_la-opensslecdsa_link.Plo \
+ ./$(DEPDIR)/libdns_la-openssleddsa_link.Plo \
+ ./$(DEPDIR)/libdns_la-opensslrsa_link.Plo \
+ ./$(DEPDIR)/libdns_la-order.Plo ./$(DEPDIR)/libdns_la-peer.Plo \
+ ./$(DEPDIR)/libdns_la-private.Plo \
+ ./$(DEPDIR)/libdns_la-rbt.Plo ./$(DEPDIR)/libdns_la-rbtdb.Plo \
+ ./$(DEPDIR)/libdns_la-rcode.Plo \
+ ./$(DEPDIR)/libdns_la-rdata.Plo \
+ ./$(DEPDIR)/libdns_la-rdatalist.Plo \
+ ./$(DEPDIR)/libdns_la-rdataset.Plo \
+ ./$(DEPDIR)/libdns_la-rdatasetiter.Plo \
+ ./$(DEPDIR)/libdns_la-rdataslab.Plo \
+ ./$(DEPDIR)/libdns_la-request.Plo \
+ ./$(DEPDIR)/libdns_la-resolver.Plo \
+ ./$(DEPDIR)/libdns_la-result.Plo \
+ ./$(DEPDIR)/libdns_la-rootns.Plo ./$(DEPDIR)/libdns_la-rpz.Plo \
+ ./$(DEPDIR)/libdns_la-rriterator.Plo \
+ ./$(DEPDIR)/libdns_la-rrl.Plo ./$(DEPDIR)/libdns_la-sdb.Plo \
+ ./$(DEPDIR)/libdns_la-sdlz.Plo ./$(DEPDIR)/libdns_la-soa.Plo \
+ ./$(DEPDIR)/libdns_la-ssu.Plo \
+ ./$(DEPDIR)/libdns_la-ssu_external.Plo \
+ ./$(DEPDIR)/libdns_la-stats.Plo ./$(DEPDIR)/libdns_la-time.Plo \
+ ./$(DEPDIR)/libdns_la-tkey.Plo \
+ ./$(DEPDIR)/libdns_la-transport.Plo \
+ ./$(DEPDIR)/libdns_la-tsec.Plo ./$(DEPDIR)/libdns_la-tsig.Plo \
+ ./$(DEPDIR)/libdns_la-ttl.Plo ./$(DEPDIR)/libdns_la-update.Plo \
+ ./$(DEPDIR)/libdns_la-validator.Plo \
+ ./$(DEPDIR)/libdns_la-view.Plo ./$(DEPDIR)/libdns_la-xfrin.Plo \
+ ./$(DEPDIR)/libdns_la-zone.Plo \
+ ./$(DEPDIR)/libdns_la-zonekey.Plo \
+ ./$(DEPDIR)/libdns_la-zoneverify.Plo \
+ ./$(DEPDIR)/libdns_la-zt.Plo
+am__mv = mv -f
+COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \
+ $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS)
+LTCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) \
+ $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \
+ $(AM_CFLAGS) $(CFLAGS)
+AM_V_CC = $(am__v_CC_@AM_V@)
+am__v_CC_ = $(am__v_CC_@AM_DEFAULT_V@)
+am__v_CC_0 = @echo " CC " $@;
+am__v_CC_1 =
+CCLD = $(CC)
+LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \
+ $(AM_LDFLAGS) $(LDFLAGS) -o $@
+AM_V_CCLD = $(am__v_CCLD_@AM_V@)
+am__v_CCLD_ = $(am__v_CCLD_@AM_DEFAULT_V@)
+am__v_CCLD_0 = @echo " CCLD " $@;
+am__v_CCLD_1 =
+SOURCES = $(libdns_la_SOURCES) $(nodist_libdns_la_SOURCES)
+DIST_SOURCES = $(am__libdns_la_SOURCES_DIST)
+am__can_run_installinfo = \
+ case $$AM_UPDATE_INFO_DIR in \
+ n|no|NO) false;; \
+ *) (install-info --version) >/dev/null 2>&1;; \
+ esac
+HEADERS = $(dst_HEADERS) $(libdns_la_HEADERS) \
+ $(nodist_libdns_la_HEADERS)
+am__extra_recursive_targets = test-recursive unit-recursive \
+ doc-recursive
+am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP)
+# Read a list of newline-separated strings from the standard input,
+# and print each of them once, without duplicates. Input order is
+# *not* preserved.
+am__uniquify_input = $(AWK) '\
+ BEGIN { nonempty = 0; } \
+ { items[$$0] = 1; nonempty = 1; } \
+ END { if (nonempty) { for (i in items) print i; }; } \
+'
+# Make sure the list of sources is unique. This is necessary because,
+# e.g., the same source file might be shared among _SOURCES variables
+# for different programs/libraries.
+am__define_uniq_tagged_files = \
+ list='$(am__tagged_files)'; \
+ unique=`for i in $$list; do \
+ if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+ done | $(am__uniquify_input)`
+am__DIST_COMMON = $(srcdir)/Makefile.in $(top_srcdir)/Makefile.top \
+ $(top_srcdir)/depcomp
+DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
+ACLOCAL = @ACLOCAL@
+AMTAR = @AMTAR@
+AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@
+AR = @AR@
+AUTOCONF = @AUTOCONF@
+AUTOHEADER = @AUTOHEADER@
+AUTOMAKE = @AUTOMAKE@
+AWK = @AWK@
+BUILD_EXEEXT = @BUILD_EXEEXT@
+BUILD_OBJEXT = @BUILD_OBJEXT@
+CC = @CC@
+CCDEPMODE = @CCDEPMODE@
+CC_FOR_BUILD = @CC_FOR_BUILD@
+CFLAGS = @CFLAGS@
+CFLAGS_FOR_BUILD = @CFLAGS_FOR_BUILD@
+CMOCKA_CFLAGS = @CMOCKA_CFLAGS@
+CMOCKA_LIBS = @CMOCKA_LIBS@
+CPP = @CPP@
+CPPFLAGS = @CPPFLAGS@
+CPPFLAGS_FOR_BUILD = @CPPFLAGS_FOR_BUILD@
+CPP_FOR_BUILD = @CPP_FOR_BUILD@
+CSCOPE = @CSCOPE@
+CTAGS = @CTAGS@
+CURL = @CURL@
+CYGPATH_W = @CYGPATH_W@
+DEFS = @DEFS@
+DEPDIR = @DEPDIR@
+DEVELOPER_MODE = @DEVELOPER_MODE@
+DLLTOOL = @DLLTOOL@
+DNSTAP_CFLAGS = @DNSTAP_CFLAGS@
+DNSTAP_LIBS = @DNSTAP_LIBS@
+DOXYGEN = @DOXYGEN@
+DSYMUTIL = @DSYMUTIL@
+DUMPBIN = @DUMPBIN@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+ETAGS = @ETAGS@
+EXEEXT = @EXEEXT@
+FGREP = @FGREP@
+FILECMD = @FILECMD@
+FSTRM_CAPTURE = @FSTRM_CAPTURE@
+FUZZ_LDFLAGS = @FUZZ_LDFLAGS@
+FUZZ_LOG_COMPILER = @FUZZ_LOG_COMPILER@
+GREP = @GREP@
+GSSAPI_CFLAGS = @GSSAPI_CFLAGS@
+GSSAPI_LIBS = @GSSAPI_LIBS@
+INSTALL = @INSTALL@
+INSTALL_DATA = @INSTALL_DATA@
+INSTALL_PROGRAM = @INSTALL_PROGRAM@
+INSTALL_SCRIPT = @INSTALL_SCRIPT@
+INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@
+JEMALLOC_CFLAGS = @JEMALLOC_CFLAGS@
+JEMALLOC_LIBS = @JEMALLOC_LIBS@
+JSON_C_CFLAGS = @JSON_C_CFLAGS@
+JSON_C_LIBS = @JSON_C_LIBS@
+KRB5_CFLAGS = @KRB5_CFLAGS@
+KRB5_CONFIG = @KRB5_CONFIG@
+KRB5_LIBS = @KRB5_LIBS@
+LATEXMK = @LATEXMK@
+LD = @LD@
+LDFLAGS = @LDFLAGS@
+LDFLAGS_FOR_BUILD = @LDFLAGS_FOR_BUILD@
+LIBCAP_LIBS = @LIBCAP_LIBS@
+LIBIDN2_CFLAGS = @LIBIDN2_CFLAGS@
+LIBIDN2_LIBS = @LIBIDN2_LIBS@
+LIBNGHTTP2_CFLAGS = @LIBNGHTTP2_CFLAGS@
+LIBNGHTTP2_LIBS = @LIBNGHTTP2_LIBS@
+LIBOBJS = @LIBOBJS@
+LIBS = @LIBS@
+LIBTOOL = @LIBTOOL@
+LIBUV_CFLAGS = @LIBUV_CFLAGS@
+LIBUV_LIBS = @LIBUV_LIBS@
+LIBXML2_CFLAGS = @LIBXML2_CFLAGS@
+LIBXML2_LIBS = @LIBXML2_LIBS@
+LIPO = @LIPO@
+LMDB_CFLAGS = @LMDB_CFLAGS@
+LMDB_LIBS = @LMDB_LIBS@
+LN_S = @LN_S@
+LTLIBOBJS = @LTLIBOBJS@
+LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@
+MAINT = @MAINT@
+MAKEINFO = @MAKEINFO@
+MANIFEST_TOOL = @MANIFEST_TOOL@
+MAXMINDDB_CFLAGS = @MAXMINDDB_CFLAGS@
+MAXMINDDB_LIBS = @MAXMINDDB_LIBS@
+MAXMINDDB_PREFIX = @MAXMINDDB_PREFIX@
+MKDIR_P = @MKDIR_P@
+NC = @NC@
+NM = @NM@
+NMEDIT = @NMEDIT@
+OBJDUMP = @OBJDUMP@
+OBJEXT = @OBJEXT@
+OPENSSL_CFLAGS = @OPENSSL_CFLAGS@
+OPENSSL_LDFLAGS = @OPENSSL_LDFLAGS@
+OPENSSL_LIBS = @OPENSSL_LIBS@
+OTOOL = @OTOOL@
+OTOOL64 = @OTOOL64@
+PACKAGE = @PACKAGE@
+PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@
+PACKAGE_NAME = @PACKAGE_NAME@
+PACKAGE_STRING = @PACKAGE_STRING@
+PACKAGE_TARNAME = @PACKAGE_TARNAME@
+PACKAGE_URL = @PACKAGE_URL@
+PACKAGE_VERSION = @PACKAGE_VERSION@
+PATH_SEPARATOR = @PATH_SEPARATOR@
+PERL = @PERL@
+PKG_CONFIG = @PKG_CONFIG@
+PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@
+PKG_CONFIG_PATH = @PKG_CONFIG_PATH@
+PROTOC_C = @PROTOC_C@
+PTHREAD_CC = @PTHREAD_CC@
+PTHREAD_CFLAGS = @PTHREAD_CFLAGS@
+PTHREAD_CXX = @PTHREAD_CXX@
+PTHREAD_LIBS = @PTHREAD_LIBS@
+PYTEST = @PYTEST@
+PYTHON = @PYTHON@
+PYTHON_EXEC_PREFIX = @PYTHON_EXEC_PREFIX@
+PYTHON_PLATFORM = @PYTHON_PLATFORM@
+PYTHON_PREFIX = @PYTHON_PREFIX@
+PYTHON_VERSION = @PYTHON_VERSION@
+RANLIB = @RANLIB@
+READLINE_CFLAGS = @READLINE_CFLAGS@
+READLINE_LIBS = @READLINE_LIBS@
+RELEASE_DATE = @RELEASE_DATE@
+SED = @SED@
+SET_MAKE = @SET_MAKE@
+SHELL = @SHELL@
+SPHINX_BUILD = @SPHINX_BUILD@
+STD_CFLAGS = @STD_CFLAGS@
+STD_CPPFLAGS = @STD_CPPFLAGS@
+STD_LDFLAGS = @STD_LDFLAGS@
+STRIP = @STRIP@
+TEST_CFLAGS = @TEST_CFLAGS@
+VERSION = @VERSION@
+XELATEX = @XELATEX@
+XSLTPROC = @XSLTPROC@
+ZLIB_CFLAGS = @ZLIB_CFLAGS@
+ZLIB_LIBS = @ZLIB_LIBS@
+abs_builddir = @abs_builddir@
+abs_srcdir = @abs_srcdir@
+abs_top_builddir = @abs_top_builddir@
+abs_top_srcdir = @abs_top_srcdir@
+ac_ct_AR = @ac_ct_AR@
+ac_ct_CC = @ac_ct_CC@
+ac_ct_CC_FOR_BUILD = @ac_ct_CC_FOR_BUILD@
+ac_ct_DUMPBIN = @ac_ct_DUMPBIN@
+am__include = @am__include@
+am__leading_dot = @am__leading_dot@
+am__quote = @am__quote@
+am__tar = @am__tar@
+am__untar = @am__untar@
+ax_pthread_config = @ax_pthread_config@
+bindir = @bindir@
+build = @build@
+build_alias = @build_alias@
+build_cpu = @build_cpu@
+build_os = @build_os@
+build_vendor = @build_vendor@
+builddir = @builddir@
+datadir = @datadir@
+datarootdir = @datarootdir@
+docdir = @docdir@
+dvidir = @dvidir@
+exec_prefix = @exec_prefix@
+host = @host@
+host_alias = @host_alias@
+host_cpu = @host_cpu@
+host_os = @host_os@
+host_vendor = @host_vendor@
+htmldir = @htmldir@
+includedir = @includedir@
+infodir = @infodir@
+install_sh = @install_sh@
+libdir = @libdir@
+libexecdir = @libexecdir@
+localedir = @localedir@
+localstatedir = @localstatedir@
+mandir = @mandir@
+mkdir_p = @mkdir_p@
+oldincludedir = @oldincludedir@
+pdfdir = @pdfdir@
+pkgpyexecdir = @pkgpyexecdir@
+pkgpythondir = @pkgpythondir@
+prefix = @prefix@
+program_transform_name = @program_transform_name@
+psdir = @psdir@
+pyexecdir = @pyexecdir@
+pythondir = @pythondir@
+runstatedir = @runstatedir@
+sbindir = @sbindir@
+sharedstatedir = @sharedstatedir@
+srcdir = @srcdir@
+sysconfdir = @sysconfdir@
+target = @target@
+target_alias = @target_alias@
+target_cpu = @target_cpu@
+target_os = @target_os@
+target_vendor = @target_vendor@
+top_build_prefix = @top_build_prefix@
+top_builddir = @top_builddir@
+top_srcdir = @top_srcdir@
+ACLOCAL_AMFLAGS = -I $(top_srcdir)/m4
+AM_CFLAGS = \
+ $(STD_CFLAGS)
+
+AM_CPPFLAGS = \
+ $(STD_CPPFLAGS) \
+ -include $(top_builddir)/config.h \
+ -I$(srcdir)/include
+
+AM_LDFLAGS = $(STD_LDFLAGS) $(am__append_1)
+LDADD =
+LIBISC_CFLAGS = \
+ -I$(top_srcdir)/include \
+ -I$(top_srcdir)/lib/isc/include \
+ -I$(top_builddir)/lib/isc/include
+
+LIBISC_LIBS = $(top_builddir)/lib/isc/libisc.la
+LIBDNS_CFLAGS = \
+ -I$(top_srcdir)/lib/dns/include \
+ -I$(top_builddir)/lib/dns/include
+
+LIBDNS_LIBS = \
+ $(top_builddir)/lib/dns/libdns.la
+
+LIBNS_CFLAGS = \
+ -I$(top_srcdir)/lib/ns/include
+
+LIBNS_LIBS = \
+ $(top_builddir)/lib/ns/libns.la
+
+LIBIRS_CFLAGS = \
+ -I$(top_srcdir)/lib/irs/include
+
+LIBIRS_LIBS = \
+ $(top_builddir)/lib/irs/libirs.la
+
+LIBISCCFG_CFLAGS = \
+ -I$(top_srcdir)/lib/isccfg/include
+
+LIBISCCFG_LIBS = \
+ $(top_builddir)/lib/isccfg/libisccfg.la
+
+LIBISCCC_CFLAGS = \
+ -I$(top_srcdir)/lib/isccc/include/
+
+LIBISCCC_LIBS = \
+ $(top_builddir)/lib/isccc/libisccc.la
+
+LIBBIND9_CFLAGS = \
+ -I$(top_srcdir)/lib/bind9/include
+
+LIBBIND9_LIBS = \
+ $(top_builddir)/lib/bind9/libbind9.la
+
+lib_LTLIBRARIES = libdns.la
+nodist_libdns_ladir = $(includedir)/dns
+nodist_libdns_la_HEADERS = \
+ include/dns/enumclass.h \
+ include/dns/enumtype.h \
+ include/dns/rdatastruct.h
+
+nodist_libdns_la_SOURCES = $(nodist_libdns_la_HEADERS) code.h \
+ $(am__append_12)
+BUILT_SOURCES = \
+ $(nodist_libdns_la_SOURCES)
+
+CLEANFILES = \
+ $(nodist_libdns_la_SOURCES) \
+ gen$(BUILD_EXEEXT)
+
+EXTRA_DIST = \
+ dnstap.proto \
+ gen.c \
+ rdata/*
+
+libdns_ladir = $(includedir)/dns
+libdns_la_HEADERS = \
+ include/dns/acl.h \
+ include/dns/adb.h \
+ include/dns/badcache.h \
+ include/dns/bit.h \
+ include/dns/byaddr.h \
+ include/dns/cache.h \
+ include/dns/callbacks.h \
+ include/dns/catz.h \
+ include/dns/cert.h \
+ include/dns/client.h \
+ include/dns/clientinfo.h \
+ include/dns/compress.h \
+ include/dns/db.h \
+ include/dns/dbiterator.h \
+ include/dns/diff.h \
+ include/dns/dispatch.h \
+ include/dns/dlz.h \
+ include/dns/dlz_dlopen.h \
+ include/dns/dns64.h \
+ include/dns/dnsrps.h \
+ include/dns/dnssec.h \
+ include/dns/ds.h \
+ include/dns/dsdigest.h \
+ include/dns/dnstap.h \
+ include/dns/dyndb.h \
+ include/dns/ecs.h \
+ include/dns/edns.h \
+ include/dns/events.h \
+ include/dns/fixedname.h \
+ include/dns/forward.h \
+ include/dns/geoip.h \
+ include/dns/ipkeylist.h \
+ include/dns/iptable.h \
+ include/dns/journal.h \
+ include/dns/kasp.h \
+ include/dns/keydata.h \
+ include/dns/keyflags.h \
+ include/dns/keymgr.h \
+ include/dns/keytable.h \
+ include/dns/keyvalues.h \
+ include/dns/librpz.h \
+ include/dns/lookup.h \
+ include/dns/log.h \
+ include/dns/master.h \
+ include/dns/masterdump.h \
+ include/dns/message.h \
+ include/dns/name.h \
+ include/dns/ncache.h \
+ include/dns/nsec.h \
+ include/dns/nsec3.h \
+ include/dns/nta.h \
+ include/dns/opcode.h \
+ include/dns/order.h \
+ include/dns/peer.h \
+ include/dns/private.h \
+ include/dns/rbt.h \
+ include/dns/rcode.h \
+ include/dns/rdata.h \
+ include/dns/rdataclass.h \
+ include/dns/rdatalist.h \
+ include/dns/rdataset.h \
+ include/dns/rdatasetiter.h \
+ include/dns/rdataslab.h \
+ include/dns/rdatatype.h \
+ include/dns/request.h \
+ include/dns/resolver.h \
+ include/dns/result.h \
+ include/dns/rootns.h \
+ include/dns/rpz.h \
+ include/dns/rriterator.h \
+ include/dns/rrl.h \
+ include/dns/sdb.h \
+ include/dns/sdlz.h \
+ include/dns/secalg.h \
+ include/dns/secproto.h \
+ include/dns/soa.h \
+ include/dns/ssu.h \
+ include/dns/stats.h \
+ include/dns/time.h \
+ include/dns/transport.h \
+ include/dns/tkey.h \
+ include/dns/tsec.h \
+ include/dns/tsig.h \
+ include/dns/ttl.h \
+ include/dns/types.h \
+ include/dns/update.h \
+ include/dns/validator.h \
+ include/dns/view.h \
+ include/dns/xfrin.h \
+ include/dns/zone.h \
+ include/dns/zonekey.h \
+ include/dns/zoneverify.h \
+ include/dns/zt.h
+
+dstdir = $(includedir)/dst
+dst_HEADERS = \
+ include/dst/dst.h \
+ include/dst/gssapi.h
+
+libdns_la_SOURCES = $(libdns_la_HEADERS) $(dst_HEADERS) acl.c adb.c \
+ badcache.c byaddr.c cache.c callbacks.c catz.c clientinfo.c \
+ compress.c db.c dbiterator.c diff.c dispatch.c dlz.c dns64.c \
+ dnsrps.c dnssec.c ds.c dst_api.c dst_internal.h dst_openssl.h \
+ dst_parse.c dst_parse.h dyndb.c ecs.c fixedname.c forward.c \
+ gssapictx.c hmac_link.c ipkeylist.c iptable.c journal.c kasp.c \
+ key.c keydata.c keymgr.c keytable.c log.c lookup.c master.c \
+ masterdump.c message.c name.c ncache.c nsec.c nsec3.c nta.c \
+ openssl_link.c openssl_shim.c openssl_shim.h openssldh_link.c \
+ opensslecdsa_link.c openssleddsa_link.c opensslrsa_link.c \
+ order.c peer.c private.c rbt.c rbtdb.h rbtdb.c rcode.c rdata.c \
+ rdatalist.c rdataset.c rdatasetiter.c rdataslab.c request.c \
+ resolver.c result.c rootns.c rpz.c rrl.c rriterator.c sdb.c \
+ sdlz.c soa.c ssu.c ssu_external.c stats.c time.c transport.c \
+ tkey.c tsec.c tsig.c ttl.c update.c validator.c view.c xfrin.c \
+ zone.c zoneverify.c zonekey.c zt.c client.c rdatalist_p.h \
+ tsig_p.h zone_p.h $(am__append_2) $(am__append_3) \
+ $(am__append_13)
+libdns_la_CPPFLAGS = $(AM_CPPFLAGS) $(LIBDNS_CFLAGS) $(LIBISC_CFLAGS) \
+ $(LIBUV_CFLAGS) $(OPENSSL_CFLAGS) $(am__append_4) \
+ $(am__append_6) $(am__append_8) $(am__append_10) \
+ $(am__append_14) $(am__append_16)
+libdns_la_LDFLAGS = \
+ $(AM_LDFLAGS) \
+ -release "$(PACKAGE_VERSION)"
+
+libdns_la_LIBADD = $(LIBISC_LIBS) $(LIBUV_LIBS) $(OPENSSL_LIBS) \
+ $(am__append_5) $(am__append_7) $(am__append_9) \
+ $(am__append_11) $(am__append_15) $(am__append_17)
+all: $(BUILT_SOURCES)
+ $(MAKE) $(AM_MAKEFLAGS) all-am
+
+.SUFFIXES:
+.SUFFIXES: .c .lo .o .obj
+$(srcdir)/Makefile.in: @MAINTAINER_MODE_TRUE@ $(srcdir)/Makefile.am $(top_srcdir)/Makefile.top $(am__configure_deps)
+ @for dep in $?; do \
+ case '$(am__configure_deps)' in \
+ *$$dep*) \
+ ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \
+ && { if test -f $@; then exit 0; else break; fi; }; \
+ exit 1;; \
+ esac; \
+ done; \
+ echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign lib/dns/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --foreign lib/dns/Makefile
+Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status
+ @case '$?' in \
+ *config.status*) \
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \
+ *) \
+ echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles)'; \
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles);; \
+ esac;
+$(top_srcdir)/Makefile.top $(am__empty):
+
+$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+
+$(top_srcdir)/configure: @MAINTAINER_MODE_TRUE@ $(am__configure_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(ACLOCAL_M4): @MAINTAINER_MODE_TRUE@ $(am__aclocal_m4_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(am__aclocal_m4_deps):
+
+install-libLTLIBRARIES: $(lib_LTLIBRARIES)
+ @$(NORMAL_INSTALL)
+ @list='$(lib_LTLIBRARIES)'; test -n "$(libdir)" || list=; \
+ list2=; for p in $$list; do \
+ if test -f $$p; then \
+ list2="$$list2 $$p"; \
+ else :; fi; \
+ done; \
+ test -z "$$list2" || { \
+ echo " $(MKDIR_P) '$(DESTDIR)$(libdir)'"; \
+ $(MKDIR_P) "$(DESTDIR)$(libdir)" || exit 1; \
+ echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 '$(DESTDIR)$(libdir)'"; \
+ $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 "$(DESTDIR)$(libdir)"; \
+ }
+
+uninstall-libLTLIBRARIES:
+ @$(NORMAL_UNINSTALL)
+ @list='$(lib_LTLIBRARIES)'; test -n "$(libdir)" || list=; \
+ for p in $$list; do \
+ $(am__strip_dir) \
+ echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f '$(DESTDIR)$(libdir)/$$f'"; \
+ $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f "$(DESTDIR)$(libdir)/$$f"; \
+ done
+
+clean-libLTLIBRARIES:
+ -test -z "$(lib_LTLIBRARIES)" || rm -f $(lib_LTLIBRARIES)
+ @list='$(lib_LTLIBRARIES)'; \
+ locs=`for p in $$list; do echo $$p; done | \
+ sed 's|^[^/]*$$|.|; s|/[^/]*$$||; s|$$|/so_locations|' | \
+ sort -u`; \
+ test -z "$$locs" || { \
+ echo rm -f $${locs}; \
+ rm -f $${locs}; \
+ }
+
+libdns.la: $(libdns_la_OBJECTS) $(libdns_la_DEPENDENCIES) $(EXTRA_libdns_la_DEPENDENCIES)
+ $(AM_V_CCLD)$(libdns_la_LINK) -rpath $(libdir) $(libdns_la_OBJECTS) $(libdns_la_LIBADD) $(LIBS)
+
+mostlyclean-compile:
+ -rm -f *.$(OBJEXT)
+
+distclean-compile:
+ -rm -f *.tab.c
+
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdns_la-acl.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdns_la-adb.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdns_la-badcache.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdns_la-byaddr.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdns_la-cache.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdns_la-callbacks.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdns_la-catz.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdns_la-client.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdns_la-clientinfo.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdns_la-compress.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdns_la-db.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdns_la-dbiterator.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdns_la-diff.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdns_la-dispatch.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdns_la-dlz.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdns_la-dns64.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdns_la-dnsrps.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdns_la-dnssec.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdns_la-dnstap.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdns_la-dnstap.pb-c.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdns_la-ds.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdns_la-dst_api.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdns_la-dst_parse.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdns_la-dyndb.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdns_la-ecs.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdns_la-fixedname.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdns_la-forward.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdns_la-geoip2.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdns_la-gssapi_link.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdns_la-gssapictx.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdns_la-hmac_link.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdns_la-ipkeylist.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdns_la-iptable.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdns_la-journal.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdns_la-kasp.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdns_la-key.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdns_la-keydata.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdns_la-keymgr.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdns_la-keytable.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdns_la-log.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdns_la-lookup.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdns_la-master.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdns_la-masterdump.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdns_la-message.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdns_la-name.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdns_la-ncache.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdns_la-nsec.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdns_la-nsec3.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdns_la-nta.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdns_la-openssl_link.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdns_la-openssl_shim.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdns_la-openssldh_link.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdns_la-opensslecdsa_link.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdns_la-openssleddsa_link.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdns_la-opensslrsa_link.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdns_la-order.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdns_la-peer.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdns_la-private.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdns_la-rbt.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdns_la-rbtdb.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdns_la-rcode.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdns_la-rdata.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdns_la-rdatalist.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdns_la-rdataset.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdns_la-rdatasetiter.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdns_la-rdataslab.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdns_la-request.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdns_la-resolver.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdns_la-result.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdns_la-rootns.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdns_la-rpz.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdns_la-rriterator.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdns_la-rrl.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdns_la-sdb.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdns_la-sdlz.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdns_la-soa.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdns_la-ssu.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdns_la-ssu_external.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdns_la-stats.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdns_la-time.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdns_la-tkey.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdns_la-transport.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdns_la-tsec.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdns_la-tsig.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdns_la-ttl.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdns_la-update.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdns_la-validator.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdns_la-view.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdns_la-xfrin.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdns_la-zone.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdns_la-zonekey.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdns_la-zoneverify.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdns_la-zt.Plo@am__quote@ # am--include-marker
+
+$(am__depfiles_remade):
+ @$(MKDIR_P) $(@D)
+ @echo '# dummy' >$@-t && $(am__mv) $@-t $@
+
+am--depfiles: $(am__depfiles_remade)
+
+.c.o:
+@am__fastdepCC_TRUE@ $(AM_V_CC)depbase=`echo $@ | sed 's|[^/]*$$|$(DEPDIR)/&|;s|\.o$$||'`;\
+@am__fastdepCC_TRUE@ $(COMPILE) -MT $@ -MD -MP -MF $$depbase.Tpo -c -o $@ $< &&\
+@am__fastdepCC_TRUE@ $(am__mv) $$depbase.Tpo $$depbase.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ $<
+
+.c.obj:
+@am__fastdepCC_TRUE@ $(AM_V_CC)depbase=`echo $@ | sed 's|[^/]*$$|$(DEPDIR)/&|;s|\.obj$$||'`;\
+@am__fastdepCC_TRUE@ $(COMPILE) -MT $@ -MD -MP -MF $$depbase.Tpo -c -o $@ `$(CYGPATH_W) '$<'` &&\
+@am__fastdepCC_TRUE@ $(am__mv) $$depbase.Tpo $$depbase.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ `$(CYGPATH_W) '$<'`
+
+.c.lo:
+@am__fastdepCC_TRUE@ $(AM_V_CC)depbase=`echo $@ | sed 's|[^/]*$$|$(DEPDIR)/&|;s|\.lo$$||'`;\
+@am__fastdepCC_TRUE@ $(LTCOMPILE) -MT $@ -MD -MP -MF $$depbase.Tpo -c -o $@ $< &&\
+@am__fastdepCC_TRUE@ $(am__mv) $$depbase.Tpo $$depbase.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LTCOMPILE) -c -o $@ $<
+
+libdns_la-acl.lo: acl.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libdns_la-acl.lo -MD -MP -MF $(DEPDIR)/libdns_la-acl.Tpo -c -o libdns_la-acl.lo `test -f 'acl.c' || echo '$(srcdir)/'`acl.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdns_la-acl.Tpo $(DEPDIR)/libdns_la-acl.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='acl.c' object='libdns_la-acl.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libdns_la-acl.lo `test -f 'acl.c' || echo '$(srcdir)/'`acl.c
+
+libdns_la-adb.lo: adb.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libdns_la-adb.lo -MD -MP -MF $(DEPDIR)/libdns_la-adb.Tpo -c -o libdns_la-adb.lo `test -f 'adb.c' || echo '$(srcdir)/'`adb.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdns_la-adb.Tpo $(DEPDIR)/libdns_la-adb.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='adb.c' object='libdns_la-adb.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libdns_la-adb.lo `test -f 'adb.c' || echo '$(srcdir)/'`adb.c
+
+libdns_la-badcache.lo: badcache.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libdns_la-badcache.lo -MD -MP -MF $(DEPDIR)/libdns_la-badcache.Tpo -c -o libdns_la-badcache.lo `test -f 'badcache.c' || echo '$(srcdir)/'`badcache.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdns_la-badcache.Tpo $(DEPDIR)/libdns_la-badcache.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='badcache.c' object='libdns_la-badcache.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libdns_la-badcache.lo `test -f 'badcache.c' || echo '$(srcdir)/'`badcache.c
+
+libdns_la-byaddr.lo: byaddr.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libdns_la-byaddr.lo -MD -MP -MF $(DEPDIR)/libdns_la-byaddr.Tpo -c -o libdns_la-byaddr.lo `test -f 'byaddr.c' || echo '$(srcdir)/'`byaddr.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdns_la-byaddr.Tpo $(DEPDIR)/libdns_la-byaddr.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='byaddr.c' object='libdns_la-byaddr.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libdns_la-byaddr.lo `test -f 'byaddr.c' || echo '$(srcdir)/'`byaddr.c
+
+libdns_la-cache.lo: cache.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libdns_la-cache.lo -MD -MP -MF $(DEPDIR)/libdns_la-cache.Tpo -c -o libdns_la-cache.lo `test -f 'cache.c' || echo '$(srcdir)/'`cache.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdns_la-cache.Tpo $(DEPDIR)/libdns_la-cache.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='cache.c' object='libdns_la-cache.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libdns_la-cache.lo `test -f 'cache.c' || echo '$(srcdir)/'`cache.c
+
+libdns_la-callbacks.lo: callbacks.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libdns_la-callbacks.lo -MD -MP -MF $(DEPDIR)/libdns_la-callbacks.Tpo -c -o libdns_la-callbacks.lo `test -f 'callbacks.c' || echo '$(srcdir)/'`callbacks.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdns_la-callbacks.Tpo $(DEPDIR)/libdns_la-callbacks.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='callbacks.c' object='libdns_la-callbacks.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libdns_la-callbacks.lo `test -f 'callbacks.c' || echo '$(srcdir)/'`callbacks.c
+
+libdns_la-catz.lo: catz.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libdns_la-catz.lo -MD -MP -MF $(DEPDIR)/libdns_la-catz.Tpo -c -o libdns_la-catz.lo `test -f 'catz.c' || echo '$(srcdir)/'`catz.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdns_la-catz.Tpo $(DEPDIR)/libdns_la-catz.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='catz.c' object='libdns_la-catz.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libdns_la-catz.lo `test -f 'catz.c' || echo '$(srcdir)/'`catz.c
+
+libdns_la-clientinfo.lo: clientinfo.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libdns_la-clientinfo.lo -MD -MP -MF $(DEPDIR)/libdns_la-clientinfo.Tpo -c -o libdns_la-clientinfo.lo `test -f 'clientinfo.c' || echo '$(srcdir)/'`clientinfo.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdns_la-clientinfo.Tpo $(DEPDIR)/libdns_la-clientinfo.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='clientinfo.c' object='libdns_la-clientinfo.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libdns_la-clientinfo.lo `test -f 'clientinfo.c' || echo '$(srcdir)/'`clientinfo.c
+
+libdns_la-compress.lo: compress.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libdns_la-compress.lo -MD -MP -MF $(DEPDIR)/libdns_la-compress.Tpo -c -o libdns_la-compress.lo `test -f 'compress.c' || echo '$(srcdir)/'`compress.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdns_la-compress.Tpo $(DEPDIR)/libdns_la-compress.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='compress.c' object='libdns_la-compress.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libdns_la-compress.lo `test -f 'compress.c' || echo '$(srcdir)/'`compress.c
+
+libdns_la-db.lo: db.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libdns_la-db.lo -MD -MP -MF $(DEPDIR)/libdns_la-db.Tpo -c -o libdns_la-db.lo `test -f 'db.c' || echo '$(srcdir)/'`db.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdns_la-db.Tpo $(DEPDIR)/libdns_la-db.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='db.c' object='libdns_la-db.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libdns_la-db.lo `test -f 'db.c' || echo '$(srcdir)/'`db.c
+
+libdns_la-dbiterator.lo: dbiterator.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libdns_la-dbiterator.lo -MD -MP -MF $(DEPDIR)/libdns_la-dbiterator.Tpo -c -o libdns_la-dbiterator.lo `test -f 'dbiterator.c' || echo '$(srcdir)/'`dbiterator.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdns_la-dbiterator.Tpo $(DEPDIR)/libdns_la-dbiterator.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='dbiterator.c' object='libdns_la-dbiterator.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libdns_la-dbiterator.lo `test -f 'dbiterator.c' || echo '$(srcdir)/'`dbiterator.c
+
+libdns_la-diff.lo: diff.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libdns_la-diff.lo -MD -MP -MF $(DEPDIR)/libdns_la-diff.Tpo -c -o libdns_la-diff.lo `test -f 'diff.c' || echo '$(srcdir)/'`diff.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdns_la-diff.Tpo $(DEPDIR)/libdns_la-diff.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='diff.c' object='libdns_la-diff.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libdns_la-diff.lo `test -f 'diff.c' || echo '$(srcdir)/'`diff.c
+
+libdns_la-dispatch.lo: dispatch.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libdns_la-dispatch.lo -MD -MP -MF $(DEPDIR)/libdns_la-dispatch.Tpo -c -o libdns_la-dispatch.lo `test -f 'dispatch.c' || echo '$(srcdir)/'`dispatch.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdns_la-dispatch.Tpo $(DEPDIR)/libdns_la-dispatch.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='dispatch.c' object='libdns_la-dispatch.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libdns_la-dispatch.lo `test -f 'dispatch.c' || echo '$(srcdir)/'`dispatch.c
+
+libdns_la-dlz.lo: dlz.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libdns_la-dlz.lo -MD -MP -MF $(DEPDIR)/libdns_la-dlz.Tpo -c -o libdns_la-dlz.lo `test -f 'dlz.c' || echo '$(srcdir)/'`dlz.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdns_la-dlz.Tpo $(DEPDIR)/libdns_la-dlz.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='dlz.c' object='libdns_la-dlz.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libdns_la-dlz.lo `test -f 'dlz.c' || echo '$(srcdir)/'`dlz.c
+
+libdns_la-dns64.lo: dns64.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libdns_la-dns64.lo -MD -MP -MF $(DEPDIR)/libdns_la-dns64.Tpo -c -o libdns_la-dns64.lo `test -f 'dns64.c' || echo '$(srcdir)/'`dns64.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdns_la-dns64.Tpo $(DEPDIR)/libdns_la-dns64.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='dns64.c' object='libdns_la-dns64.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libdns_la-dns64.lo `test -f 'dns64.c' || echo '$(srcdir)/'`dns64.c
+
+libdns_la-dnsrps.lo: dnsrps.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libdns_la-dnsrps.lo -MD -MP -MF $(DEPDIR)/libdns_la-dnsrps.Tpo -c -o libdns_la-dnsrps.lo `test -f 'dnsrps.c' || echo '$(srcdir)/'`dnsrps.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdns_la-dnsrps.Tpo $(DEPDIR)/libdns_la-dnsrps.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='dnsrps.c' object='libdns_la-dnsrps.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libdns_la-dnsrps.lo `test -f 'dnsrps.c' || echo '$(srcdir)/'`dnsrps.c
+
+libdns_la-dnssec.lo: dnssec.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libdns_la-dnssec.lo -MD -MP -MF $(DEPDIR)/libdns_la-dnssec.Tpo -c -o libdns_la-dnssec.lo `test -f 'dnssec.c' || echo '$(srcdir)/'`dnssec.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdns_la-dnssec.Tpo $(DEPDIR)/libdns_la-dnssec.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='dnssec.c' object='libdns_la-dnssec.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libdns_la-dnssec.lo `test -f 'dnssec.c' || echo '$(srcdir)/'`dnssec.c
+
+libdns_la-ds.lo: ds.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libdns_la-ds.lo -MD -MP -MF $(DEPDIR)/libdns_la-ds.Tpo -c -o libdns_la-ds.lo `test -f 'ds.c' || echo '$(srcdir)/'`ds.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdns_la-ds.Tpo $(DEPDIR)/libdns_la-ds.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='ds.c' object='libdns_la-ds.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libdns_la-ds.lo `test -f 'ds.c' || echo '$(srcdir)/'`ds.c
+
+libdns_la-dst_api.lo: dst_api.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libdns_la-dst_api.lo -MD -MP -MF $(DEPDIR)/libdns_la-dst_api.Tpo -c -o libdns_la-dst_api.lo `test -f 'dst_api.c' || echo '$(srcdir)/'`dst_api.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdns_la-dst_api.Tpo $(DEPDIR)/libdns_la-dst_api.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='dst_api.c' object='libdns_la-dst_api.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libdns_la-dst_api.lo `test -f 'dst_api.c' || echo '$(srcdir)/'`dst_api.c
+
+libdns_la-dst_parse.lo: dst_parse.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libdns_la-dst_parse.lo -MD -MP -MF $(DEPDIR)/libdns_la-dst_parse.Tpo -c -o libdns_la-dst_parse.lo `test -f 'dst_parse.c' || echo '$(srcdir)/'`dst_parse.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdns_la-dst_parse.Tpo $(DEPDIR)/libdns_la-dst_parse.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='dst_parse.c' object='libdns_la-dst_parse.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libdns_la-dst_parse.lo `test -f 'dst_parse.c' || echo '$(srcdir)/'`dst_parse.c
+
+libdns_la-dyndb.lo: dyndb.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libdns_la-dyndb.lo -MD -MP -MF $(DEPDIR)/libdns_la-dyndb.Tpo -c -o libdns_la-dyndb.lo `test -f 'dyndb.c' || echo '$(srcdir)/'`dyndb.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdns_la-dyndb.Tpo $(DEPDIR)/libdns_la-dyndb.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='dyndb.c' object='libdns_la-dyndb.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libdns_la-dyndb.lo `test -f 'dyndb.c' || echo '$(srcdir)/'`dyndb.c
+
+libdns_la-ecs.lo: ecs.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libdns_la-ecs.lo -MD -MP -MF $(DEPDIR)/libdns_la-ecs.Tpo -c -o libdns_la-ecs.lo `test -f 'ecs.c' || echo '$(srcdir)/'`ecs.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdns_la-ecs.Tpo $(DEPDIR)/libdns_la-ecs.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='ecs.c' object='libdns_la-ecs.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libdns_la-ecs.lo `test -f 'ecs.c' || echo '$(srcdir)/'`ecs.c
+
+libdns_la-fixedname.lo: fixedname.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libdns_la-fixedname.lo -MD -MP -MF $(DEPDIR)/libdns_la-fixedname.Tpo -c -o libdns_la-fixedname.lo `test -f 'fixedname.c' || echo '$(srcdir)/'`fixedname.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdns_la-fixedname.Tpo $(DEPDIR)/libdns_la-fixedname.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='fixedname.c' object='libdns_la-fixedname.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libdns_la-fixedname.lo `test -f 'fixedname.c' || echo '$(srcdir)/'`fixedname.c
+
+libdns_la-forward.lo: forward.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libdns_la-forward.lo -MD -MP -MF $(DEPDIR)/libdns_la-forward.Tpo -c -o libdns_la-forward.lo `test -f 'forward.c' || echo '$(srcdir)/'`forward.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdns_la-forward.Tpo $(DEPDIR)/libdns_la-forward.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='forward.c' object='libdns_la-forward.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libdns_la-forward.lo `test -f 'forward.c' || echo '$(srcdir)/'`forward.c
+
+libdns_la-gssapictx.lo: gssapictx.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libdns_la-gssapictx.lo -MD -MP -MF $(DEPDIR)/libdns_la-gssapictx.Tpo -c -o libdns_la-gssapictx.lo `test -f 'gssapictx.c' || echo '$(srcdir)/'`gssapictx.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdns_la-gssapictx.Tpo $(DEPDIR)/libdns_la-gssapictx.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='gssapictx.c' object='libdns_la-gssapictx.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libdns_la-gssapictx.lo `test -f 'gssapictx.c' || echo '$(srcdir)/'`gssapictx.c
+
+libdns_la-hmac_link.lo: hmac_link.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libdns_la-hmac_link.lo -MD -MP -MF $(DEPDIR)/libdns_la-hmac_link.Tpo -c -o libdns_la-hmac_link.lo `test -f 'hmac_link.c' || echo '$(srcdir)/'`hmac_link.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdns_la-hmac_link.Tpo $(DEPDIR)/libdns_la-hmac_link.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='hmac_link.c' object='libdns_la-hmac_link.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libdns_la-hmac_link.lo `test -f 'hmac_link.c' || echo '$(srcdir)/'`hmac_link.c
+
+libdns_la-ipkeylist.lo: ipkeylist.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libdns_la-ipkeylist.lo -MD -MP -MF $(DEPDIR)/libdns_la-ipkeylist.Tpo -c -o libdns_la-ipkeylist.lo `test -f 'ipkeylist.c' || echo '$(srcdir)/'`ipkeylist.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdns_la-ipkeylist.Tpo $(DEPDIR)/libdns_la-ipkeylist.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='ipkeylist.c' object='libdns_la-ipkeylist.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libdns_la-ipkeylist.lo `test -f 'ipkeylist.c' || echo '$(srcdir)/'`ipkeylist.c
+
+libdns_la-iptable.lo: iptable.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libdns_la-iptable.lo -MD -MP -MF $(DEPDIR)/libdns_la-iptable.Tpo -c -o libdns_la-iptable.lo `test -f 'iptable.c' || echo '$(srcdir)/'`iptable.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdns_la-iptable.Tpo $(DEPDIR)/libdns_la-iptable.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='iptable.c' object='libdns_la-iptable.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libdns_la-iptable.lo `test -f 'iptable.c' || echo '$(srcdir)/'`iptable.c
+
+libdns_la-journal.lo: journal.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libdns_la-journal.lo -MD -MP -MF $(DEPDIR)/libdns_la-journal.Tpo -c -o libdns_la-journal.lo `test -f 'journal.c' || echo '$(srcdir)/'`journal.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdns_la-journal.Tpo $(DEPDIR)/libdns_la-journal.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='journal.c' object='libdns_la-journal.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libdns_la-journal.lo `test -f 'journal.c' || echo '$(srcdir)/'`journal.c
+
+libdns_la-kasp.lo: kasp.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libdns_la-kasp.lo -MD -MP -MF $(DEPDIR)/libdns_la-kasp.Tpo -c -o libdns_la-kasp.lo `test -f 'kasp.c' || echo '$(srcdir)/'`kasp.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdns_la-kasp.Tpo $(DEPDIR)/libdns_la-kasp.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='kasp.c' object='libdns_la-kasp.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libdns_la-kasp.lo `test -f 'kasp.c' || echo '$(srcdir)/'`kasp.c
+
+libdns_la-key.lo: key.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libdns_la-key.lo -MD -MP -MF $(DEPDIR)/libdns_la-key.Tpo -c -o libdns_la-key.lo `test -f 'key.c' || echo '$(srcdir)/'`key.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdns_la-key.Tpo $(DEPDIR)/libdns_la-key.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='key.c' object='libdns_la-key.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libdns_la-key.lo `test -f 'key.c' || echo '$(srcdir)/'`key.c
+
+libdns_la-keydata.lo: keydata.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libdns_la-keydata.lo -MD -MP -MF $(DEPDIR)/libdns_la-keydata.Tpo -c -o libdns_la-keydata.lo `test -f 'keydata.c' || echo '$(srcdir)/'`keydata.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdns_la-keydata.Tpo $(DEPDIR)/libdns_la-keydata.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='keydata.c' object='libdns_la-keydata.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libdns_la-keydata.lo `test -f 'keydata.c' || echo '$(srcdir)/'`keydata.c
+
+libdns_la-keymgr.lo: keymgr.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libdns_la-keymgr.lo -MD -MP -MF $(DEPDIR)/libdns_la-keymgr.Tpo -c -o libdns_la-keymgr.lo `test -f 'keymgr.c' || echo '$(srcdir)/'`keymgr.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdns_la-keymgr.Tpo $(DEPDIR)/libdns_la-keymgr.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='keymgr.c' object='libdns_la-keymgr.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libdns_la-keymgr.lo `test -f 'keymgr.c' || echo '$(srcdir)/'`keymgr.c
+
+libdns_la-keytable.lo: keytable.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libdns_la-keytable.lo -MD -MP -MF $(DEPDIR)/libdns_la-keytable.Tpo -c -o libdns_la-keytable.lo `test -f 'keytable.c' || echo '$(srcdir)/'`keytable.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdns_la-keytable.Tpo $(DEPDIR)/libdns_la-keytable.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='keytable.c' object='libdns_la-keytable.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libdns_la-keytable.lo `test -f 'keytable.c' || echo '$(srcdir)/'`keytable.c
+
+libdns_la-log.lo: log.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libdns_la-log.lo -MD -MP -MF $(DEPDIR)/libdns_la-log.Tpo -c -o libdns_la-log.lo `test -f 'log.c' || echo '$(srcdir)/'`log.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdns_la-log.Tpo $(DEPDIR)/libdns_la-log.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='log.c' object='libdns_la-log.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libdns_la-log.lo `test -f 'log.c' || echo '$(srcdir)/'`log.c
+
+libdns_la-lookup.lo: lookup.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libdns_la-lookup.lo -MD -MP -MF $(DEPDIR)/libdns_la-lookup.Tpo -c -o libdns_la-lookup.lo `test -f 'lookup.c' || echo '$(srcdir)/'`lookup.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdns_la-lookup.Tpo $(DEPDIR)/libdns_la-lookup.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='lookup.c' object='libdns_la-lookup.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libdns_la-lookup.lo `test -f 'lookup.c' || echo '$(srcdir)/'`lookup.c
+
+libdns_la-master.lo: master.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libdns_la-master.lo -MD -MP -MF $(DEPDIR)/libdns_la-master.Tpo -c -o libdns_la-master.lo `test -f 'master.c' || echo '$(srcdir)/'`master.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdns_la-master.Tpo $(DEPDIR)/libdns_la-master.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='master.c' object='libdns_la-master.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libdns_la-master.lo `test -f 'master.c' || echo '$(srcdir)/'`master.c
+
+libdns_la-masterdump.lo: masterdump.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libdns_la-masterdump.lo -MD -MP -MF $(DEPDIR)/libdns_la-masterdump.Tpo -c -o libdns_la-masterdump.lo `test -f 'masterdump.c' || echo '$(srcdir)/'`masterdump.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdns_la-masterdump.Tpo $(DEPDIR)/libdns_la-masterdump.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='masterdump.c' object='libdns_la-masterdump.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libdns_la-masterdump.lo `test -f 'masterdump.c' || echo '$(srcdir)/'`masterdump.c
+
+libdns_la-message.lo: message.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libdns_la-message.lo -MD -MP -MF $(DEPDIR)/libdns_la-message.Tpo -c -o libdns_la-message.lo `test -f 'message.c' || echo '$(srcdir)/'`message.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdns_la-message.Tpo $(DEPDIR)/libdns_la-message.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='message.c' object='libdns_la-message.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libdns_la-message.lo `test -f 'message.c' || echo '$(srcdir)/'`message.c
+
+libdns_la-name.lo: name.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libdns_la-name.lo -MD -MP -MF $(DEPDIR)/libdns_la-name.Tpo -c -o libdns_la-name.lo `test -f 'name.c' || echo '$(srcdir)/'`name.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdns_la-name.Tpo $(DEPDIR)/libdns_la-name.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='name.c' object='libdns_la-name.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libdns_la-name.lo `test -f 'name.c' || echo '$(srcdir)/'`name.c
+
+libdns_la-ncache.lo: ncache.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libdns_la-ncache.lo -MD -MP -MF $(DEPDIR)/libdns_la-ncache.Tpo -c -o libdns_la-ncache.lo `test -f 'ncache.c' || echo '$(srcdir)/'`ncache.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdns_la-ncache.Tpo $(DEPDIR)/libdns_la-ncache.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='ncache.c' object='libdns_la-ncache.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libdns_la-ncache.lo `test -f 'ncache.c' || echo '$(srcdir)/'`ncache.c
+
+libdns_la-nsec.lo: nsec.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libdns_la-nsec.lo -MD -MP -MF $(DEPDIR)/libdns_la-nsec.Tpo -c -o libdns_la-nsec.lo `test -f 'nsec.c' || echo '$(srcdir)/'`nsec.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdns_la-nsec.Tpo $(DEPDIR)/libdns_la-nsec.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='nsec.c' object='libdns_la-nsec.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libdns_la-nsec.lo `test -f 'nsec.c' || echo '$(srcdir)/'`nsec.c
+
+libdns_la-nsec3.lo: nsec3.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libdns_la-nsec3.lo -MD -MP -MF $(DEPDIR)/libdns_la-nsec3.Tpo -c -o libdns_la-nsec3.lo `test -f 'nsec3.c' || echo '$(srcdir)/'`nsec3.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdns_la-nsec3.Tpo $(DEPDIR)/libdns_la-nsec3.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='nsec3.c' object='libdns_la-nsec3.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libdns_la-nsec3.lo `test -f 'nsec3.c' || echo '$(srcdir)/'`nsec3.c
+
+libdns_la-nta.lo: nta.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libdns_la-nta.lo -MD -MP -MF $(DEPDIR)/libdns_la-nta.Tpo -c -o libdns_la-nta.lo `test -f 'nta.c' || echo '$(srcdir)/'`nta.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdns_la-nta.Tpo $(DEPDIR)/libdns_la-nta.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='nta.c' object='libdns_la-nta.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libdns_la-nta.lo `test -f 'nta.c' || echo '$(srcdir)/'`nta.c
+
+libdns_la-openssl_link.lo: openssl_link.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libdns_la-openssl_link.lo -MD -MP -MF $(DEPDIR)/libdns_la-openssl_link.Tpo -c -o libdns_la-openssl_link.lo `test -f 'openssl_link.c' || echo '$(srcdir)/'`openssl_link.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdns_la-openssl_link.Tpo $(DEPDIR)/libdns_la-openssl_link.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='openssl_link.c' object='libdns_la-openssl_link.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libdns_la-openssl_link.lo `test -f 'openssl_link.c' || echo '$(srcdir)/'`openssl_link.c
+
+libdns_la-openssl_shim.lo: openssl_shim.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libdns_la-openssl_shim.lo -MD -MP -MF $(DEPDIR)/libdns_la-openssl_shim.Tpo -c -o libdns_la-openssl_shim.lo `test -f 'openssl_shim.c' || echo '$(srcdir)/'`openssl_shim.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdns_la-openssl_shim.Tpo $(DEPDIR)/libdns_la-openssl_shim.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='openssl_shim.c' object='libdns_la-openssl_shim.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libdns_la-openssl_shim.lo `test -f 'openssl_shim.c' || echo '$(srcdir)/'`openssl_shim.c
+
+libdns_la-openssldh_link.lo: openssldh_link.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libdns_la-openssldh_link.lo -MD -MP -MF $(DEPDIR)/libdns_la-openssldh_link.Tpo -c -o libdns_la-openssldh_link.lo `test -f 'openssldh_link.c' || echo '$(srcdir)/'`openssldh_link.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdns_la-openssldh_link.Tpo $(DEPDIR)/libdns_la-openssldh_link.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='openssldh_link.c' object='libdns_la-openssldh_link.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libdns_la-openssldh_link.lo `test -f 'openssldh_link.c' || echo '$(srcdir)/'`openssldh_link.c
+
+libdns_la-opensslecdsa_link.lo: opensslecdsa_link.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libdns_la-opensslecdsa_link.lo -MD -MP -MF $(DEPDIR)/libdns_la-opensslecdsa_link.Tpo -c -o libdns_la-opensslecdsa_link.lo `test -f 'opensslecdsa_link.c' || echo '$(srcdir)/'`opensslecdsa_link.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdns_la-opensslecdsa_link.Tpo $(DEPDIR)/libdns_la-opensslecdsa_link.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='opensslecdsa_link.c' object='libdns_la-opensslecdsa_link.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libdns_la-opensslecdsa_link.lo `test -f 'opensslecdsa_link.c' || echo '$(srcdir)/'`opensslecdsa_link.c
+
+libdns_la-openssleddsa_link.lo: openssleddsa_link.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libdns_la-openssleddsa_link.lo -MD -MP -MF $(DEPDIR)/libdns_la-openssleddsa_link.Tpo -c -o libdns_la-openssleddsa_link.lo `test -f 'openssleddsa_link.c' || echo '$(srcdir)/'`openssleddsa_link.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdns_la-openssleddsa_link.Tpo $(DEPDIR)/libdns_la-openssleddsa_link.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='openssleddsa_link.c' object='libdns_la-openssleddsa_link.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libdns_la-openssleddsa_link.lo `test -f 'openssleddsa_link.c' || echo '$(srcdir)/'`openssleddsa_link.c
+
+libdns_la-opensslrsa_link.lo: opensslrsa_link.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libdns_la-opensslrsa_link.lo -MD -MP -MF $(DEPDIR)/libdns_la-opensslrsa_link.Tpo -c -o libdns_la-opensslrsa_link.lo `test -f 'opensslrsa_link.c' || echo '$(srcdir)/'`opensslrsa_link.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdns_la-opensslrsa_link.Tpo $(DEPDIR)/libdns_la-opensslrsa_link.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='opensslrsa_link.c' object='libdns_la-opensslrsa_link.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libdns_la-opensslrsa_link.lo `test -f 'opensslrsa_link.c' || echo '$(srcdir)/'`opensslrsa_link.c
+
+libdns_la-order.lo: order.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libdns_la-order.lo -MD -MP -MF $(DEPDIR)/libdns_la-order.Tpo -c -o libdns_la-order.lo `test -f 'order.c' || echo '$(srcdir)/'`order.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdns_la-order.Tpo $(DEPDIR)/libdns_la-order.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='order.c' object='libdns_la-order.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libdns_la-order.lo `test -f 'order.c' || echo '$(srcdir)/'`order.c
+
+libdns_la-peer.lo: peer.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libdns_la-peer.lo -MD -MP -MF $(DEPDIR)/libdns_la-peer.Tpo -c -o libdns_la-peer.lo `test -f 'peer.c' || echo '$(srcdir)/'`peer.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdns_la-peer.Tpo $(DEPDIR)/libdns_la-peer.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='peer.c' object='libdns_la-peer.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libdns_la-peer.lo `test -f 'peer.c' || echo '$(srcdir)/'`peer.c
+
+libdns_la-private.lo: private.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libdns_la-private.lo -MD -MP -MF $(DEPDIR)/libdns_la-private.Tpo -c -o libdns_la-private.lo `test -f 'private.c' || echo '$(srcdir)/'`private.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdns_la-private.Tpo $(DEPDIR)/libdns_la-private.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='private.c' object='libdns_la-private.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libdns_la-private.lo `test -f 'private.c' || echo '$(srcdir)/'`private.c
+
+libdns_la-rbt.lo: rbt.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libdns_la-rbt.lo -MD -MP -MF $(DEPDIR)/libdns_la-rbt.Tpo -c -o libdns_la-rbt.lo `test -f 'rbt.c' || echo '$(srcdir)/'`rbt.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdns_la-rbt.Tpo $(DEPDIR)/libdns_la-rbt.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='rbt.c' object='libdns_la-rbt.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libdns_la-rbt.lo `test -f 'rbt.c' || echo '$(srcdir)/'`rbt.c
+
+libdns_la-rbtdb.lo: rbtdb.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libdns_la-rbtdb.lo -MD -MP -MF $(DEPDIR)/libdns_la-rbtdb.Tpo -c -o libdns_la-rbtdb.lo `test -f 'rbtdb.c' || echo '$(srcdir)/'`rbtdb.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdns_la-rbtdb.Tpo $(DEPDIR)/libdns_la-rbtdb.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='rbtdb.c' object='libdns_la-rbtdb.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libdns_la-rbtdb.lo `test -f 'rbtdb.c' || echo '$(srcdir)/'`rbtdb.c
+
+libdns_la-rcode.lo: rcode.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libdns_la-rcode.lo -MD -MP -MF $(DEPDIR)/libdns_la-rcode.Tpo -c -o libdns_la-rcode.lo `test -f 'rcode.c' || echo '$(srcdir)/'`rcode.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdns_la-rcode.Tpo $(DEPDIR)/libdns_la-rcode.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='rcode.c' object='libdns_la-rcode.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libdns_la-rcode.lo `test -f 'rcode.c' || echo '$(srcdir)/'`rcode.c
+
+libdns_la-rdata.lo: rdata.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libdns_la-rdata.lo -MD -MP -MF $(DEPDIR)/libdns_la-rdata.Tpo -c -o libdns_la-rdata.lo `test -f 'rdata.c' || echo '$(srcdir)/'`rdata.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdns_la-rdata.Tpo $(DEPDIR)/libdns_la-rdata.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='rdata.c' object='libdns_la-rdata.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libdns_la-rdata.lo `test -f 'rdata.c' || echo '$(srcdir)/'`rdata.c
+
+libdns_la-rdatalist.lo: rdatalist.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libdns_la-rdatalist.lo -MD -MP -MF $(DEPDIR)/libdns_la-rdatalist.Tpo -c -o libdns_la-rdatalist.lo `test -f 'rdatalist.c' || echo '$(srcdir)/'`rdatalist.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdns_la-rdatalist.Tpo $(DEPDIR)/libdns_la-rdatalist.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='rdatalist.c' object='libdns_la-rdatalist.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libdns_la-rdatalist.lo `test -f 'rdatalist.c' || echo '$(srcdir)/'`rdatalist.c
+
+libdns_la-rdataset.lo: rdataset.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libdns_la-rdataset.lo -MD -MP -MF $(DEPDIR)/libdns_la-rdataset.Tpo -c -o libdns_la-rdataset.lo `test -f 'rdataset.c' || echo '$(srcdir)/'`rdataset.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdns_la-rdataset.Tpo $(DEPDIR)/libdns_la-rdataset.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='rdataset.c' object='libdns_la-rdataset.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libdns_la-rdataset.lo `test -f 'rdataset.c' || echo '$(srcdir)/'`rdataset.c
+
+libdns_la-rdatasetiter.lo: rdatasetiter.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libdns_la-rdatasetiter.lo -MD -MP -MF $(DEPDIR)/libdns_la-rdatasetiter.Tpo -c -o libdns_la-rdatasetiter.lo `test -f 'rdatasetiter.c' || echo '$(srcdir)/'`rdatasetiter.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdns_la-rdatasetiter.Tpo $(DEPDIR)/libdns_la-rdatasetiter.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='rdatasetiter.c' object='libdns_la-rdatasetiter.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libdns_la-rdatasetiter.lo `test -f 'rdatasetiter.c' || echo '$(srcdir)/'`rdatasetiter.c
+
+libdns_la-rdataslab.lo: rdataslab.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libdns_la-rdataslab.lo -MD -MP -MF $(DEPDIR)/libdns_la-rdataslab.Tpo -c -o libdns_la-rdataslab.lo `test -f 'rdataslab.c' || echo '$(srcdir)/'`rdataslab.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdns_la-rdataslab.Tpo $(DEPDIR)/libdns_la-rdataslab.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='rdataslab.c' object='libdns_la-rdataslab.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libdns_la-rdataslab.lo `test -f 'rdataslab.c' || echo '$(srcdir)/'`rdataslab.c
+
+libdns_la-request.lo: request.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libdns_la-request.lo -MD -MP -MF $(DEPDIR)/libdns_la-request.Tpo -c -o libdns_la-request.lo `test -f 'request.c' || echo '$(srcdir)/'`request.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdns_la-request.Tpo $(DEPDIR)/libdns_la-request.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='request.c' object='libdns_la-request.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libdns_la-request.lo `test -f 'request.c' || echo '$(srcdir)/'`request.c
+
+libdns_la-resolver.lo: resolver.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libdns_la-resolver.lo -MD -MP -MF $(DEPDIR)/libdns_la-resolver.Tpo -c -o libdns_la-resolver.lo `test -f 'resolver.c' || echo '$(srcdir)/'`resolver.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdns_la-resolver.Tpo $(DEPDIR)/libdns_la-resolver.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='resolver.c' object='libdns_la-resolver.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libdns_la-resolver.lo `test -f 'resolver.c' || echo '$(srcdir)/'`resolver.c
+
+libdns_la-result.lo: result.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libdns_la-result.lo -MD -MP -MF $(DEPDIR)/libdns_la-result.Tpo -c -o libdns_la-result.lo `test -f 'result.c' || echo '$(srcdir)/'`result.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdns_la-result.Tpo $(DEPDIR)/libdns_la-result.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='result.c' object='libdns_la-result.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libdns_la-result.lo `test -f 'result.c' || echo '$(srcdir)/'`result.c
+
+libdns_la-rootns.lo: rootns.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libdns_la-rootns.lo -MD -MP -MF $(DEPDIR)/libdns_la-rootns.Tpo -c -o libdns_la-rootns.lo `test -f 'rootns.c' || echo '$(srcdir)/'`rootns.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdns_la-rootns.Tpo $(DEPDIR)/libdns_la-rootns.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='rootns.c' object='libdns_la-rootns.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libdns_la-rootns.lo `test -f 'rootns.c' || echo '$(srcdir)/'`rootns.c
+
+libdns_la-rpz.lo: rpz.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libdns_la-rpz.lo -MD -MP -MF $(DEPDIR)/libdns_la-rpz.Tpo -c -o libdns_la-rpz.lo `test -f 'rpz.c' || echo '$(srcdir)/'`rpz.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdns_la-rpz.Tpo $(DEPDIR)/libdns_la-rpz.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='rpz.c' object='libdns_la-rpz.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libdns_la-rpz.lo `test -f 'rpz.c' || echo '$(srcdir)/'`rpz.c
+
+libdns_la-rrl.lo: rrl.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libdns_la-rrl.lo -MD -MP -MF $(DEPDIR)/libdns_la-rrl.Tpo -c -o libdns_la-rrl.lo `test -f 'rrl.c' || echo '$(srcdir)/'`rrl.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdns_la-rrl.Tpo $(DEPDIR)/libdns_la-rrl.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='rrl.c' object='libdns_la-rrl.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libdns_la-rrl.lo `test -f 'rrl.c' || echo '$(srcdir)/'`rrl.c
+
+libdns_la-rriterator.lo: rriterator.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libdns_la-rriterator.lo -MD -MP -MF $(DEPDIR)/libdns_la-rriterator.Tpo -c -o libdns_la-rriterator.lo `test -f 'rriterator.c' || echo '$(srcdir)/'`rriterator.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdns_la-rriterator.Tpo $(DEPDIR)/libdns_la-rriterator.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='rriterator.c' object='libdns_la-rriterator.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libdns_la-rriterator.lo `test -f 'rriterator.c' || echo '$(srcdir)/'`rriterator.c
+
+libdns_la-sdb.lo: sdb.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libdns_la-sdb.lo -MD -MP -MF $(DEPDIR)/libdns_la-sdb.Tpo -c -o libdns_la-sdb.lo `test -f 'sdb.c' || echo '$(srcdir)/'`sdb.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdns_la-sdb.Tpo $(DEPDIR)/libdns_la-sdb.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='sdb.c' object='libdns_la-sdb.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libdns_la-sdb.lo `test -f 'sdb.c' || echo '$(srcdir)/'`sdb.c
+
+libdns_la-sdlz.lo: sdlz.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libdns_la-sdlz.lo -MD -MP -MF $(DEPDIR)/libdns_la-sdlz.Tpo -c -o libdns_la-sdlz.lo `test -f 'sdlz.c' || echo '$(srcdir)/'`sdlz.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdns_la-sdlz.Tpo $(DEPDIR)/libdns_la-sdlz.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='sdlz.c' object='libdns_la-sdlz.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libdns_la-sdlz.lo `test -f 'sdlz.c' || echo '$(srcdir)/'`sdlz.c
+
+libdns_la-soa.lo: soa.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libdns_la-soa.lo -MD -MP -MF $(DEPDIR)/libdns_la-soa.Tpo -c -o libdns_la-soa.lo `test -f 'soa.c' || echo '$(srcdir)/'`soa.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdns_la-soa.Tpo $(DEPDIR)/libdns_la-soa.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='soa.c' object='libdns_la-soa.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libdns_la-soa.lo `test -f 'soa.c' || echo '$(srcdir)/'`soa.c
+
+libdns_la-ssu.lo: ssu.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libdns_la-ssu.lo -MD -MP -MF $(DEPDIR)/libdns_la-ssu.Tpo -c -o libdns_la-ssu.lo `test -f 'ssu.c' || echo '$(srcdir)/'`ssu.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdns_la-ssu.Tpo $(DEPDIR)/libdns_la-ssu.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='ssu.c' object='libdns_la-ssu.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libdns_la-ssu.lo `test -f 'ssu.c' || echo '$(srcdir)/'`ssu.c
+
+libdns_la-ssu_external.lo: ssu_external.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libdns_la-ssu_external.lo -MD -MP -MF $(DEPDIR)/libdns_la-ssu_external.Tpo -c -o libdns_la-ssu_external.lo `test -f 'ssu_external.c' || echo '$(srcdir)/'`ssu_external.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdns_la-ssu_external.Tpo $(DEPDIR)/libdns_la-ssu_external.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='ssu_external.c' object='libdns_la-ssu_external.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libdns_la-ssu_external.lo `test -f 'ssu_external.c' || echo '$(srcdir)/'`ssu_external.c
+
+libdns_la-stats.lo: stats.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libdns_la-stats.lo -MD -MP -MF $(DEPDIR)/libdns_la-stats.Tpo -c -o libdns_la-stats.lo `test -f 'stats.c' || echo '$(srcdir)/'`stats.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdns_la-stats.Tpo $(DEPDIR)/libdns_la-stats.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='stats.c' object='libdns_la-stats.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libdns_la-stats.lo `test -f 'stats.c' || echo '$(srcdir)/'`stats.c
+
+libdns_la-time.lo: time.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libdns_la-time.lo -MD -MP -MF $(DEPDIR)/libdns_la-time.Tpo -c -o libdns_la-time.lo `test -f 'time.c' || echo '$(srcdir)/'`time.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdns_la-time.Tpo $(DEPDIR)/libdns_la-time.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='time.c' object='libdns_la-time.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libdns_la-time.lo `test -f 'time.c' || echo '$(srcdir)/'`time.c
+
+libdns_la-transport.lo: transport.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libdns_la-transport.lo -MD -MP -MF $(DEPDIR)/libdns_la-transport.Tpo -c -o libdns_la-transport.lo `test -f 'transport.c' || echo '$(srcdir)/'`transport.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdns_la-transport.Tpo $(DEPDIR)/libdns_la-transport.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='transport.c' object='libdns_la-transport.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libdns_la-transport.lo `test -f 'transport.c' || echo '$(srcdir)/'`transport.c
+
+libdns_la-tkey.lo: tkey.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libdns_la-tkey.lo -MD -MP -MF $(DEPDIR)/libdns_la-tkey.Tpo -c -o libdns_la-tkey.lo `test -f 'tkey.c' || echo '$(srcdir)/'`tkey.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdns_la-tkey.Tpo $(DEPDIR)/libdns_la-tkey.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='tkey.c' object='libdns_la-tkey.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libdns_la-tkey.lo `test -f 'tkey.c' || echo '$(srcdir)/'`tkey.c
+
+libdns_la-tsec.lo: tsec.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libdns_la-tsec.lo -MD -MP -MF $(DEPDIR)/libdns_la-tsec.Tpo -c -o libdns_la-tsec.lo `test -f 'tsec.c' || echo '$(srcdir)/'`tsec.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdns_la-tsec.Tpo $(DEPDIR)/libdns_la-tsec.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='tsec.c' object='libdns_la-tsec.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libdns_la-tsec.lo `test -f 'tsec.c' || echo '$(srcdir)/'`tsec.c
+
+libdns_la-tsig.lo: tsig.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libdns_la-tsig.lo -MD -MP -MF $(DEPDIR)/libdns_la-tsig.Tpo -c -o libdns_la-tsig.lo `test -f 'tsig.c' || echo '$(srcdir)/'`tsig.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdns_la-tsig.Tpo $(DEPDIR)/libdns_la-tsig.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='tsig.c' object='libdns_la-tsig.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libdns_la-tsig.lo `test -f 'tsig.c' || echo '$(srcdir)/'`tsig.c
+
+libdns_la-ttl.lo: ttl.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libdns_la-ttl.lo -MD -MP -MF $(DEPDIR)/libdns_la-ttl.Tpo -c -o libdns_la-ttl.lo `test -f 'ttl.c' || echo '$(srcdir)/'`ttl.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdns_la-ttl.Tpo $(DEPDIR)/libdns_la-ttl.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='ttl.c' object='libdns_la-ttl.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libdns_la-ttl.lo `test -f 'ttl.c' || echo '$(srcdir)/'`ttl.c
+
+libdns_la-update.lo: update.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libdns_la-update.lo -MD -MP -MF $(DEPDIR)/libdns_la-update.Tpo -c -o libdns_la-update.lo `test -f 'update.c' || echo '$(srcdir)/'`update.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdns_la-update.Tpo $(DEPDIR)/libdns_la-update.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='update.c' object='libdns_la-update.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libdns_la-update.lo `test -f 'update.c' || echo '$(srcdir)/'`update.c
+
+libdns_la-validator.lo: validator.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libdns_la-validator.lo -MD -MP -MF $(DEPDIR)/libdns_la-validator.Tpo -c -o libdns_la-validator.lo `test -f 'validator.c' || echo '$(srcdir)/'`validator.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdns_la-validator.Tpo $(DEPDIR)/libdns_la-validator.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='validator.c' object='libdns_la-validator.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libdns_la-validator.lo `test -f 'validator.c' || echo '$(srcdir)/'`validator.c
+
+libdns_la-view.lo: view.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libdns_la-view.lo -MD -MP -MF $(DEPDIR)/libdns_la-view.Tpo -c -o libdns_la-view.lo `test -f 'view.c' || echo '$(srcdir)/'`view.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdns_la-view.Tpo $(DEPDIR)/libdns_la-view.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='view.c' object='libdns_la-view.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libdns_la-view.lo `test -f 'view.c' || echo '$(srcdir)/'`view.c
+
+libdns_la-xfrin.lo: xfrin.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libdns_la-xfrin.lo -MD -MP -MF $(DEPDIR)/libdns_la-xfrin.Tpo -c -o libdns_la-xfrin.lo `test -f 'xfrin.c' || echo '$(srcdir)/'`xfrin.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdns_la-xfrin.Tpo $(DEPDIR)/libdns_la-xfrin.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='xfrin.c' object='libdns_la-xfrin.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libdns_la-xfrin.lo `test -f 'xfrin.c' || echo '$(srcdir)/'`xfrin.c
+
+libdns_la-zone.lo: zone.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libdns_la-zone.lo -MD -MP -MF $(DEPDIR)/libdns_la-zone.Tpo -c -o libdns_la-zone.lo `test -f 'zone.c' || echo '$(srcdir)/'`zone.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdns_la-zone.Tpo $(DEPDIR)/libdns_la-zone.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='zone.c' object='libdns_la-zone.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libdns_la-zone.lo `test -f 'zone.c' || echo '$(srcdir)/'`zone.c
+
+libdns_la-zoneverify.lo: zoneverify.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libdns_la-zoneverify.lo -MD -MP -MF $(DEPDIR)/libdns_la-zoneverify.Tpo -c -o libdns_la-zoneverify.lo `test -f 'zoneverify.c' || echo '$(srcdir)/'`zoneverify.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdns_la-zoneverify.Tpo $(DEPDIR)/libdns_la-zoneverify.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='zoneverify.c' object='libdns_la-zoneverify.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libdns_la-zoneverify.lo `test -f 'zoneverify.c' || echo '$(srcdir)/'`zoneverify.c
+
+libdns_la-zonekey.lo: zonekey.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libdns_la-zonekey.lo -MD -MP -MF $(DEPDIR)/libdns_la-zonekey.Tpo -c -o libdns_la-zonekey.lo `test -f 'zonekey.c' || echo '$(srcdir)/'`zonekey.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdns_la-zonekey.Tpo $(DEPDIR)/libdns_la-zonekey.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='zonekey.c' object='libdns_la-zonekey.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libdns_la-zonekey.lo `test -f 'zonekey.c' || echo '$(srcdir)/'`zonekey.c
+
+libdns_la-zt.lo: zt.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libdns_la-zt.lo -MD -MP -MF $(DEPDIR)/libdns_la-zt.Tpo -c -o libdns_la-zt.lo `test -f 'zt.c' || echo '$(srcdir)/'`zt.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdns_la-zt.Tpo $(DEPDIR)/libdns_la-zt.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='zt.c' object='libdns_la-zt.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libdns_la-zt.lo `test -f 'zt.c' || echo '$(srcdir)/'`zt.c
+
+libdns_la-client.lo: client.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libdns_la-client.lo -MD -MP -MF $(DEPDIR)/libdns_la-client.Tpo -c -o libdns_la-client.lo `test -f 'client.c' || echo '$(srcdir)/'`client.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdns_la-client.Tpo $(DEPDIR)/libdns_la-client.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='client.c' object='libdns_la-client.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libdns_la-client.lo `test -f 'client.c' || echo '$(srcdir)/'`client.c
+
+libdns_la-gssapi_link.lo: gssapi_link.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libdns_la-gssapi_link.lo -MD -MP -MF $(DEPDIR)/libdns_la-gssapi_link.Tpo -c -o libdns_la-gssapi_link.lo `test -f 'gssapi_link.c' || echo '$(srcdir)/'`gssapi_link.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdns_la-gssapi_link.Tpo $(DEPDIR)/libdns_la-gssapi_link.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='gssapi_link.c' object='libdns_la-gssapi_link.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libdns_la-gssapi_link.lo `test -f 'gssapi_link.c' || echo '$(srcdir)/'`gssapi_link.c
+
+libdns_la-geoip2.lo: geoip2.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libdns_la-geoip2.lo -MD -MP -MF $(DEPDIR)/libdns_la-geoip2.Tpo -c -o libdns_la-geoip2.lo `test -f 'geoip2.c' || echo '$(srcdir)/'`geoip2.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdns_la-geoip2.Tpo $(DEPDIR)/libdns_la-geoip2.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='geoip2.c' object='libdns_la-geoip2.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libdns_la-geoip2.lo `test -f 'geoip2.c' || echo '$(srcdir)/'`geoip2.c
+
+libdns_la-dnstap.lo: dnstap.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libdns_la-dnstap.lo -MD -MP -MF $(DEPDIR)/libdns_la-dnstap.Tpo -c -o libdns_la-dnstap.lo `test -f 'dnstap.c' || echo '$(srcdir)/'`dnstap.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdns_la-dnstap.Tpo $(DEPDIR)/libdns_la-dnstap.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='dnstap.c' object='libdns_la-dnstap.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libdns_la-dnstap.lo `test -f 'dnstap.c' || echo '$(srcdir)/'`dnstap.c
+
+libdns_la-dnstap.pb-c.lo: dnstap.pb-c.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libdns_la-dnstap.pb-c.lo -MD -MP -MF $(DEPDIR)/libdns_la-dnstap.pb-c.Tpo -c -o libdns_la-dnstap.pb-c.lo `test -f 'dnstap.pb-c.c' || echo '$(srcdir)/'`dnstap.pb-c.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdns_la-dnstap.pb-c.Tpo $(DEPDIR)/libdns_la-dnstap.pb-c.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='dnstap.pb-c.c' object='libdns_la-dnstap.pb-c.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libdns_la-dnstap.pb-c.lo `test -f 'dnstap.pb-c.c' || echo '$(srcdir)/'`dnstap.pb-c.c
+
+mostlyclean-libtool:
+ -rm -f *.lo
+
+clean-libtool:
+ -rm -rf .libs _libs
+install-dstHEADERS: $(dst_HEADERS)
+ @$(NORMAL_INSTALL)
+ @list='$(dst_HEADERS)'; test -n "$(dstdir)" || list=; \
+ if test -n "$$list"; then \
+ echo " $(MKDIR_P) '$(DESTDIR)$(dstdir)'"; \
+ $(MKDIR_P) "$(DESTDIR)$(dstdir)" || exit 1; \
+ fi; \
+ for p in $$list; do \
+ if test -f "$$p"; then d=; else d="$(srcdir)/"; fi; \
+ echo "$$d$$p"; \
+ done | $(am__base_list) | \
+ while read files; do \
+ echo " $(INSTALL_HEADER) $$files '$(DESTDIR)$(dstdir)'"; \
+ $(INSTALL_HEADER) $$files "$(DESTDIR)$(dstdir)" || exit $$?; \
+ done
+
+uninstall-dstHEADERS:
+ @$(NORMAL_UNINSTALL)
+ @list='$(dst_HEADERS)'; test -n "$(dstdir)" || list=; \
+ files=`for p in $$list; do echo $$p; done | sed -e 's|^.*/||'`; \
+ dir='$(DESTDIR)$(dstdir)'; $(am__uninstall_files_from_dir)
+install-libdns_laHEADERS: $(libdns_la_HEADERS)
+ @$(NORMAL_INSTALL)
+ @list='$(libdns_la_HEADERS)'; test -n "$(libdns_ladir)" || list=; \
+ if test -n "$$list"; then \
+ echo " $(MKDIR_P) '$(DESTDIR)$(libdns_ladir)'"; \
+ $(MKDIR_P) "$(DESTDIR)$(libdns_ladir)" || exit 1; \
+ fi; \
+ for p in $$list; do \
+ if test -f "$$p"; then d=; else d="$(srcdir)/"; fi; \
+ echo "$$d$$p"; \
+ done | $(am__base_list) | \
+ while read files; do \
+ echo " $(INSTALL_HEADER) $$files '$(DESTDIR)$(libdns_ladir)'"; \
+ $(INSTALL_HEADER) $$files "$(DESTDIR)$(libdns_ladir)" || exit $$?; \
+ done
+
+uninstall-libdns_laHEADERS:
+ @$(NORMAL_UNINSTALL)
+ @list='$(libdns_la_HEADERS)'; test -n "$(libdns_ladir)" || list=; \
+ files=`for p in $$list; do echo $$p; done | sed -e 's|^.*/||'`; \
+ dir='$(DESTDIR)$(libdns_ladir)'; $(am__uninstall_files_from_dir)
+install-nodist_libdns_laHEADERS: $(nodist_libdns_la_HEADERS)
+ @$(NORMAL_INSTALL)
+ @list='$(nodist_libdns_la_HEADERS)'; test -n "$(libdns_ladir)" || list=; \
+ if test -n "$$list"; then \
+ echo " $(MKDIR_P) '$(DESTDIR)$(libdns_ladir)'"; \
+ $(MKDIR_P) "$(DESTDIR)$(libdns_ladir)" || exit 1; \
+ fi; \
+ for p in $$list; do \
+ if test -f "$$p"; then d=; else d="$(srcdir)/"; fi; \
+ echo "$$d$$p"; \
+ done | $(am__base_list) | \
+ while read files; do \
+ echo " $(INSTALL_HEADER) $$files '$(DESTDIR)$(libdns_ladir)'"; \
+ $(INSTALL_HEADER) $$files "$(DESTDIR)$(libdns_ladir)" || exit $$?; \
+ done
+
+uninstall-nodist_libdns_laHEADERS:
+ @$(NORMAL_UNINSTALL)
+ @list='$(nodist_libdns_la_HEADERS)'; test -n "$(libdns_ladir)" || list=; \
+ files=`for p in $$list; do echo $$p; done | sed -e 's|^.*/||'`; \
+ dir='$(DESTDIR)$(libdns_ladir)'; $(am__uninstall_files_from_dir)
+test-local:
+unit-local:
+doc-local:
+
+ID: $(am__tagged_files)
+ $(am__define_uniq_tagged_files); mkid -fID $$unique
+tags: tags-am
+TAGS: tags
+
+tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ set x; \
+ here=`pwd`; \
+ $(am__define_uniq_tagged_files); \
+ shift; \
+ if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \
+ test -n "$$unique" || unique=$$empty_fix; \
+ if test $$# -gt 0; then \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ "$$@" $$unique; \
+ else \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ $$unique; \
+ fi; \
+ fi
+ctags: ctags-am
+
+CTAGS: ctags
+ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ $(am__define_uniq_tagged_files); \
+ test -z "$(CTAGS_ARGS)$$unique" \
+ || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \
+ $$unique
+
+GTAGS:
+ here=`$(am__cd) $(top_builddir) && pwd` \
+ && $(am__cd) $(top_srcdir) \
+ && gtags -i $(GTAGS_ARGS) "$$here"
+cscopelist: cscopelist-am
+
+cscopelist-am: $(am__tagged_files)
+ list='$(am__tagged_files)'; \
+ case "$(srcdir)" in \
+ [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \
+ *) sdir=$(subdir)/$(srcdir) ;; \
+ esac; \
+ for i in $$list; do \
+ if test -f "$$i"; then \
+ echo "$(subdir)/$$i"; \
+ else \
+ echo "$$sdir/$$i"; \
+ fi; \
+ done >> $(top_builddir)/cscope.files
+
+distclean-tags:
+ -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags
+distdir: $(BUILT_SOURCES)
+ $(MAKE) $(AM_MAKEFLAGS) distdir-am
+
+distdir-am: $(DISTFILES)
+ @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ list='$(DISTFILES)'; \
+ dist_files=`for file in $$list; do echo $$file; done | \
+ sed -e "s|^$$srcdirstrip/||;t" \
+ -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \
+ case $$dist_files in \
+ */*) $(MKDIR_P) `echo "$$dist_files" | \
+ sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \
+ sort -u` ;; \
+ esac; \
+ for file in $$dist_files; do \
+ if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \
+ if test -d $$d/$$file; then \
+ dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \
+ if test -d "$(distdir)/$$file"; then \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \
+ cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \
+ else \
+ test -f "$(distdir)/$$file" \
+ || cp -p $$d/$$file "$(distdir)/$$file" \
+ || exit 1; \
+ fi; \
+ done
+check-am: all-am
+check: $(BUILT_SOURCES)
+ $(MAKE) $(AM_MAKEFLAGS) check-am
+all-am: Makefile $(LTLIBRARIES) $(HEADERS)
+installdirs:
+ for dir in "$(DESTDIR)$(libdir)" "$(DESTDIR)$(dstdir)" "$(DESTDIR)$(libdns_ladir)" "$(DESTDIR)$(libdns_ladir)"; do \
+ test -z "$$dir" || $(MKDIR_P) "$$dir"; \
+ done
+install: $(BUILT_SOURCES)
+ $(MAKE) $(AM_MAKEFLAGS) install-am
+install-exec: $(BUILT_SOURCES)
+ $(MAKE) $(AM_MAKEFLAGS) install-exec-am
+install-data: install-data-am
+uninstall: uninstall-am
+
+install-am: all-am
+ @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am
+
+installcheck: installcheck-am
+install-strip:
+ if test -z '$(STRIP)'; then \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ install; \
+ else \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \
+ fi
+mostlyclean-generic:
+
+clean-generic:
+ -test -z "$(CLEANFILES)" || rm -f $(CLEANFILES)
+
+distclean-generic:
+ -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES)
+ -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES)
+
+maintainer-clean-generic:
+ @echo "This command is intended for maintainers to use"
+ @echo "it deletes files that may require special tools to rebuild."
+ -test -z "$(BUILT_SOURCES)" || rm -f $(BUILT_SOURCES)
+clean: clean-am
+
+clean-am: clean-generic clean-libLTLIBRARIES clean-libtool \
+ mostlyclean-am
+
+distclean: distclean-am
+ -rm -f ./$(DEPDIR)/libdns_la-acl.Plo
+ -rm -f ./$(DEPDIR)/libdns_la-adb.Plo
+ -rm -f ./$(DEPDIR)/libdns_la-badcache.Plo
+ -rm -f ./$(DEPDIR)/libdns_la-byaddr.Plo
+ -rm -f ./$(DEPDIR)/libdns_la-cache.Plo
+ -rm -f ./$(DEPDIR)/libdns_la-callbacks.Plo
+ -rm -f ./$(DEPDIR)/libdns_la-catz.Plo
+ -rm -f ./$(DEPDIR)/libdns_la-client.Plo
+ -rm -f ./$(DEPDIR)/libdns_la-clientinfo.Plo
+ -rm -f ./$(DEPDIR)/libdns_la-compress.Plo
+ -rm -f ./$(DEPDIR)/libdns_la-db.Plo
+ -rm -f ./$(DEPDIR)/libdns_la-dbiterator.Plo
+ -rm -f ./$(DEPDIR)/libdns_la-diff.Plo
+ -rm -f ./$(DEPDIR)/libdns_la-dispatch.Plo
+ -rm -f ./$(DEPDIR)/libdns_la-dlz.Plo
+ -rm -f ./$(DEPDIR)/libdns_la-dns64.Plo
+ -rm -f ./$(DEPDIR)/libdns_la-dnsrps.Plo
+ -rm -f ./$(DEPDIR)/libdns_la-dnssec.Plo
+ -rm -f ./$(DEPDIR)/libdns_la-dnstap.Plo
+ -rm -f ./$(DEPDIR)/libdns_la-dnstap.pb-c.Plo
+ -rm -f ./$(DEPDIR)/libdns_la-ds.Plo
+ -rm -f ./$(DEPDIR)/libdns_la-dst_api.Plo
+ -rm -f ./$(DEPDIR)/libdns_la-dst_parse.Plo
+ -rm -f ./$(DEPDIR)/libdns_la-dyndb.Plo
+ -rm -f ./$(DEPDIR)/libdns_la-ecs.Plo
+ -rm -f ./$(DEPDIR)/libdns_la-fixedname.Plo
+ -rm -f ./$(DEPDIR)/libdns_la-forward.Plo
+ -rm -f ./$(DEPDIR)/libdns_la-geoip2.Plo
+ -rm -f ./$(DEPDIR)/libdns_la-gssapi_link.Plo
+ -rm -f ./$(DEPDIR)/libdns_la-gssapictx.Plo
+ -rm -f ./$(DEPDIR)/libdns_la-hmac_link.Plo
+ -rm -f ./$(DEPDIR)/libdns_la-ipkeylist.Plo
+ -rm -f ./$(DEPDIR)/libdns_la-iptable.Plo
+ -rm -f ./$(DEPDIR)/libdns_la-journal.Plo
+ -rm -f ./$(DEPDIR)/libdns_la-kasp.Plo
+ -rm -f ./$(DEPDIR)/libdns_la-key.Plo
+ -rm -f ./$(DEPDIR)/libdns_la-keydata.Plo
+ -rm -f ./$(DEPDIR)/libdns_la-keymgr.Plo
+ -rm -f ./$(DEPDIR)/libdns_la-keytable.Plo
+ -rm -f ./$(DEPDIR)/libdns_la-log.Plo
+ -rm -f ./$(DEPDIR)/libdns_la-lookup.Plo
+ -rm -f ./$(DEPDIR)/libdns_la-master.Plo
+ -rm -f ./$(DEPDIR)/libdns_la-masterdump.Plo
+ -rm -f ./$(DEPDIR)/libdns_la-message.Plo
+ -rm -f ./$(DEPDIR)/libdns_la-name.Plo
+ -rm -f ./$(DEPDIR)/libdns_la-ncache.Plo
+ -rm -f ./$(DEPDIR)/libdns_la-nsec.Plo
+ -rm -f ./$(DEPDIR)/libdns_la-nsec3.Plo
+ -rm -f ./$(DEPDIR)/libdns_la-nta.Plo
+ -rm -f ./$(DEPDIR)/libdns_la-openssl_link.Plo
+ -rm -f ./$(DEPDIR)/libdns_la-openssl_shim.Plo
+ -rm -f ./$(DEPDIR)/libdns_la-openssldh_link.Plo
+ -rm -f ./$(DEPDIR)/libdns_la-opensslecdsa_link.Plo
+ -rm -f ./$(DEPDIR)/libdns_la-openssleddsa_link.Plo
+ -rm -f ./$(DEPDIR)/libdns_la-opensslrsa_link.Plo
+ -rm -f ./$(DEPDIR)/libdns_la-order.Plo
+ -rm -f ./$(DEPDIR)/libdns_la-peer.Plo
+ -rm -f ./$(DEPDIR)/libdns_la-private.Plo
+ -rm -f ./$(DEPDIR)/libdns_la-rbt.Plo
+ -rm -f ./$(DEPDIR)/libdns_la-rbtdb.Plo
+ -rm -f ./$(DEPDIR)/libdns_la-rcode.Plo
+ -rm -f ./$(DEPDIR)/libdns_la-rdata.Plo
+ -rm -f ./$(DEPDIR)/libdns_la-rdatalist.Plo
+ -rm -f ./$(DEPDIR)/libdns_la-rdataset.Plo
+ -rm -f ./$(DEPDIR)/libdns_la-rdatasetiter.Plo
+ -rm -f ./$(DEPDIR)/libdns_la-rdataslab.Plo
+ -rm -f ./$(DEPDIR)/libdns_la-request.Plo
+ -rm -f ./$(DEPDIR)/libdns_la-resolver.Plo
+ -rm -f ./$(DEPDIR)/libdns_la-result.Plo
+ -rm -f ./$(DEPDIR)/libdns_la-rootns.Plo
+ -rm -f ./$(DEPDIR)/libdns_la-rpz.Plo
+ -rm -f ./$(DEPDIR)/libdns_la-rriterator.Plo
+ -rm -f ./$(DEPDIR)/libdns_la-rrl.Plo
+ -rm -f ./$(DEPDIR)/libdns_la-sdb.Plo
+ -rm -f ./$(DEPDIR)/libdns_la-sdlz.Plo
+ -rm -f ./$(DEPDIR)/libdns_la-soa.Plo
+ -rm -f ./$(DEPDIR)/libdns_la-ssu.Plo
+ -rm -f ./$(DEPDIR)/libdns_la-ssu_external.Plo
+ -rm -f ./$(DEPDIR)/libdns_la-stats.Plo
+ -rm -f ./$(DEPDIR)/libdns_la-time.Plo
+ -rm -f ./$(DEPDIR)/libdns_la-tkey.Plo
+ -rm -f ./$(DEPDIR)/libdns_la-transport.Plo
+ -rm -f ./$(DEPDIR)/libdns_la-tsec.Plo
+ -rm -f ./$(DEPDIR)/libdns_la-tsig.Plo
+ -rm -f ./$(DEPDIR)/libdns_la-ttl.Plo
+ -rm -f ./$(DEPDIR)/libdns_la-update.Plo
+ -rm -f ./$(DEPDIR)/libdns_la-validator.Plo
+ -rm -f ./$(DEPDIR)/libdns_la-view.Plo
+ -rm -f ./$(DEPDIR)/libdns_la-xfrin.Plo
+ -rm -f ./$(DEPDIR)/libdns_la-zone.Plo
+ -rm -f ./$(DEPDIR)/libdns_la-zonekey.Plo
+ -rm -f ./$(DEPDIR)/libdns_la-zoneverify.Plo
+ -rm -f ./$(DEPDIR)/libdns_la-zt.Plo
+ -rm -f Makefile
+distclean-am: clean-am distclean-compile distclean-generic \
+ distclean-tags
+
+doc: doc-am
+
+doc-am: doc-local
+
+dvi: dvi-am
+
+dvi-am:
+
+html: html-am
+
+html-am:
+
+info: info-am
+
+info-am:
+
+install-data-am: install-dstHEADERS install-libdns_laHEADERS \
+ install-nodist_libdns_laHEADERS
+
+install-dvi: install-dvi-am
+
+install-dvi-am:
+
+install-exec-am: install-libLTLIBRARIES
+
+install-html: install-html-am
+
+install-html-am:
+
+install-info: install-info-am
+
+install-info-am:
+
+install-man:
+
+install-pdf: install-pdf-am
+
+install-pdf-am:
+
+install-ps: install-ps-am
+
+install-ps-am:
+
+installcheck-am:
+
+maintainer-clean: maintainer-clean-am
+ -rm -f ./$(DEPDIR)/libdns_la-acl.Plo
+ -rm -f ./$(DEPDIR)/libdns_la-adb.Plo
+ -rm -f ./$(DEPDIR)/libdns_la-badcache.Plo
+ -rm -f ./$(DEPDIR)/libdns_la-byaddr.Plo
+ -rm -f ./$(DEPDIR)/libdns_la-cache.Plo
+ -rm -f ./$(DEPDIR)/libdns_la-callbacks.Plo
+ -rm -f ./$(DEPDIR)/libdns_la-catz.Plo
+ -rm -f ./$(DEPDIR)/libdns_la-client.Plo
+ -rm -f ./$(DEPDIR)/libdns_la-clientinfo.Plo
+ -rm -f ./$(DEPDIR)/libdns_la-compress.Plo
+ -rm -f ./$(DEPDIR)/libdns_la-db.Plo
+ -rm -f ./$(DEPDIR)/libdns_la-dbiterator.Plo
+ -rm -f ./$(DEPDIR)/libdns_la-diff.Plo
+ -rm -f ./$(DEPDIR)/libdns_la-dispatch.Plo
+ -rm -f ./$(DEPDIR)/libdns_la-dlz.Plo
+ -rm -f ./$(DEPDIR)/libdns_la-dns64.Plo
+ -rm -f ./$(DEPDIR)/libdns_la-dnsrps.Plo
+ -rm -f ./$(DEPDIR)/libdns_la-dnssec.Plo
+ -rm -f ./$(DEPDIR)/libdns_la-dnstap.Plo
+ -rm -f ./$(DEPDIR)/libdns_la-dnstap.pb-c.Plo
+ -rm -f ./$(DEPDIR)/libdns_la-ds.Plo
+ -rm -f ./$(DEPDIR)/libdns_la-dst_api.Plo
+ -rm -f ./$(DEPDIR)/libdns_la-dst_parse.Plo
+ -rm -f ./$(DEPDIR)/libdns_la-dyndb.Plo
+ -rm -f ./$(DEPDIR)/libdns_la-ecs.Plo
+ -rm -f ./$(DEPDIR)/libdns_la-fixedname.Plo
+ -rm -f ./$(DEPDIR)/libdns_la-forward.Plo
+ -rm -f ./$(DEPDIR)/libdns_la-geoip2.Plo
+ -rm -f ./$(DEPDIR)/libdns_la-gssapi_link.Plo
+ -rm -f ./$(DEPDIR)/libdns_la-gssapictx.Plo
+ -rm -f ./$(DEPDIR)/libdns_la-hmac_link.Plo
+ -rm -f ./$(DEPDIR)/libdns_la-ipkeylist.Plo
+ -rm -f ./$(DEPDIR)/libdns_la-iptable.Plo
+ -rm -f ./$(DEPDIR)/libdns_la-journal.Plo
+ -rm -f ./$(DEPDIR)/libdns_la-kasp.Plo
+ -rm -f ./$(DEPDIR)/libdns_la-key.Plo
+ -rm -f ./$(DEPDIR)/libdns_la-keydata.Plo
+ -rm -f ./$(DEPDIR)/libdns_la-keymgr.Plo
+ -rm -f ./$(DEPDIR)/libdns_la-keytable.Plo
+ -rm -f ./$(DEPDIR)/libdns_la-log.Plo
+ -rm -f ./$(DEPDIR)/libdns_la-lookup.Plo
+ -rm -f ./$(DEPDIR)/libdns_la-master.Plo
+ -rm -f ./$(DEPDIR)/libdns_la-masterdump.Plo
+ -rm -f ./$(DEPDIR)/libdns_la-message.Plo
+ -rm -f ./$(DEPDIR)/libdns_la-name.Plo
+ -rm -f ./$(DEPDIR)/libdns_la-ncache.Plo
+ -rm -f ./$(DEPDIR)/libdns_la-nsec.Plo
+ -rm -f ./$(DEPDIR)/libdns_la-nsec3.Plo
+ -rm -f ./$(DEPDIR)/libdns_la-nta.Plo
+ -rm -f ./$(DEPDIR)/libdns_la-openssl_link.Plo
+ -rm -f ./$(DEPDIR)/libdns_la-openssl_shim.Plo
+ -rm -f ./$(DEPDIR)/libdns_la-openssldh_link.Plo
+ -rm -f ./$(DEPDIR)/libdns_la-opensslecdsa_link.Plo
+ -rm -f ./$(DEPDIR)/libdns_la-openssleddsa_link.Plo
+ -rm -f ./$(DEPDIR)/libdns_la-opensslrsa_link.Plo
+ -rm -f ./$(DEPDIR)/libdns_la-order.Plo
+ -rm -f ./$(DEPDIR)/libdns_la-peer.Plo
+ -rm -f ./$(DEPDIR)/libdns_la-private.Plo
+ -rm -f ./$(DEPDIR)/libdns_la-rbt.Plo
+ -rm -f ./$(DEPDIR)/libdns_la-rbtdb.Plo
+ -rm -f ./$(DEPDIR)/libdns_la-rcode.Plo
+ -rm -f ./$(DEPDIR)/libdns_la-rdata.Plo
+ -rm -f ./$(DEPDIR)/libdns_la-rdatalist.Plo
+ -rm -f ./$(DEPDIR)/libdns_la-rdataset.Plo
+ -rm -f ./$(DEPDIR)/libdns_la-rdatasetiter.Plo
+ -rm -f ./$(DEPDIR)/libdns_la-rdataslab.Plo
+ -rm -f ./$(DEPDIR)/libdns_la-request.Plo
+ -rm -f ./$(DEPDIR)/libdns_la-resolver.Plo
+ -rm -f ./$(DEPDIR)/libdns_la-result.Plo
+ -rm -f ./$(DEPDIR)/libdns_la-rootns.Plo
+ -rm -f ./$(DEPDIR)/libdns_la-rpz.Plo
+ -rm -f ./$(DEPDIR)/libdns_la-rriterator.Plo
+ -rm -f ./$(DEPDIR)/libdns_la-rrl.Plo
+ -rm -f ./$(DEPDIR)/libdns_la-sdb.Plo
+ -rm -f ./$(DEPDIR)/libdns_la-sdlz.Plo
+ -rm -f ./$(DEPDIR)/libdns_la-soa.Plo
+ -rm -f ./$(DEPDIR)/libdns_la-ssu.Plo
+ -rm -f ./$(DEPDIR)/libdns_la-ssu_external.Plo
+ -rm -f ./$(DEPDIR)/libdns_la-stats.Plo
+ -rm -f ./$(DEPDIR)/libdns_la-time.Plo
+ -rm -f ./$(DEPDIR)/libdns_la-tkey.Plo
+ -rm -f ./$(DEPDIR)/libdns_la-transport.Plo
+ -rm -f ./$(DEPDIR)/libdns_la-tsec.Plo
+ -rm -f ./$(DEPDIR)/libdns_la-tsig.Plo
+ -rm -f ./$(DEPDIR)/libdns_la-ttl.Plo
+ -rm -f ./$(DEPDIR)/libdns_la-update.Plo
+ -rm -f ./$(DEPDIR)/libdns_la-validator.Plo
+ -rm -f ./$(DEPDIR)/libdns_la-view.Plo
+ -rm -f ./$(DEPDIR)/libdns_la-xfrin.Plo
+ -rm -f ./$(DEPDIR)/libdns_la-zone.Plo
+ -rm -f ./$(DEPDIR)/libdns_la-zonekey.Plo
+ -rm -f ./$(DEPDIR)/libdns_la-zoneverify.Plo
+ -rm -f ./$(DEPDIR)/libdns_la-zt.Plo
+ -rm -f Makefile
+maintainer-clean-am: distclean-am maintainer-clean-generic
+
+mostlyclean: mostlyclean-am
+
+mostlyclean-am: mostlyclean-compile mostlyclean-generic \
+ mostlyclean-libtool
+
+pdf: pdf-am
+
+pdf-am:
+
+ps: ps-am
+
+ps-am:
+
+test: test-am
+
+test-am: test-local
+
+uninstall-am: uninstall-dstHEADERS uninstall-libLTLIBRARIES \
+ uninstall-libdns_laHEADERS uninstall-nodist_libdns_laHEADERS
+
+unit: unit-am
+
+unit-am: unit-local
+
+.MAKE: all check install install-am install-exec install-strip
+
+.PHONY: CTAGS GTAGS TAGS all all-am am--depfiles check check-am clean \
+ clean-generic clean-libLTLIBRARIES clean-libtool cscopelist-am \
+ ctags ctags-am distclean distclean-compile distclean-generic \
+ distclean-libtool distclean-tags distdir doc-am doc-local dvi \
+ dvi-am html html-am info info-am install install-am \
+ install-data install-data-am install-dstHEADERS install-dvi \
+ install-dvi-am install-exec install-exec-am install-html \
+ install-html-am install-info install-info-am \
+ install-libLTLIBRARIES install-libdns_laHEADERS install-man \
+ install-nodist_libdns_laHEADERS install-pdf install-pdf-am \
+ install-ps install-ps-am install-strip installcheck \
+ installcheck-am installdirs maintainer-clean \
+ maintainer-clean-generic mostlyclean mostlyclean-compile \
+ mostlyclean-generic mostlyclean-libtool pdf pdf-am ps ps-am \
+ tags tags-am test-am test-local uninstall uninstall-am \
+ uninstall-dstHEADERS uninstall-libLTLIBRARIES \
+ uninstall-libdns_laHEADERS uninstall-nodist_libdns_laHEADERS \
+ unit-am unit-local
+
+.PRECIOUS: Makefile
+
+
+gen$(BUILD_EXEEXT): gen.c
+ $(CC_FOR_BUILD) -g -I. $(srcdir)/gen.c -o $@
+
+include/dns/enumtype.h: gen Makefile
+ mkdir -p include/dns
+ $(builddir)/gen -s $(srcdir) -t > $@
+
+include/dns/enumclass.h: gen Makefile
+ mkdir -p include/dns
+ $(builddir)/gen -s $(srcdir) -c > $@
+
+include/dns/rdatastruct.h: gen rdata/rdatastructpre.h rdata/rdatastructsuf.h Makefile
+ mkdir -p include/dns
+ $(builddir)/gen -s $(srcdir) -i \
+ -P $(srcdir)/rdata/rdatastructpre.h \
+ -S $(srcdir)/rdata/rdatastructsuf.h > $@
+
+code.h: gen Makefile
+ $(builddir)/gen -s $(srcdir) > $@
+
+@HAVE_DNSTAP_TRUE@dnstap.pb-c.h dnstap.pb-c.c: dnstap.proto
+@HAVE_DNSTAP_TRUE@ $(PROTOC_C) --proto_path=$(srcdir) --c_out=. dnstap.proto
+
+# Tell versions [3.59,3.63) of GNU make to not export all variables.
+# Otherwise a system limit (for SysV at least) may be exceeded.
+.NOEXPORT:
diff --git a/lib/dns/acl.c b/lib/dns/acl.c
new file mode 100644
index 0000000..cea74f7
--- /dev/null
+++ b/lib/dns/acl.c
@@ -0,0 +1,863 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/*! \file */
+
+#include <inttypes.h>
+#include <stdbool.h>
+
+#include <isc/mem.h>
+#include <isc/once.h>
+#include <isc/string.h>
+#include <isc/util.h>
+
+#include <dns/acl.h>
+#include <dns/iptable.h>
+
+#define DNS_ACLENV_MAGIC ISC_MAGIC('a', 'c', 'n', 'v')
+#define VALID_ACLENV(a) ISC_MAGIC_VALID(a, DNS_ACLENV_MAGIC)
+
+/*
+ * Create a new ACL, including an IP table and an array with room
+ * for 'n' ACL elements. The elements are uninitialized and the
+ * length is 0.
+ */
+isc_result_t
+dns_acl_create(isc_mem_t *mctx, int n, dns_acl_t **target) {
+ isc_result_t result;
+ dns_acl_t *acl;
+
+ /*
+ * Work around silly limitation of isc_mem_get().
+ */
+ if (n == 0) {
+ n = 1;
+ }
+
+ acl = isc_mem_get(mctx, sizeof(*acl));
+
+ acl->mctx = NULL;
+ isc_mem_attach(mctx, &acl->mctx);
+
+ acl->name = NULL;
+
+ isc_refcount_init(&acl->refcount, 1);
+
+ result = dns_iptable_create(mctx, &acl->iptable);
+ if (result != ISC_R_SUCCESS) {
+ isc_mem_put(mctx, acl, sizeof(*acl));
+ return (result);
+ }
+
+ acl->elements = NULL;
+ acl->alloc = 0;
+ acl->length = 0;
+ acl->has_negatives = false;
+
+ ISC_LINK_INIT(acl, nextincache);
+ /*
+ * Must set magic early because we use dns_acl_detach() to clean up.
+ */
+ acl->magic = DNS_ACL_MAGIC;
+
+ acl->elements = isc_mem_get(mctx, n * sizeof(dns_aclelement_t));
+ acl->alloc = n;
+ memset(acl->elements, 0, n * sizeof(dns_aclelement_t));
+ ISC_LIST_INIT(acl->ports_and_transports);
+ acl->port_proto_entries = 0;
+
+ *target = acl;
+ return (ISC_R_SUCCESS);
+}
+
+/*
+ * Create a new ACL and initialize it with the value "any" or "none",
+ * depending on the value of the "neg" parameter.
+ * "any" is a positive iptable entry with bit length 0.
+ * "none" is the same as "!any".
+ */
+static isc_result_t
+dns_acl_anyornone(isc_mem_t *mctx, bool neg, dns_acl_t **target) {
+ isc_result_t result;
+ dns_acl_t *acl = NULL;
+
+ result = dns_acl_create(mctx, 0, &acl);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+
+ result = dns_iptable_addprefix(acl->iptable, NULL, 0, !neg);
+ if (result != ISC_R_SUCCESS) {
+ dns_acl_detach(&acl);
+ return (result);
+ }
+
+ *target = acl;
+ return (result);
+}
+
+/*
+ * Create a new ACL that matches everything.
+ */
+isc_result_t
+dns_acl_any(isc_mem_t *mctx, dns_acl_t **target) {
+ return (dns_acl_anyornone(mctx, false, target));
+}
+
+/*
+ * Create a new ACL that matches nothing.
+ */
+isc_result_t
+dns_acl_none(isc_mem_t *mctx, dns_acl_t **target) {
+ return (dns_acl_anyornone(mctx, true, target));
+}
+
+/*
+ * If pos is true, test whether acl is set to "{ any; }"
+ * If pos is false, test whether acl is set to "{ none; }"
+ */
+static bool
+dns_acl_isanyornone(dns_acl_t *acl, bool pos) {
+ /* Should never happen but let's be safe */
+ if (acl == NULL || acl->iptable == NULL ||
+ acl->iptable->radix == NULL || acl->iptable->radix->head == NULL ||
+ acl->iptable->radix->head->prefix == NULL)
+ {
+ return (false);
+ }
+
+ if (acl->length != 0 || dns_acl_node_count(acl) != 1) {
+ return (false);
+ }
+
+ if (acl->iptable->radix->head->prefix->bitlen == 0 &&
+ acl->iptable->radix->head->data[0] != NULL &&
+ acl->iptable->radix->head->data[0] ==
+ acl->iptable->radix->head->data[1] &&
+ *(bool *)(acl->iptable->radix->head->data[0]) == pos)
+ {
+ return (true);
+ }
+
+ return (false); /* All others */
+}
+
+/*
+ * Test whether acl is set to "{ any; }"
+ */
+bool
+dns_acl_isany(dns_acl_t *acl) {
+ return (dns_acl_isanyornone(acl, true));
+}
+
+/*
+ * Test whether acl is set to "{ none; }"
+ */
+bool
+dns_acl_isnone(dns_acl_t *acl) {
+ return (dns_acl_isanyornone(acl, false));
+}
+
+/*
+ * Determine whether a given address or signer matches a given ACL.
+ * For a match with a positive ACL element or iptable radix entry,
+ * return with a positive value in match; for a match with a negated ACL
+ * element or radix entry, return with a negative value in match.
+ */
+
+isc_result_t
+dns_acl_match(const isc_netaddr_t *reqaddr, const dns_name_t *reqsigner,
+ const dns_acl_t *acl, dns_aclenv_t *env, int *match,
+ const dns_aclelement_t **matchelt) {
+ uint16_t bitlen;
+ isc_prefix_t pfx;
+ isc_radix_node_t *node = NULL;
+ const isc_netaddr_t *addr = reqaddr;
+ isc_netaddr_t v4addr;
+ isc_result_t result;
+ int match_num = -1;
+ unsigned int i;
+
+ REQUIRE(reqaddr != NULL);
+ REQUIRE(matchelt == NULL || *matchelt == NULL);
+
+ if (env != NULL && env->match_mapped && addr->family == AF_INET6 &&
+ IN6_IS_ADDR_V4MAPPED(&addr->type.in6))
+ {
+ isc_netaddr_fromv4mapped(&v4addr, addr);
+ addr = &v4addr;
+ }
+
+ /* Always match with host addresses. */
+ bitlen = (addr->family == AF_INET6) ? 128 : 32;
+ NETADDR_TO_PREFIX_T(addr, pfx, bitlen);
+
+ /* Assume no match. */
+ *match = 0;
+
+ /* Search radix. */
+ result = isc_radix_search(acl->iptable->radix, &node, &pfx);
+
+ /* Found a match. */
+ if (result == ISC_R_SUCCESS && node != NULL) {
+ int fam = ISC_RADIX_FAMILY(&pfx);
+ match_num = node->node_num[fam];
+ if (*(bool *)node->data[fam]) {
+ *match = match_num;
+ } else {
+ *match = -match_num;
+ }
+ }
+
+ isc_refcount_destroy(&pfx.refcount);
+
+ /* Now search non-radix elements for a match with a lower node_num. */
+ for (i = 0; i < acl->length; i++) {
+ dns_aclelement_t *e = &acl->elements[i];
+
+ /* Already found a better match? */
+ if (match_num != -1 && match_num < e->node_num) {
+ break;
+ }
+
+ if (dns_aclelement_match(reqaddr, reqsigner, e, env, matchelt))
+ {
+ if (match_num == -1 || e->node_num < match_num) {
+ if (e->negative) {
+ *match = -e->node_num;
+ } else {
+ *match = e->node_num;
+ }
+ }
+ break;
+ }
+ }
+
+ return (ISC_R_SUCCESS);
+}
+
+isc_result_t
+dns_acl_match_port_transport(const isc_netaddr_t *reqaddr,
+ const in_port_t local_port,
+ const isc_nmsocket_type_t transport,
+ const bool encrypted, const dns_name_t *reqsigner,
+ const dns_acl_t *acl, dns_aclenv_t *env,
+ int *match, const dns_aclelement_t **matchelt) {
+ isc_result_t result = ISC_R_SUCCESS;
+ dns_acl_port_transports_t *next;
+
+ REQUIRE(reqaddr != NULL);
+ REQUIRE(DNS_ACL_VALID(acl));
+
+ if (!ISC_LIST_EMPTY(acl->ports_and_transports)) {
+ result = ISC_R_FAILURE;
+ for (next = ISC_LIST_HEAD(acl->ports_and_transports);
+ next != NULL; next = ISC_LIST_NEXT(next, link))
+ {
+ bool match_port = true;
+ bool match_transport = true;
+
+ if (next->port != 0) {
+ /* Port is specified. */
+ match_port = (local_port == next->port);
+ }
+ if (next->transports != 0) {
+ /* Transport protocol is specified. */
+ match_transport =
+ ((transport & next->transports) ==
+ transport &&
+ next->encrypted == encrypted);
+ }
+
+ if (match_port && match_transport) {
+ result = next->negative ? ISC_R_FAILURE
+ : ISC_R_SUCCESS;
+ break;
+ }
+ }
+ }
+
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+
+ return (dns_acl_match(reqaddr, reqsigner, acl, env, match, matchelt));
+}
+
+/*
+ * Merge the contents of one ACL into another. Call dns_iptable_merge()
+ * for the IP tables, then concatenate the element arrays.
+ *
+ * If pos is set to false, then the nested ACL is to be negated. This
+ * means reverse the sense of each *positive* element or IP table node,
+ * but leave negatives alone, so as to prevent a double-negative causing
+ * an unexpected positive match in the parent ACL.
+ */
+isc_result_t
+dns_acl_merge(dns_acl_t *dest, dns_acl_t *source, bool pos) {
+ isc_result_t result;
+ unsigned int newalloc, nelem, i;
+ int max_node = 0, nodes;
+
+ /* Resize the element array if needed. */
+ if (dest->length + source->length > dest->alloc) {
+ void *newmem;
+
+ newalloc = dest->alloc + source->alloc;
+ if (newalloc < 4) {
+ newalloc = 4;
+ }
+
+ newmem = isc_mem_get(dest->mctx,
+ newalloc * sizeof(dns_aclelement_t));
+
+ /* Zero. */
+ memset(newmem, 0, newalloc * sizeof(dns_aclelement_t));
+
+ /* Copy in the original elements */
+ memmove(newmem, dest->elements,
+ dest->length * sizeof(dns_aclelement_t));
+
+ /* Release the memory for the old elements array */
+ isc_mem_put(dest->mctx, dest->elements,
+ dest->alloc * sizeof(dns_aclelement_t));
+ dest->elements = newmem;
+ dest->alloc = newalloc;
+ }
+
+ /*
+ * Now copy in the new elements, increasing their node_num
+ * values so as to keep the new ACL consistent. If we're
+ * negating, then negate positive elements, but keep negative
+ * elements the same for security reasons.
+ */
+ nelem = dest->length;
+ dest->length += source->length;
+ for (i = 0; i < source->length; i++) {
+ if (source->elements[i].node_num > max_node) {
+ max_node = source->elements[i].node_num;
+ }
+
+ /* Copy type. */
+ dest->elements[nelem + i].type = source->elements[i].type;
+
+ /* Adjust node numbering. */
+ dest->elements[nelem + i].node_num =
+ source->elements[i].node_num + dns_acl_node_count(dest);
+
+ /* Duplicate nested acl. */
+ if (source->elements[i].type == dns_aclelementtype_nestedacl &&
+ source->elements[i].nestedacl != NULL)
+ {
+ dns_acl_attach(source->elements[i].nestedacl,
+ &dest->elements[nelem + i].nestedacl);
+ }
+
+ /* Duplicate key name. */
+ if (source->elements[i].type == dns_aclelementtype_keyname) {
+ dns_name_init(&dest->elements[nelem + i].keyname, NULL);
+ dns_name_dup(&source->elements[i].keyname, dest->mctx,
+ &dest->elements[nelem + i].keyname);
+ }
+
+#if defined(HAVE_GEOIP2)
+ /* Duplicate GeoIP data */
+ if (source->elements[i].type == dns_aclelementtype_geoip) {
+ dest->elements[nelem + i].geoip_elem =
+ source->elements[i].geoip_elem;
+ }
+#endif /* if defined(HAVE_GEOIP2) */
+
+ /* reverse sense of positives if this is a negative acl */
+ if (!pos && !source->elements[i].negative) {
+ dest->elements[nelem + i].negative = true;
+ } else {
+ dest->elements[nelem + i].negative =
+ source->elements[i].negative;
+ }
+ }
+
+ /*
+ * Merge the iptables. Make sure the destination ACL's
+ * node_count value is set correctly afterward.
+ */
+ nodes = max_node + dns_acl_node_count(dest);
+ result = dns_iptable_merge(dest->iptable, source->iptable, pos);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+ if (nodes > dns_acl_node_count(dest)) {
+ dns_acl_node_count(dest) = nodes;
+ }
+
+ /*
+ * Merge ports and transports
+ */
+ dns_acl_merge_ports_transports(dest, source, pos);
+
+ return (ISC_R_SUCCESS);
+}
+
+/*
+ * Like dns_acl_match, but matches against the single ACL element 'e'
+ * rather than a complete ACL, and returns true iff it matched.
+ *
+ * To determine whether the match was positive or negative, the
+ * caller should examine e->negative. Since the element 'e' may be
+ * a reference to a named ACL or a nested ACL, a matching element
+ * returned through 'matchelt' is not necessarily 'e' itself.
+ */
+
+bool
+dns_aclelement_match(const isc_netaddr_t *reqaddr, const dns_name_t *reqsigner,
+ const dns_aclelement_t *e, dns_aclenv_t *env,
+ const dns_aclelement_t **matchelt) {
+ dns_acl_t *inner = NULL;
+ int indirectmatch;
+ isc_result_t result;
+
+ switch (e->type) {
+ case dns_aclelementtype_keyname:
+ if (reqsigner != NULL && dns_name_equal(reqsigner, &e->keyname))
+ {
+ if (matchelt != NULL) {
+ *matchelt = e;
+ }
+ return (true);
+ } else {
+ return (false);
+ }
+
+ case dns_aclelementtype_nestedacl:
+ dns_acl_attach(e->nestedacl, &inner);
+ break;
+
+ case dns_aclelementtype_localhost:
+ if (env == NULL) {
+ return (false);
+ }
+ RWLOCK(&env->rwlock, isc_rwlocktype_read);
+ if (env->localhost == NULL) {
+ RWUNLOCK(&env->rwlock, isc_rwlocktype_read);
+ return (false);
+ }
+ dns_acl_attach(env->localhost, &inner);
+ RWUNLOCK(&env->rwlock, isc_rwlocktype_read);
+ break;
+
+ case dns_aclelementtype_localnets:
+ if (env == NULL) {
+ return (false);
+ }
+ RWLOCK(&env->rwlock, isc_rwlocktype_read);
+ if (env->localnets == NULL) {
+ RWUNLOCK(&env->rwlock, isc_rwlocktype_read);
+ return (false);
+ }
+ dns_acl_attach(env->localnets, &inner);
+ RWUNLOCK(&env->rwlock, isc_rwlocktype_read);
+ break;
+
+#if defined(HAVE_GEOIP2)
+ case dns_aclelementtype_geoip:
+ if (env == NULL || env->geoip == NULL) {
+ return (false);
+ }
+ return (dns_geoip_match(reqaddr, env->geoip, &e->geoip_elem));
+#endif /* if defined(HAVE_GEOIP2) */
+ default:
+ UNREACHABLE();
+ }
+
+ result = dns_acl_match(reqaddr, reqsigner, inner, env, &indirectmatch,
+ matchelt);
+ INSIST(result == ISC_R_SUCCESS);
+
+ dns_acl_detach(&inner);
+
+ /*
+ * Treat negative matches in indirect ACLs as "no match".
+ * That way, a negated indirect ACL will never become a
+ * surprise positive match through double negation.
+ * XXXDCL this should be documented.
+ */
+ if (indirectmatch > 0) {
+ if (matchelt != NULL) {
+ *matchelt = e;
+ }
+ return (true);
+ }
+
+ /*
+ * A negative indirect match may have set *matchelt, but we don't
+ * want it set when we return.
+ */
+ if (matchelt != NULL) {
+ *matchelt = NULL;
+ }
+
+ return (false);
+}
+
+void
+dns_acl_attach(dns_acl_t *source, dns_acl_t **target) {
+ REQUIRE(DNS_ACL_VALID(source));
+
+ isc_refcount_increment(&source->refcount);
+ *target = source;
+}
+
+static void
+destroy(dns_acl_t *dacl) {
+ unsigned int i;
+ dns_acl_port_transports_t *port_proto;
+
+ INSIST(!ISC_LINK_LINKED(dacl, nextincache));
+
+ for (i = 0; i < dacl->length; i++) {
+ dns_aclelement_t *de = &dacl->elements[i];
+ if (de->type == dns_aclelementtype_keyname) {
+ dns_name_free(&de->keyname, dacl->mctx);
+ } else if (de->type == dns_aclelementtype_nestedacl) {
+ dns_acl_detach(&de->nestedacl);
+ }
+ }
+ if (dacl->elements != NULL) {
+ isc_mem_put(dacl->mctx, dacl->elements,
+ dacl->alloc * sizeof(dns_aclelement_t));
+ }
+ if (dacl->name != NULL) {
+ isc_mem_free(dacl->mctx, dacl->name);
+ }
+ if (dacl->iptable != NULL) {
+ dns_iptable_detach(&dacl->iptable);
+ }
+
+ port_proto = ISC_LIST_HEAD(dacl->ports_and_transports);
+ while (port_proto != NULL) {
+ dns_acl_port_transports_t *next = NULL;
+
+ next = ISC_LIST_NEXT(port_proto, link);
+ ISC_LIST_DEQUEUE(dacl->ports_and_transports, port_proto, link);
+ isc_mem_put(dacl->mctx, port_proto, sizeof(*port_proto));
+ port_proto = next;
+ }
+
+ isc_refcount_destroy(&dacl->refcount);
+ dacl->magic = 0;
+ isc_mem_putanddetach(&dacl->mctx, dacl, sizeof(*dacl));
+}
+
+void
+dns_acl_detach(dns_acl_t **aclp) {
+ REQUIRE(aclp != NULL && DNS_ACL_VALID(*aclp));
+ dns_acl_t *acl = *aclp;
+ *aclp = NULL;
+
+ if (isc_refcount_decrement(&acl->refcount) == 1) {
+ destroy(acl);
+ }
+}
+
+static isc_once_t insecure_prefix_once = ISC_ONCE_INIT;
+static isc_mutex_t insecure_prefix_lock;
+static bool insecure_prefix_found;
+
+static void
+initialize_action(void) {
+ isc_mutex_init(&insecure_prefix_lock);
+}
+
+/*
+ * Called via isc_radix_process() to find IP table nodes that are
+ * insecure.
+ */
+static void
+is_insecure(isc_prefix_t *prefix, void **data) {
+ /*
+ * If all nonexistent or negative then this node is secure.
+ */
+ if ((data[0] == NULL || !*(bool *)data[0]) &&
+ (data[1] == NULL || !*(bool *)data[1]))
+ {
+ return;
+ }
+
+ /*
+ * If a loopback address found and the other family
+ * entry doesn't exist or is negative, return.
+ */
+ if (prefix->bitlen == 32 &&
+ htonl(prefix->add.sin.s_addr) == INADDR_LOOPBACK &&
+ (data[1] == NULL || !*(bool *)data[1]))
+ {
+ return;
+ }
+
+ if (prefix->bitlen == 128 && IN6_IS_ADDR_LOOPBACK(&prefix->add.sin6) &&
+ (data[0] == NULL || !*(bool *)data[0]))
+ {
+ return;
+ }
+
+ /* Non-negated, non-loopback */
+ insecure_prefix_found = true; /* LOCKED */
+ return;
+}
+
+/*
+ * Return true iff the acl 'a' is considered insecure, that is,
+ * if it contains IP addresses other than those of the local host.
+ * This is intended for applications such as printing warning
+ * messages for suspect ACLs; it is not intended for making access
+ * control decisions. We make no guarantee that an ACL for which
+ * this function returns false is safe.
+ */
+bool
+dns_acl_isinsecure(const dns_acl_t *a) {
+ unsigned int i;
+ bool insecure;
+
+ RUNTIME_CHECK(isc_once_do(&insecure_prefix_once, initialize_action) ==
+ ISC_R_SUCCESS);
+
+ /*
+ * Walk radix tree to find out if there are any non-negated,
+ * non-loopback prefixes.
+ */
+ LOCK(&insecure_prefix_lock);
+ insecure_prefix_found = false;
+ isc_radix_process(a->iptable->radix, is_insecure);
+ insecure = insecure_prefix_found;
+ UNLOCK(&insecure_prefix_lock);
+ if (insecure) {
+ return (true);
+ }
+
+ /* Now check non-radix elements */
+ for (i = 0; i < a->length; i++) {
+ dns_aclelement_t *e = &a->elements[i];
+
+ /* A negated match can never be insecure. */
+ if (e->negative) {
+ continue;
+ }
+
+ switch (e->type) {
+ case dns_aclelementtype_keyname:
+ case dns_aclelementtype_localhost:
+ continue;
+
+ case dns_aclelementtype_nestedacl:
+ if (dns_acl_isinsecure(e->nestedacl)) {
+ return (true);
+ }
+ continue;
+
+#if defined(HAVE_GEOIP2)
+ case dns_aclelementtype_geoip:
+#endif /* if defined(HAVE_GEOIP2) */
+ case dns_aclelementtype_localnets:
+ return (true);
+
+ default:
+ UNREACHABLE();
+ }
+ }
+
+ /* No insecure elements were found. */
+ return (false);
+}
+
+/*%
+ * Check whether an address/signer is allowed by a given acl/aclenv.
+ */
+bool
+dns_acl_allowed(isc_netaddr_t *addr, const dns_name_t *signer, dns_acl_t *acl,
+ dns_aclenv_t *aclenv) {
+ int match;
+ isc_result_t result;
+
+ if (acl == NULL) {
+ return (true);
+ }
+ result = dns_acl_match(addr, signer, acl, aclenv, &match, NULL);
+ if (result == ISC_R_SUCCESS && match > 0) {
+ return (true);
+ }
+ return (false);
+}
+
+/*
+ * Initialize ACL environment, setting up localhost and localnets ACLs
+ */
+isc_result_t
+dns_aclenv_create(isc_mem_t *mctx, dns_aclenv_t **envp) {
+ isc_result_t result;
+ dns_aclenv_t *env = isc_mem_get(mctx, sizeof(*env));
+ *env = (dns_aclenv_t){ 0 };
+
+ isc_mem_attach(mctx, &env->mctx);
+ isc_refcount_init(&env->references, 1);
+ isc_rwlock_init(&env->rwlock, 0, 0);
+
+ result = dns_acl_create(mctx, 0, &env->localhost);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup_rwlock;
+ }
+ result = dns_acl_create(mctx, 0, &env->localnets);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup_localhost;
+ }
+ env->match_mapped = false;
+#if defined(HAVE_GEOIP2)
+ env->geoip = NULL;
+#endif /* if defined(HAVE_GEOIP2) */
+
+ env->magic = DNS_ACLENV_MAGIC;
+
+ *envp = env;
+
+ return (ISC_R_SUCCESS);
+
+cleanup_localhost:
+ dns_acl_detach(&env->localhost);
+cleanup_rwlock:
+ isc_rwlock_destroy(&env->rwlock);
+ isc_mem_putanddetach(&env->mctx, env, sizeof(*env));
+ return (result);
+}
+
+void
+dns_aclenv_set(dns_aclenv_t *env, dns_acl_t *localhost, dns_acl_t *localnets) {
+ REQUIRE(VALID_ACLENV(env));
+
+ RWLOCK(&env->rwlock, isc_rwlocktype_write);
+ dns_acl_detach(&env->localhost);
+ dns_acl_attach(localhost, &env->localhost);
+ dns_acl_detach(&env->localnets);
+ dns_acl_attach(localnets, &env->localnets);
+ RWUNLOCK(&env->rwlock, isc_rwlocktype_write);
+}
+
+void
+dns_aclenv_copy(dns_aclenv_t *t, dns_aclenv_t *s) {
+ REQUIRE(VALID_ACLENV(s));
+ REQUIRE(VALID_ACLENV(t));
+
+ RWLOCK(&t->rwlock, isc_rwlocktype_write);
+ RWLOCK(&s->rwlock, isc_rwlocktype_read);
+ dns_acl_detach(&t->localhost);
+ dns_acl_attach(s->localhost, &t->localhost);
+ dns_acl_detach(&t->localnets);
+ dns_acl_attach(s->localnets, &t->localnets);
+
+ t->match_mapped = s->match_mapped;
+#if defined(HAVE_GEOIP2)
+ t->geoip = s->geoip;
+#endif /* if defined(HAVE_GEOIP2) */
+
+ RWUNLOCK(&s->rwlock, isc_rwlocktype_read);
+ RWUNLOCK(&t->rwlock, isc_rwlocktype_write);
+}
+
+static void
+dns__aclenv_destroy(dns_aclenv_t *aclenv) {
+ REQUIRE(VALID_ACLENV(aclenv));
+
+ aclenv->magic = 0;
+
+ isc_refcount_destroy(&aclenv->references);
+ dns_acl_detach(&aclenv->localhost);
+ dns_acl_detach(&aclenv->localnets);
+ isc_rwlock_destroy(&aclenv->rwlock);
+
+ isc_mem_putanddetach(&aclenv->mctx, aclenv, sizeof(*aclenv));
+}
+
+void
+dns_aclenv_attach(dns_aclenv_t *source, dns_aclenv_t **targetp) {
+ REQUIRE(VALID_ACLENV(source));
+ REQUIRE(targetp != NULL && *targetp == NULL);
+
+ isc_refcount_increment(&source->references);
+ *targetp = source;
+}
+
+void
+dns_aclenv_detach(dns_aclenv_t **aclenvp) {
+ dns_aclenv_t *aclenv = NULL;
+
+ REQUIRE(aclenvp != NULL && VALID_ACLENV(*aclenvp));
+
+ aclenv = *aclenvp;
+ *aclenvp = NULL;
+
+ if (isc_refcount_decrement(&aclenv->references) == 1) {
+ dns__aclenv_destroy(aclenv);
+ }
+}
+
+void
+dns_acl_add_port_transports(dns_acl_t *acl, const in_port_t port,
+ const uint32_t transports, const bool encrypted,
+ const bool negative) {
+ dns_acl_port_transports_t *port_proto;
+ REQUIRE(DNS_ACL_VALID(acl));
+ REQUIRE(port != 0 || transports != 0);
+
+ port_proto = isc_mem_get(acl->mctx, sizeof(*port_proto));
+ *port_proto = (dns_acl_port_transports_t){ .port = port,
+ .transports = transports,
+ .encrypted = encrypted,
+ .negative = negative };
+
+ ISC_LINK_INIT(port_proto, link);
+
+ ISC_LIST_APPEND(acl->ports_and_transports, port_proto, link);
+ acl->port_proto_entries++;
+}
+
+void
+dns_acl_merge_ports_transports(dns_acl_t *dest, dns_acl_t *source, bool pos) {
+ dns_acl_port_transports_t *next;
+
+ REQUIRE(DNS_ACL_VALID(dest));
+ REQUIRE(DNS_ACL_VALID(source));
+
+ const bool negative = !pos;
+
+ /*
+ * Merge ports and transports
+ */
+ for (next = ISC_LIST_HEAD(source->ports_and_transports); next != NULL;
+ next = ISC_LIST_NEXT(next, link))
+ {
+ const bool next_positive = !next->negative;
+ bool add_negative;
+
+ /*
+ * Reverse sense of positives if this is a negative acl. The
+ * logic is used (and, thus, enforced) by dns_acl_merge(),
+ * from which dns_acl_merge_ports_transports() is called.
+ */
+ if (negative && next_positive) {
+ add_negative = true;
+ } else {
+ add_negative = next->negative;
+ }
+
+ dns_acl_add_port_transports(dest, next->port, next->transports,
+ next->encrypted, add_negative);
+ }
+}
diff --git a/lib/dns/adb.c b/lib/dns/adb.c
new file mode 100644
index 0000000..449fe34
--- /dev/null
+++ b/lib/dns/adb.c
@@ -0,0 +1,4749 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/*! \file
+ *
+ * \note
+ * In finds, if task == NULL, no events will be generated, and no events
+ * have been sent. If task != NULL but taskaction == NULL, an event has been
+ * posted but not yet freed. If neither are NULL, no event was posted.
+ *
+ */
+
+#include <inttypes.h>
+#include <limits.h>
+#include <stdbool.h>
+
+#include <isc/mutexblock.h>
+#include <isc/netaddr.h>
+#include <isc/print.h>
+#include <isc/random.h>
+#include <isc/result.h>
+#include <isc/stats.h>
+#include <isc/string.h> /* Required for HP/UX (and others?) */
+#include <isc/task.h>
+#include <isc/util.h>
+
+#include <dns/adb.h>
+#include <dns/db.h>
+#include <dns/events.h>
+#include <dns/log.h>
+#include <dns/rdata.h>
+#include <dns/rdataset.h>
+#include <dns/rdatastruct.h>
+#include <dns/rdatatype.h>
+#include <dns/resolver.h>
+#include <dns/stats.h>
+
+#define DNS_ADB_MAGIC ISC_MAGIC('D', 'a', 'd', 'b')
+#define DNS_ADB_VALID(x) ISC_MAGIC_VALID(x, DNS_ADB_MAGIC)
+#define DNS_ADBNAME_MAGIC ISC_MAGIC('a', 'd', 'b', 'N')
+#define DNS_ADBNAME_VALID(x) ISC_MAGIC_VALID(x, DNS_ADBNAME_MAGIC)
+#define DNS_ADBNAMEHOOK_MAGIC ISC_MAGIC('a', 'd', 'N', 'H')
+#define DNS_ADBNAMEHOOK_VALID(x) ISC_MAGIC_VALID(x, DNS_ADBNAMEHOOK_MAGIC)
+#define DNS_ADBLAMEINFO_MAGIC ISC_MAGIC('a', 'd', 'b', 'Z')
+#define DNS_ADBLAMEINFO_VALID(x) ISC_MAGIC_VALID(x, DNS_ADBLAMEINFO_MAGIC)
+#define DNS_ADBENTRY_MAGIC ISC_MAGIC('a', 'd', 'b', 'E')
+#define DNS_ADBENTRY_VALID(x) ISC_MAGIC_VALID(x, DNS_ADBENTRY_MAGIC)
+#define DNS_ADBFETCH_MAGIC ISC_MAGIC('a', 'd', 'F', '4')
+#define DNS_ADBFETCH_VALID(x) ISC_MAGIC_VALID(x, DNS_ADBFETCH_MAGIC)
+#define DNS_ADBFETCH6_MAGIC ISC_MAGIC('a', 'd', 'F', '6')
+#define DNS_ADBFETCH6_VALID(x) ISC_MAGIC_VALID(x, DNS_ADBFETCH6_MAGIC)
+
+/*!
+ * For type 3 negative cache entries, we will remember that the address is
+ * broken for this long. XXXMLG This is also used for actual addresses, too.
+ * The intent is to keep us from constantly asking about A/AAAA records
+ * if the zone has extremely low TTLs.
+ */
+#define ADB_CACHE_MINIMUM 10 /*%< seconds */
+#define ADB_CACHE_MAXIMUM 86400 /*%< seconds (86400 = 24 hours) */
+#define ADB_ENTRY_WINDOW 1800 /*%< seconds */
+
+/*%
+ * The period in seconds after which an ADB name entry is regarded as stale
+ * and forced to be cleaned up.
+ * TODO: This should probably be configurable at run-time.
+ */
+#ifndef ADB_STALE_MARGIN
+#define ADB_STALE_MARGIN 1800
+#endif /* ifndef ADB_STALE_MARGIN */
+
+#define FREE_ITEMS 64 /*%< free count for memory pools */
+#define FILL_COUNT 16 /*%< fill count for memory pools */
+
+#define DNS_ADB_INVALIDBUCKET (-1) /*%< invalid bucket address */
+
+#define DNS_ADB_MINADBSIZE (1024U * 1024U) /*%< 1 Megabyte */
+
+typedef ISC_LIST(dns_adbname_t) dns_adbnamelist_t;
+typedef struct dns_adbnamehook dns_adbnamehook_t;
+typedef ISC_LIST(dns_adbnamehook_t) dns_adbnamehooklist_t;
+typedef struct dns_adblameinfo dns_adblameinfo_t;
+typedef ISC_LIST(dns_adbentry_t) dns_adbentrylist_t;
+typedef struct dns_adbfetch dns_adbfetch_t;
+typedef struct dns_adbfetch6 dns_adbfetch6_t;
+
+/*% dns adb structure */
+struct dns_adb {
+ unsigned int magic;
+
+ isc_mutex_t lock;
+ isc_mutex_t reflock; /*%< Covers irefcnt, erefcnt */
+ isc_mutex_t overmemlock; /*%< Covers overmem */
+ isc_mem_t *mctx;
+ isc_mem_t *hmctx;
+ dns_view_t *view;
+
+ isc_taskmgr_t *taskmgr;
+ isc_task_t *task;
+ isc_task_t *excl;
+
+ isc_interval_t tick_interval;
+ int next_cleanbucket;
+
+ unsigned int irefcnt;
+ unsigned int erefcnt;
+
+ isc_refcount_t ahrefcnt;
+ isc_refcount_t nhrefcnt;
+
+ /*!
+ * Bucketized locks and lists for names.
+ *
+ * XXXRTH Have a per-bucket structure that contains all of these?
+ */
+ unsigned int nnames;
+ isc_mutex_t namescntlock;
+ unsigned int namescnt;
+ dns_adbnamelist_t *names;
+ dns_adbnamelist_t *deadnames;
+ isc_mutex_t *namelocks;
+ bool *name_sd;
+ unsigned int *name_refcnt;
+
+ /*!
+ * Bucketized locks and lists for entries.
+ *
+ * XXXRTH Have a per-bucket structure that contains all of these?
+ */
+ unsigned int nentries;
+ isc_mutex_t entriescntlock;
+ unsigned int entriescnt;
+ dns_adbentrylist_t *entries;
+ dns_adbentrylist_t *deadentries;
+ isc_mutex_t *entrylocks;
+ bool *entry_sd; /*%< shutting down */
+ unsigned int *entry_refcnt;
+
+ isc_event_t cevent;
+ bool cevent_out;
+ atomic_bool shutting_down;
+ isc_eventlist_t whenshutdown;
+ isc_event_t growentries;
+ bool growentries_sent;
+ isc_event_t grownames;
+ bool grownames_sent;
+
+ uint32_t quota;
+ uint32_t atr_freq;
+ double atr_low;
+ double atr_high;
+ double atr_discount;
+};
+
+/*
+ * XXXMLG Document these structures.
+ */
+
+/*% dns_adbname structure */
+struct dns_adbname {
+ unsigned int magic;
+ dns_name_t name;
+ dns_adb_t *adb;
+ unsigned int partial_result;
+ unsigned int flags;
+ int lock_bucket;
+ dns_name_t target;
+ isc_stdtime_t expire_target;
+ isc_stdtime_t expire_v4;
+ isc_stdtime_t expire_v6;
+ unsigned int chains;
+ dns_adbnamehooklist_t v4;
+ dns_adbnamehooklist_t v6;
+ dns_adbfetch_t *fetch_a;
+ dns_adbfetch_t *fetch_aaaa;
+ unsigned int fetch_err;
+ unsigned int fetch6_err;
+ dns_adbfindlist_t finds;
+ /* for LRU-based management */
+ isc_stdtime_t last_used;
+
+ ISC_LINK(dns_adbname_t) plink;
+};
+
+/*% The adbfetch structure */
+struct dns_adbfetch {
+ unsigned int magic;
+ dns_fetch_t *fetch;
+ dns_rdataset_t rdataset;
+ unsigned int depth;
+};
+
+/*%
+ * This is a small widget that dangles off a dns_adbname_t. It contains a
+ * pointer to the address information about this host, and a link to the next
+ * namehook that will contain the next address this host has.
+ */
+struct dns_adbnamehook {
+ unsigned int magic;
+ dns_adbentry_t *entry;
+ ISC_LINK(dns_adbnamehook_t) plink;
+};
+
+/*%
+ * This is a small widget that holds qname-specific information about an
+ * address. Currently limited to lameness, but could just as easily be
+ * extended to other types of information about zones.
+ */
+struct dns_adblameinfo {
+ unsigned int magic;
+
+ dns_name_t qname;
+ dns_rdatatype_t qtype;
+ isc_stdtime_t lame_timer;
+
+ ISC_LINK(dns_adblameinfo_t) plink;
+};
+
+/*%
+ * An address entry. It holds quite a bit of information about addresses,
+ * including edns state (in "flags"), rtt, and of course the address of
+ * the host.
+ */
+struct dns_adbentry {
+ unsigned int magic;
+
+ int lock_bucket;
+ unsigned int refcnt;
+ unsigned int nh;
+
+ unsigned int flags;
+ unsigned int srtt;
+ uint16_t udpsize;
+ unsigned int completed;
+ unsigned int timeouts;
+ unsigned char plain;
+ unsigned char plainto;
+ unsigned char edns;
+ unsigned char ednsto;
+
+ uint8_t mode;
+ atomic_uint_fast32_t quota;
+ atomic_uint_fast32_t active;
+ double atr;
+
+ isc_sockaddr_t sockaddr;
+ unsigned char *cookie;
+ uint16_t cookielen;
+
+ isc_stdtime_t expires;
+ isc_stdtime_t lastage;
+ /*%<
+ * A nonzero 'expires' field indicates that the entry should
+ * persist until that time. This allows entries found
+ * using dns_adb_findaddrinfo() to persist for a limited time
+ * even though they are not necessarily associated with a
+ * name.
+ */
+
+ ISC_LIST(dns_adblameinfo_t) lameinfo;
+ ISC_LINK(dns_adbentry_t) plink;
+};
+
+/*
+ * Internal functions (and prototypes).
+ */
+static dns_adbname_t *
+new_adbname(dns_adb_t *, const dns_name_t *);
+static void
+free_adbname(dns_adb_t *, dns_adbname_t **);
+static dns_adbnamehook_t *
+new_adbnamehook(dns_adb_t *, dns_adbentry_t *);
+static void
+free_adbnamehook(dns_adb_t *, dns_adbnamehook_t **);
+static dns_adblameinfo_t *
+new_adblameinfo(dns_adb_t *, const dns_name_t *, dns_rdatatype_t);
+static void
+free_adblameinfo(dns_adb_t *, dns_adblameinfo_t **);
+static dns_adbentry_t *
+new_adbentry(dns_adb_t *);
+static void
+free_adbentry(dns_adb_t *, dns_adbentry_t **);
+static dns_adbfind_t *
+new_adbfind(dns_adb_t *);
+static bool
+free_adbfind(dns_adb_t *, dns_adbfind_t **);
+static dns_adbaddrinfo_t *
+new_adbaddrinfo(dns_adb_t *, dns_adbentry_t *, in_port_t);
+static dns_adbfetch_t *
+new_adbfetch(dns_adb_t *);
+static void
+free_adbfetch(dns_adb_t *, dns_adbfetch_t **);
+static dns_adbname_t *
+find_name_and_lock(dns_adb_t *, const dns_name_t *, unsigned int, int *);
+static dns_adbentry_t *
+find_entry_and_lock(dns_adb_t *, const isc_sockaddr_t *, int *, isc_stdtime_t);
+static void
+dump_adb(dns_adb_t *, FILE *, bool debug, isc_stdtime_t);
+static void
+print_dns_name(FILE *, const dns_name_t *);
+static void
+print_namehook_list(FILE *, const char *legend, dns_adb_t *adb,
+ dns_adbnamehooklist_t *list, bool debug, isc_stdtime_t now);
+static void
+print_find_list(FILE *, dns_adbname_t *);
+static void
+print_fetch_list(FILE *, dns_adbname_t *);
+static bool
+dec_adb_irefcnt(dns_adb_t *);
+static void
+inc_adb_irefcnt(dns_adb_t *);
+static void
+inc_adb_erefcnt(dns_adb_t *);
+static void
+inc_entry_refcnt(dns_adb_t *, dns_adbentry_t *, bool);
+static bool
+dec_entry_refcnt(dns_adb_t *, bool, dns_adbentry_t *, bool, isc_stdtime_t);
+static void
+violate_locking_hierarchy(isc_mutex_t *, isc_mutex_t *);
+static bool
+clean_namehooks(dns_adb_t *, dns_adbnamehooklist_t *);
+static void
+clean_target(dns_adb_t *, dns_name_t *);
+static void
+clean_finds_at_name(dns_adbname_t *, isc_eventtype_t, unsigned int);
+static bool
+check_expire_namehooks(dns_adbname_t *, isc_stdtime_t);
+static bool
+check_expire_entry(dns_adb_t *, dns_adbentry_t **, isc_stdtime_t);
+static void
+cancel_fetches_at_name(dns_adbname_t *);
+static isc_result_t
+dbfind_name(dns_adbname_t *, isc_stdtime_t, dns_rdatatype_t);
+static isc_result_t
+fetch_name(dns_adbname_t *, bool, unsigned int, isc_counter_t *qc,
+ dns_rdatatype_t);
+static void
+check_exit(dns_adb_t *);
+static void
+destroy(dns_adb_t *);
+static bool
+shutdown_names(dns_adb_t *);
+static bool
+shutdown_entries(dns_adb_t *);
+static void
+link_name(dns_adb_t *, int, dns_adbname_t *);
+static bool
+unlink_name(dns_adb_t *, dns_adbname_t *);
+static void
+link_entry(dns_adb_t *, int, dns_adbentry_t *);
+static bool
+unlink_entry(dns_adb_t *, dns_adbentry_t *);
+static bool
+kill_name(dns_adbname_t **, isc_eventtype_t);
+static void
+water(void *, int);
+static void
+dump_entry(FILE *, dns_adb_t *, dns_adbentry_t *, bool, isc_stdtime_t);
+static void
+adjustsrtt(dns_adbaddrinfo_t *addr, unsigned int rtt, unsigned int factor,
+ isc_stdtime_t now);
+static void
+shutdown_task(isc_task_t *task, isc_event_t *ev);
+static void
+log_quota(dns_adbentry_t *entry, const char *fmt, ...) ISC_FORMAT_PRINTF(2, 3);
+
+/*
+ * Private flag(s) for adbfind objects. These are used internally and
+ * are not meant to be seen or used by the caller; however, we use the
+ * same flags field as for DNS_ADBFIND_xxx flags, so we must be careful
+ * that there is no overlap between these values and those. To make it
+ * easier, we will number these starting from the most significant bit
+ * instead of the least significant.
+ */
+enum {
+ FIND_EVENT_SENT = 1 << 31,
+ FIND_EVENT_FREED = 1 << 30,
+};
+#define FIND_EVENTSENT(h) (((h)->flags & FIND_EVENT_SENT) != 0)
+#define FIND_EVENTFREED(h) (((h)->flags & FIND_EVENT_FREED) != 0)
+
+/*
+ * Private flag(s) for adbname objects.
+ */
+enum {
+ NAME_IS_DEAD = 1 << 31,
+ NAME_NEEDS_POKE = 1 << 30,
+};
+#define NAME_DEAD(n) (((n)->flags & NAME_IS_DEAD) != 0)
+#define NAME_NEEDSPOKE(n) (((n)->flags & NAME_NEEDS_POKE) != 0)
+#define NAME_GLUEOK(n) (((n)->flags & DNS_ADBFIND_GLUEOK) != 0)
+#define NAME_HINTOK(n) (((n)->flags & DNS_ADBFIND_HINTOK) != 0)
+
+/*
+ * Private flag(s) for adbentry objects. Note that these will also
+ * be used for addrinfo flags, and in resolver.c we'll use the same
+ * field for FCTX_ADDRINFO_xxx flags to store information about remote
+ * servers, so we must be careful that there is no overlap between
+ * these values and those. To make it easier, we will number these
+ * starting from the most significant bit instead of the least
+ * significant.
+ */
+enum {
+ ENTRY_IS_DEAD = 1 << 31,
+};
+
+/*
+ * To the name, address classes are all that really exist. If it has a
+ * V6 address it doesn't care if it came from a AAAA query.
+ */
+#define NAME_HAS_V4(n) (!ISC_LIST_EMPTY((n)->v4))
+#define NAME_HAS_V6(n) (!ISC_LIST_EMPTY((n)->v6))
+#define NAME_HAS_ADDRS(n) (NAME_HAS_V4(n) || NAME_HAS_V6(n))
+
+/*
+ * Fetches are broken out into A and AAAA types. In some cases,
+ * however, it makes more sense to test for a particular class of fetches,
+ * like V4 or V6 above.
+ */
+#define NAME_FETCH_A(n) ((n)->fetch_a != NULL)
+#define NAME_FETCH_AAAA(n) ((n)->fetch_aaaa != NULL)
+#define NAME_FETCH(n) (NAME_FETCH_A(n) || NAME_FETCH_AAAA(n))
+
+/*
+ * Find options and tests to see if there are addresses on the list.
+ */
+#define FIND_WANTEVENT(fn) (((fn)->options & DNS_ADBFIND_WANTEVENT) != 0)
+#define FIND_WANTEMPTYEVENT(fn) (((fn)->options & DNS_ADBFIND_EMPTYEVENT) != 0)
+#define FIND_AVOIDFETCHES(fn) (((fn)->options & DNS_ADBFIND_AVOIDFETCHES) != 0)
+#define FIND_STARTATZONE(fn) (((fn)->options & DNS_ADBFIND_STARTATZONE) != 0)
+#define FIND_HINTOK(fn) (((fn)->options & DNS_ADBFIND_HINTOK) != 0)
+#define FIND_GLUEOK(fn) (((fn)->options & DNS_ADBFIND_GLUEOK) != 0)
+#define FIND_HAS_ADDRS(fn) (!ISC_LIST_EMPTY((fn)->list))
+#define FIND_RETURNLAME(fn) (((fn)->options & DNS_ADBFIND_RETURNLAME) != 0)
+#define FIND_NOFETCH(fn) (((fn)->options & DNS_ADBFIND_NOFETCH) != 0)
+
+/*
+ * These are currently used on simple unsigned ints, so they are
+ * not really associated with any particular type.
+ */
+#define WANT_INET(x) (((x)&DNS_ADBFIND_INET) != 0)
+#define WANT_INET6(x) (((x)&DNS_ADBFIND_INET6) != 0)
+
+#define EXPIRE_OK(exp, now) ((exp == INT_MAX) || (exp < now))
+
+/*
+ * Find out if the flags on a name (nf) indicate if it is a hint or
+ * glue, and compare this to the appropriate bits set in o, to see if
+ * this is ok.
+ */
+#define GLUE_OK(nf, o) (!NAME_GLUEOK(nf) || (((o)&DNS_ADBFIND_GLUEOK) != 0))
+#define HINT_OK(nf, o) (!NAME_HINTOK(nf) || (((o)&DNS_ADBFIND_HINTOK) != 0))
+#define GLUEHINT_OK(nf, o) (GLUE_OK(nf, o) || HINT_OK(nf, o))
+#define STARTATZONE_MATCHES(nf, o) \
+ (((nf)->flags & DNS_ADBFIND_STARTATZONE) == \
+ ((o)&DNS_ADBFIND_STARTATZONE))
+
+#define ENTER_LEVEL ISC_LOG_DEBUG(50)
+#define EXIT_LEVEL ENTER_LEVEL
+#define CLEAN_LEVEL ISC_LOG_DEBUG(100)
+#define DEF_LEVEL ISC_LOG_DEBUG(5)
+#define NCACHE_LEVEL ISC_LOG_DEBUG(20)
+
+#define NCACHE_RESULT(r) \
+ ((r) == DNS_R_NCACHENXDOMAIN || (r) == DNS_R_NCACHENXRRSET)
+#define AUTH_NX(r) ((r) == DNS_R_NXDOMAIN || (r) == DNS_R_NXRRSET)
+#define NXDOMAIN_RESULT(r) \
+ ((r) == DNS_R_NXDOMAIN || (r) == DNS_R_NCACHENXDOMAIN)
+#define NXRRSET_RESULT(r) \
+ ((r) == DNS_R_NCACHENXRRSET || (r) == DNS_R_NXRRSET || \
+ (r) == DNS_R_HINTNXRRSET)
+
+/*
+ * Error state rankings.
+ */
+
+#define FIND_ERR_SUCCESS 0 /* highest rank */
+#define FIND_ERR_CANCELED 1
+#define FIND_ERR_FAILURE 2
+#define FIND_ERR_NXDOMAIN 3
+#define FIND_ERR_NXRRSET 4
+#define FIND_ERR_UNEXPECTED 5
+#define FIND_ERR_NOTFOUND 6
+#define FIND_ERR_MAX 7
+
+static const char *errnames[] = { "success", "canceled", "failure",
+ "nxdomain", "nxrrset", "unexpected",
+ "not_found" };
+
+#define NEWERR(old, new) (ISC_MIN((old), (new)))
+
+static isc_result_t find_err_map[FIND_ERR_MAX] = {
+ ISC_R_SUCCESS, ISC_R_CANCELED, ISC_R_FAILURE, DNS_R_NXDOMAIN,
+ DNS_R_NXRRSET, ISC_R_UNEXPECTED, ISC_R_NOTFOUND /* not YET found */
+};
+
+static void
+DP(int level, const char *format, ...) ISC_FORMAT_PRINTF(2, 3);
+
+static void
+DP(int level, const char *format, ...) {
+ va_list args;
+
+ va_start(args, format);
+ isc_log_vwrite(dns_lctx, DNS_LOGCATEGORY_DATABASE, DNS_LOGMODULE_ADB,
+ level, format, args);
+ va_end(args);
+}
+
+/*%
+ * Increment resolver-related statistics counters.
+ */
+static void
+inc_stats(dns_adb_t *adb, isc_statscounter_t counter) {
+ if (adb->view->resstats != NULL) {
+ isc_stats_increment(adb->view->resstats, counter);
+ }
+}
+
+/*%
+ * Set adb-related statistics counters.
+ */
+static void
+set_adbstat(dns_adb_t *adb, uint64_t val, isc_statscounter_t counter) {
+ if (adb->view->adbstats != NULL) {
+ isc_stats_set(adb->view->adbstats, val, counter);
+ }
+}
+
+static void
+dec_adbstats(dns_adb_t *adb, isc_statscounter_t counter) {
+ if (adb->view->adbstats != NULL) {
+ isc_stats_decrement(adb->view->adbstats, counter);
+ }
+}
+
+static void
+inc_adbstats(dns_adb_t *adb, isc_statscounter_t counter) {
+ if (adb->view->adbstats != NULL) {
+ isc_stats_increment(adb->view->adbstats, counter);
+ }
+}
+
+static dns_ttl_t
+ttlclamp(dns_ttl_t ttl) {
+ if (ttl < ADB_CACHE_MINIMUM) {
+ ttl = ADB_CACHE_MINIMUM;
+ }
+ if (ttl > ADB_CACHE_MAXIMUM) {
+ ttl = ADB_CACHE_MAXIMUM;
+ }
+
+ return (ttl);
+}
+
+/*
+ * Hashing is most efficient if the number of buckets is prime.
+ * The sequence below is the closest previous primes to 2^n and
+ * 1.5 * 2^n, for values of n from 10 to 28. (The tables will
+ * no longer grow beyond 2^28 entries.)
+ */
+static const unsigned nbuckets[] = {
+ 1021, 1531, 2039, 3067, 4093, 6143,
+ 8191, 12281, 16381, 24571, 32749, 49193,
+ 65521, 98299, 131071, 199603, 262139, 393209,
+ 524287, 768431, 1048573, 1572853, 2097143, 3145721,
+ 4194301, 6291449, 8388593, 12582893, 16777213, 25165813,
+ 33554393, 50331599, 67108859, 100663291, 134217689, 201326557,
+ 268535431, 0
+};
+
+static void
+grow_entries(isc_task_t *task, isc_event_t *ev) {
+ dns_adb_t *adb;
+ dns_adbentry_t *e;
+ dns_adbentrylist_t *newdeadentries = NULL;
+ dns_adbentrylist_t *newentries = NULL;
+ bool *newentry_sd = NULL;
+ isc_mutex_t *newentrylocks = NULL;
+ isc_result_t result;
+ unsigned int *newentry_refcnt = NULL;
+ unsigned int i, n, bucket;
+
+ adb = ev->ev_arg;
+ INSIST(DNS_ADB_VALID(adb));
+
+ isc_event_free(&ev);
+
+ result = isc_task_beginexclusive(task);
+ if (result != ISC_R_SUCCESS) {
+ goto check_exit;
+ }
+
+ i = 0;
+ while (nbuckets[i] != 0 && adb->nentries >= nbuckets[i]) {
+ i++;
+ }
+ if (nbuckets[i] != 0) {
+ n = nbuckets[i];
+ } else {
+ goto done;
+ }
+
+ DP(ISC_LOG_INFO, "adb: grow_entries to %u starting", n);
+
+ /*
+ * Are we shutting down?
+ */
+ for (i = 0; i < adb->nentries; i++) {
+ if (adb->entry_sd[i]) {
+ goto cleanup;
+
+ /*
+ * Grab all the resources we need.
+ */
+ }
+ }
+
+ /*
+ * Grab all the resources we need.
+ */
+ newentries = isc_mem_get(adb->hmctx, sizeof(*newentries) * n);
+ newdeadentries = isc_mem_get(adb->hmctx, sizeof(*newdeadentries) * n);
+ newentrylocks = isc_mem_get(adb->hmctx, sizeof(*newentrylocks) * n);
+ newentry_sd = isc_mem_get(adb->hmctx, sizeof(*newentry_sd) * n);
+ newentry_refcnt = isc_mem_get(adb->hmctx, sizeof(*newentry_refcnt) * n);
+
+ /*
+ * Initialise the new resources.
+ */
+ isc_mutexblock_init(newentrylocks, n);
+
+ for (i = 0; i < n; i++) {
+ ISC_LIST_INIT(newentries[i]);
+ ISC_LIST_INIT(newdeadentries[i]);
+ newentry_sd[i] = false;
+ newentry_refcnt[i] = 0;
+ adb->irefcnt++;
+ }
+
+ /*
+ * Move entries to new arrays.
+ */
+ for (i = 0; i < adb->nentries; i++) {
+ e = ISC_LIST_HEAD(adb->entries[i]);
+ while (e != NULL) {
+ ISC_LIST_UNLINK(adb->entries[i], e, plink);
+ bucket = isc_sockaddr_hash(&e->sockaddr, true) % n;
+ e->lock_bucket = bucket;
+ ISC_LIST_APPEND(newentries[bucket], e, plink);
+ INSIST(adb->entry_refcnt[i] > 0);
+ adb->entry_refcnt[i]--;
+ newentry_refcnt[bucket]++;
+ e = ISC_LIST_HEAD(adb->entries[i]);
+ }
+ e = ISC_LIST_HEAD(adb->deadentries[i]);
+ while (e != NULL) {
+ ISC_LIST_UNLINK(adb->deadentries[i], e, plink);
+ bucket = isc_sockaddr_hash(&e->sockaddr, true) % n;
+ e->lock_bucket = bucket;
+ ISC_LIST_APPEND(newdeadentries[bucket], e, plink);
+ INSIST(adb->entry_refcnt[i] > 0);
+ adb->entry_refcnt[i]--;
+ newentry_refcnt[bucket]++;
+ e = ISC_LIST_HEAD(adb->deadentries[i]);
+ }
+ INSIST(adb->entry_refcnt[i] == 0);
+ adb->irefcnt--;
+ }
+
+ /*
+ * Cleanup old resources.
+ */
+ isc_mutexblock_destroy(adb->entrylocks, adb->nentries);
+ isc_mem_put(adb->hmctx, adb->entries,
+ sizeof(*adb->entries) * adb->nentries);
+ isc_mem_put(adb->hmctx, adb->deadentries,
+ sizeof(*adb->deadentries) * adb->nentries);
+ isc_mem_put(adb->hmctx, adb->entrylocks,
+ sizeof(*adb->entrylocks) * adb->nentries);
+ isc_mem_put(adb->hmctx, adb->entry_sd,
+ sizeof(*adb->entry_sd) * adb->nentries);
+ isc_mem_put(adb->hmctx, adb->entry_refcnt,
+ sizeof(*adb->entry_refcnt) * adb->nentries);
+
+ /*
+ * Install new resources.
+ */
+ adb->entries = newentries;
+ adb->deadentries = newdeadentries;
+ adb->entrylocks = newentrylocks;
+ adb->entry_sd = newentry_sd;
+ adb->entry_refcnt = newentry_refcnt;
+ adb->nentries = n;
+
+ set_adbstat(adb, adb->nentries, dns_adbstats_nentries);
+
+ /*
+ * Only on success do we set adb->growentries_sent to false.
+ * This will prevent us being continuously being called on error.
+ */
+ adb->growentries_sent = false;
+ goto done;
+
+cleanup:
+ if (newentries != NULL) {
+ isc_mem_put(adb->mctx, newentries, sizeof(*newentries) * n);
+ }
+ if (newdeadentries != NULL) {
+ isc_mem_put(adb->mctx, newdeadentries,
+ sizeof(*newdeadentries) * n);
+ }
+ if (newentrylocks != NULL) {
+ isc_mem_put(adb->mctx, newentrylocks,
+ sizeof(*newentrylocks) * n);
+ }
+ if (newentry_sd != NULL) {
+ isc_mem_put(adb->mctx, newentry_sd, sizeof(*newentry_sd) * n);
+ }
+ if (newentry_refcnt != NULL) {
+ isc_mem_put(adb->mctx, newentry_refcnt,
+ sizeof(*newentry_refcnt) * n);
+ }
+done:
+ isc_task_endexclusive(task);
+
+check_exit:
+ LOCK(&adb->lock);
+ if (dec_adb_irefcnt(adb)) {
+ check_exit(adb);
+ }
+ UNLOCK(&adb->lock);
+ DP(ISC_LOG_INFO, "adb: grow_entries finished");
+}
+
+static void
+grow_names(isc_task_t *task, isc_event_t *ev) {
+ dns_adb_t *adb;
+ dns_adbname_t *name;
+ dns_adbnamelist_t *newdeadnames = NULL;
+ dns_adbnamelist_t *newnames = NULL;
+ bool *newname_sd = NULL;
+ isc_mutex_t *newnamelocks = NULL;
+ isc_result_t result;
+ unsigned int *newname_refcnt = NULL;
+ unsigned int i, n;
+ unsigned int bucket;
+
+ adb = ev->ev_arg;
+ INSIST(DNS_ADB_VALID(adb));
+
+ isc_event_free(&ev);
+
+ result = isc_task_beginexclusive(task);
+ if (result != ISC_R_SUCCESS) {
+ goto check_exit;
+ }
+
+ i = 0;
+ while (nbuckets[i] != 0 && adb->nnames >= nbuckets[i]) {
+ i++;
+ }
+ if (nbuckets[i] != 0) {
+ n = nbuckets[i];
+ } else {
+ goto done;
+ }
+
+ DP(ISC_LOG_INFO, "adb: grow_names to %u starting", n);
+
+ /*
+ * Are we shutting down?
+ */
+ for (i = 0; i < adb->nnames; i++) {
+ if (adb->name_sd[i]) {
+ goto cleanup;
+
+ /*
+ * Grab all the resources we need.
+ */
+ }
+ }
+
+ /*
+ * Grab all the resources we need.
+ */
+ newnames = isc_mem_get(adb->hmctx, sizeof(*newnames) * n);
+ newdeadnames = isc_mem_get(adb->hmctx, sizeof(*newdeadnames) * n);
+ newnamelocks = isc_mem_get(adb->hmctx, sizeof(*newnamelocks) * n);
+ newname_sd = isc_mem_get(adb->hmctx, sizeof(*newname_sd) * n);
+ newname_refcnt = isc_mem_get(adb->hmctx, sizeof(*newname_refcnt) * n);
+
+ /*
+ * Initialise the new resources.
+ */
+ isc_mutexblock_init(newnamelocks, n);
+
+ for (i = 0; i < n; i++) {
+ ISC_LIST_INIT(newnames[i]);
+ ISC_LIST_INIT(newdeadnames[i]);
+ newname_sd[i] = false;
+ newname_refcnt[i] = 0;
+ adb->irefcnt++;
+ }
+
+ /*
+ * Move names to new arrays.
+ */
+ for (i = 0; i < adb->nnames; i++) {
+ name = ISC_LIST_HEAD(adb->names[i]);
+ while (name != NULL) {
+ ISC_LIST_UNLINK(adb->names[i], name, plink);
+ bucket = dns_name_fullhash(&name->name, true) % n;
+ name->lock_bucket = bucket;
+ ISC_LIST_APPEND(newnames[bucket], name, plink);
+ INSIST(adb->name_refcnt[i] > 0);
+ adb->name_refcnt[i]--;
+ newname_refcnt[bucket]++;
+ name = ISC_LIST_HEAD(adb->names[i]);
+ }
+ name = ISC_LIST_HEAD(adb->deadnames[i]);
+ while (name != NULL) {
+ ISC_LIST_UNLINK(adb->deadnames[i], name, plink);
+ bucket = dns_name_fullhash(&name->name, true) % n;
+ name->lock_bucket = bucket;
+ ISC_LIST_APPEND(newdeadnames[bucket], name, plink);
+ INSIST(adb->name_refcnt[i] > 0);
+ adb->name_refcnt[i]--;
+ newname_refcnt[bucket]++;
+ name = ISC_LIST_HEAD(adb->deadnames[i]);
+ }
+ INSIST(adb->name_refcnt[i] == 0);
+ adb->irefcnt--;
+ }
+
+ /*
+ * Cleanup old resources.
+ */
+ isc_mutexblock_destroy(adb->namelocks, adb->nnames);
+ isc_mem_put(adb->hmctx, adb->names, sizeof(*adb->names) * adb->nnames);
+ isc_mem_put(adb->hmctx, adb->deadnames,
+ sizeof(*adb->deadnames) * adb->nnames);
+ isc_mem_put(adb->hmctx, adb->namelocks,
+ sizeof(*adb->namelocks) * adb->nnames);
+ isc_mem_put(adb->hmctx, adb->name_sd,
+ sizeof(*adb->name_sd) * adb->nnames);
+ isc_mem_put(adb->hmctx, adb->name_refcnt,
+ sizeof(*adb->name_refcnt) * adb->nnames);
+
+ /*
+ * Install new resources.
+ */
+ adb->names = newnames;
+ adb->deadnames = newdeadnames;
+ adb->namelocks = newnamelocks;
+ adb->name_sd = newname_sd;
+ adb->name_refcnt = newname_refcnt;
+ adb->nnames = n;
+
+ set_adbstat(adb, adb->nnames, dns_adbstats_nnames);
+
+ /*
+ * Only on success do we set adb->grownames_sent to false.
+ * This will prevent us being continuously being called on error.
+ */
+ adb->grownames_sent = false;
+ goto done;
+
+cleanup:
+ if (newnames != NULL) {
+ isc_mem_put(adb->hmctx, newnames, sizeof(*newnames) * n);
+ }
+ if (newdeadnames != NULL) {
+ isc_mem_put(adb->hmctx, newdeadnames,
+ sizeof(*newdeadnames) * n);
+ }
+ if (newnamelocks != NULL) {
+ isc_mem_put(adb->hmctx, newnamelocks,
+ sizeof(*newnamelocks) * n);
+ }
+ if (newname_sd != NULL) {
+ isc_mem_put(adb->hmctx, newname_sd, sizeof(*newname_sd) * n);
+ }
+ if (newname_refcnt != NULL) {
+ isc_mem_put(adb->hmctx, newname_refcnt,
+ sizeof(*newname_refcnt) * n);
+ }
+done:
+ isc_task_endexclusive(task);
+
+check_exit:
+ LOCK(&adb->lock);
+ if (dec_adb_irefcnt(adb)) {
+ check_exit(adb);
+ }
+ UNLOCK(&adb->lock);
+ DP(ISC_LOG_INFO, "adb: grow_names finished");
+}
+
+/*
+ * Requires the adbname bucket be locked and that no entry buckets be locked.
+ *
+ * This code handles A and AAAA rdatasets only.
+ */
+static isc_result_t
+import_rdataset(dns_adbname_t *adbname, dns_rdataset_t *rdataset,
+ isc_stdtime_t now) {
+ isc_result_t result;
+ dns_adb_t *adb = NULL;
+ dns_adbnamehook_t *nh = NULL;
+ dns_adbnamehook_t *anh = NULL;
+ dns_rdata_t rdata = DNS_RDATA_INIT;
+ struct in_addr ina;
+ struct in6_addr in6a;
+ isc_sockaddr_t sockaddr;
+ dns_adbentry_t *foundentry = NULL; /* NO CLEAN UP! */
+ int addr_bucket;
+ bool new_addresses_added;
+ dns_rdatatype_t rdtype;
+ dns_adbnamehooklist_t *hookhead = NULL;
+
+ INSIST(DNS_ADBNAME_VALID(adbname));
+ adb = adbname->adb;
+ INSIST(DNS_ADB_VALID(adb));
+
+ rdtype = rdataset->type;
+ INSIST((rdtype == dns_rdatatype_a) || (rdtype == dns_rdatatype_aaaa));
+
+ addr_bucket = DNS_ADB_INVALIDBUCKET;
+ new_addresses_added = false;
+
+ result = dns_rdataset_first(rdataset);
+ while (result == ISC_R_SUCCESS) {
+ dns_rdata_reset(&rdata);
+ dns_rdataset_current(rdataset, &rdata);
+ if (rdtype == dns_rdatatype_a) {
+ INSIST(rdata.length == 4);
+ memmove(&ina.s_addr, rdata.data, 4);
+ isc_sockaddr_fromin(&sockaddr, &ina, 0);
+ hookhead = &adbname->v4;
+ } else {
+ INSIST(rdata.length == 16);
+ memmove(in6a.s6_addr, rdata.data, 16);
+ isc_sockaddr_fromin6(&sockaddr, &in6a, 0);
+ hookhead = &adbname->v6;
+ }
+
+ INSIST(nh == NULL);
+ nh = new_adbnamehook(adb, NULL);
+ foundentry = find_entry_and_lock(adb, &sockaddr, &addr_bucket,
+ now);
+ if (foundentry == NULL) {
+ dns_adbentry_t *entry;
+
+ entry = new_adbentry(adb);
+ entry->sockaddr = sockaddr;
+ entry->refcnt = 1;
+ entry->nh = 1;
+
+ nh->entry = entry;
+
+ link_entry(adb, addr_bucket, entry);
+ } else {
+ for (anh = ISC_LIST_HEAD(*hookhead); anh != NULL;
+ anh = ISC_LIST_NEXT(anh, plink))
+ {
+ if (anh->entry == foundentry) {
+ break;
+ }
+ }
+ if (anh == NULL) {
+ foundentry->refcnt++;
+ foundentry->nh++;
+ nh->entry = foundentry;
+ } else {
+ free_adbnamehook(adb, &nh);
+ }
+ }
+
+ new_addresses_added = true;
+ if (nh != NULL) {
+ ISC_LIST_APPEND(*hookhead, nh, plink);
+ }
+ nh = NULL;
+ result = dns_rdataset_next(rdataset);
+ }
+
+ if (addr_bucket != DNS_ADB_INVALIDBUCKET) {
+ UNLOCK(&adb->entrylocks[addr_bucket]);
+ }
+
+ if (rdataset->trust == dns_trust_glue ||
+ rdataset->trust == dns_trust_additional)
+ {
+ rdataset->ttl = ADB_CACHE_MINIMUM;
+ } else if (rdataset->trust == dns_trust_ultimate) {
+ rdataset->ttl = 0;
+ } else {
+ rdataset->ttl = ttlclamp(rdataset->ttl);
+ }
+
+ if (rdtype == dns_rdatatype_a) {
+ DP(NCACHE_LEVEL, "expire_v4 set to MIN(%u,%u) import_rdataset",
+ adbname->expire_v4, now + rdataset->ttl);
+ adbname->expire_v4 = ISC_MIN(
+ adbname->expire_v4,
+ ISC_MIN(now + ADB_ENTRY_WINDOW, now + rdataset->ttl));
+ } else {
+ DP(NCACHE_LEVEL, "expire_v6 set to MIN(%u,%u) import_rdataset",
+ adbname->expire_v6, now + rdataset->ttl);
+ adbname->expire_v6 = ISC_MIN(
+ adbname->expire_v6,
+ ISC_MIN(now + ADB_ENTRY_WINDOW, now + rdataset->ttl));
+ }
+
+ if (new_addresses_added) {
+ /*
+ * Lie a little here. This is more or less so code that cares
+ * can find out if any new information was added or not.
+ */
+ return (ISC_R_SUCCESS);
+ }
+
+ return (result);
+}
+
+/*
+ * Requires the name's bucket be locked.
+ */
+static bool
+kill_name(dns_adbname_t **n, isc_eventtype_t ev) {
+ dns_adbname_t *name;
+ bool result = false;
+ bool result4, result6;
+ int bucket;
+ dns_adb_t *adb;
+
+ INSIST(n != NULL);
+ name = *n;
+ *n = NULL;
+ INSIST(DNS_ADBNAME_VALID(name));
+ adb = name->adb;
+ INSIST(DNS_ADB_VALID(adb));
+
+ DP(DEF_LEVEL, "killing name %p", name);
+
+ /*
+ * If we're dead already, just check to see if we should go
+ * away now or not.
+ */
+ if (NAME_DEAD(name) && !NAME_FETCH(name)) {
+ result = unlink_name(adb, name);
+ free_adbname(adb, &name);
+ if (result) {
+ result = dec_adb_irefcnt(adb);
+ }
+ return (result);
+ }
+
+ /*
+ * Clean up the name's various lists. These two are destructive
+ * in that they will always empty the list.
+ */
+ clean_finds_at_name(name, ev, DNS_ADBFIND_ADDRESSMASK);
+ result4 = clean_namehooks(adb, &name->v4);
+ result6 = clean_namehooks(adb, &name->v6);
+ clean_target(adb, &name->target);
+ result = (result4 || result6);
+
+ /*
+ * If fetches are running, cancel them. If none are running, we can
+ * just kill the name here.
+ */
+ if (!NAME_FETCH(name)) {
+ INSIST(!result);
+ result = unlink_name(adb, name);
+ free_adbname(adb, &name);
+ if (result) {
+ result = dec_adb_irefcnt(adb);
+ }
+ } else {
+ cancel_fetches_at_name(name);
+ if (!NAME_DEAD(name)) {
+ bucket = name->lock_bucket;
+ ISC_LIST_UNLINK(adb->names[bucket], name, plink);
+ ISC_LIST_APPEND(adb->deadnames[bucket], name, plink);
+ name->flags |= NAME_IS_DEAD;
+ }
+ }
+ return (result);
+}
+
+/*
+ * Requires the name's bucket be locked and no entry buckets be locked.
+ */
+static bool
+check_expire_namehooks(dns_adbname_t *name, isc_stdtime_t now) {
+ dns_adb_t *adb;
+ bool result4 = false;
+ bool result6 = false;
+
+ INSIST(DNS_ADBNAME_VALID(name));
+ adb = name->adb;
+ INSIST(DNS_ADB_VALID(adb));
+
+ /*
+ * Check to see if we need to remove the v4 addresses
+ */
+ if (!NAME_FETCH_A(name) && EXPIRE_OK(name->expire_v4, now)) {
+ if (NAME_HAS_V4(name)) {
+ DP(DEF_LEVEL, "expiring v4 for name %p", name);
+ result4 = clean_namehooks(adb, &name->v4);
+ name->partial_result &= ~DNS_ADBFIND_INET;
+ }
+ name->expire_v4 = INT_MAX;
+ name->fetch_err = FIND_ERR_UNEXPECTED;
+ }
+
+ /*
+ * Check to see if we need to remove the v6 addresses
+ */
+ if (!NAME_FETCH_AAAA(name) && EXPIRE_OK(name->expire_v6, now)) {
+ if (NAME_HAS_V6(name)) {
+ DP(DEF_LEVEL, "expiring v6 for name %p", name);
+ result6 = clean_namehooks(adb, &name->v6);
+ name->partial_result &= ~DNS_ADBFIND_INET6;
+ }
+ name->expire_v6 = INT_MAX;
+ name->fetch6_err = FIND_ERR_UNEXPECTED;
+ }
+
+ /*
+ * Check to see if we need to remove the alias target.
+ */
+ if (EXPIRE_OK(name->expire_target, now)) {
+ clean_target(adb, &name->target);
+ name->expire_target = INT_MAX;
+ }
+ return (result4 || result6);
+}
+
+/*
+ * Requires the name's bucket be locked.
+ */
+static void
+link_name(dns_adb_t *adb, int bucket, dns_adbname_t *name) {
+ INSIST(name->lock_bucket == DNS_ADB_INVALIDBUCKET);
+
+ ISC_LIST_PREPEND(adb->names[bucket], name, plink);
+ name->lock_bucket = bucket;
+ adb->name_refcnt[bucket]++;
+}
+
+/*
+ * Requires the name's bucket be locked.
+ */
+static bool
+unlink_name(dns_adb_t *adb, dns_adbname_t *name) {
+ int bucket;
+ bool result = false;
+
+ bucket = name->lock_bucket;
+ INSIST(bucket != DNS_ADB_INVALIDBUCKET);
+
+ if (NAME_DEAD(name)) {
+ ISC_LIST_UNLINK(adb->deadnames[bucket], name, plink);
+ } else {
+ ISC_LIST_UNLINK(adb->names[bucket], name, plink);
+ }
+ name->lock_bucket = DNS_ADB_INVALIDBUCKET;
+ INSIST(adb->name_refcnt[bucket] > 0);
+ adb->name_refcnt[bucket]--;
+ if (adb->name_sd[bucket] && adb->name_refcnt[bucket] == 0) {
+ result = true;
+ }
+ return (result);
+}
+
+/*
+ * Requires the entry's bucket be locked.
+ */
+static void
+link_entry(dns_adb_t *adb, int bucket, dns_adbentry_t *entry) {
+ int i;
+ dns_adbentry_t *e;
+
+ if (isc_mem_isovermem(adb->mctx)) {
+ for (i = 0; i < 2; i++) {
+ e = ISC_LIST_TAIL(adb->entries[bucket]);
+ if (e == NULL) {
+ break;
+ }
+ if (e->refcnt == 0) {
+ unlink_entry(adb, e);
+ free_adbentry(adb, &e);
+ continue;
+ }
+ INSIST((e->flags & ENTRY_IS_DEAD) == 0);
+ e->flags |= ENTRY_IS_DEAD;
+ ISC_LIST_UNLINK(adb->entries[bucket], e, plink);
+ ISC_LIST_PREPEND(adb->deadentries[bucket], e, plink);
+ }
+ }
+
+ ISC_LIST_PREPEND(adb->entries[bucket], entry, plink);
+ entry->lock_bucket = bucket;
+ adb->entry_refcnt[bucket]++;
+}
+
+/*
+ * Requires the entry's bucket be locked.
+ */
+static bool
+unlink_entry(dns_adb_t *adb, dns_adbentry_t *entry) {
+ int bucket;
+ bool result = false;
+
+ bucket = entry->lock_bucket;
+ INSIST(bucket != DNS_ADB_INVALIDBUCKET);
+
+ if ((entry->flags & ENTRY_IS_DEAD) != 0) {
+ ISC_LIST_UNLINK(adb->deadentries[bucket], entry, plink);
+ } else {
+ ISC_LIST_UNLINK(adb->entries[bucket], entry, plink);
+ }
+ entry->lock_bucket = DNS_ADB_INVALIDBUCKET;
+ INSIST(adb->entry_refcnt[bucket] > 0);
+ adb->entry_refcnt[bucket]--;
+ if (adb->entry_sd[bucket] && adb->entry_refcnt[bucket] == 0) {
+ result = true;
+ }
+ return (result);
+}
+
+static void
+violate_locking_hierarchy(isc_mutex_t *have, isc_mutex_t *want) {
+ if (isc_mutex_trylock(want) != ISC_R_SUCCESS) {
+ UNLOCK(have);
+ LOCK(want);
+ LOCK(have);
+ }
+}
+
+/*
+ * The ADB _MUST_ be locked before calling. Also, exit conditions must be
+ * checked after calling this function.
+ */
+static bool
+shutdown_names(dns_adb_t *adb) {
+ unsigned int bucket;
+ bool result = false;
+ dns_adbname_t *name;
+ dns_adbname_t *next_name;
+
+ for (bucket = 0; bucket < adb->nnames; bucket++) {
+ LOCK(&adb->namelocks[bucket]);
+ adb->name_sd[bucket] = true;
+
+ name = ISC_LIST_HEAD(adb->names[bucket]);
+ if (name == NULL) {
+ /*
+ * This bucket has no names. We must decrement the
+ * irefcnt ourselves, since it will not be
+ * automatically triggered by a name being unlinked.
+ */
+ INSIST(!result);
+ result = dec_adb_irefcnt(adb);
+ } else {
+ /*
+ * Run through the list. For each name, clean up finds
+ * found there, and cancel any fetches running. When
+ * all the fetches are canceled, the name will destroy
+ * itself.
+ */
+ while (name != NULL) {
+ next_name = ISC_LIST_NEXT(name, plink);
+ INSIST(!result);
+ result = kill_name(&name,
+ DNS_EVENT_ADBSHUTDOWN);
+ name = next_name;
+ }
+ }
+
+ UNLOCK(&adb->namelocks[bucket]);
+ }
+ return (result);
+}
+
+/*
+ * The ADB _MUST_ be locked before calling. Also, exit conditions must be
+ * checked after calling this function.
+ */
+static bool
+shutdown_entries(dns_adb_t *adb) {
+ unsigned int bucket;
+ bool result = false;
+ dns_adbentry_t *entry;
+ dns_adbentry_t *next_entry;
+
+ for (bucket = 0; bucket < adb->nentries; bucket++) {
+ LOCK(&adb->entrylocks[bucket]);
+ adb->entry_sd[bucket] = true;
+
+ entry = ISC_LIST_HEAD(adb->entries[bucket]);
+ if (adb->entry_refcnt[bucket] == 0) {
+ /*
+ * This bucket has no entries. We must decrement the
+ * irefcnt ourselves, since it will not be
+ * automatically triggered by an entry being unlinked.
+ */
+ result = dec_adb_irefcnt(adb);
+ } else {
+ /*
+ * Run through the list. Cleanup any entries not
+ * associated with names, and which are not in use.
+ */
+ while (entry != NULL) {
+ next_entry = ISC_LIST_NEXT(entry, plink);
+ if (entry->refcnt == 0 && entry->expires != 0) {
+ result = unlink_entry(adb, entry);
+ free_adbentry(adb, &entry);
+ if (result) {
+ result = dec_adb_irefcnt(adb);
+ }
+ }
+ entry = next_entry;
+ }
+ }
+
+ UNLOCK(&adb->entrylocks[bucket]);
+ }
+ return (result);
+}
+
+/*
+ * Name bucket must be locked
+ */
+static void
+cancel_fetches_at_name(dns_adbname_t *name) {
+ if (NAME_FETCH_A(name)) {
+ dns_resolver_cancelfetch(name->fetch_a->fetch);
+ }
+
+ if (NAME_FETCH_AAAA(name)) {
+ dns_resolver_cancelfetch(name->fetch_aaaa->fetch);
+ }
+}
+
+/*
+ * Assumes the name bucket is locked.
+ */
+static bool
+clean_namehooks(dns_adb_t *adb, dns_adbnamehooklist_t *namehooks) {
+ dns_adbentry_t *entry;
+ dns_adbnamehook_t *namehook;
+ int addr_bucket;
+ bool result = false;
+ bool overmem = isc_mem_isovermem(adb->mctx);
+
+ addr_bucket = DNS_ADB_INVALIDBUCKET;
+ namehook = ISC_LIST_HEAD(*namehooks);
+ while (namehook != NULL) {
+ INSIST(DNS_ADBNAMEHOOK_VALID(namehook));
+
+ /*
+ * Clean up the entry if needed.
+ */
+ entry = namehook->entry;
+ if (entry != NULL) {
+ INSIST(DNS_ADBENTRY_VALID(entry));
+
+ if (addr_bucket != entry->lock_bucket) {
+ if (addr_bucket != DNS_ADB_INVALIDBUCKET) {
+ UNLOCK(&adb->entrylocks[addr_bucket]);
+ }
+ addr_bucket = entry->lock_bucket;
+ INSIST(addr_bucket != DNS_ADB_INVALIDBUCKET);
+ LOCK(&adb->entrylocks[addr_bucket]);
+ }
+
+ entry->nh--;
+ result = dec_entry_refcnt(adb, overmem, entry, false,
+ INT_MAX);
+ }
+
+ /*
+ * Free the namehook
+ */
+ namehook->entry = NULL;
+ ISC_LIST_UNLINK(*namehooks, namehook, plink);
+ free_adbnamehook(adb, &namehook);
+
+ namehook = ISC_LIST_HEAD(*namehooks);
+ }
+
+ if (addr_bucket != DNS_ADB_INVALIDBUCKET) {
+ UNLOCK(&adb->entrylocks[addr_bucket]);
+ }
+ return (result);
+}
+
+static void
+clean_target(dns_adb_t *adb, dns_name_t *target) {
+ if (dns_name_countlabels(target) > 0) {
+ dns_name_free(target, adb->mctx);
+ dns_name_init(target, NULL);
+ }
+}
+
+static isc_result_t
+set_target(dns_adb_t *adb, const dns_name_t *name, const dns_name_t *fname,
+ dns_rdataset_t *rdataset, dns_name_t *target) {
+ isc_result_t result;
+ dns_namereln_t namereln;
+ unsigned int nlabels;
+ int order;
+ dns_rdata_t rdata = DNS_RDATA_INIT;
+ dns_fixedname_t fixed1, fixed2;
+ dns_name_t *prefix, *new_target;
+
+ REQUIRE(dns_name_countlabels(target) == 0);
+
+ if (rdataset->type == dns_rdatatype_cname) {
+ dns_rdata_cname_t cname;
+
+ /*
+ * Copy the CNAME's target into the target name.
+ */
+ result = dns_rdataset_first(rdataset);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+ dns_rdataset_current(rdataset, &rdata);
+ result = dns_rdata_tostruct(&rdata, &cname, NULL);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+ dns_name_dup(&cname.cname, adb->mctx, target);
+ dns_rdata_freestruct(&cname);
+ } else {
+ dns_rdata_dname_t dname;
+
+ INSIST(rdataset->type == dns_rdatatype_dname);
+ namereln = dns_name_fullcompare(name, fname, &order, &nlabels);
+ INSIST(namereln == dns_namereln_subdomain);
+ /*
+ * Get the target name of the DNAME.
+ */
+ result = dns_rdataset_first(rdataset);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+ dns_rdataset_current(rdataset, &rdata);
+ result = dns_rdata_tostruct(&rdata, &dname, NULL);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+ /*
+ * Construct the new target name.
+ */
+ prefix = dns_fixedname_initname(&fixed1);
+ new_target = dns_fixedname_initname(&fixed2);
+ dns_name_split(name, nlabels, prefix, NULL);
+ result = dns_name_concatenate(prefix, &dname.dname, new_target,
+ NULL);
+ dns_rdata_freestruct(&dname);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+ dns_name_dup(new_target, adb->mctx, target);
+ }
+
+ return (ISC_R_SUCCESS);
+}
+
+/*
+ * Assumes nothing is locked, since this is called by the client.
+ */
+static void
+event_free(isc_event_t *event) {
+ dns_adbfind_t *find;
+
+ INSIST(event != NULL);
+ find = event->ev_destroy_arg;
+ INSIST(DNS_ADBFIND_VALID(find));
+
+ LOCK(&find->lock);
+ find->flags |= FIND_EVENT_FREED;
+ event->ev_destroy_arg = NULL;
+ UNLOCK(&find->lock);
+}
+
+/*
+ * Assumes the name bucket is locked.
+ */
+static void
+clean_finds_at_name(dns_adbname_t *name, isc_eventtype_t evtype,
+ unsigned int addrs) {
+ isc_event_t *ev;
+ isc_task_t *task;
+ dns_adbfind_t *find;
+ dns_adbfind_t *next_find;
+ bool process;
+ unsigned int wanted, notify;
+
+ DP(ENTER_LEVEL,
+ "ENTER clean_finds_at_name, name %p, evtype %08x, addrs %08x", name,
+ evtype, addrs);
+
+ find = ISC_LIST_HEAD(name->finds);
+ while (find != NULL) {
+ LOCK(&find->lock);
+ next_find = ISC_LIST_NEXT(find, plink);
+
+ process = false;
+ wanted = find->flags & DNS_ADBFIND_ADDRESSMASK;
+ notify = wanted & addrs;
+
+ switch (evtype) {
+ case DNS_EVENT_ADBMOREADDRESSES:
+ DP(ISC_LOG_DEBUG(3), "DNS_EVENT_ADBMOREADDRESSES");
+ if ((notify) != 0) {
+ find->flags &= ~addrs;
+ process = true;
+ }
+ break;
+ case DNS_EVENT_ADBNOMOREADDRESSES:
+ DP(ISC_LOG_DEBUG(3), "DNS_EVENT_ADBNOMOREADDRESSES");
+ find->flags &= ~addrs;
+ wanted = find->flags & DNS_ADBFIND_ADDRESSMASK;
+ if (wanted == 0) {
+ process = true;
+ }
+ break;
+ default:
+ find->flags &= ~addrs;
+ process = true;
+ }
+
+ if (process) {
+ DP(DEF_LEVEL, "cfan: processing find %p", find);
+ /*
+ * Unlink the find from the name, letting the caller
+ * call dns_adb_destroyfind() on it to clean it up
+ * later.
+ */
+ ISC_LIST_UNLINK(name->finds, find, plink);
+ find->adbname = NULL;
+ find->name_bucket = DNS_ADB_INVALIDBUCKET;
+
+ INSIST(!FIND_EVENTSENT(find));
+
+ ev = &find->event;
+ task = ev->ev_sender;
+ ev->ev_sender = find;
+ find->result_v4 = find_err_map[name->fetch_err];
+ find->result_v6 = find_err_map[name->fetch6_err];
+ ev->ev_type = evtype;
+ ev->ev_destroy = event_free;
+ ev->ev_destroy_arg = find;
+
+ DP(DEF_LEVEL, "sending event %p to task %p for find %p",
+ ev, task, find);
+
+ isc_task_sendanddetach(&task, (isc_event_t **)&ev);
+ find->flags |= FIND_EVENT_SENT;
+ } else {
+ DP(DEF_LEVEL, "cfan: skipping find %p", find);
+ }
+
+ UNLOCK(&find->lock);
+ find = next_find;
+ }
+ DP(ENTER_LEVEL, "EXIT clean_finds_at_name, name %p", name);
+}
+
+static void
+check_exit(dns_adb_t *adb) {
+ isc_event_t *event;
+ /*
+ * The caller must be holding the adb lock.
+ */
+ if (atomic_load(&adb->shutting_down)) {
+ /*
+ * If there aren't any external references either, we're
+ * done. Send the control event to initiate shutdown.
+ */
+ INSIST(!adb->cevent_out); /* Sanity check. */
+ ISC_EVENT_INIT(&adb->cevent, sizeof(adb->cevent), 0, NULL,
+ DNS_EVENT_ADBCONTROL, shutdown_task, adb, adb,
+ NULL, NULL);
+ event = &adb->cevent;
+ isc_task_send(adb->task, &event);
+ adb->cevent_out = true;
+ }
+}
+
+static bool
+dec_adb_irefcnt(dns_adb_t *adb) {
+ isc_event_t *event;
+ isc_task_t *etask;
+ bool result = false;
+
+ LOCK(&adb->reflock);
+
+ INSIST(adb->irefcnt > 0);
+ adb->irefcnt--;
+
+ if (adb->irefcnt == 0) {
+ event = ISC_LIST_HEAD(adb->whenshutdown);
+ while (event != NULL) {
+ ISC_LIST_UNLINK(adb->whenshutdown, event, ev_link);
+ etask = event->ev_sender;
+ event->ev_sender = adb;
+ isc_task_sendanddetach(&etask, &event);
+ event = ISC_LIST_HEAD(adb->whenshutdown);
+ }
+ }
+
+ if (adb->irefcnt == 0 && adb->erefcnt == 0) {
+ result = true;
+ }
+ UNLOCK(&adb->reflock);
+ return (result);
+}
+
+static void
+inc_adb_irefcnt(dns_adb_t *adb) {
+ LOCK(&adb->reflock);
+ adb->irefcnt++;
+ UNLOCK(&adb->reflock);
+}
+
+static void
+inc_adb_erefcnt(dns_adb_t *adb) {
+ LOCK(&adb->reflock);
+ adb->erefcnt++;
+ UNLOCK(&adb->reflock);
+}
+
+static void
+inc_entry_refcnt(dns_adb_t *adb, dns_adbentry_t *entry, bool lock) {
+ int bucket;
+
+ bucket = entry->lock_bucket;
+
+ if (lock) {
+ LOCK(&adb->entrylocks[bucket]);
+ }
+
+ entry->refcnt++;
+
+ if (lock) {
+ UNLOCK(&adb->entrylocks[bucket]);
+ }
+}
+
+static bool
+dec_entry_refcnt(dns_adb_t *adb, bool overmem, dns_adbentry_t *entry, bool lock,
+ isc_stdtime_t now) {
+ int bucket;
+ bool destroy_entry = false;
+ bool result = false;
+
+ bucket = entry->lock_bucket;
+
+ if (lock) {
+ LOCK(&adb->entrylocks[bucket]);
+ }
+
+ INSIST(entry->refcnt > 0);
+ entry->refcnt--;
+
+ if (entry->refcnt == 0 &&
+ (adb->entry_sd[bucket] || entry->expires == 0 ||
+ (overmem && entry->expires + ADB_CACHE_MINIMUM < now) ||
+ (entry->flags & ENTRY_IS_DEAD) != 0))
+ {
+ destroy_entry = true;
+ result = unlink_entry(adb, entry);
+ }
+
+ if (lock) {
+ UNLOCK(&adb->entrylocks[bucket]);
+ }
+
+ if (!destroy_entry) {
+ return (result);
+ }
+
+ entry->lock_bucket = DNS_ADB_INVALIDBUCKET;
+
+ free_adbentry(adb, &entry);
+ if (result) {
+ result = dec_adb_irefcnt(adb);
+ }
+
+ return (result);
+}
+
+static dns_adbname_t *
+new_adbname(dns_adb_t *adb, const dns_name_t *dnsname) {
+ dns_adbname_t *name;
+
+ name = isc_mem_get(adb->mctx, sizeof(*name));
+
+ dns_name_init(&name->name, NULL);
+ dns_name_dup(dnsname, adb->mctx, &name->name);
+ dns_name_init(&name->target, NULL);
+ name->magic = DNS_ADBNAME_MAGIC;
+ name->adb = adb;
+ name->partial_result = 0;
+ name->flags = 0;
+ name->expire_v4 = INT_MAX;
+ name->expire_v6 = INT_MAX;
+ name->expire_target = INT_MAX;
+ name->chains = 0;
+ name->lock_bucket = DNS_ADB_INVALIDBUCKET;
+ ISC_LIST_INIT(name->v4);
+ ISC_LIST_INIT(name->v6);
+ name->fetch_a = NULL;
+ name->fetch_aaaa = NULL;
+ name->fetch_err = FIND_ERR_UNEXPECTED;
+ name->fetch6_err = FIND_ERR_UNEXPECTED;
+ ISC_LIST_INIT(name->finds);
+ ISC_LINK_INIT(name, plink);
+
+ LOCK(&adb->namescntlock);
+ adb->namescnt++;
+ inc_adbstats(adb, dns_adbstats_namescnt);
+ if (!adb->grownames_sent && adb->excl != NULL &&
+ adb->namescnt > (adb->nnames * 8))
+ {
+ isc_event_t *event = &adb->grownames;
+ inc_adb_irefcnt(adb);
+ isc_task_send(adb->excl, &event);
+ adb->grownames_sent = true;
+ }
+ UNLOCK(&adb->namescntlock);
+
+ return (name);
+}
+
+static void
+free_adbname(dns_adb_t *adb, dns_adbname_t **name) {
+ dns_adbname_t *n;
+
+ INSIST(name != NULL && DNS_ADBNAME_VALID(*name));
+ n = *name;
+ *name = NULL;
+
+ INSIST(!NAME_HAS_V4(n));
+ INSIST(!NAME_HAS_V6(n));
+ INSIST(!NAME_FETCH(n));
+ INSIST(ISC_LIST_EMPTY(n->finds));
+ INSIST(!ISC_LINK_LINKED(n, plink));
+ INSIST(n->lock_bucket == DNS_ADB_INVALIDBUCKET);
+ INSIST(n->adb == adb);
+
+ n->magic = 0;
+ dns_name_free(&n->name, adb->mctx);
+
+ isc_mem_put(adb->mctx, n, sizeof(*n));
+ LOCK(&adb->namescntlock);
+ adb->namescnt--;
+ dec_adbstats(adb, dns_adbstats_namescnt);
+ UNLOCK(&adb->namescntlock);
+}
+
+static dns_adbnamehook_t *
+new_adbnamehook(dns_adb_t *adb, dns_adbentry_t *entry) {
+ dns_adbnamehook_t *nh;
+
+ nh = isc_mem_get(adb->mctx, sizeof(*nh));
+ isc_refcount_increment0(&adb->nhrefcnt);
+
+ nh->magic = DNS_ADBNAMEHOOK_MAGIC;
+ nh->entry = entry;
+ ISC_LINK_INIT(nh, plink);
+
+ return (nh);
+}
+
+static void
+free_adbnamehook(dns_adb_t *adb, dns_adbnamehook_t **namehook) {
+ dns_adbnamehook_t *nh;
+
+ INSIST(namehook != NULL && DNS_ADBNAMEHOOK_VALID(*namehook));
+ nh = *namehook;
+ *namehook = NULL;
+
+ INSIST(nh->entry == NULL);
+ INSIST(!ISC_LINK_LINKED(nh, plink));
+
+ nh->magic = 0;
+
+ isc_refcount_decrement(&adb->nhrefcnt);
+ isc_mem_put(adb->mctx, nh, sizeof(*nh));
+}
+
+static dns_adblameinfo_t *
+new_adblameinfo(dns_adb_t *adb, const dns_name_t *qname,
+ dns_rdatatype_t qtype) {
+ dns_adblameinfo_t *li;
+
+ li = isc_mem_get(adb->mctx, sizeof(*li));
+
+ dns_name_init(&li->qname, NULL);
+ dns_name_dup(qname, adb->mctx, &li->qname);
+ li->magic = DNS_ADBLAMEINFO_MAGIC;
+ li->lame_timer = 0;
+ li->qtype = qtype;
+ ISC_LINK_INIT(li, plink);
+
+ return (li);
+}
+
+static void
+free_adblameinfo(dns_adb_t *adb, dns_adblameinfo_t **lameinfo) {
+ dns_adblameinfo_t *li;
+
+ INSIST(lameinfo != NULL && DNS_ADBLAMEINFO_VALID(*lameinfo));
+ li = *lameinfo;
+ *lameinfo = NULL;
+
+ INSIST(!ISC_LINK_LINKED(li, plink));
+
+ dns_name_free(&li->qname, adb->mctx);
+
+ li->magic = 0;
+
+ isc_mem_put(adb->mctx, li, sizeof(*li));
+}
+
+static dns_adbentry_t *
+new_adbentry(dns_adb_t *adb) {
+ dns_adbentry_t *e;
+
+ e = isc_mem_get(adb->mctx, sizeof(*e));
+
+ e->magic = DNS_ADBENTRY_MAGIC;
+ e->lock_bucket = DNS_ADB_INVALIDBUCKET;
+ e->refcnt = 0;
+ e->nh = 0;
+ e->flags = 0;
+ e->udpsize = 0;
+ e->edns = 0;
+ e->ednsto = 0;
+ e->completed = 0;
+ e->timeouts = 0;
+ e->plain = 0;
+ e->plainto = 0;
+ e->cookie = NULL;
+ e->cookielen = 0;
+ e->srtt = (isc_random_uniform(0x1f)) + 1;
+ e->lastage = 0;
+ e->expires = 0;
+ atomic_init(&e->active, 0);
+ e->mode = 0;
+ atomic_init(&e->quota, adb->quota);
+ e->atr = 0.0;
+ ISC_LIST_INIT(e->lameinfo);
+ ISC_LINK_INIT(e, plink);
+ LOCK(&adb->entriescntlock);
+ adb->entriescnt++;
+ inc_adbstats(adb, dns_adbstats_entriescnt);
+ if (!adb->growentries_sent && adb->excl != NULL &&
+ adb->entriescnt > (adb->nentries * 8))
+ {
+ isc_event_t *event = &adb->growentries;
+ inc_adb_irefcnt(adb);
+ isc_task_send(adb->excl, &event);
+ adb->growentries_sent = true;
+ }
+ UNLOCK(&adb->entriescntlock);
+
+ return (e);
+}
+
+static void
+free_adbentry(dns_adb_t *adb, dns_adbentry_t **entry) {
+ dns_adbentry_t *e;
+ dns_adblameinfo_t *li;
+ uint_fast32_t active;
+
+ INSIST(entry != NULL && DNS_ADBENTRY_VALID(*entry));
+ e = *entry;
+ *entry = NULL;
+
+ active = atomic_load_acquire(&e->active);
+ INSIST(active == 0);
+ INSIST(e->lock_bucket == DNS_ADB_INVALIDBUCKET);
+ INSIST(e->refcnt == 0);
+ INSIST(!ISC_LINK_LINKED(e, plink));
+
+ e->magic = 0;
+
+ if (e->cookie != NULL) {
+ isc_mem_put(adb->mctx, e->cookie, e->cookielen);
+ }
+
+ li = ISC_LIST_HEAD(e->lameinfo);
+ while (li != NULL) {
+ ISC_LIST_UNLINK(e->lameinfo, li, plink);
+ free_adblameinfo(adb, &li);
+ li = ISC_LIST_HEAD(e->lameinfo);
+ }
+
+ isc_mem_put(adb->mctx, e, sizeof(*e));
+ LOCK(&adb->entriescntlock);
+ adb->entriescnt--;
+ dec_adbstats(adb, dns_adbstats_entriescnt);
+ UNLOCK(&adb->entriescntlock);
+}
+
+static dns_adbfind_t *
+new_adbfind(dns_adb_t *adb) {
+ dns_adbfind_t *h;
+
+ h = isc_mem_get(adb->mctx, sizeof(*h));
+ isc_refcount_increment0(&adb->ahrefcnt);
+
+ /*
+ * Public members.
+ */
+ h->magic = 0;
+ h->adb = adb;
+ h->partial_result = 0;
+ h->options = 0;
+ h->flags = 0;
+ h->result_v4 = ISC_R_UNEXPECTED;
+ h->result_v6 = ISC_R_UNEXPECTED;
+ ISC_LINK_INIT(h, publink);
+ ISC_LINK_INIT(h, plink);
+ ISC_LIST_INIT(h->list);
+ h->adbname = NULL;
+ h->name_bucket = DNS_ADB_INVALIDBUCKET;
+
+ /*
+ * private members
+ */
+ isc_mutex_init(&h->lock);
+
+ ISC_EVENT_INIT(&h->event, sizeof(isc_event_t), 0, 0, 0, NULL, NULL,
+ NULL, NULL, h);
+
+ inc_adb_irefcnt(adb);
+ h->magic = DNS_ADBFIND_MAGIC;
+ return (h);
+}
+
+static dns_adbfetch_t *
+new_adbfetch(dns_adb_t *adb) {
+ dns_adbfetch_t *f;
+
+ f = isc_mem_get(adb->mctx, sizeof(*f));
+
+ f->magic = 0;
+ f->fetch = NULL;
+
+ dns_rdataset_init(&f->rdataset);
+
+ f->magic = DNS_ADBFETCH_MAGIC;
+
+ return (f);
+}
+
+static void
+free_adbfetch(dns_adb_t *adb, dns_adbfetch_t **fetch) {
+ dns_adbfetch_t *f;
+
+ INSIST(fetch != NULL && DNS_ADBFETCH_VALID(*fetch));
+ f = *fetch;
+ *fetch = NULL;
+
+ f->magic = 0;
+
+ if (dns_rdataset_isassociated(&f->rdataset)) {
+ dns_rdataset_disassociate(&f->rdataset);
+ }
+
+ isc_mem_put(adb->mctx, f, sizeof(*f));
+}
+
+static bool
+free_adbfind(dns_adb_t *adb, dns_adbfind_t **findp) {
+ dns_adbfind_t *find;
+
+ INSIST(findp != NULL && DNS_ADBFIND_VALID(*findp));
+ find = *findp;
+ *findp = NULL;
+
+ INSIST(!FIND_HAS_ADDRS(find));
+ INSIST(!ISC_LINK_LINKED(find, publink));
+ INSIST(!ISC_LINK_LINKED(find, plink));
+ INSIST(find->name_bucket == DNS_ADB_INVALIDBUCKET);
+ INSIST(find->adbname == NULL);
+
+ find->magic = 0;
+
+ isc_mutex_destroy(&find->lock);
+
+ isc_refcount_decrement(&adb->ahrefcnt);
+ isc_mem_put(adb->mctx, find, sizeof(*find));
+ return (dec_adb_irefcnt(adb));
+}
+
+/*
+ * Copy bits from the entry into the newly allocated addrinfo. The entry
+ * must be locked, and the reference count must be bumped up by one
+ * if this function returns a valid pointer.
+ */
+static dns_adbaddrinfo_t *
+new_adbaddrinfo(dns_adb_t *adb, dns_adbentry_t *entry, in_port_t port) {
+ dns_adbaddrinfo_t *ai;
+
+ ai = isc_mem_get(adb->mctx, sizeof(*ai));
+
+ ai->magic = DNS_ADBADDRINFO_MAGIC;
+ ai->sockaddr = entry->sockaddr;
+ isc_sockaddr_setport(&ai->sockaddr, port);
+ ai->srtt = entry->srtt;
+ ai->flags = entry->flags;
+ ai->entry = entry;
+ ISC_LINK_INIT(ai, publink);
+
+ return (ai);
+}
+
+static void
+free_adbaddrinfo(dns_adb_t *adb, dns_adbaddrinfo_t **ainfo) {
+ dns_adbaddrinfo_t *ai;
+
+ INSIST(ainfo != NULL && DNS_ADBADDRINFO_VALID(*ainfo));
+ ai = *ainfo;
+ *ainfo = NULL;
+
+ INSIST(ai->entry == NULL);
+ INSIST(!ISC_LINK_LINKED(ai, publink));
+
+ ai->magic = 0;
+
+ isc_mem_put(adb->mctx, ai, sizeof(*ai));
+}
+
+/*
+ * Search for the name. NOTE: The bucket is kept locked on both
+ * success and failure, so it must always be unlocked by the caller!
+ *
+ * On the first call to this function, *bucketp must be set to
+ * DNS_ADB_INVALIDBUCKET.
+ */
+static dns_adbname_t *
+find_name_and_lock(dns_adb_t *adb, const dns_name_t *name, unsigned int options,
+ int *bucketp) {
+ dns_adbname_t *adbname;
+ int bucket;
+
+ bucket = dns_name_fullhash(name, false) % adb->nnames;
+
+ if (*bucketp == DNS_ADB_INVALIDBUCKET) {
+ LOCK(&adb->namelocks[bucket]);
+ *bucketp = bucket;
+ } else if (*bucketp != bucket) {
+ UNLOCK(&adb->namelocks[*bucketp]);
+ LOCK(&adb->namelocks[bucket]);
+ *bucketp = bucket;
+ }
+
+ adbname = ISC_LIST_HEAD(adb->names[bucket]);
+ while (adbname != NULL) {
+ if (!NAME_DEAD(adbname)) {
+ if (dns_name_equal(name, &adbname->name) &&
+ GLUEHINT_OK(adbname, options) &&
+ STARTATZONE_MATCHES(adbname, options))
+ {
+ return (adbname);
+ }
+ }
+ adbname = ISC_LIST_NEXT(adbname, plink);
+ }
+
+ return (NULL);
+}
+
+/*
+ * Search for the address. NOTE: The bucket is kept locked on both
+ * success and failure, so it must always be unlocked by the caller.
+ *
+ * On the first call to this function, *bucketp must be set to
+ * DNS_ADB_INVALIDBUCKET. This will cause a lock to occur. On
+ * later calls (within the same "lock path") it can be left alone, so
+ * if this function is called multiple times locking is only done if
+ * the bucket changes.
+ */
+static dns_adbentry_t *
+find_entry_and_lock(dns_adb_t *adb, const isc_sockaddr_t *addr, int *bucketp,
+ isc_stdtime_t now) {
+ dns_adbentry_t *entry, *entry_next;
+ int bucket;
+
+ bucket = isc_sockaddr_hash(addr, true) % adb->nentries;
+
+ if (*bucketp == DNS_ADB_INVALIDBUCKET) {
+ LOCK(&adb->entrylocks[bucket]);
+ *bucketp = bucket;
+ } else if (*bucketp != bucket) {
+ UNLOCK(&adb->entrylocks[*bucketp]);
+ LOCK(&adb->entrylocks[bucket]);
+ *bucketp = bucket;
+ }
+
+ /* Search the list, while cleaning up expired entries. */
+ for (entry = ISC_LIST_HEAD(adb->entries[bucket]); entry != NULL;
+ entry = entry_next)
+ {
+ entry_next = ISC_LIST_NEXT(entry, plink);
+ (void)check_expire_entry(adb, &entry, now);
+ if (entry != NULL &&
+ (entry->expires == 0 || entry->expires > now) &&
+ isc_sockaddr_equal(addr, &entry->sockaddr))
+ {
+ ISC_LIST_UNLINK(adb->entries[bucket], entry, plink);
+ ISC_LIST_PREPEND(adb->entries[bucket], entry, plink);
+ return (entry);
+ }
+ }
+
+ return (NULL);
+}
+
+/*
+ * Entry bucket MUST be locked!
+ */
+static bool
+entry_is_lame(dns_adb_t *adb, dns_adbentry_t *entry, const dns_name_t *qname,
+ dns_rdatatype_t qtype, isc_stdtime_t now) {
+ dns_adblameinfo_t *li, *next_li;
+ bool is_bad;
+
+ is_bad = false;
+
+ li = ISC_LIST_HEAD(entry->lameinfo);
+ if (li == NULL) {
+ return (false);
+ }
+ while (li != NULL) {
+ next_li = ISC_LIST_NEXT(li, plink);
+
+ /*
+ * Has the entry expired?
+ */
+ if (li->lame_timer < now) {
+ ISC_LIST_UNLINK(entry->lameinfo, li, plink);
+ free_adblameinfo(adb, &li);
+ }
+
+ /*
+ * Order tests from least to most expensive.
+ *
+ * We do not break out of the main loop here as
+ * we use the loop for house keeping.
+ */
+ if (li != NULL && !is_bad && li->qtype == qtype &&
+ dns_name_equal(qname, &li->qname))
+ {
+ is_bad = true;
+ }
+
+ li = next_li;
+ }
+
+ return (is_bad);
+}
+
+static void
+log_quota(dns_adbentry_t *entry, const char *fmt, ...) {
+ va_list ap;
+ char msgbuf[2048];
+ char addrbuf[ISC_NETADDR_FORMATSIZE];
+ isc_netaddr_t netaddr;
+
+ va_start(ap, fmt);
+ vsnprintf(msgbuf, sizeof(msgbuf), fmt, ap);
+ va_end(ap);
+
+ isc_netaddr_fromsockaddr(&netaddr, &entry->sockaddr);
+ isc_netaddr_format(&netaddr, addrbuf, sizeof(addrbuf));
+
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, DNS_LOGMODULE_ADB,
+ ISC_LOG_INFO,
+ "adb: quota %s (%" PRIuFAST32 "/%" PRIuFAST32 "): %s",
+ addrbuf, atomic_load_relaxed(&entry->active),
+ atomic_load_relaxed(&entry->quota), msgbuf);
+}
+
+static void
+copy_namehook_lists(dns_adb_t *adb, dns_adbfind_t *find,
+ const dns_name_t *qname, dns_rdatatype_t qtype,
+ dns_adbname_t *name, isc_stdtime_t now) {
+ dns_adbnamehook_t *namehook;
+ dns_adbaddrinfo_t *addrinfo;
+ dns_adbentry_t *entry;
+ int bucket;
+
+ bucket = DNS_ADB_INVALIDBUCKET;
+
+ if ((find->options & DNS_ADBFIND_INET) != 0) {
+ namehook = ISC_LIST_HEAD(name->v4);
+ while (namehook != NULL) {
+ entry = namehook->entry;
+ bucket = entry->lock_bucket;
+ INSIST(bucket != DNS_ADB_INVALIDBUCKET);
+ LOCK(&adb->entrylocks[bucket]);
+
+ if (dns_adbentry_overquota(entry)) {
+ find->options |= (DNS_ADBFIND_LAMEPRUNED |
+ DNS_ADBFIND_OVERQUOTA);
+ goto nextv4;
+ }
+
+ if (!FIND_RETURNLAME(find) &&
+ entry_is_lame(adb, entry, qname, qtype, now))
+ {
+ find->options |= DNS_ADBFIND_LAMEPRUNED;
+ goto nextv4;
+ }
+
+ addrinfo = new_adbaddrinfo(adb, entry, find->port);
+
+ /*
+ * Found a valid entry. Add it to the find's list.
+ */
+ inc_entry_refcnt(adb, entry, false);
+ ISC_LIST_APPEND(find->list, addrinfo, publink);
+ addrinfo = NULL;
+ nextv4:
+ UNLOCK(&adb->entrylocks[bucket]);
+ bucket = DNS_ADB_INVALIDBUCKET;
+ namehook = ISC_LIST_NEXT(namehook, plink);
+ }
+ }
+
+ if ((find->options & DNS_ADBFIND_INET6) != 0) {
+ namehook = ISC_LIST_HEAD(name->v6);
+ while (namehook != NULL) {
+ entry = namehook->entry;
+ bucket = entry->lock_bucket;
+ INSIST(bucket != DNS_ADB_INVALIDBUCKET);
+ LOCK(&adb->entrylocks[bucket]);
+
+ if (dns_adbentry_overquota(entry)) {
+ find->options |= (DNS_ADBFIND_LAMEPRUNED |
+ DNS_ADBFIND_OVERQUOTA);
+ goto nextv6;
+ }
+
+ if (!FIND_RETURNLAME(find) &&
+ entry_is_lame(adb, entry, qname, qtype, now))
+ {
+ find->options |= DNS_ADBFIND_LAMEPRUNED;
+ goto nextv6;
+ }
+ addrinfo = new_adbaddrinfo(adb, entry, find->port);
+
+ /*
+ * Found a valid entry. Add it to the find's list.
+ */
+ inc_entry_refcnt(adb, entry, false);
+ ISC_LIST_APPEND(find->list, addrinfo, publink);
+ addrinfo = NULL;
+ nextv6:
+ UNLOCK(&adb->entrylocks[bucket]);
+ bucket = DNS_ADB_INVALIDBUCKET;
+ namehook = ISC_LIST_NEXT(namehook, plink);
+ }
+ }
+
+ if (bucket != DNS_ADB_INVALIDBUCKET) {
+ UNLOCK(&adb->entrylocks[bucket]);
+ }
+}
+
+static void
+shutdown_task(isc_task_t *task, isc_event_t *ev) {
+ dns_adb_t *adb;
+
+ UNUSED(task);
+
+ adb = ev->ev_arg;
+ INSIST(DNS_ADB_VALID(adb));
+
+ isc_event_free(&ev);
+ /*
+ * Wait for lock around check_exit() call to be released.
+ */
+ LOCK(&adb->lock);
+ UNLOCK(&adb->lock);
+ destroy(adb);
+}
+
+/*
+ * Name bucket must be locked; adb may be locked; no other locks held.
+ */
+static bool
+check_expire_name(dns_adbname_t **namep, isc_stdtime_t now) {
+ dns_adbname_t *name;
+ bool result = false;
+
+ INSIST(namep != NULL && DNS_ADBNAME_VALID(*namep));
+ name = *namep;
+
+ if (NAME_HAS_V4(name) || NAME_HAS_V6(name)) {
+ return (result);
+ }
+ if (NAME_FETCH(name)) {
+ return (result);
+ }
+ if (!EXPIRE_OK(name->expire_v4, now)) {
+ return (result);
+ }
+ if (!EXPIRE_OK(name->expire_v6, now)) {
+ return (result);
+ }
+ if (!EXPIRE_OK(name->expire_target, now)) {
+ return (result);
+ }
+
+ /*
+ * The name is empty. Delete it.
+ */
+ *namep = NULL;
+ result = kill_name(&name, DNS_EVENT_ADBEXPIRED);
+
+ /*
+ * Our caller, or one of its callers, will be calling check_exit() at
+ * some point, so we don't need to do it here.
+ */
+ return (result);
+}
+
+/*%
+ * Examine the tail entry of the LRU list to see if it expires or is stale
+ * (unused for some period); if so, the name entry will be freed. If the ADB
+ * is in the overmem condition, the tail and the next to tail entries
+ * will be unconditionally removed (unless they have an outstanding fetch).
+ * We don't care about a race on 'overmem' at the risk of causing some
+ * collateral damage or a small delay in starting cleanup, so we don't bother
+ * to lock ADB (if it's not locked).
+ *
+ * Name bucket must be locked; adb may be locked; no other locks held.
+ */
+static void
+check_stale_name(dns_adb_t *adb, int bucket, isc_stdtime_t now) {
+ int victims, max_victims;
+ dns_adbname_t *victim, *next_victim;
+ bool overmem = isc_mem_isovermem(adb->mctx);
+ int scans = 0;
+
+ INSIST(bucket != DNS_ADB_INVALIDBUCKET);
+
+ max_victims = overmem ? 2 : 1;
+
+ /*
+ * We limit the number of scanned entries to 10 (arbitrary choice)
+ * in order to avoid examining too many entries when there are many
+ * tail entries that have fetches (this should be rare, but could
+ * happen).
+ */
+ victim = ISC_LIST_TAIL(adb->names[bucket]);
+ for (victims = 0; victim != NULL && victims < max_victims && scans < 10;
+ victim = next_victim)
+ {
+ INSIST(!NAME_DEAD(victim));
+ scans++;
+ next_victim = ISC_LIST_PREV(victim, plink);
+ (void)check_expire_name(&victim, now);
+ if (victim == NULL) {
+ victims++;
+ goto next;
+ }
+
+ /*
+ * Make sure that we are not purging ADB names that has been
+ * just created.
+ */
+ if (victim->last_used + ADB_CACHE_MINIMUM >= now) {
+ break;
+ }
+
+ if (!NAME_FETCH(victim) &&
+ (overmem || victim->last_used + ADB_STALE_MARGIN <= now))
+ {
+ RUNTIME_CHECK(
+ !kill_name(&victim, DNS_EVENT_ADBCANCELED));
+ victims++;
+ }
+
+ next:
+ if (!overmem) {
+ break;
+ }
+ }
+}
+
+/*
+ * Entry bucket must be locked; adb may be locked; no other locks held.
+ */
+static bool
+check_expire_entry(dns_adb_t *adb, dns_adbentry_t **entryp, isc_stdtime_t now) {
+ dns_adbentry_t *entry;
+ bool result = false;
+
+ INSIST(entryp != NULL && DNS_ADBENTRY_VALID(*entryp));
+ entry = *entryp;
+
+ if (entry->refcnt != 0) {
+ return (result);
+ }
+
+ if (entry->expires == 0 || entry->expires > now) {
+ return (result);
+ }
+
+ /*
+ * The entry is not in use. Delete it.
+ */
+ *entryp = NULL;
+ DP(DEF_LEVEL, "killing entry %p", entry);
+ INSIST(ISC_LINK_LINKED(entry, plink));
+ result = unlink_entry(adb, entry);
+ free_adbentry(adb, &entry);
+ if (result) {
+ dec_adb_irefcnt(adb);
+ }
+ return (result);
+}
+
+/*
+ * ADB must be locked, and no other locks held.
+ */
+static bool
+cleanup_names(dns_adb_t *adb, int bucket, isc_stdtime_t now) {
+ dns_adbname_t *name;
+ dns_adbname_t *next_name;
+ bool result = false;
+
+ DP(CLEAN_LEVEL, "cleaning name bucket %d", bucket);
+
+ LOCK(&adb->namelocks[bucket]);
+ if (adb->name_sd[bucket]) {
+ UNLOCK(&adb->namelocks[bucket]);
+ return (result);
+ }
+
+ name = ISC_LIST_HEAD(adb->names[bucket]);
+ while (name != NULL) {
+ next_name = ISC_LIST_NEXT(name, plink);
+ INSIST(!result);
+ result = check_expire_namehooks(name, now);
+ if (!result) {
+ result = check_expire_name(&name, now);
+ }
+ name = next_name;
+ }
+ UNLOCK(&adb->namelocks[bucket]);
+ return (result);
+}
+
+/*
+ * ADB must be locked, and no other locks held.
+ */
+static bool
+cleanup_entries(dns_adb_t *adb, int bucket, isc_stdtime_t now) {
+ dns_adbentry_t *entry, *next_entry;
+ bool result = false;
+
+ DP(CLEAN_LEVEL, "cleaning entry bucket %d", bucket);
+
+ LOCK(&adb->entrylocks[bucket]);
+ entry = ISC_LIST_HEAD(adb->entries[bucket]);
+ while (entry != NULL) {
+ next_entry = ISC_LIST_NEXT(entry, plink);
+ INSIST(!result);
+ result = check_expire_entry(adb, &entry, now);
+ entry = next_entry;
+ }
+ UNLOCK(&adb->entrylocks[bucket]);
+ return (result);
+}
+
+static void
+destroy(dns_adb_t *adb) {
+ adb->magic = 0;
+
+ isc_task_detach(&adb->task);
+ if (adb->excl != NULL) {
+ isc_task_detach(&adb->excl);
+ }
+
+ isc_mutexblock_destroy(adb->entrylocks, adb->nentries);
+ isc_mem_put(adb->hmctx, adb->entries,
+ sizeof(*adb->entries) * adb->nentries);
+ isc_mem_put(adb->hmctx, adb->deadentries,
+ sizeof(*adb->deadentries) * adb->nentries);
+ isc_mem_put(adb->hmctx, adb->entrylocks,
+ sizeof(*adb->entrylocks) * adb->nentries);
+ isc_mem_put(adb->hmctx, adb->entry_sd,
+ sizeof(*adb->entry_sd) * adb->nentries);
+ isc_mem_put(adb->hmctx, adb->entry_refcnt,
+ sizeof(*adb->entry_refcnt) * adb->nentries);
+
+ isc_mutexblock_destroy(adb->namelocks, adb->nnames);
+ isc_mem_put(adb->hmctx, adb->names, sizeof(*adb->names) * adb->nnames);
+ isc_mem_put(adb->hmctx, adb->deadnames,
+ sizeof(*adb->deadnames) * adb->nnames);
+ isc_mem_put(adb->hmctx, adb->namelocks,
+ sizeof(*adb->namelocks) * adb->nnames);
+ isc_mem_put(adb->hmctx, adb->name_sd,
+ sizeof(*adb->name_sd) * adb->nnames);
+ isc_mem_put(adb->hmctx, adb->name_refcnt,
+ sizeof(*adb->name_refcnt) * adb->nnames);
+
+ isc_mem_destroy(&adb->hmctx);
+
+ isc_mutex_destroy(&adb->reflock);
+ isc_mutex_destroy(&adb->lock);
+ isc_mutex_destroy(&adb->overmemlock);
+ isc_mutex_destroy(&adb->entriescntlock);
+ isc_mutex_destroy(&adb->namescntlock);
+
+ isc_mem_putanddetach(&adb->mctx, adb, sizeof(dns_adb_t));
+}
+
+/*
+ * Public functions.
+ */
+
+isc_result_t
+dns_adb_create(isc_mem_t *mem, dns_view_t *view, isc_timermgr_t *timermgr,
+ isc_taskmgr_t *taskmgr, dns_adb_t **newadb) {
+ dns_adb_t *adb;
+ isc_result_t result;
+ unsigned int i;
+
+ REQUIRE(mem != NULL);
+ REQUIRE(view != NULL);
+ REQUIRE(timermgr != NULL); /* this is actually unused */
+ REQUIRE(taskmgr != NULL);
+ REQUIRE(newadb != NULL && *newadb == NULL);
+
+ UNUSED(timermgr);
+
+ adb = isc_mem_get(mem, sizeof(dns_adb_t));
+
+ /*
+ * Initialize things here that cannot fail, and especially things
+ * that must be NULL for the error return to work properly.
+ */
+ adb->magic = 0;
+ adb->erefcnt = 1;
+ adb->irefcnt = 0;
+ adb->task = NULL;
+ adb->excl = NULL;
+ adb->mctx = NULL;
+ adb->hmctx = NULL;
+ adb->view = view;
+ adb->taskmgr = taskmgr;
+ adb->next_cleanbucket = 0;
+ ISC_EVENT_INIT(&adb->cevent, sizeof(adb->cevent), 0, NULL, 0, NULL,
+ NULL, NULL, NULL, NULL);
+ adb->cevent_out = false;
+ atomic_init(&adb->shutting_down, false);
+ ISC_LIST_INIT(adb->whenshutdown);
+
+ adb->nentries = nbuckets[0];
+ adb->entriescnt = 0;
+ adb->entries = NULL;
+ adb->deadentries = NULL;
+ adb->entry_sd = NULL;
+ adb->entry_refcnt = NULL;
+ adb->entrylocks = NULL;
+ ISC_EVENT_INIT(&adb->growentries, sizeof(adb->growentries), 0, NULL,
+ DNS_EVENT_ADBGROWENTRIES, grow_entries, adb, adb, NULL,
+ NULL);
+ adb->growentries_sent = false;
+
+ adb->quota = 0;
+ adb->atr_freq = 0;
+ adb->atr_low = 0.0;
+ adb->atr_high = 0.0;
+ adb->atr_discount = 0.0;
+
+ adb->nnames = nbuckets[0];
+ adb->namescnt = 0;
+ adb->names = NULL;
+ adb->deadnames = NULL;
+ adb->name_sd = NULL;
+ adb->name_refcnt = NULL;
+ adb->namelocks = NULL;
+ ISC_EVENT_INIT(&adb->grownames, sizeof(adb->grownames), 0, NULL,
+ DNS_EVENT_ADBGROWNAMES, grow_names, adb, adb, NULL,
+ NULL);
+ adb->grownames_sent = false;
+
+ result = isc_taskmgr_excltask(adb->taskmgr, &adb->excl);
+ if (result != ISC_R_SUCCESS) {
+ DP(DEF_LEVEL,
+ "adb: task-exclusive mode unavailable, "
+ "initializing table sizes to %u\n",
+ nbuckets[11]);
+ adb->nentries = nbuckets[11];
+ adb->nnames = nbuckets[11];
+ }
+
+ isc_mem_attach(mem, &adb->mctx);
+
+ isc_mutex_init(&adb->lock);
+ isc_mutex_init(&adb->reflock);
+ isc_mutex_init(&adb->overmemlock);
+ isc_mutex_init(&adb->entriescntlock);
+ isc_mutex_init(&adb->namescntlock);
+
+ isc_mem_create(&adb->hmctx);
+ isc_mem_setname(adb->hmctx, "ADB_hashmaps");
+
+#define ALLOCENTRY(adb, el) \
+ do { \
+ (adb)->el = isc_mem_get((adb)->hmctx, \
+ sizeof(*(adb)->el) * (adb)->nentries); \
+ } while (0)
+ ALLOCENTRY(adb, entries);
+ ALLOCENTRY(adb, deadentries);
+ ALLOCENTRY(adb, entrylocks);
+ ALLOCENTRY(adb, entry_sd);
+ ALLOCENTRY(adb, entry_refcnt);
+#undef ALLOCENTRY
+
+#define ALLOCNAME(adb, el) \
+ do { \
+ (adb)->el = isc_mem_get((adb)->hmctx, \
+ sizeof(*(adb)->el) * (adb)->nnames); \
+ } while (0)
+ ALLOCNAME(adb, names);
+ ALLOCNAME(adb, deadnames);
+ ALLOCNAME(adb, namelocks);
+ ALLOCNAME(adb, name_sd);
+ ALLOCNAME(adb, name_refcnt);
+#undef ALLOCNAME
+
+ /*
+ * Initialize the bucket locks for names and elements.
+ * May as well initialize the list heads, too.
+ */
+ isc_mutexblock_init(adb->namelocks, adb->nnames);
+
+ for (i = 0; i < adb->nnames; i++) {
+ ISC_LIST_INIT(adb->names[i]);
+ ISC_LIST_INIT(adb->deadnames[i]);
+ adb->name_sd[i] = false;
+ adb->name_refcnt[i] = 0;
+ adb->irefcnt++;
+ }
+ for (i = 0; i < adb->nentries; i++) {
+ ISC_LIST_INIT(adb->entries[i]);
+ ISC_LIST_INIT(adb->deadentries[i]);
+ adb->entry_sd[i] = false;
+ adb->entry_refcnt[i] = 0;
+ adb->irefcnt++;
+ }
+ isc_mutexblock_init(adb->entrylocks, adb->nentries);
+
+ isc_refcount_init(&adb->ahrefcnt, 0);
+ isc_refcount_init(&adb->nhrefcnt, 0);
+
+ /*
+ * Allocate an internal task.
+ */
+ result = isc_task_create(adb->taskmgr, 0, &adb->task);
+ if (result != ISC_R_SUCCESS) {
+ goto fail2;
+ }
+
+ isc_task_setname(adb->task, "ADB", adb);
+
+ result = isc_stats_create(adb->mctx, &view->adbstats, dns_adbstats_max);
+ if (result != ISC_R_SUCCESS) {
+ goto fail2;
+ }
+
+ set_adbstat(adb, adb->nentries, dns_adbstats_nentries);
+ set_adbstat(adb, adb->nnames, dns_adbstats_nnames);
+
+ /*
+ * Normal return.
+ */
+ adb->magic = DNS_ADB_MAGIC;
+ *newadb = adb;
+ return (ISC_R_SUCCESS);
+
+fail2:
+ if (adb->task != NULL) {
+ isc_task_detach(&adb->task);
+ }
+
+ /* clean up entrylocks */
+ isc_mutexblock_destroy(adb->entrylocks, adb->nentries);
+ isc_mutexblock_destroy(adb->namelocks, adb->nnames);
+
+ if (adb->entries != NULL) {
+ isc_mem_put(adb->hmctx, adb->entries,
+ sizeof(*adb->entries) * adb->nentries);
+ }
+ if (adb->deadentries != NULL) {
+ isc_mem_put(adb->hmctx, adb->deadentries,
+ sizeof(*adb->deadentries) * adb->nentries);
+ }
+ if (adb->entrylocks != NULL) {
+ isc_mem_put(adb->hmctx, adb->entrylocks,
+ sizeof(*adb->entrylocks) * adb->nentries);
+ }
+ if (adb->entry_sd != NULL) {
+ isc_mem_put(adb->hmctx, adb->entry_sd,
+ sizeof(*adb->entry_sd) * adb->nentries);
+ }
+ if (adb->entry_refcnt != NULL) {
+ isc_mem_put(adb->hmctx, adb->entry_refcnt,
+ sizeof(*adb->entry_refcnt) * adb->nentries);
+ }
+ if (adb->names != NULL) {
+ isc_mem_put(adb->hmctx, adb->names,
+ sizeof(*adb->names) * adb->nnames);
+ }
+ if (adb->deadnames != NULL) {
+ isc_mem_put(adb->hmctx, adb->deadnames,
+ sizeof(*adb->deadnames) * adb->nnames);
+ }
+ if (adb->namelocks != NULL) {
+ isc_mem_put(adb->hmctx, adb->namelocks,
+ sizeof(*adb->namelocks) * adb->nnames);
+ }
+ if (adb->name_sd != NULL) {
+ isc_mem_put(adb->hmctx, adb->name_sd,
+ sizeof(*adb->name_sd) * adb->nnames);
+ }
+ if (adb->name_refcnt != NULL) {
+ isc_mem_put(adb->hmctx, adb->name_refcnt,
+ sizeof(*adb->name_refcnt) * adb->nnames);
+ }
+
+ isc_mem_destroy(&adb->hmctx);
+
+ isc_mutex_destroy(&adb->namescntlock);
+ isc_mutex_destroy(&adb->entriescntlock);
+ isc_mutex_destroy(&adb->overmemlock);
+ isc_mutex_destroy(&adb->reflock);
+ isc_mutex_destroy(&adb->lock);
+ if (adb->excl != NULL) {
+ isc_task_detach(&adb->excl);
+ }
+ isc_mem_putanddetach(&adb->mctx, adb, sizeof(dns_adb_t));
+
+ return (result);
+}
+
+void
+dns_adb_attach(dns_adb_t *adb, dns_adb_t **adbx) {
+ REQUIRE(DNS_ADB_VALID(adb));
+ REQUIRE(adbx != NULL && *adbx == NULL);
+
+ inc_adb_erefcnt(adb);
+ *adbx = adb;
+}
+
+void
+dns_adb_detach(dns_adb_t **adbx) {
+ dns_adb_t *adb;
+ bool need_exit_check;
+
+ REQUIRE(adbx != NULL && DNS_ADB_VALID(*adbx));
+
+ adb = *adbx;
+ *adbx = NULL;
+
+ LOCK(&adb->reflock);
+ INSIST(adb->erefcnt > 0);
+ adb->erefcnt--;
+ need_exit_check = (adb->erefcnt == 0 && adb->irefcnt == 0);
+ UNLOCK(&adb->reflock);
+
+ if (need_exit_check) {
+ LOCK(&adb->lock);
+ INSIST(atomic_load(&adb->shutting_down));
+ check_exit(adb);
+ UNLOCK(&adb->lock);
+ }
+}
+
+void
+dns_adb_whenshutdown(dns_adb_t *adb, isc_task_t *task, isc_event_t **eventp) {
+ isc_task_t *tclone;
+ isc_event_t *event;
+ bool zeroirefcnt;
+
+ /*
+ * Send '*eventp' to 'task' when 'adb' has shutdown.
+ */
+
+ REQUIRE(DNS_ADB_VALID(adb));
+ REQUIRE(eventp != NULL);
+
+ event = *eventp;
+ *eventp = NULL;
+
+ LOCK(&adb->lock);
+ LOCK(&adb->reflock);
+
+ zeroirefcnt = (adb->irefcnt == 0);
+
+ if (atomic_load(&adb->shutting_down) && zeroirefcnt &&
+ isc_refcount_current(&adb->ahrefcnt) == 0)
+ {
+ /*
+ * We're already shutdown. Send the event.
+ */
+ event->ev_sender = adb;
+ isc_task_send(task, &event);
+ } else {
+ tclone = NULL;
+ isc_task_attach(task, &tclone);
+ event->ev_sender = tclone;
+ ISC_LIST_APPEND(adb->whenshutdown, event, ev_link);
+ }
+
+ UNLOCK(&adb->reflock);
+ UNLOCK(&adb->lock);
+}
+
+static void
+shutdown_stage2(isc_task_t *task, isc_event_t *event) {
+ dns_adb_t *adb;
+
+ UNUSED(task);
+
+ adb = event->ev_arg;
+ INSIST(DNS_ADB_VALID(adb));
+
+ LOCK(&adb->lock);
+ INSIST(atomic_load(&adb->shutting_down));
+ adb->cevent_out = false;
+ (void)shutdown_names(adb);
+ (void)shutdown_entries(adb);
+ if (dec_adb_irefcnt(adb)) {
+ check_exit(adb);
+ }
+ UNLOCK(&adb->lock);
+}
+
+void
+dns_adb_shutdown(dns_adb_t *adb) {
+ isc_event_t *event;
+
+ /*
+ * Shutdown 'adb'.
+ */
+
+ LOCK(&adb->lock);
+
+ if (atomic_compare_exchange_strong(&adb->shutting_down,
+ &(bool){ false }, true))
+ {
+ isc_mem_clearwater(adb->mctx);
+ /*
+ * Isolate shutdown_names and shutdown_entries calls.
+ */
+ inc_adb_irefcnt(adb);
+ ISC_EVENT_INIT(&adb->cevent, sizeof(adb->cevent), 0, NULL,
+ DNS_EVENT_ADBCONTROL, shutdown_stage2, adb, adb,
+ NULL, NULL);
+ adb->cevent_out = true;
+ event = &adb->cevent;
+ isc_task_send(adb->task, &event);
+ }
+
+ UNLOCK(&adb->lock);
+}
+
+isc_result_t
+dns_adb_createfind(dns_adb_t *adb, isc_task_t *task, isc_taskaction_t action,
+ void *arg, const dns_name_t *name, const dns_name_t *qname,
+ dns_rdatatype_t qtype, unsigned int options,
+ isc_stdtime_t now, dns_name_t *target, in_port_t port,
+ unsigned int depth, isc_counter_t *qc,
+ dns_adbfind_t **findp) {
+ dns_adbfind_t *find = NULL;
+ dns_adbname_t *adbname = NULL;
+ int bucket;
+ bool want_event = true;
+ bool start_at_zone = false;
+ bool alias = false;
+ bool have_address = false;
+ isc_result_t result;
+ unsigned int wanted_addresses = (options & DNS_ADBFIND_ADDRESSMASK);
+ unsigned int wanted_fetches = 0;
+ unsigned int query_pending = 0;
+ char namebuf[DNS_NAME_FORMATSIZE];
+
+ REQUIRE(DNS_ADB_VALID(adb));
+ if (task != NULL) {
+ REQUIRE(action != NULL);
+ }
+ REQUIRE(name != NULL);
+ REQUIRE(qname != NULL);
+ REQUIRE(findp != NULL && *findp == NULL);
+ REQUIRE(target == NULL || dns_name_hasbuffer(target));
+
+ REQUIRE((options & DNS_ADBFIND_ADDRESSMASK) != 0);
+
+ result = ISC_R_UNEXPECTED;
+ POST(result);
+
+ if (atomic_load(&adb->shutting_down)) {
+ DP(DEF_LEVEL, "dns_adb_createfind: returning "
+ "ISC_R_SHUTTINGDOWN");
+
+ return (ISC_R_SHUTTINGDOWN);
+ }
+
+ if (now == 0) {
+ isc_stdtime_get(&now);
+ }
+
+ /*
+ * XXXMLG Move this comment somewhere else!
+ *
+ * Look up the name in our internal database.
+ *
+ * Possibilities: Note that these are not always exclusive.
+ *
+ * No name found. In this case, allocate a new name header and
+ * an initial namehook or two.
+ *
+ * Name found, valid addresses present. Allocate one addrinfo
+ * structure for each found and append it to the linked list
+ * of addresses for this header.
+ *
+ * Name found, queries pending. In this case, if a task was
+ * passed in, allocate a job id, attach it to the name's job
+ * list and remember to tell the caller that there will be
+ * more info coming later.
+ */
+
+ find = new_adbfind(adb);
+
+ find->port = port;
+
+ /*
+ * Remember what types of addresses we are interested in.
+ */
+ find->options = options;
+ find->flags |= wanted_addresses;
+ if (FIND_WANTEVENT(find)) {
+ REQUIRE(task != NULL);
+ }
+
+ if (isc_log_wouldlog(dns_lctx, DEF_LEVEL)) {
+ dns_name_format(name, namebuf, sizeof(namebuf));
+ } else {
+ namebuf[0] = 0;
+ }
+
+ /*
+ * Try to see if we know anything about this name at all.
+ */
+ bucket = DNS_ADB_INVALIDBUCKET;
+ adbname = find_name_and_lock(adb, name, find->options, &bucket);
+ INSIST(bucket != DNS_ADB_INVALIDBUCKET);
+ if (adb->name_sd[bucket]) {
+ DP(DEF_LEVEL, "dns_adb_createfind: returning "
+ "ISC_R_SHUTTINGDOWN");
+ RUNTIME_CHECK(!free_adbfind(adb, &find));
+ result = ISC_R_SHUTTINGDOWN;
+ goto out;
+ }
+
+ /*
+ * Nothing found. Allocate a new adbname structure for this name.
+ */
+ if (adbname == NULL) {
+ /*
+ * See if there is any stale name at the end of list, and purge
+ * it if so.
+ */
+ check_stale_name(adb, bucket, now);
+
+ adbname = new_adbname(adb, name);
+ link_name(adb, bucket, adbname);
+ if (FIND_HINTOK(find)) {
+ adbname->flags |= DNS_ADBFIND_HINTOK;
+ }
+ if (FIND_GLUEOK(find)) {
+ adbname->flags |= DNS_ADBFIND_GLUEOK;
+ }
+ if (FIND_STARTATZONE(find)) {
+ adbname->flags |= DNS_ADBFIND_STARTATZONE;
+ }
+ } else {
+ /* Move this name forward in the LRU list */
+ ISC_LIST_UNLINK(adb->names[bucket], adbname, plink);
+ ISC_LIST_PREPEND(adb->names[bucket], adbname, plink);
+ }
+ adbname->last_used = now;
+
+ /*
+ * Expire old entries, etc.
+ */
+ RUNTIME_CHECK(!check_expire_namehooks(adbname, now));
+
+ /*
+ * Do we know that the name is an alias?
+ */
+ if (!EXPIRE_OK(adbname->expire_target, now)) {
+ /*
+ * Yes, it is.
+ */
+ DP(DEF_LEVEL,
+ "dns_adb_createfind: name %s (%p) is an alias (cached)",
+ namebuf, adbname);
+ alias = true;
+ goto post_copy;
+ }
+
+ /*
+ * Try to populate the name from the database and/or
+ * start fetches. First try looking for an A record
+ * in the database.
+ */
+ if (!NAME_HAS_V4(adbname) && EXPIRE_OK(adbname->expire_v4, now) &&
+ WANT_INET(wanted_addresses))
+ {
+ result = dbfind_name(adbname, now, dns_rdatatype_a);
+ if (result == ISC_R_SUCCESS) {
+ DP(DEF_LEVEL,
+ "dns_adb_createfind: found A for name %s (%p) in db",
+ namebuf, adbname);
+ goto v6;
+ }
+
+ /*
+ * Did we get a CNAME or DNAME?
+ */
+ if (result == DNS_R_ALIAS) {
+ DP(DEF_LEVEL,
+ "dns_adb_createfind: name %s (%p) is an alias",
+ namebuf, adbname);
+ alias = true;
+ goto post_copy;
+ }
+
+ /*
+ * If the name doesn't exist at all, don't bother with
+ * v6 queries; they won't work.
+ *
+ * If the name does exist but we didn't get our data, go
+ * ahead and try AAAA.
+ *
+ * If the result is neither of these, try a fetch for A.
+ */
+ if (NXDOMAIN_RESULT(result)) {
+ goto fetch;
+ } else if (NXRRSET_RESULT(result)) {
+ goto v6;
+ }
+
+ if (!NAME_FETCH_A(adbname)) {
+ wanted_fetches |= DNS_ADBFIND_INET;
+ }
+ }
+
+v6:
+ if (!NAME_HAS_V6(adbname) && EXPIRE_OK(adbname->expire_v6, now) &&
+ WANT_INET6(wanted_addresses))
+ {
+ result = dbfind_name(adbname, now, dns_rdatatype_aaaa);
+ if (result == ISC_R_SUCCESS) {
+ DP(DEF_LEVEL,
+ "dns_adb_createfind: found AAAA for name %s (%p)",
+ namebuf, adbname);
+ goto fetch;
+ }
+
+ /*
+ * Did we get a CNAME or DNAME?
+ */
+ if (result == DNS_R_ALIAS) {
+ DP(DEF_LEVEL,
+ "dns_adb_createfind: name %s (%p) is an alias",
+ namebuf, adbname);
+ alias = true;
+ goto post_copy;
+ }
+
+ /*
+ * Listen to negative cache hints, and don't start
+ * another query.
+ */
+ if (NCACHE_RESULT(result) || AUTH_NX(result)) {
+ goto fetch;
+ }
+
+ if (!NAME_FETCH_AAAA(adbname)) {
+ wanted_fetches |= DNS_ADBFIND_INET6;
+ }
+ }
+
+fetch:
+ if ((WANT_INET(wanted_addresses) && NAME_HAS_V4(adbname)) ||
+ (WANT_INET6(wanted_addresses) && NAME_HAS_V6(adbname)))
+ {
+ have_address = true;
+ } else {
+ have_address = false;
+ }
+ if (wanted_fetches != 0 && !(FIND_AVOIDFETCHES(find) && have_address) &&
+ !FIND_NOFETCH(find))
+ {
+ /*
+ * We're missing at least one address family. Either the
+ * caller hasn't instructed us to avoid fetches, or we don't
+ * know anything about any of the address families that would
+ * be acceptable so we have to launch fetches.
+ */
+
+ if (FIND_STARTATZONE(find)) {
+ start_at_zone = true;
+ }
+
+ /*
+ * Start V4.
+ */
+ if (WANT_INET(wanted_fetches) &&
+ fetch_name(adbname, start_at_zone, depth, qc,
+ dns_rdatatype_a) == ISC_R_SUCCESS)
+ {
+ DP(DEF_LEVEL,
+ "dns_adb_createfind: "
+ "started A fetch for name %s (%p)",
+ namebuf, adbname);
+ }
+
+ /*
+ * Start V6.
+ */
+ if (WANT_INET6(wanted_fetches) &&
+ fetch_name(adbname, start_at_zone, depth, qc,
+ dns_rdatatype_aaaa) == ISC_R_SUCCESS)
+ {
+ DP(DEF_LEVEL,
+ "dns_adb_createfind: "
+ "started AAAA fetch for name %s (%p)",
+ namebuf, adbname);
+ }
+ }
+
+ /*
+ * Run through the name and copy out the bits we are
+ * interested in.
+ */
+ copy_namehook_lists(adb, find, qname, qtype, adbname, now);
+
+post_copy:
+ if (NAME_FETCH_A(adbname)) {
+ query_pending |= DNS_ADBFIND_INET;
+ }
+ if (NAME_FETCH_AAAA(adbname)) {
+ query_pending |= DNS_ADBFIND_INET6;
+ }
+
+ /*
+ * Attach to the name's query list if there are queries
+ * already running, and we have been asked to.
+ */
+ if (!FIND_WANTEVENT(find)) {
+ want_event = false;
+ }
+ if (FIND_WANTEMPTYEVENT(find) && FIND_HAS_ADDRS(find)) {
+ want_event = false;
+ }
+ if ((wanted_addresses & query_pending) == 0) {
+ want_event = false;
+ }
+ if (alias) {
+ want_event = false;
+ }
+ if (want_event) {
+ bool empty;
+ find->adbname = adbname;
+ find->name_bucket = bucket;
+ empty = ISC_LIST_EMPTY(adbname->finds);
+ ISC_LIST_APPEND(adbname->finds, find, plink);
+ find->query_pending = (query_pending & wanted_addresses);
+ find->flags &= ~DNS_ADBFIND_ADDRESSMASK;
+ find->flags |= (find->query_pending & DNS_ADBFIND_ADDRESSMASK);
+ DP(DEF_LEVEL, "createfind: attaching find %p to adbname %p %d",
+ find, adbname, empty);
+ } else {
+ /*
+ * Remove the flag so the caller knows there will never
+ * be an event, and set internal flags to fake that
+ * the event was sent and freed, so dns_adb_destroyfind() will
+ * do the right thing.
+ */
+ find->query_pending = (query_pending & wanted_addresses);
+ find->options &= ~DNS_ADBFIND_WANTEVENT;
+ find->flags |= (FIND_EVENT_SENT | FIND_EVENT_FREED);
+ find->flags &= ~DNS_ADBFIND_ADDRESSMASK;
+ }
+
+ find->partial_result |= (adbname->partial_result & wanted_addresses);
+ if (alias) {
+ if (target != NULL) {
+ dns_name_copy(&adbname->target, target);
+ }
+ result = DNS_R_ALIAS;
+ } else {
+ result = ISC_R_SUCCESS;
+ }
+
+ /*
+ * Copy out error flags from the name structure into the find.
+ */
+ find->result_v4 = find_err_map[adbname->fetch_err];
+ find->result_v6 = find_err_map[adbname->fetch6_err];
+
+out:
+ if (find != NULL) {
+ if (want_event) {
+ isc_task_t *taskp = NULL;
+
+ INSIST((find->flags & DNS_ADBFIND_ADDRESSMASK) != 0);
+ isc_task_attach(task, &taskp);
+ find->event.ev_sender = taskp;
+ find->event.ev_action = action;
+ find->event.ev_arg = arg;
+ }
+
+ *findp = find;
+ }
+
+ UNLOCK(&adb->namelocks[bucket]);
+ return (result);
+}
+
+void
+dns_adb_destroyfind(dns_adbfind_t **findp) {
+ dns_adbfind_t *find;
+ dns_adbentry_t *entry;
+ dns_adbaddrinfo_t *ai;
+ int bucket;
+ dns_adb_t *adb;
+ bool overmem;
+ isc_stdtime_t now;
+
+ REQUIRE(findp != NULL && DNS_ADBFIND_VALID(*findp));
+ find = *findp;
+ *findp = NULL;
+
+ LOCK(&find->lock);
+
+ DP(DEF_LEVEL, "dns_adb_destroyfind on find %p", find);
+
+ adb = find->adb;
+ REQUIRE(DNS_ADB_VALID(adb));
+
+ REQUIRE(FIND_EVENTFREED(find));
+
+ bucket = find->name_bucket;
+ INSIST(bucket == DNS_ADB_INVALIDBUCKET);
+
+ UNLOCK(&find->lock);
+
+ /*
+ * The find doesn't exist on any list, and nothing is locked.
+ * Return the find to the memory pool, and decrement the adb's
+ * reference count.
+ */
+ isc_stdtime_get(&now);
+ overmem = isc_mem_isovermem(adb->mctx);
+ ai = ISC_LIST_HEAD(find->list);
+ while (ai != NULL) {
+ ISC_LIST_UNLINK(find->list, ai, publink);
+ entry = ai->entry;
+ ai->entry = NULL;
+ INSIST(DNS_ADBENTRY_VALID(entry));
+ RUNTIME_CHECK(
+ !dec_entry_refcnt(adb, overmem, entry, true, now));
+ free_adbaddrinfo(adb, &ai);
+ ai = ISC_LIST_HEAD(find->list);
+ }
+
+ /*
+ * WARNING: The find is freed with the adb locked. This is done
+ * to avoid a race condition where we free the find, some other
+ * thread tests to see if it should be destroyed, detects it should
+ * be, destroys it, and then we try to lock it for our check, but the
+ * lock is destroyed.
+ */
+ LOCK(&adb->lock);
+ if (free_adbfind(adb, &find)) {
+ check_exit(adb);
+ }
+ UNLOCK(&adb->lock);
+}
+
+void
+dns_adb_cancelfind(dns_adbfind_t *find) {
+ isc_event_t *ev;
+ isc_task_t *task;
+ dns_adb_t *adb;
+ int bucket;
+ int unlock_bucket;
+
+ LOCK(&find->lock);
+
+ DP(DEF_LEVEL, "dns_adb_cancelfind on find %p", find);
+
+ adb = find->adb;
+ REQUIRE(DNS_ADB_VALID(adb));
+
+ REQUIRE(!FIND_EVENTFREED(find));
+ REQUIRE(FIND_WANTEVENT(find));
+
+ bucket = find->name_bucket;
+ if (bucket == DNS_ADB_INVALIDBUCKET) {
+ goto cleanup;
+ }
+
+ /*
+ * We need to get the adbname's lock to unlink the find.
+ */
+ unlock_bucket = bucket;
+ violate_locking_hierarchy(&find->lock, &adb->namelocks[unlock_bucket]);
+ bucket = find->name_bucket;
+ if (bucket != DNS_ADB_INVALIDBUCKET) {
+ ISC_LIST_UNLINK(find->adbname->finds, find, plink);
+ find->adbname = NULL;
+ find->name_bucket = DNS_ADB_INVALIDBUCKET;
+ }
+ UNLOCK(&adb->namelocks[unlock_bucket]);
+ bucket = DNS_ADB_INVALIDBUCKET;
+ POST(bucket);
+
+cleanup:
+
+ if (!FIND_EVENTSENT(find)) {
+ ev = &find->event;
+ task = ev->ev_sender;
+ ev->ev_sender = find;
+ ev->ev_type = DNS_EVENT_ADBCANCELED;
+ ev->ev_destroy = event_free;
+ ev->ev_destroy_arg = find;
+ find->result_v4 = ISC_R_CANCELED;
+ find->result_v6 = ISC_R_CANCELED;
+
+ DP(DEF_LEVEL, "sending event %p to task %p for find %p", ev,
+ task, find);
+
+ isc_task_sendanddetach(&task, (isc_event_t **)&ev);
+ }
+
+ UNLOCK(&find->lock);
+}
+
+void
+dns_adb_dump(dns_adb_t *adb, FILE *f) {
+ unsigned int i;
+ isc_stdtime_t now;
+
+ REQUIRE(DNS_ADB_VALID(adb));
+ REQUIRE(f != NULL);
+
+ /*
+ * Lock the adb itself, lock all the name buckets, then lock all
+ * the entry buckets. This should put the adb into a state where
+ * nothing can change, so we can iterate through everything and
+ * print at our leisure.
+ */
+
+ LOCK(&adb->lock);
+ isc_stdtime_get(&now);
+
+ for (i = 0; i < adb->nnames; i++) {
+ RUNTIME_CHECK(!cleanup_names(adb, i, now));
+ }
+ for (i = 0; i < adb->nentries; i++) {
+ RUNTIME_CHECK(!cleanup_entries(adb, i, now));
+ }
+
+ dump_adb(adb, f, false, now);
+ UNLOCK(&adb->lock);
+}
+
+static void
+dump_ttl(FILE *f, const char *legend, isc_stdtime_t value, isc_stdtime_t now) {
+ if (value == INT_MAX) {
+ return;
+ }
+ fprintf(f, " [%s TTL %d]", legend, (int)(value - now));
+}
+
+static void
+dump_adb(dns_adb_t *adb, FILE *f, bool debug, isc_stdtime_t now) {
+ dns_adbname_t *name;
+ dns_adbentry_t *entry;
+
+ fprintf(f, ";\n; Address database dump\n;\n");
+ fprintf(f, "; [edns success/timeout]\n");
+ fprintf(f, "; [plain success/timeout]\n;\n");
+ if (debug) {
+ LOCK(&adb->reflock);
+ fprintf(f,
+ "; addr %p, erefcnt %u, irefcnt %u, finds out "
+ "%" PRIuFAST32 "\n",
+ adb, adb->erefcnt, adb->irefcnt,
+ isc_refcount_current(&adb->nhrefcnt));
+ UNLOCK(&adb->reflock);
+ }
+
+/*
+ * In TSAN mode we need to lock the locks individually, as TSAN
+ * can't handle more than 64 locks locked by one thread.
+ * In regular mode we want a consistent dump so we need to
+ * lock everything.
+ */
+#ifndef __SANITIZE_THREAD__
+ for (size_t i = 0; i < adb->nnames; i++) {
+ LOCK(&adb->namelocks[i]);
+ }
+ for (size_t i = 0; i < adb->nentries; i++) {
+ LOCK(&adb->entrylocks[i]);
+ }
+#endif /* ifndef __SANITIZE_THREAD__ */
+
+ /*
+ * Dump the names
+ */
+ for (size_t i = 0; i < adb->nnames; i++) {
+#ifdef __SANITIZE_THREAD__
+ LOCK(&adb->namelocks[i]);
+#endif /* ifdef __SANITIZE_THREAD__ */
+ name = ISC_LIST_HEAD(adb->names[i]);
+ if (name == NULL) {
+#ifdef __SANITIZE_THREAD__
+ UNLOCK(&adb->namelocks[i]);
+#endif /* ifdef __SANITIZE_THREAD__ */
+ continue;
+ }
+ if (debug) {
+ fprintf(f, "; bucket %zu\n", i);
+ }
+ for (; name != NULL; name = ISC_LIST_NEXT(name, plink)) {
+ if (debug) {
+ fprintf(f, "; name %p (flags %08x)\n", name,
+ name->flags);
+ }
+ fprintf(f, "; ");
+ print_dns_name(f, &name->name);
+ if (dns_name_countlabels(&name->target) > 0) {
+ fprintf(f, " alias ");
+ print_dns_name(f, &name->target);
+ }
+
+ dump_ttl(f, "v4", name->expire_v4, now);
+ dump_ttl(f, "v6", name->expire_v6, now);
+ dump_ttl(f, "target", name->expire_target, now);
+
+ fprintf(f, " [v4 %s] [v6 %s]",
+ errnames[name->fetch_err],
+ errnames[name->fetch6_err]);
+
+ fprintf(f, "\n");
+
+ print_namehook_list(f, "v4", adb, &name->v4, debug,
+ now);
+ print_namehook_list(f, "v6", adb, &name->v6, debug,
+ now);
+
+ if (debug) {
+ print_fetch_list(f, name);
+ print_find_list(f, name);
+ }
+ }
+#ifdef __SANITIZE_THREAD__
+ UNLOCK(&adb->namelocks[i]);
+#endif /* ifdef __SANITIZE_THREAD__ */
+ }
+
+ fprintf(f, ";\n; Unassociated entries\n;\n");
+
+ for (size_t i = 0; i < adb->nentries; i++) {
+#ifdef __SANITIZE_THREAD__
+ LOCK(&adb->entrylocks[i]);
+#endif /* ifdef __SANITIZE_THREAD__ */
+ entry = ISC_LIST_HEAD(adb->entries[i]);
+ while (entry != NULL) {
+ if (entry->nh == 0) {
+ dump_entry(f, adb, entry, debug, now);
+ }
+ entry = ISC_LIST_NEXT(entry, plink);
+ }
+#ifdef __SANITIZE_THREAD__
+ UNLOCK(&adb->entrylocks[i]);
+#endif /* ifdef __SANITIZE_THREAD__ */
+ }
+
+#ifndef __SANITIZE_THREAD__
+ /*
+ * Unlock everything
+ */
+ for (ssize_t i = adb->nentries - 1; i >= 0; i--) {
+ UNLOCK(&adb->entrylocks[i]);
+ }
+ for (ssize_t i = adb->nnames - 1; i >= 0; i--) {
+ UNLOCK(&adb->namelocks[i]);
+ }
+#endif /* ifndef __SANITIZE_THREAD__ */
+}
+
+static void
+dump_entry(FILE *f, dns_adb_t *adb, dns_adbentry_t *entry, bool debug,
+ isc_stdtime_t now) {
+ char addrbuf[ISC_NETADDR_FORMATSIZE];
+ char typebuf[DNS_RDATATYPE_FORMATSIZE];
+ isc_netaddr_t netaddr;
+ dns_adblameinfo_t *li;
+
+ isc_netaddr_fromsockaddr(&netaddr, &entry->sockaddr);
+ isc_netaddr_format(&netaddr, addrbuf, sizeof(addrbuf));
+
+ if (debug) {
+ fprintf(f, ";\t%p: refcnt %u\n", entry, entry->refcnt);
+ }
+
+ fprintf(f,
+ ";\t%s [srtt %u] [flags %08x] [edns %u/%u] "
+ "[plain %u/%u]",
+ addrbuf, entry->srtt, entry->flags, entry->edns, entry->ednsto,
+ entry->plain, entry->plainto);
+ if (entry->udpsize != 0U) {
+ fprintf(f, " [udpsize %u]", entry->udpsize);
+ }
+ if (entry->cookie != NULL) {
+ unsigned int i;
+ fprintf(f, " [cookie=");
+ for (i = 0; i < entry->cookielen; i++) {
+ fprintf(f, "%02x", entry->cookie[i]);
+ }
+ fprintf(f, "]");
+ }
+ if (entry->expires != 0) {
+ fprintf(f, " [ttl %d]", (int)(entry->expires - now));
+ }
+
+ if (adb != NULL && adb->quota != 0 && adb->atr_freq != 0) {
+ uint_fast32_t quota = atomic_load_relaxed(&entry->quota);
+ fprintf(f, " [atr %0.2f] [quota %" PRIuFAST32 "]", entry->atr,
+ quota);
+ }
+
+ fprintf(f, "\n");
+ for (li = ISC_LIST_HEAD(entry->lameinfo); li != NULL;
+ li = ISC_LIST_NEXT(li, plink))
+ {
+ fprintf(f, ";\t\t");
+ print_dns_name(f, &li->qname);
+ dns_rdatatype_format(li->qtype, typebuf, sizeof(typebuf));
+ fprintf(f, " %s [lame TTL %d]\n", typebuf,
+ (int)(li->lame_timer - now));
+ }
+}
+
+void
+dns_adb_dumpfind(dns_adbfind_t *find, FILE *f) {
+ char tmp[512];
+ const char *tmpp;
+ dns_adbaddrinfo_t *ai;
+ isc_sockaddr_t *sa;
+
+ /*
+ * Not used currently, in the API Just In Case we
+ * want to dump out the name and/or entries too.
+ */
+
+ LOCK(&find->lock);
+
+ fprintf(f, ";Find %p\n", find);
+ fprintf(f, ";\tqpending %08x partial %08x options %08x flags %08x\n",
+ find->query_pending, find->partial_result, find->options,
+ find->flags);
+ fprintf(f, ";\tname_bucket %d, name %p, event sender %p\n",
+ find->name_bucket, find->adbname, find->event.ev_sender);
+
+ ai = ISC_LIST_HEAD(find->list);
+ if (ai != NULL) {
+ fprintf(f, "\tAddresses:\n");
+ }
+ while (ai != NULL) {
+ sa = &ai->sockaddr;
+ switch (sa->type.sa.sa_family) {
+ case AF_INET:
+ tmpp = inet_ntop(AF_INET, &sa->type.sin.sin_addr, tmp,
+ sizeof(tmp));
+ break;
+ case AF_INET6:
+ tmpp = inet_ntop(AF_INET6, &sa->type.sin6.sin6_addr,
+ tmp, sizeof(tmp));
+ break;
+ default:
+ tmpp = "UnkFamily";
+ }
+
+ if (tmpp == NULL) {
+ tmpp = "BadAddress";
+ }
+
+ fprintf(f,
+ "\t\tentry %p, flags %08x"
+ " srtt %u addr %s\n",
+ ai->entry, ai->flags, ai->srtt, tmpp);
+
+ ai = ISC_LIST_NEXT(ai, publink);
+ }
+
+ UNLOCK(&find->lock);
+}
+
+static void
+print_dns_name(FILE *f, const dns_name_t *name) {
+ char buf[DNS_NAME_FORMATSIZE];
+
+ INSIST(f != NULL);
+
+ dns_name_format(name, buf, sizeof(buf));
+ fprintf(f, "%s", buf);
+}
+
+static void
+print_namehook_list(FILE *f, const char *legend, dns_adb_t *adb,
+ dns_adbnamehooklist_t *list, bool debug,
+ isc_stdtime_t now) {
+ dns_adbnamehook_t *nh;
+
+ for (nh = ISC_LIST_HEAD(*list); nh != NULL;
+ nh = ISC_LIST_NEXT(nh, plink))
+ {
+ if (debug) {
+ fprintf(f, ";\tHook(%s) %p\n", legend, nh);
+ }
+#ifdef __SANITIZE_THREAD__
+ LOCK(&adb->entrylocks[nh->entry->lock_bucket]);
+#endif
+ dump_entry(f, adb, nh->entry, debug, now);
+#ifdef __SANITIZE_THREAD__
+ UNLOCK(&adb->entrylocks[nh->entry->lock_bucket]);
+#endif
+ }
+}
+
+static void
+print_fetch(FILE *f, dns_adbfetch_t *ft, const char *type) {
+ fprintf(f, "\t\tFetch(%s): %p -> { fetch %p }\n", type, ft, ft->fetch);
+}
+
+static void
+print_fetch_list(FILE *f, dns_adbname_t *n) {
+ if (NAME_FETCH_A(n)) {
+ print_fetch(f, n->fetch_a, "A");
+ }
+ if (NAME_FETCH_AAAA(n)) {
+ print_fetch(f, n->fetch_aaaa, "AAAA");
+ }
+}
+
+static void
+print_find_list(FILE *f, dns_adbname_t *name) {
+ dns_adbfind_t *find;
+
+ find = ISC_LIST_HEAD(name->finds);
+ while (find != NULL) {
+ dns_adb_dumpfind(find, f);
+ find = ISC_LIST_NEXT(find, plink);
+ }
+}
+
+static isc_result_t
+dbfind_name(dns_adbname_t *adbname, isc_stdtime_t now, dns_rdatatype_t rdtype) {
+ isc_result_t result;
+ dns_rdataset_t rdataset;
+ dns_adb_t *adb;
+ dns_fixedname_t foundname;
+ dns_name_t *fname;
+
+ INSIST(DNS_ADBNAME_VALID(adbname));
+ adb = adbname->adb;
+ INSIST(DNS_ADB_VALID(adb));
+ INSIST(rdtype == dns_rdatatype_a || rdtype == dns_rdatatype_aaaa);
+
+ fname = dns_fixedname_initname(&foundname);
+ dns_rdataset_init(&rdataset);
+
+ if (rdtype == dns_rdatatype_a) {
+ adbname->fetch_err = FIND_ERR_UNEXPECTED;
+ } else {
+ adbname->fetch6_err = FIND_ERR_UNEXPECTED;
+ }
+
+ /*
+ * We need to specify whether to search static-stub zones (if
+ * configured) depending on whether this is a "start at zone" lookup,
+ * i.e., whether it's a "bailiwick" glue. If it's bailiwick (in which
+ * case DNS_ADBFIND_STARTATZONE is set) we need to stop the search at
+ * any matching static-stub zone without looking into the cache to honor
+ * the configuration on which server we should send queries to.
+ */
+ result =
+ dns_view_find(adb->view, &adbname->name, rdtype, now,
+ NAME_GLUEOK(adbname) ? DNS_DBFIND_GLUEOK : 0,
+ NAME_HINTOK(adbname),
+ ((adbname->flags & DNS_ADBFIND_STARTATZONE) != 0),
+ NULL, NULL, fname, &rdataset, NULL);
+
+ /* XXXVIX this switch statement is too sparse to gen a jump table. */
+ switch (result) {
+ case DNS_R_GLUE:
+ case DNS_R_HINT:
+ case ISC_R_SUCCESS:
+ /*
+ * Found in the database. Even if we can't copy out
+ * any information, return success, or else a fetch
+ * will be made, which will only make things worse.
+ */
+ if (rdtype == dns_rdatatype_a) {
+ adbname->fetch_err = FIND_ERR_SUCCESS;
+ } else {
+ adbname->fetch6_err = FIND_ERR_SUCCESS;
+ }
+ result = import_rdataset(adbname, &rdataset, now);
+ break;
+ case DNS_R_NXDOMAIN:
+ case DNS_R_NXRRSET:
+ /*
+ * We're authoritative and the data doesn't exist.
+ * Make up a negative cache entry so we don't ask again
+ * for a while.
+ *
+ * XXXRTH What time should we use? I'm putting in 30 seconds
+ * for now.
+ */
+ if (rdtype == dns_rdatatype_a) {
+ adbname->expire_v4 = now + 30;
+ DP(NCACHE_LEVEL,
+ "adb name %p: Caching auth negative entry for A",
+ adbname);
+ if (result == DNS_R_NXDOMAIN) {
+ adbname->fetch_err = FIND_ERR_NXDOMAIN;
+ } else {
+ adbname->fetch_err = FIND_ERR_NXRRSET;
+ }
+ } else {
+ DP(NCACHE_LEVEL,
+ "adb name %p: Caching auth negative entry for AAAA",
+ adbname);
+ adbname->expire_v6 = now + 30;
+ if (result == DNS_R_NXDOMAIN) {
+ adbname->fetch6_err = FIND_ERR_NXDOMAIN;
+ } else {
+ adbname->fetch6_err = FIND_ERR_NXRRSET;
+ }
+ }
+ break;
+ case DNS_R_NCACHENXDOMAIN:
+ case DNS_R_NCACHENXRRSET:
+ /*
+ * We found a negative cache entry. Pull the TTL from it
+ * so we won't ask again for a while.
+ */
+ rdataset.ttl = ttlclamp(rdataset.ttl);
+ if (rdtype == dns_rdatatype_a) {
+ adbname->expire_v4 = rdataset.ttl + now;
+ if (result == DNS_R_NCACHENXDOMAIN) {
+ adbname->fetch_err = FIND_ERR_NXDOMAIN;
+ } else {
+ adbname->fetch_err = FIND_ERR_NXRRSET;
+ }
+ DP(NCACHE_LEVEL,
+ "adb name %p: Caching negative entry for A (ttl %u)",
+ adbname, rdataset.ttl);
+ } else {
+ DP(NCACHE_LEVEL,
+ "adb name %p: Caching negative entry for AAAA (ttl "
+ "%u)",
+ adbname, rdataset.ttl);
+ adbname->expire_v6 = rdataset.ttl + now;
+ if (result == DNS_R_NCACHENXDOMAIN) {
+ adbname->fetch6_err = FIND_ERR_NXDOMAIN;
+ } else {
+ adbname->fetch6_err = FIND_ERR_NXRRSET;
+ }
+ }
+ break;
+ case DNS_R_CNAME:
+ case DNS_R_DNAME:
+ /*
+ * Clear the hint and glue flags, so this will match
+ * more often.
+ */
+ adbname->flags &= ~(DNS_ADBFIND_GLUEOK | DNS_ADBFIND_HINTOK);
+
+ rdataset.ttl = ttlclamp(rdataset.ttl);
+ clean_target(adb, &adbname->target);
+ adbname->expire_target = INT_MAX;
+ result = set_target(adb, &adbname->name, fname, &rdataset,
+ &adbname->target);
+ if (result == ISC_R_SUCCESS) {
+ result = DNS_R_ALIAS;
+ DP(NCACHE_LEVEL, "adb name %p: caching alias target",
+ adbname);
+ adbname->expire_target = rdataset.ttl + now;
+ }
+ if (rdtype == dns_rdatatype_a) {
+ adbname->fetch_err = FIND_ERR_SUCCESS;
+ } else {
+ adbname->fetch6_err = FIND_ERR_SUCCESS;
+ }
+ break;
+ default:
+ break;
+ }
+
+ if (dns_rdataset_isassociated(&rdataset)) {
+ dns_rdataset_disassociate(&rdataset);
+ }
+
+ return (result);
+}
+
+static void
+fetch_callback(isc_task_t *task, isc_event_t *ev) {
+ dns_fetchevent_t *dev;
+ dns_adbname_t *name;
+ dns_adb_t *adb;
+ dns_adbfetch_t *fetch;
+ int bucket;
+ isc_eventtype_t ev_status;
+ isc_stdtime_t now;
+ isc_result_t result;
+ unsigned int address_type;
+ bool want_check_exit = false;
+
+ UNUSED(task);
+
+ INSIST(ev->ev_type == DNS_EVENT_FETCHDONE);
+ dev = (dns_fetchevent_t *)ev;
+ name = ev->ev_arg;
+ INSIST(DNS_ADBNAME_VALID(name));
+ adb = name->adb;
+ INSIST(DNS_ADB_VALID(adb));
+
+ bucket = name->lock_bucket;
+ LOCK(&adb->namelocks[bucket]);
+
+ INSIST(NAME_FETCH_A(name) || NAME_FETCH_AAAA(name));
+ address_type = 0;
+ if (NAME_FETCH_A(name) && (name->fetch_a->fetch == dev->fetch)) {
+ address_type = DNS_ADBFIND_INET;
+ fetch = name->fetch_a;
+ name->fetch_a = NULL;
+ } else if (NAME_FETCH_AAAA(name) &&
+ (name->fetch_aaaa->fetch == dev->fetch))
+ {
+ address_type = DNS_ADBFIND_INET6;
+ fetch = name->fetch_aaaa;
+ name->fetch_aaaa = NULL;
+ } else {
+ fetch = NULL;
+ }
+
+ INSIST(address_type != 0 && fetch != NULL);
+
+ dns_resolver_destroyfetch(&fetch->fetch);
+ dev->fetch = NULL;
+
+ ev_status = DNS_EVENT_ADBNOMOREADDRESSES;
+
+ /*
+ * Cleanup things we don't care about.
+ */
+ if (dev->node != NULL) {
+ dns_db_detachnode(dev->db, &dev->node);
+ }
+ if (dev->db != NULL) {
+ dns_db_detach(&dev->db);
+ }
+
+ /*
+ * If this name is marked as dead, clean up, throwing away
+ * potentially good data.
+ */
+ if (NAME_DEAD(name)) {
+ free_adbfetch(adb, &fetch);
+ isc_event_free(&ev);
+
+ want_check_exit = kill_name(&name, DNS_EVENT_ADBCANCELED);
+
+ UNLOCK(&adb->namelocks[bucket]);
+
+ if (want_check_exit) {
+ LOCK(&adb->lock);
+ check_exit(adb);
+ UNLOCK(&adb->lock);
+ }
+
+ return;
+ }
+
+ isc_stdtime_get(&now);
+
+ /*
+ * If we got a negative cache response, remember it.
+ */
+ if (NCACHE_RESULT(dev->result)) {
+ dev->rdataset->ttl = ttlclamp(dev->rdataset->ttl);
+ if (address_type == DNS_ADBFIND_INET) {
+ DP(NCACHE_LEVEL,
+ "adb fetch name %p: "
+ "caching negative entry for A (ttl %u)",
+ name, dev->rdataset->ttl);
+ name->expire_v4 = ISC_MIN(name->expire_v4,
+ dev->rdataset->ttl + now);
+ if (dev->result == DNS_R_NCACHENXDOMAIN) {
+ name->fetch_err = FIND_ERR_NXDOMAIN;
+ } else {
+ name->fetch_err = FIND_ERR_NXRRSET;
+ }
+ inc_stats(adb, dns_resstatscounter_gluefetchv4fail);
+ } else {
+ DP(NCACHE_LEVEL,
+ "adb fetch name %p: "
+ "caching negative entry for AAAA (ttl %u)",
+ name, dev->rdataset->ttl);
+ name->expire_v6 = ISC_MIN(name->expire_v6,
+ dev->rdataset->ttl + now);
+ if (dev->result == DNS_R_NCACHENXDOMAIN) {
+ name->fetch6_err = FIND_ERR_NXDOMAIN;
+ } else {
+ name->fetch6_err = FIND_ERR_NXRRSET;
+ }
+ inc_stats(adb, dns_resstatscounter_gluefetchv6fail);
+ }
+ goto out;
+ }
+
+ /*
+ * Handle CNAME/DNAME.
+ */
+ if (dev->result == DNS_R_CNAME || dev->result == DNS_R_DNAME) {
+ dev->rdataset->ttl = ttlclamp(dev->rdataset->ttl);
+ clean_target(adb, &name->target);
+ name->expire_target = INT_MAX;
+ result = set_target(adb, &name->name, dev->foundname,
+ dev->rdataset, &name->target);
+ if (result == ISC_R_SUCCESS) {
+ DP(NCACHE_LEVEL,
+ "adb fetch name %p: caching alias target", name);
+ name->expire_target = dev->rdataset->ttl + now;
+ }
+ goto check_result;
+ }
+
+ /*
+ * Did we get back junk? If so, and there are no more fetches
+ * sitting out there, tell all the finds about it.
+ */
+ if (dev->result != ISC_R_SUCCESS) {
+ char buf[DNS_NAME_FORMATSIZE];
+
+ dns_name_format(&name->name, buf, sizeof(buf));
+ DP(DEF_LEVEL, "adb: fetch of '%s' %s failed: %s", buf,
+ address_type == DNS_ADBFIND_INET ? "A" : "AAAA",
+ isc_result_totext(dev->result));
+ /*
+ * Don't record a failure unless this is the initial
+ * fetch of a chain.
+ */
+ if (fetch->depth > 1) {
+ goto out;
+ }
+ /* XXXMLG Don't pound on bad servers. */
+ if (address_type == DNS_ADBFIND_INET) {
+ name->expire_v4 = ISC_MIN(name->expire_v4, now + 10);
+ name->fetch_err = FIND_ERR_FAILURE;
+ inc_stats(adb, dns_resstatscounter_gluefetchv4fail);
+ } else {
+ name->expire_v6 = ISC_MIN(name->expire_v6, now + 10);
+ name->fetch6_err = FIND_ERR_FAILURE;
+ inc_stats(adb, dns_resstatscounter_gluefetchv6fail);
+ }
+ goto out;
+ }
+
+ /*
+ * We got something potentially useful.
+ */
+ result = import_rdataset(name, &fetch->rdataset, now);
+
+check_result:
+ if (result == ISC_R_SUCCESS) {
+ ev_status = DNS_EVENT_ADBMOREADDRESSES;
+ if (address_type == DNS_ADBFIND_INET) {
+ name->fetch_err = FIND_ERR_SUCCESS;
+ } else {
+ name->fetch6_err = FIND_ERR_SUCCESS;
+ }
+ }
+
+out:
+ free_adbfetch(adb, &fetch);
+ isc_event_free(&ev);
+
+ clean_finds_at_name(name, ev_status, address_type);
+
+ UNLOCK(&adb->namelocks[bucket]);
+}
+
+static isc_result_t
+fetch_name(dns_adbname_t *adbname, bool start_at_zone, unsigned int depth,
+ isc_counter_t *qc, dns_rdatatype_t type) {
+ isc_result_t result;
+ dns_adbfetch_t *fetch = NULL;
+ dns_adb_t *adb;
+ dns_fixedname_t fixed;
+ dns_name_t *name;
+ dns_rdataset_t rdataset;
+ dns_rdataset_t *nameservers;
+ unsigned int options;
+
+ INSIST(DNS_ADBNAME_VALID(adbname));
+ adb = adbname->adb;
+ INSIST(DNS_ADB_VALID(adb));
+
+ INSIST((type == dns_rdatatype_a && !NAME_FETCH_A(adbname)) ||
+ (type == dns_rdatatype_aaaa && !NAME_FETCH_AAAA(adbname)));
+
+ adbname->fetch_err = FIND_ERR_NOTFOUND;
+
+ name = NULL;
+ nameservers = NULL;
+ dns_rdataset_init(&rdataset);
+
+ options = DNS_FETCHOPT_NOVALIDATE;
+ if (start_at_zone) {
+ DP(ENTER_LEVEL, "fetch_name: starting at zone for name %p",
+ adbname);
+ name = dns_fixedname_initname(&fixed);
+ result = dns_view_findzonecut(adb->view, &adbname->name, name,
+ NULL, 0, 0, true, false,
+ &rdataset, NULL);
+ if (result != ISC_R_SUCCESS && result != DNS_R_HINT) {
+ goto cleanup;
+ }
+ nameservers = &rdataset;
+ options |= DNS_FETCHOPT_UNSHARED;
+ }
+
+ fetch = new_adbfetch(adb);
+ fetch->depth = depth;
+
+ /*
+ * We're not minimizing this query, as nothing user-related should
+ * be leaked here.
+ * However, if we'd ever want to change it we'd have to modify
+ * createfetch to find deepest cached name when we're providing
+ * domain and nameservers.
+ */
+ result = dns_resolver_createfetch(
+ adb->view->resolver, &adbname->name, type, name, nameservers,
+ NULL, NULL, 0, options, depth, qc, adb->task, fetch_callback,
+ adbname, &fetch->rdataset, NULL, &fetch->fetch);
+ if (result != ISC_R_SUCCESS) {
+ DP(ENTER_LEVEL, "fetch_name: createfetch failed with %s",
+ isc_result_totext(result));
+ goto cleanup;
+ }
+
+ if (type == dns_rdatatype_a) {
+ adbname->fetch_a = fetch;
+ inc_stats(adb, dns_resstatscounter_gluefetchv4);
+ } else {
+ adbname->fetch_aaaa = fetch;
+ inc_stats(adb, dns_resstatscounter_gluefetchv6);
+ }
+ fetch = NULL; /* Keep us from cleaning this up below. */
+
+cleanup:
+ if (fetch != NULL) {
+ free_adbfetch(adb, &fetch);
+ }
+ if (dns_rdataset_isassociated(&rdataset)) {
+ dns_rdataset_disassociate(&rdataset);
+ }
+
+ return (result);
+}
+
+/*
+ * XXXMLG Needs to take a find argument and an address info, no zone or adb,
+ * since these can be extracted from the find itself.
+ */
+isc_result_t
+dns_adb_marklame(dns_adb_t *adb, dns_adbaddrinfo_t *addr,
+ const dns_name_t *qname, dns_rdatatype_t qtype,
+ isc_stdtime_t expire_time) {
+ dns_adblameinfo_t *li;
+ int bucket;
+ isc_result_t result = ISC_R_SUCCESS;
+
+ REQUIRE(DNS_ADB_VALID(adb));
+ REQUIRE(DNS_ADBADDRINFO_VALID(addr));
+ REQUIRE(qname != NULL);
+
+ bucket = addr->entry->lock_bucket;
+ LOCK(&adb->entrylocks[bucket]);
+ li = ISC_LIST_HEAD(addr->entry->lameinfo);
+ while (li != NULL &&
+ (li->qtype != qtype || !dns_name_equal(qname, &li->qname)))
+ {
+ li = ISC_LIST_NEXT(li, plink);
+ }
+ if (li != NULL) {
+ if (expire_time > li->lame_timer) {
+ li->lame_timer = expire_time;
+ }
+ goto unlock;
+ }
+ li = new_adblameinfo(adb, qname, qtype);
+ li->lame_timer = expire_time;
+
+ ISC_LIST_PREPEND(addr->entry->lameinfo, li, plink);
+unlock:
+ UNLOCK(&adb->entrylocks[bucket]);
+
+ return (result);
+}
+
+void
+dns_adb_adjustsrtt(dns_adb_t *adb, dns_adbaddrinfo_t *addr, unsigned int rtt,
+ unsigned int factor) {
+ int bucket;
+ isc_stdtime_t now = 0;
+
+ REQUIRE(DNS_ADB_VALID(adb));
+ REQUIRE(DNS_ADBADDRINFO_VALID(addr));
+ REQUIRE(factor <= 10);
+
+ bucket = addr->entry->lock_bucket;
+ LOCK(&adb->entrylocks[bucket]);
+
+ if (addr->entry->expires == 0 || factor == DNS_ADB_RTTADJAGE) {
+ isc_stdtime_get(&now);
+ }
+ adjustsrtt(addr, rtt, factor, now);
+
+ UNLOCK(&adb->entrylocks[bucket]);
+}
+
+void
+dns_adb_agesrtt(dns_adb_t *adb, dns_adbaddrinfo_t *addr, isc_stdtime_t now) {
+ int bucket;
+
+ REQUIRE(DNS_ADB_VALID(adb));
+ REQUIRE(DNS_ADBADDRINFO_VALID(addr));
+
+ bucket = addr->entry->lock_bucket;
+ LOCK(&adb->entrylocks[bucket]);
+
+ adjustsrtt(addr, 0, DNS_ADB_RTTADJAGE, now);
+
+ UNLOCK(&adb->entrylocks[bucket]);
+}
+
+static void
+adjustsrtt(dns_adbaddrinfo_t *addr, unsigned int rtt, unsigned int factor,
+ isc_stdtime_t now) {
+ uint64_t new_srtt;
+
+ if (factor == DNS_ADB_RTTADJAGE) {
+ if (addr->entry->lastage != now) {
+ new_srtt = addr->entry->srtt;
+ new_srtt <<= 9;
+ new_srtt -= addr->entry->srtt;
+ new_srtt >>= 9;
+ addr->entry->lastage = now;
+ } else {
+ new_srtt = addr->entry->srtt;
+ }
+ } else {
+ new_srtt = ((uint64_t)addr->entry->srtt / 10 * factor) +
+ ((uint64_t)rtt / 10 * (10 - factor));
+ }
+
+ addr->entry->srtt = (unsigned int)new_srtt;
+ addr->srtt = (unsigned int)new_srtt;
+
+ if (addr->entry->expires == 0) {
+ addr->entry->expires = now + ADB_ENTRY_WINDOW;
+ }
+}
+
+void
+dns_adb_changeflags(dns_adb_t *adb, dns_adbaddrinfo_t *addr, unsigned int bits,
+ unsigned int mask) {
+ int bucket;
+ isc_stdtime_t now;
+
+ REQUIRE(DNS_ADB_VALID(adb));
+ REQUIRE(DNS_ADBADDRINFO_VALID(addr));
+
+ REQUIRE((bits & ENTRY_IS_DEAD) == 0);
+ REQUIRE((mask & ENTRY_IS_DEAD) == 0);
+
+ bucket = addr->entry->lock_bucket;
+ LOCK(&adb->entrylocks[bucket]);
+
+ addr->entry->flags = (addr->entry->flags & ~mask) | (bits & mask);
+ if (addr->entry->expires == 0) {
+ isc_stdtime_get(&now);
+ addr->entry->expires = now + ADB_ENTRY_WINDOW;
+ }
+
+ /*
+ * Note that we do not update the other bits in addr->flags with
+ * the most recent values from addr->entry->flags.
+ */
+ addr->flags = (addr->flags & ~mask) | (bits & mask);
+
+ UNLOCK(&adb->entrylocks[bucket]);
+}
+
+/*
+ * The polynomial backoff curve (10000 / ((10 + n) / 10)^(3/2)) <0..99> drops
+ * fairly aggressively at first, then slows down and tails off at around 2-3%.
+ *
+ * These will be used to make quota adjustments.
+ */
+static int quota_adj[] = {
+ 10000, 8668, 7607, 6747, 6037, 5443, 4941, 4512, 4141, 3818, 3536,
+ 3286, 3065, 2867, 2690, 2530, 2385, 2254, 2134, 2025, 1925, 1832,
+ 1747, 1668, 1595, 1527, 1464, 1405, 1350, 1298, 1250, 1205, 1162,
+ 1121, 1083, 1048, 1014, 981, 922, 894, 868, 843, 820, 797,
+ 775, 755, 735, 716, 698, 680, 664, 648, 632, 618, 603,
+ 590, 577, 564, 552, 540, 529, 518, 507, 497, 487, 477,
+ 468, 459, 450, 442, 434, 426, 418, 411, 404, 397, 390,
+ 383, 377, 370, 364, 358, 353, 347, 342, 336, 331, 326,
+ 321, 316, 312, 307, 303, 298, 294, 290, 286, 282, 278
+};
+
+#define QUOTA_ADJ_SIZE (sizeof(quota_adj) / sizeof(quota_adj[0]))
+
+/*
+ * Caller must hold adbentry lock
+ */
+static void
+maybe_adjust_quota(dns_adb_t *adb, dns_adbaddrinfo_t *addr, bool timeout) {
+ double tr;
+
+ UNUSED(adb);
+
+ if (adb->quota == 0 || adb->atr_freq == 0) {
+ return;
+ }
+
+ if (timeout) {
+ addr->entry->timeouts++;
+ }
+
+ if (addr->entry->completed++ <= adb->atr_freq) {
+ return;
+ }
+
+ /*
+ * Calculate an exponential rolling average of the timeout ratio
+ *
+ * XXX: Integer arithmetic might be better than floating point
+ */
+ tr = (double)addr->entry->timeouts / addr->entry->completed;
+ addr->entry->timeouts = addr->entry->completed = 0;
+ INSIST(addr->entry->atr >= 0.0);
+ INSIST(addr->entry->atr <= 1.0);
+ INSIST(adb->atr_discount >= 0.0);
+ INSIST(adb->atr_discount <= 1.0);
+ addr->entry->atr *= 1.0 - adb->atr_discount;
+ addr->entry->atr += tr * adb->atr_discount;
+ addr->entry->atr = ISC_CLAMP(addr->entry->atr, 0.0, 1.0);
+
+ if (addr->entry->atr < adb->atr_low && addr->entry->mode > 0) {
+ uint_fast32_t new_quota =
+ adb->quota * quota_adj[--addr->entry->mode] / 10000;
+ atomic_store_release(&addr->entry->quota,
+ ISC_MAX(1, new_quota));
+ log_quota(addr->entry,
+ "atr %0.2f, quota increased to %" PRIuFAST32,
+ addr->entry->atr, new_quota);
+ } else if (addr->entry->atr > adb->atr_high &&
+ addr->entry->mode < (QUOTA_ADJ_SIZE - 1))
+ {
+ uint_fast32_t new_quota =
+ adb->quota * quota_adj[++addr->entry->mode] / 10000;
+ atomic_store_release(&addr->entry->quota,
+ ISC_MAX(1, new_quota));
+ log_quota(addr->entry,
+ "atr %0.2f, quota decreased to %" PRIuFAST32,
+ addr->entry->atr, new_quota);
+ }
+}
+
+#define EDNSTOS 3U
+
+void
+dns_adb_plainresponse(dns_adb_t *adb, dns_adbaddrinfo_t *addr) {
+ int bucket;
+
+ REQUIRE(DNS_ADB_VALID(adb));
+ REQUIRE(DNS_ADBADDRINFO_VALID(addr));
+
+ bucket = addr->entry->lock_bucket;
+ LOCK(&adb->entrylocks[bucket]);
+
+ maybe_adjust_quota(adb, addr, false);
+
+ addr->entry->plain++;
+ if (addr->entry->plain == 0xff) {
+ addr->entry->edns >>= 1;
+ addr->entry->ednsto >>= 1;
+ addr->entry->plain >>= 1;
+ addr->entry->plainto >>= 1;
+ }
+ UNLOCK(&adb->entrylocks[bucket]);
+}
+
+void
+dns_adb_timeout(dns_adb_t *adb, dns_adbaddrinfo_t *addr) {
+ int bucket;
+
+ REQUIRE(DNS_ADB_VALID(adb));
+ REQUIRE(DNS_ADBADDRINFO_VALID(addr));
+
+ bucket = addr->entry->lock_bucket;
+ LOCK(&adb->entrylocks[bucket]);
+
+ maybe_adjust_quota(adb, addr, true);
+
+ addr->entry->plainto++;
+ if (addr->entry->plainto == 0xff) {
+ addr->entry->edns >>= 1;
+ addr->entry->ednsto >>= 1;
+ addr->entry->plain >>= 1;
+ addr->entry->plainto >>= 1;
+ }
+ UNLOCK(&adb->entrylocks[bucket]);
+}
+
+void
+dns_adb_ednsto(dns_adb_t *adb, dns_adbaddrinfo_t *addr) {
+ int bucket;
+
+ REQUIRE(DNS_ADB_VALID(adb));
+ REQUIRE(DNS_ADBADDRINFO_VALID(addr));
+
+ bucket = addr->entry->lock_bucket;
+ LOCK(&adb->entrylocks[bucket]);
+
+ maybe_adjust_quota(adb, addr, true);
+
+ addr->entry->ednsto++;
+ if (addr->entry->ednsto == 0xff) {
+ addr->entry->edns >>= 1;
+ addr->entry->ednsto >>= 1;
+ addr->entry->plain >>= 1;
+ addr->entry->plainto >>= 1;
+ }
+ UNLOCK(&adb->entrylocks[bucket]);
+}
+
+void
+dns_adb_setudpsize(dns_adb_t *adb, dns_adbaddrinfo_t *addr, unsigned int size) {
+ int bucket;
+
+ REQUIRE(DNS_ADB_VALID(adb));
+ REQUIRE(DNS_ADBADDRINFO_VALID(addr));
+
+ bucket = addr->entry->lock_bucket;
+ LOCK(&adb->entrylocks[bucket]);
+ if (size < 512U) {
+ size = 512U;
+ }
+ if (size > addr->entry->udpsize) {
+ addr->entry->udpsize = size;
+ }
+
+ maybe_adjust_quota(adb, addr, false);
+
+ addr->entry->edns++;
+ if (addr->entry->edns == 0xff) {
+ addr->entry->edns >>= 1;
+ addr->entry->ednsto >>= 1;
+ addr->entry->plain >>= 1;
+ addr->entry->plainto >>= 1;
+ }
+ UNLOCK(&adb->entrylocks[bucket]);
+}
+
+unsigned int
+dns_adb_getudpsize(dns_adb_t *adb, dns_adbaddrinfo_t *addr) {
+ int bucket;
+ unsigned int size;
+
+ REQUIRE(DNS_ADB_VALID(adb));
+ REQUIRE(DNS_ADBADDRINFO_VALID(addr));
+
+ bucket = addr->entry->lock_bucket;
+ LOCK(&adb->entrylocks[bucket]);
+ size = addr->entry->udpsize;
+ UNLOCK(&adb->entrylocks[bucket]);
+
+ return (size);
+}
+
+void
+dns_adb_setcookie(dns_adb_t *adb, dns_adbaddrinfo_t *addr,
+ const unsigned char *cookie, size_t len) {
+ int bucket;
+
+ REQUIRE(DNS_ADB_VALID(adb));
+ REQUIRE(DNS_ADBADDRINFO_VALID(addr));
+
+ bucket = addr->entry->lock_bucket;
+ LOCK(&adb->entrylocks[bucket]);
+
+ if (addr->entry->cookie != NULL &&
+ (cookie == NULL || len != addr->entry->cookielen))
+ {
+ isc_mem_put(adb->mctx, addr->entry->cookie,
+ addr->entry->cookielen);
+ addr->entry->cookie = NULL;
+ addr->entry->cookielen = 0;
+ }
+
+ if (addr->entry->cookie == NULL && cookie != NULL && len != 0U) {
+ addr->entry->cookie = isc_mem_get(adb->mctx, len);
+ addr->entry->cookielen = (uint16_t)len;
+ }
+
+ if (addr->entry->cookie != NULL) {
+ memmove(addr->entry->cookie, cookie, len);
+ }
+ UNLOCK(&adb->entrylocks[bucket]);
+}
+
+size_t
+dns_adb_getcookie(dns_adb_t *adb, dns_adbaddrinfo_t *addr,
+ unsigned char *cookie, size_t len) {
+ int bucket;
+
+ REQUIRE(DNS_ADB_VALID(adb));
+ REQUIRE(DNS_ADBADDRINFO_VALID(addr));
+
+ bucket = addr->entry->lock_bucket;
+ LOCK(&adb->entrylocks[bucket]);
+ if (cookie != NULL && addr->entry->cookie != NULL &&
+ len >= addr->entry->cookielen)
+ {
+ memmove(cookie, addr->entry->cookie, addr->entry->cookielen);
+ len = addr->entry->cookielen;
+ } else {
+ len = 0;
+ }
+ UNLOCK(&adb->entrylocks[bucket]);
+
+ return (len);
+}
+
+isc_result_t
+dns_adb_findaddrinfo(dns_adb_t *adb, const isc_sockaddr_t *sa,
+ dns_adbaddrinfo_t **addrp, isc_stdtime_t now) {
+ int bucket;
+ dns_adbentry_t *entry;
+ dns_adbaddrinfo_t *addr;
+ isc_result_t result;
+ in_port_t port;
+
+ REQUIRE(DNS_ADB_VALID(adb));
+ REQUIRE(addrp != NULL && *addrp == NULL);
+
+ UNUSED(now);
+
+ result = ISC_R_SUCCESS;
+ bucket = DNS_ADB_INVALIDBUCKET;
+ entry = find_entry_and_lock(adb, sa, &bucket, now);
+ INSIST(bucket != DNS_ADB_INVALIDBUCKET);
+ if (adb->entry_sd[bucket]) {
+ result = ISC_R_SHUTTINGDOWN;
+ goto unlock;
+ }
+ if (entry == NULL) {
+ /*
+ * We don't know anything about this address.
+ */
+ entry = new_adbentry(adb);
+ entry->sockaddr = *sa;
+ link_entry(adb, bucket, entry);
+ DP(ENTER_LEVEL, "findaddrinfo: new entry %p", entry);
+ } else {
+ DP(ENTER_LEVEL, "findaddrinfo: found entry %p", entry);
+ }
+
+ port = isc_sockaddr_getport(sa);
+ addr = new_adbaddrinfo(adb, entry, port);
+ inc_entry_refcnt(adb, entry, false);
+ *addrp = addr;
+
+unlock:
+ UNLOCK(&adb->entrylocks[bucket]);
+
+ return (result);
+}
+
+void
+dns_adb_freeaddrinfo(dns_adb_t *adb, dns_adbaddrinfo_t **addrp) {
+ dns_adbaddrinfo_t *addr;
+ dns_adbentry_t *entry;
+ int bucket;
+ isc_stdtime_t now;
+ bool want_check_exit = false;
+ bool overmem;
+
+ REQUIRE(DNS_ADB_VALID(adb));
+ REQUIRE(addrp != NULL);
+ addr = *addrp;
+ *addrp = NULL;
+ REQUIRE(DNS_ADBADDRINFO_VALID(addr));
+ entry = addr->entry;
+ REQUIRE(DNS_ADBENTRY_VALID(entry));
+
+ overmem = isc_mem_isovermem(adb->mctx);
+
+ bucket = addr->entry->lock_bucket;
+ LOCK(&adb->entrylocks[bucket]);
+
+ isc_stdtime_get(&now);
+ if (entry->expires == 0) {
+ entry->expires = now + ADB_ENTRY_WINDOW;
+ }
+
+ want_check_exit = dec_entry_refcnt(adb, overmem, entry, false, now);
+
+ UNLOCK(&adb->entrylocks[bucket]);
+
+ addr->entry = NULL;
+ free_adbaddrinfo(adb, &addr);
+
+ if (want_check_exit) {
+ LOCK(&adb->lock);
+ check_exit(adb);
+ UNLOCK(&adb->lock);
+ }
+}
+
+void
+dns_adb_flush(dns_adb_t *adb) {
+ unsigned int i;
+
+ INSIST(DNS_ADB_VALID(adb));
+
+ LOCK(&adb->lock);
+
+ /*
+ * Call our cleanup routines.
+ */
+ for (i = 0; i < adb->nnames; i++) {
+ RUNTIME_CHECK(!cleanup_names(adb, i, INT_MAX));
+ }
+ for (i = 0; i < adb->nentries; i++) {
+ RUNTIME_CHECK(!cleanup_entries(adb, i, INT_MAX));
+ }
+
+#ifdef DUMP_ADB_AFTER_CLEANING
+ dump_adb(adb, stdout, true, INT_MAX);
+#endif /* ifdef DUMP_ADB_AFTER_CLEANING */
+
+ UNLOCK(&adb->lock);
+}
+
+void
+dns_adb_flushname(dns_adb_t *adb, const dns_name_t *name) {
+ dns_adbname_t *adbname;
+ dns_adbname_t *nextname;
+ unsigned int bucket;
+
+ REQUIRE(DNS_ADB_VALID(adb));
+ REQUIRE(name != NULL);
+
+ LOCK(&adb->lock);
+ bucket = dns_name_hash(name, false) % adb->nnames;
+ LOCK(&adb->namelocks[bucket]);
+ adbname = ISC_LIST_HEAD(adb->names[bucket]);
+ while (adbname != NULL) {
+ nextname = ISC_LIST_NEXT(adbname, plink);
+ if (!NAME_DEAD(adbname) && dns_name_equal(name, &adbname->name))
+ {
+ RUNTIME_CHECK(
+ !kill_name(&adbname, DNS_EVENT_ADBCANCELED));
+ }
+ adbname = nextname;
+ }
+ UNLOCK(&adb->namelocks[bucket]);
+ UNLOCK(&adb->lock);
+}
+
+void
+dns_adb_flushnames(dns_adb_t *adb, const dns_name_t *name) {
+ dns_adbname_t *adbname, *nextname;
+ unsigned int i;
+
+ REQUIRE(DNS_ADB_VALID(adb));
+ REQUIRE(name != NULL);
+
+ LOCK(&adb->lock);
+ for (i = 0; i < adb->nnames; i++) {
+ LOCK(&adb->namelocks[i]);
+ adbname = ISC_LIST_HEAD(adb->names[i]);
+ while (adbname != NULL) {
+ bool ret;
+ nextname = ISC_LIST_NEXT(adbname, plink);
+ if (!NAME_DEAD(adbname) &&
+ dns_name_issubdomain(&adbname->name, name))
+ {
+ ret = kill_name(&adbname,
+ DNS_EVENT_ADBCANCELED);
+ RUNTIME_CHECK(!ret);
+ }
+ adbname = nextname;
+ }
+ UNLOCK(&adb->namelocks[i]);
+ }
+ UNLOCK(&adb->lock);
+}
+
+static void
+water(void *arg, int mark) {
+ /*
+ * We're going to change the way to handle overmem condition: use
+ * isc_mem_isovermem() instead of storing the state via this callback,
+ * since the latter way tends to cause race conditions.
+ * To minimize the change, and in case we re-enable the callback
+ * approach, however, keep this function at the moment.
+ */
+
+ dns_adb_t *adb = arg;
+ bool overmem = (mark == ISC_MEM_HIWATER);
+
+ REQUIRE(DNS_ADB_VALID(adb));
+
+ DP(ISC_LOG_DEBUG(1), "adb reached %s water mark",
+ overmem ? "high" : "low");
+}
+
+void
+dns_adb_setadbsize(dns_adb_t *adb, size_t size) {
+ size_t hiwater, lowater;
+
+ INSIST(DNS_ADB_VALID(adb));
+
+ if (size != 0U && size < DNS_ADB_MINADBSIZE) {
+ size = DNS_ADB_MINADBSIZE;
+ }
+
+ hiwater = size - (size >> 3); /* Approximately 7/8ths. */
+ lowater = size - (size >> 2); /* Approximately 3/4ths. */
+
+ if (size == 0U || hiwater == 0U || lowater == 0U) {
+ isc_mem_clearwater(adb->mctx);
+ } else {
+ isc_mem_setwater(adb->mctx, water, adb, hiwater, lowater);
+ }
+}
+
+void
+dns_adb_setquota(dns_adb_t *adb, uint32_t quota, uint32_t freq, double low,
+ double high, double discount) {
+ REQUIRE(DNS_ADB_VALID(adb));
+
+ adb->quota = quota;
+ adb->atr_freq = freq;
+ adb->atr_low = low;
+ adb->atr_high = high;
+ adb->atr_discount = discount;
+}
+
+bool
+dns_adbentry_overquota(dns_adbentry_t *entry) {
+ REQUIRE(DNS_ADBENTRY_VALID(entry));
+
+ uint_fast32_t quota = atomic_load_relaxed(&entry->quota);
+ uint_fast32_t active = atomic_load_acquire(&entry->active);
+
+ return (quota != 0 && active >= quota);
+}
+
+void
+dns_adb_beginudpfetch(dns_adb_t *adb, dns_adbaddrinfo_t *addr) {
+ uint_fast32_t active;
+
+ REQUIRE(DNS_ADB_VALID(adb));
+ REQUIRE(DNS_ADBADDRINFO_VALID(addr));
+
+ active = atomic_fetch_add_relaxed(&addr->entry->active, 1);
+ INSIST(active != UINT32_MAX);
+}
+
+void
+dns_adb_endudpfetch(dns_adb_t *adb, dns_adbaddrinfo_t *addr) {
+ uint_fast32_t active;
+
+ REQUIRE(DNS_ADB_VALID(adb));
+ REQUIRE(DNS_ADBADDRINFO_VALID(addr));
+
+ active = atomic_fetch_sub_release(&addr->entry->active, 1);
+ INSIST(active != 0);
+}
diff --git a/lib/dns/badcache.c b/lib/dns/badcache.c
new file mode 100644
index 0000000..dcad721
--- /dev/null
+++ b/lib/dns/badcache.c
@@ -0,0 +1,522 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/*! \file */
+
+#include <inttypes.h>
+#include <stdbool.h>
+
+#include <isc/buffer.h>
+#include <isc/hash.h>
+#include <isc/log.h>
+#include <isc/mem.h>
+#include <isc/mutex.h>
+#include <isc/print.h>
+#include <isc/rwlock.h>
+#include <isc/string.h>
+#include <isc/time.h>
+#include <isc/util.h>
+
+#include <dns/badcache.h>
+#include <dns/fixedname.h>
+#include <dns/name.h>
+#include <dns/rdatatype.h>
+#include <dns/types.h>
+
+typedef struct dns_bcentry dns_bcentry_t;
+
+struct dns_badcache {
+ unsigned int magic;
+ isc_rwlock_t lock;
+ isc_mem_t *mctx;
+
+ isc_mutex_t *tlocks;
+ dns_bcentry_t **table;
+
+ atomic_uint_fast32_t count;
+ atomic_uint_fast32_t sweep;
+
+ unsigned int minsize;
+ unsigned int size;
+};
+
+#define BADCACHE_MAGIC ISC_MAGIC('B', 'd', 'C', 'a')
+#define VALID_BADCACHE(m) ISC_MAGIC_VALID(m, BADCACHE_MAGIC)
+
+struct dns_bcentry {
+ dns_bcentry_t *next;
+ dns_rdatatype_t type;
+ isc_time_t expire;
+ uint32_t flags;
+ unsigned int hashval;
+ dns_fixedname_t fname;
+ dns_name_t *name;
+};
+
+static void
+badcache_resize(dns_badcache_t *bc, isc_time_t *now);
+
+isc_result_t
+dns_badcache_init(isc_mem_t *mctx, unsigned int size, dns_badcache_t **bcp) {
+ dns_badcache_t *bc = NULL;
+ unsigned int i;
+
+ REQUIRE(bcp != NULL && *bcp == NULL);
+ REQUIRE(mctx != NULL);
+
+ bc = isc_mem_get(mctx, sizeof(dns_badcache_t));
+ memset(bc, 0, sizeof(dns_badcache_t));
+
+ isc_mem_attach(mctx, &bc->mctx);
+ isc_rwlock_init(&bc->lock, 0, 0);
+
+ bc->table = isc_mem_get(bc->mctx, sizeof(*bc->table) * size);
+ bc->tlocks = isc_mem_get(bc->mctx, sizeof(isc_mutex_t) * size);
+ for (i = 0; i < size; i++) {
+ isc_mutex_init(&bc->tlocks[i]);
+ }
+ bc->size = bc->minsize = size;
+ memset(bc->table, 0, bc->size * sizeof(dns_bcentry_t *));
+
+ atomic_init(&bc->count, 0);
+ atomic_init(&bc->sweep, 0);
+ bc->magic = BADCACHE_MAGIC;
+
+ *bcp = bc;
+ return (ISC_R_SUCCESS);
+}
+
+void
+dns_badcache_destroy(dns_badcache_t **bcp) {
+ dns_badcache_t *bc;
+ unsigned int i;
+
+ REQUIRE(bcp != NULL && *bcp != NULL);
+ bc = *bcp;
+ *bcp = NULL;
+
+ dns_badcache_flush(bc);
+
+ bc->magic = 0;
+ isc_rwlock_destroy(&bc->lock);
+ for (i = 0; i < bc->size; i++) {
+ isc_mutex_destroy(&bc->tlocks[i]);
+ }
+ isc_mem_put(bc->mctx, bc->table, sizeof(dns_bcentry_t *) * bc->size);
+ isc_mem_put(bc->mctx, bc->tlocks, sizeof(isc_mutex_t) * bc->size);
+ isc_mem_putanddetach(&bc->mctx, bc, sizeof(dns_badcache_t));
+}
+
+static void
+badcache_resize(dns_badcache_t *bc, isc_time_t *now) {
+ dns_bcentry_t **newtable, *bad, *next;
+ isc_mutex_t *newlocks;
+ unsigned int newsize, i;
+ bool grow;
+
+ RWLOCK(&bc->lock, isc_rwlocktype_write);
+
+ /*
+ * XXXWPK we will have a thundering herd problem here,
+ * as all threads will wait on the RWLOCK when there's
+ * a need to resize badcache.
+ * However, it happens so rarely it should not be a
+ * performance issue. This is because we double the
+ * size every time we grow it, and we don't shrink
+ * unless the number of entries really shrunk. In a
+ * high load situation, the number of badcache entries
+ * will eventually stabilize.
+ */
+ if (atomic_load_relaxed(&bc->count) > bc->size * 8) {
+ grow = true;
+ } else if (atomic_load_relaxed(&bc->count) < bc->size * 2 &&
+ bc->size > bc->minsize)
+ {
+ grow = false;
+ } else {
+ /* Someone resized it already, bail. */
+ RWUNLOCK(&bc->lock, isc_rwlocktype_write);
+ return;
+ }
+
+ if (grow) {
+ newsize = bc->size * 2 + 1;
+ } else {
+ newsize = (bc->size - 1) / 2;
+#ifdef __clang_analyzer__
+ /*
+ * XXXWPK there's a bug in clang static analyzer -
+ * `value % newsize` is considered undefined even though
+ * we check if newsize is larger than 0. This helps.
+ */
+ newsize += 1;
+#endif
+ }
+ RUNTIME_CHECK(newsize > 0);
+
+ newtable = isc_mem_get(bc->mctx, sizeof(dns_bcentry_t *) * newsize);
+ memset(newtable, 0, sizeof(dns_bcentry_t *) * newsize);
+
+ newlocks = isc_mem_get(bc->mctx, sizeof(isc_mutex_t) * newsize);
+
+ /* Copy existing mutexes */
+ for (i = 0; i < newsize && i < bc->size; i++) {
+ newlocks[i] = bc->tlocks[i];
+ }
+ /* Initialize additional mutexes if we're growing */
+ for (i = bc->size; i < newsize; i++) {
+ isc_mutex_init(&newlocks[i]);
+ }
+ /* Destroy extra mutexes if we're shrinking */
+ for (i = newsize; i < bc->size; i++) {
+ isc_mutex_destroy(&bc->tlocks[i]);
+ }
+
+ for (i = 0; atomic_load_relaxed(&bc->count) > 0 && i < bc->size; i++) {
+ for (bad = bc->table[i]; bad != NULL; bad = next) {
+ next = bad->next;
+ if (isc_time_compare(&bad->expire, now) < 0) {
+ isc_mem_put(bc->mctx, bad, sizeof(*bad));
+ atomic_fetch_sub_relaxed(&bc->count, 1);
+ } else {
+ bad->next = newtable[bad->hashval % newsize];
+ newtable[bad->hashval % newsize] = bad;
+ }
+ }
+ bc->table[i] = NULL;
+ }
+
+ isc_mem_put(bc->mctx, bc->tlocks, sizeof(isc_mutex_t) * bc->size);
+ bc->tlocks = newlocks;
+
+ isc_mem_put(bc->mctx, bc->table, sizeof(*bc->table) * bc->size);
+ bc->size = newsize;
+ bc->table = newtable;
+
+ RWUNLOCK(&bc->lock, isc_rwlocktype_write);
+}
+
+void
+dns_badcache_add(dns_badcache_t *bc, const dns_name_t *name,
+ dns_rdatatype_t type, bool update, uint32_t flags,
+ isc_time_t *expire) {
+ isc_result_t result;
+ unsigned int hashval, hash;
+ dns_bcentry_t *bad, *prev, *next;
+ isc_time_t now;
+ bool resize = false;
+
+ REQUIRE(VALID_BADCACHE(bc));
+ REQUIRE(name != NULL);
+ REQUIRE(expire != NULL);
+
+ RWLOCK(&bc->lock, isc_rwlocktype_read);
+
+ result = isc_time_now(&now);
+ if (result != ISC_R_SUCCESS) {
+ isc_time_settoepoch(&now);
+ }
+
+ hashval = dns_name_hash(name, false);
+ hash = hashval % bc->size;
+ LOCK(&bc->tlocks[hash]);
+ prev = NULL;
+ for (bad = bc->table[hash]; bad != NULL; bad = next) {
+ next = bad->next;
+ if (bad->type == type && dns_name_equal(name, bad->name)) {
+ if (update) {
+ bad->expire = *expire;
+ bad->flags = flags;
+ }
+ break;
+ }
+ if (isc_time_compare(&bad->expire, &now) < 0) {
+ if (prev == NULL) {
+ bc->table[hash] = bad->next;
+ } else {
+ prev->next = bad->next;
+ }
+ isc_mem_put(bc->mctx, bad, sizeof(*bad));
+ atomic_fetch_sub_relaxed(&bc->count, 1);
+ } else {
+ prev = bad;
+ }
+ }
+
+ if (bad == NULL) {
+ unsigned count;
+ isc_buffer_t buffer;
+
+ bad = isc_mem_get(bc->mctx, sizeof(*bad));
+ *bad = (dns_bcentry_t){ .type = type,
+ .hashval = hashval,
+ .expire = *expire,
+ .flags = flags,
+ .next = bc->table[hash] };
+
+ isc_buffer_init(&buffer, bad + 1, name->length);
+ bad->name = dns_fixedname_initname(&bad->fname);
+ dns_name_copy(name, bad->name);
+ bc->table[hash] = bad;
+
+ count = atomic_fetch_add_relaxed(&bc->count, 1);
+ if ((count > bc->size * 8) ||
+ (count < bc->size * 2 && bc->size > bc->minsize))
+ {
+ resize = true;
+ }
+ } else {
+ bad->expire = *expire;
+ }
+
+ UNLOCK(&bc->tlocks[hash]);
+ RWUNLOCK(&bc->lock, isc_rwlocktype_read);
+ if (resize) {
+ badcache_resize(bc, &now);
+ }
+}
+
+bool
+dns_badcache_find(dns_badcache_t *bc, const dns_name_t *name,
+ dns_rdatatype_t type, uint32_t *flagp, isc_time_t *now) {
+ dns_bcentry_t *bad, *prev, *next;
+ bool answer = false;
+ unsigned int i;
+ unsigned int hash;
+
+ REQUIRE(VALID_BADCACHE(bc));
+ REQUIRE(name != NULL);
+ REQUIRE(now != NULL);
+
+ RWLOCK(&bc->lock, isc_rwlocktype_read);
+
+ /*
+ * XXXMUKS: dns_name_equal() is expensive as it does a
+ * octet-by-octet comparison, and it can be made better in two
+ * ways here. First, lowercase the names (use
+ * dns_name_downcase() instead of dns_name_copy() in
+ * dns_badcache_add()) so that dns_name_caseequal() can be used
+ * which the compiler will emit as SIMD instructions. Second,
+ * don't put multiple copies of the same name in the chain (or
+ * multiple names will have to be matched for equality), but use
+ * name->link to store the type specific part.
+ */
+
+ if (atomic_load_relaxed(&bc->count) == 0) {
+ goto skip;
+ }
+
+ hash = dns_name_hash(name, false) % bc->size;
+ prev = NULL;
+ LOCK(&bc->tlocks[hash]);
+ for (bad = bc->table[hash]; bad != NULL; bad = next) {
+ next = bad->next;
+ /*
+ * Search the hash list. Clean out expired records as we go.
+ */
+ if (isc_time_compare(&bad->expire, now) < 0) {
+ if (prev != NULL) {
+ prev->next = bad->next;
+ } else {
+ bc->table[hash] = bad->next;
+ }
+
+ isc_mem_put(bc->mctx, bad, sizeof(*bad));
+ atomic_fetch_sub(&bc->count, 1);
+ continue;
+ }
+ if (bad->type == type && dns_name_equal(name, bad->name)) {
+ if (flagp != NULL) {
+ *flagp = bad->flags;
+ }
+ answer = true;
+ break;
+ }
+ prev = bad;
+ }
+ UNLOCK(&bc->tlocks[hash]);
+skip:
+
+ /*
+ * Slow sweep to clean out stale records.
+ */
+ i = atomic_fetch_add(&bc->sweep, 1) % bc->size;
+ if (isc_mutex_trylock(&bc->tlocks[i]) == ISC_R_SUCCESS) {
+ bad = bc->table[i];
+ if (bad != NULL && isc_time_compare(&bad->expire, now) < 0) {
+ bc->table[i] = bad->next;
+ isc_mem_put(bc->mctx, bad, sizeof(*bad));
+ atomic_fetch_sub_relaxed(&bc->count, 1);
+ }
+ UNLOCK(&bc->tlocks[i]);
+ }
+
+ RWUNLOCK(&bc->lock, isc_rwlocktype_read);
+ return (answer);
+}
+
+void
+dns_badcache_flush(dns_badcache_t *bc) {
+ dns_bcentry_t *entry, *next;
+ unsigned int i;
+
+ RWLOCK(&bc->lock, isc_rwlocktype_write);
+ REQUIRE(VALID_BADCACHE(bc));
+
+ for (i = 0; atomic_load_relaxed(&bc->count) > 0 && i < bc->size; i++) {
+ for (entry = bc->table[i]; entry != NULL; entry = next) {
+ next = entry->next;
+ isc_mem_put(bc->mctx, entry, sizeof(*entry));
+ atomic_fetch_sub_relaxed(&bc->count, 1);
+ }
+ bc->table[i] = NULL;
+ }
+ RWUNLOCK(&bc->lock, isc_rwlocktype_write);
+}
+
+void
+dns_badcache_flushname(dns_badcache_t *bc, const dns_name_t *name) {
+ dns_bcentry_t *bad, *prev, *next;
+ isc_result_t result;
+ isc_time_t now;
+ unsigned int hash;
+
+ REQUIRE(VALID_BADCACHE(bc));
+ REQUIRE(name != NULL);
+
+ RWLOCK(&bc->lock, isc_rwlocktype_read);
+
+ result = isc_time_now(&now);
+ if (result != ISC_R_SUCCESS) {
+ isc_time_settoepoch(&now);
+ }
+ hash = dns_name_hash(name, false) % bc->size;
+ LOCK(&bc->tlocks[hash]);
+ prev = NULL;
+ for (bad = bc->table[hash]; bad != NULL; bad = next) {
+ int n;
+ next = bad->next;
+ n = isc_time_compare(&bad->expire, &now);
+ if (n < 0 || dns_name_equal(name, bad->name)) {
+ if (prev == NULL) {
+ bc->table[hash] = bad->next;
+ } else {
+ prev->next = bad->next;
+ }
+
+ isc_mem_put(bc->mctx, bad, sizeof(*bad));
+ atomic_fetch_sub_relaxed(&bc->count, 1);
+ } else {
+ prev = bad;
+ }
+ }
+ UNLOCK(&bc->tlocks[hash]);
+
+ RWUNLOCK(&bc->lock, isc_rwlocktype_read);
+}
+
+void
+dns_badcache_flushtree(dns_badcache_t *bc, const dns_name_t *name) {
+ dns_bcentry_t *bad, *prev, *next;
+ unsigned int i;
+ int n;
+ isc_time_t now;
+ isc_result_t result;
+
+ REQUIRE(VALID_BADCACHE(bc));
+ REQUIRE(name != NULL);
+
+ /*
+ * We write lock the tree to avoid relocking every node
+ * individually.
+ */
+ RWLOCK(&bc->lock, isc_rwlocktype_write);
+
+ result = isc_time_now(&now);
+ if (result != ISC_R_SUCCESS) {
+ isc_time_settoepoch(&now);
+ }
+
+ for (i = 0; atomic_load_relaxed(&bc->count) > 0 && i < bc->size; i++) {
+ prev = NULL;
+ for (bad = bc->table[i]; bad != NULL; bad = next) {
+ next = bad->next;
+ n = isc_time_compare(&bad->expire, &now);
+ if (n < 0 || dns_name_issubdomain(bad->name, name)) {
+ if (prev == NULL) {
+ bc->table[i] = bad->next;
+ } else {
+ prev->next = bad->next;
+ }
+
+ isc_mem_put(bc->mctx, bad, sizeof(*bad));
+ atomic_fetch_sub_relaxed(&bc->count, 1);
+ } else {
+ prev = bad;
+ }
+ }
+ }
+
+ RWUNLOCK(&bc->lock, isc_rwlocktype_write);
+}
+
+void
+dns_badcache_print(dns_badcache_t *bc, const char *cachename, FILE *fp) {
+ char namebuf[DNS_NAME_FORMATSIZE];
+ char typebuf[DNS_RDATATYPE_FORMATSIZE];
+ dns_bcentry_t *bad, *next, *prev;
+ isc_time_t now;
+ unsigned int i;
+ uint64_t t;
+
+ REQUIRE(VALID_BADCACHE(bc));
+ REQUIRE(cachename != NULL);
+ REQUIRE(fp != NULL);
+
+ /*
+ * We write lock the tree to avoid relocking every node
+ * individually.
+ */
+ RWLOCK(&bc->lock, isc_rwlocktype_write);
+ fprintf(fp, ";\n; %s\n;\n", cachename);
+
+ TIME_NOW(&now);
+ for (i = 0; atomic_load_relaxed(&bc->count) > 0 && i < bc->size; i++) {
+ prev = NULL;
+ for (bad = bc->table[i]; bad != NULL; bad = next) {
+ next = bad->next;
+ if (isc_time_compare(&bad->expire, &now) < 0) {
+ if (prev != NULL) {
+ prev->next = bad->next;
+ } else {
+ bc->table[i] = bad->next;
+ }
+
+ isc_mem_put(bc->mctx, bad, sizeof(*bad));
+ atomic_fetch_sub_relaxed(&bc->count, 1);
+ continue;
+ }
+ prev = bad;
+ dns_name_format(bad->name, namebuf, sizeof(namebuf));
+ dns_rdatatype_format(bad->type, typebuf,
+ sizeof(typebuf));
+ t = isc_time_microdiff(&bad->expire, &now);
+ t /= 1000;
+ fprintf(fp,
+ "; %s/%s [ttl "
+ "%" PRIu64 "]\n",
+ namebuf, typebuf, t);
+ }
+ }
+ RWUNLOCK(&bc->lock, isc_rwlocktype_write);
+}
diff --git a/lib/dns/byaddr.c b/lib/dns/byaddr.c
new file mode 100644
index 0000000..8fa668a
--- /dev/null
+++ b/lib/dns/byaddr.c
@@ -0,0 +1,282 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/*! \file */
+
+#include <stdbool.h>
+
+#include <isc/mem.h>
+#include <isc/netaddr.h>
+#include <isc/print.h>
+#include <isc/result.h>
+#include <isc/string.h> /* Required for HP/UX (and others?) */
+#include <isc/task.h>
+#include <isc/util.h>
+
+#include <dns/byaddr.h>
+#include <dns/db.h>
+#include <dns/events.h>
+#include <dns/lookup.h>
+#include <dns/rdata.h>
+#include <dns/rdataset.h>
+#include <dns/rdatastruct.h>
+#include <dns/resolver.h>
+#include <dns/view.h>
+
+/*
+ * XXXRTH We could use a static event...
+ */
+
+static char hex_digits[] = { '0', '1', '2', '3', '4', '5', '6', '7',
+ '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' };
+
+isc_result_t
+dns_byaddr_createptrname(const isc_netaddr_t *address, unsigned int options,
+ dns_name_t *name) {
+ char textname[128];
+ const unsigned char *bytes;
+ int i;
+ char *cp;
+ isc_buffer_t buffer;
+ unsigned int len;
+
+ REQUIRE(address != NULL);
+ UNUSED(options);
+
+ /*
+ * We create the text representation and then convert to a
+ * dns_name_t. This is not maximally efficient, but it keeps all
+ * of the knowledge of wire format in the dns_name_ routines.
+ */
+
+ bytes = (const unsigned char *)(&address->type);
+ if (address->family == AF_INET) {
+ (void)snprintf(textname, sizeof(textname),
+ "%u.%u.%u.%u.in-addr.arpa.",
+ ((unsigned int)bytes[3] & 0xffU),
+ ((unsigned int)bytes[2] & 0xffU),
+ ((unsigned int)bytes[1] & 0xffU),
+ ((unsigned int)bytes[0] & 0xffU));
+ } else if (address->family == AF_INET6) {
+ size_t remaining;
+
+ cp = textname;
+ for (i = 15; i >= 0; i--) {
+ *cp++ = hex_digits[bytes[i] & 0x0f];
+ *cp++ = '.';
+ *cp++ = hex_digits[(bytes[i] >> 4) & 0x0f];
+ *cp++ = '.';
+ }
+ remaining = sizeof(textname) - (cp - textname);
+ strlcpy(cp, "ip6.arpa.", remaining);
+ } else {
+ return (ISC_R_NOTIMPLEMENTED);
+ }
+
+ len = (unsigned int)strlen(textname);
+ isc_buffer_init(&buffer, textname, len);
+ isc_buffer_add(&buffer, len);
+ return (dns_name_fromtext(name, &buffer, dns_rootname, 0, NULL));
+}
+
+struct dns_byaddr {
+ /* Unlocked. */
+ unsigned int magic;
+ isc_mem_t *mctx;
+ isc_mutex_t lock;
+ dns_fixedname_t name;
+ /* Locked by lock. */
+ unsigned int options;
+ dns_lookup_t *lookup;
+ isc_task_t *task;
+ dns_byaddrevent_t *event;
+ bool canceled;
+};
+
+#define BYADDR_MAGIC ISC_MAGIC('B', 'y', 'A', 'd')
+#define VALID_BYADDR(b) ISC_MAGIC_VALID(b, BYADDR_MAGIC)
+
+#define MAX_RESTARTS 16
+
+static isc_result_t
+copy_ptr_targets(dns_byaddr_t *byaddr, dns_rdataset_t *rdataset) {
+ isc_result_t result;
+ dns_name_t *name;
+ dns_rdata_t rdata = DNS_RDATA_INIT;
+
+ /*
+ * The caller must be holding the byaddr's lock.
+ */
+
+ result = dns_rdataset_first(rdataset);
+ while (result == ISC_R_SUCCESS) {
+ dns_rdata_ptr_t ptr;
+ dns_rdataset_current(rdataset, &rdata);
+ result = dns_rdata_tostruct(&rdata, &ptr, NULL);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+ name = isc_mem_get(byaddr->mctx, sizeof(*name));
+ dns_name_init(name, NULL);
+ dns_name_dup(&ptr.ptr, byaddr->mctx, name);
+ dns_rdata_freestruct(&ptr);
+ ISC_LIST_APPEND(byaddr->event->names, name, link);
+ dns_rdata_reset(&rdata);
+ result = dns_rdataset_next(rdataset);
+ }
+ if (result == ISC_R_NOMORE) {
+ result = ISC_R_SUCCESS;
+ }
+
+ return (result);
+}
+
+static void
+lookup_done(isc_task_t *task, isc_event_t *event) {
+ dns_byaddr_t *byaddr = event->ev_arg;
+ dns_lookupevent_t *levent;
+ isc_result_t result;
+
+ REQUIRE(event->ev_type == DNS_EVENT_LOOKUPDONE);
+ REQUIRE(VALID_BYADDR(byaddr));
+ REQUIRE(byaddr->task == task);
+
+ UNUSED(task);
+
+ levent = (dns_lookupevent_t *)event;
+
+ if (levent->result == ISC_R_SUCCESS) {
+ result = copy_ptr_targets(byaddr, levent->rdataset);
+ byaddr->event->result = result;
+ } else {
+ byaddr->event->result = levent->result;
+ }
+ isc_event_free(&event);
+ isc_task_sendanddetach(&byaddr->task, (isc_event_t **)&byaddr->event);
+}
+
+static void
+bevent_destroy(isc_event_t *event) {
+ dns_byaddrevent_t *bevent;
+ dns_name_t *name, *next_name;
+ isc_mem_t *mctx;
+
+ REQUIRE(event->ev_type == DNS_EVENT_BYADDRDONE);
+ mctx = event->ev_destroy_arg;
+ bevent = (dns_byaddrevent_t *)event;
+
+ for (name = ISC_LIST_HEAD(bevent->names); name != NULL;
+ name = next_name)
+ {
+ next_name = ISC_LIST_NEXT(name, link);
+ ISC_LIST_UNLINK(bevent->names, name, link);
+ dns_name_free(name, mctx);
+ isc_mem_put(mctx, name, sizeof(*name));
+ }
+ isc_mem_put(mctx, event, event->ev_size);
+}
+
+isc_result_t
+dns_byaddr_create(isc_mem_t *mctx, const isc_netaddr_t *address,
+ dns_view_t *view, unsigned int options, isc_task_t *task,
+ isc_taskaction_t action, void *arg, dns_byaddr_t **byaddrp) {
+ isc_result_t result;
+ dns_byaddr_t *byaddr;
+ isc_event_t *ievent;
+
+ byaddr = isc_mem_get(mctx, sizeof(*byaddr));
+ byaddr->mctx = NULL;
+ isc_mem_attach(mctx, &byaddr->mctx);
+ byaddr->options = options;
+
+ byaddr->event = isc_mem_get(mctx, sizeof(*byaddr->event));
+ ISC_EVENT_INIT(byaddr->event, sizeof(*byaddr->event), 0, NULL,
+ DNS_EVENT_BYADDRDONE, action, arg, byaddr,
+ bevent_destroy, mctx);
+ byaddr->event->result = ISC_R_FAILURE;
+ ISC_LIST_INIT(byaddr->event->names);
+
+ byaddr->task = NULL;
+ isc_task_attach(task, &byaddr->task);
+
+ isc_mutex_init(&byaddr->lock);
+
+ dns_fixedname_init(&byaddr->name);
+
+ result = dns_byaddr_createptrname(address, options,
+ dns_fixedname_name(&byaddr->name));
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup_lock;
+ }
+
+ byaddr->lookup = NULL;
+ result = dns_lookup_create(mctx, dns_fixedname_name(&byaddr->name),
+ dns_rdatatype_ptr, view, 0, task,
+ lookup_done, byaddr, &byaddr->lookup);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup_lock;
+ }
+
+ byaddr->canceled = false;
+ byaddr->magic = BYADDR_MAGIC;
+
+ *byaddrp = byaddr;
+
+ return (ISC_R_SUCCESS);
+
+cleanup_lock:
+ isc_mutex_destroy(&byaddr->lock);
+
+ ievent = (isc_event_t *)byaddr->event;
+ isc_event_free(&ievent);
+ byaddr->event = NULL;
+
+ isc_task_detach(&byaddr->task);
+
+ isc_mem_putanddetach(&mctx, byaddr, sizeof(*byaddr));
+
+ return (result);
+}
+
+void
+dns_byaddr_cancel(dns_byaddr_t *byaddr) {
+ REQUIRE(VALID_BYADDR(byaddr));
+
+ LOCK(&byaddr->lock);
+
+ if (!byaddr->canceled) {
+ byaddr->canceled = true;
+ if (byaddr->lookup != NULL) {
+ dns_lookup_cancel(byaddr->lookup);
+ }
+ }
+
+ UNLOCK(&byaddr->lock);
+}
+
+void
+dns_byaddr_destroy(dns_byaddr_t **byaddrp) {
+ dns_byaddr_t *byaddr;
+
+ REQUIRE(byaddrp != NULL);
+ byaddr = *byaddrp;
+ *byaddrp = NULL;
+ REQUIRE(VALID_BYADDR(byaddr));
+ REQUIRE(byaddr->event == NULL);
+ REQUIRE(byaddr->task == NULL);
+ dns_lookup_destroy(&byaddr->lookup);
+
+ isc_mutex_destroy(&byaddr->lock);
+ byaddr->magic = 0;
+ isc_mem_putanddetach(&byaddr->mctx, byaddr, sizeof(*byaddr));
+}
diff --git a/lib/dns/cache.c b/lib/dns/cache.c
new file mode 100644
index 0000000..7ffb6f8
--- /dev/null
+++ b/lib/dns/cache.c
@@ -0,0 +1,1444 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/*! \file */
+
+#include <inttypes.h>
+#include <stdbool.h>
+
+#include <isc/mem.h>
+#include <isc/print.h>
+#include <isc/refcount.h>
+#include <isc/result.h>
+#include <isc/stats.h>
+#include <isc/string.h>
+#include <isc/task.h>
+#include <isc/time.h>
+#include <isc/timer.h>
+#include <isc/util.h>
+
+#include <dns/cache.h>
+#include <dns/db.h>
+#include <dns/dbiterator.h>
+#include <dns/events.h>
+#include <dns/log.h>
+#include <dns/masterdump.h>
+#include <dns/rdata.h>
+#include <dns/rdataset.h>
+#include <dns/rdatasetiter.h>
+#include <dns/stats.h>
+
+#ifdef HAVE_JSON_C
+#include <json_object.h>
+#endif /* HAVE_JSON_C */
+
+#ifdef HAVE_LIBXML2
+#include <libxml/xmlwriter.h>
+#define ISC_XMLCHAR (const xmlChar *)
+#endif /* HAVE_LIBXML2 */
+
+#include "rbtdb.h"
+
+#define CACHE_MAGIC ISC_MAGIC('$', '$', '$', '$')
+#define VALID_CACHE(cache) ISC_MAGIC_VALID(cache, CACHE_MAGIC)
+
+/*!
+ * Control incremental cleaning.
+ * DNS_CACHE_MINSIZE is how many bytes is the floor for
+ * dns_cache_setcachesize(). See also DNS_CACHE_CLEANERINCREMENT
+ */
+#define DNS_CACHE_MINSIZE 2097152U /*%< Bytes. 2097152 = 2 MB */
+/*!
+ * Control incremental cleaning.
+ * CLEANERINCREMENT is how many nodes are examined in one pass.
+ * See also DNS_CACHE_MINSIZE
+ */
+#define DNS_CACHE_CLEANERINCREMENT 1000U /*%< Number of nodes. */
+
+/***
+ *** Types
+ ***/
+
+/*
+ * A cache_cleaner_t encapsulates the state of the periodic
+ * cache cleaning.
+ */
+
+typedef struct cache_cleaner cache_cleaner_t;
+
+typedef enum {
+ cleaner_s_idle, /*%< Waiting for cleaning interval to expire. */
+ cleaner_s_busy, /*%< Currently cleaning. */
+ cleaner_s_done /*%< Freed enough memory after being overmem. */
+} cleaner_state_t;
+
+/*
+ * Convenience macros for comprehensive assertion checking.
+ */
+#define CLEANER_IDLE(c) \
+ ((c)->state == cleaner_s_idle && (c)->resched_event != NULL)
+#define CLEANER_BUSY(c) \
+ ((c)->state == cleaner_s_busy && (c)->iterator != NULL && \
+ (c)->resched_event == NULL)
+
+/*%
+ * Accesses to a cache cleaner object are synchronized through
+ * task/event serialization, or locked from the cache object.
+ */
+struct cache_cleaner {
+ isc_mutex_t lock;
+ /*%<
+ * Locks overmem_event, overmem. Note: never allocate memory
+ * while holding this lock - that could lead to deadlock since
+ * the lock is take by water() which is called from the memory
+ * allocator.
+ */
+
+ dns_cache_t *cache;
+ isc_task_t *task;
+ isc_event_t *resched_event; /*% Sent by cleaner task to
+ * itself to reschedule */
+ isc_event_t *overmem_event;
+
+ dns_dbiterator_t *iterator;
+ unsigned int increment; /*% Number of names to
+ * clean in one increment */
+ cleaner_state_t state; /*% Idle/Busy. */
+ bool overmem; /*% The cache is in an overmem state.
+ * */
+ bool replaceiterator;
+};
+
+/*%
+ * The actual cache object.
+ */
+
+struct dns_cache {
+ /* Unlocked. */
+ unsigned int magic;
+ isc_mutex_t lock;
+ isc_mem_t *mctx; /* Main cache memory */
+ isc_mem_t *hmctx; /* Heap memory */
+ char *name;
+ isc_refcount_t references;
+ isc_refcount_t live_tasks;
+
+ /* Locked by 'lock'. */
+ dns_rdataclass_t rdclass;
+ dns_db_t *db;
+ cache_cleaner_t cleaner;
+ char *db_type;
+ int db_argc;
+ char **db_argv;
+ size_t size;
+ dns_ttl_t serve_stale_ttl;
+ dns_ttl_t serve_stale_refresh;
+ isc_stats_t *stats;
+};
+
+/***
+ *** Functions
+ ***/
+
+static isc_result_t
+cache_cleaner_init(dns_cache_t *cache, isc_taskmgr_t *taskmgr,
+ isc_timermgr_t *timermgr, cache_cleaner_t *cleaner);
+
+static void
+incremental_cleaning_action(isc_task_t *task, isc_event_t *event);
+
+static void
+cleaner_shutdown_action(isc_task_t *task, isc_event_t *event);
+
+static void
+overmem_cleaning_action(isc_task_t *task, isc_event_t *event);
+
+static void
+water(void *arg, int mark);
+
+static isc_result_t
+cache_create_db(dns_cache_t *cache, dns_db_t **db) {
+ isc_result_t result;
+ result = dns_db_create(cache->mctx, cache->db_type, dns_rootname,
+ dns_dbtype_cache, cache->rdclass, cache->db_argc,
+ cache->db_argv, db);
+ if (result == ISC_R_SUCCESS) {
+ dns_db_setservestalettl(*db, cache->serve_stale_ttl);
+ dns_db_setservestalerefresh(*db, cache->serve_stale_refresh);
+ }
+ return (result);
+}
+
+isc_result_t
+dns_cache_create(isc_mem_t *cmctx, isc_mem_t *hmctx, isc_taskmgr_t *taskmgr,
+ isc_timermgr_t *timermgr, dns_rdataclass_t rdclass,
+ const char *cachename, const char *db_type,
+ unsigned int db_argc, char **db_argv, dns_cache_t **cachep) {
+ isc_result_t result;
+ dns_cache_t *cache;
+ int i, extra = 0;
+ isc_task_t *dbtask;
+
+ REQUIRE(cachep != NULL);
+ REQUIRE(*cachep == NULL);
+ REQUIRE(cmctx != NULL);
+ REQUIRE(hmctx != NULL);
+ REQUIRE(cachename != NULL);
+
+ cache = isc_mem_get(cmctx, sizeof(*cache));
+
+ cache->mctx = cache->hmctx = NULL;
+ isc_mem_attach(cmctx, &cache->mctx);
+ isc_mem_attach(hmctx, &cache->hmctx);
+
+ cache->name = NULL;
+ if (cachename != NULL) {
+ cache->name = isc_mem_strdup(cmctx, cachename);
+ }
+
+ isc_mutex_init(&cache->lock);
+
+ isc_refcount_init(&cache->references, 1);
+ isc_refcount_init(&cache->live_tasks, 1);
+ cache->rdclass = rdclass;
+ cache->serve_stale_ttl = 0;
+
+ cache->stats = NULL;
+ result = isc_stats_create(cmctx, &cache->stats,
+ dns_cachestatscounter_max);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup_lock;
+ }
+
+ cache->db_type = isc_mem_strdup(cmctx, db_type);
+
+ /*
+ * For databases of type "rbt" we pass hmctx to dns_db_create()
+ * via cache->db_argv, followed by the rest of the arguments in
+ * db_argv (of which there really shouldn't be any).
+ */
+ if (strcmp(cache->db_type, "rbt") == 0) {
+ extra = 1;
+ }
+
+ cache->db_argc = db_argc + extra;
+ cache->db_argv = NULL;
+
+ if (cache->db_argc != 0) {
+ cache->db_argv = isc_mem_get(cmctx,
+ cache->db_argc * sizeof(char *));
+
+ for (i = 0; i < cache->db_argc; i++) {
+ cache->db_argv[i] = NULL;
+ }
+
+ cache->db_argv[0] = (char *)hmctx;
+ for (i = extra; i < cache->db_argc; i++) {
+ cache->db_argv[i] = isc_mem_strdup(cmctx,
+ db_argv[i - extra]);
+ }
+ }
+
+ /*
+ * Create the database
+ */
+ cache->db = NULL;
+ result = cache_create_db(cache, &cache->db);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup_dbargv;
+ }
+ if (taskmgr != NULL) {
+ dbtask = NULL;
+ result = isc_task_create(taskmgr, 1, &dbtask);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup_db;
+ }
+
+ isc_task_setname(dbtask, "cache_dbtask", NULL);
+ dns_db_settask(cache->db, dbtask);
+ isc_task_detach(&dbtask);
+ }
+
+ cache->magic = CACHE_MAGIC;
+
+ /*
+ * RBT-type cache DB has its own mechanism of cache cleaning and doesn't
+ * need the control of the generic cleaner.
+ */
+ if (strcmp(db_type, "rbt") == 0) {
+ result = cache_cleaner_init(cache, NULL, NULL, &cache->cleaner);
+ } else {
+ result = cache_cleaner_init(cache, taskmgr, timermgr,
+ &cache->cleaner);
+ }
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup_db;
+ }
+
+ result = dns_db_setcachestats(cache->db, cache->stats);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup_db;
+ }
+
+ *cachep = cache;
+ return (ISC_R_SUCCESS);
+
+cleanup_db:
+ dns_db_detach(&cache->db);
+cleanup_dbargv:
+ for (i = extra; i < cache->db_argc; i++) {
+ if (cache->db_argv[i] != NULL) {
+ isc_mem_free(cmctx, cache->db_argv[i]);
+ }
+ }
+ if (cache->db_argv != NULL) {
+ isc_mem_put(cmctx, cache->db_argv,
+ cache->db_argc * sizeof(char *));
+ }
+ isc_mem_free(cmctx, cache->db_type);
+ isc_stats_detach(&cache->stats);
+cleanup_lock:
+ isc_mutex_destroy(&cache->lock);
+ if (cache->name != NULL) {
+ isc_mem_free(cmctx, cache->name);
+ }
+ isc_mem_detach(&cache->hmctx);
+ isc_mem_putanddetach(&cache->mctx, cache, sizeof(*cache));
+ return (result);
+}
+
+static void
+cache_free(dns_cache_t *cache) {
+ REQUIRE(VALID_CACHE(cache));
+
+ isc_refcount_destroy(&cache->references);
+ isc_refcount_destroy(&cache->live_tasks);
+
+ isc_mem_clearwater(cache->mctx);
+
+ if (cache->cleaner.task != NULL) {
+ isc_task_detach(&cache->cleaner.task);
+ }
+
+ if (cache->cleaner.overmem_event != NULL) {
+ isc_event_free(&cache->cleaner.overmem_event);
+ }
+
+ if (cache->cleaner.resched_event != NULL) {
+ isc_event_free(&cache->cleaner.resched_event);
+ }
+
+ if (cache->cleaner.iterator != NULL) {
+ dns_dbiterator_destroy(&cache->cleaner.iterator);
+ }
+
+ isc_mutex_destroy(&cache->cleaner.lock);
+
+ if (cache->db != NULL) {
+ dns_db_detach(&cache->db);
+ }
+
+ if (cache->db_argv != NULL) {
+ /*
+ * We don't free db_argv[0] in "rbt" cache databases
+ * as it's a pointer to hmctx
+ */
+ int extra = 0;
+ if (strcmp(cache->db_type, "rbt") == 0) {
+ extra = 1;
+ }
+ for (int i = extra; i < cache->db_argc; i++) {
+ if (cache->db_argv[i] != NULL) {
+ isc_mem_free(cache->mctx, cache->db_argv[i]);
+ }
+ }
+ isc_mem_put(cache->mctx, cache->db_argv,
+ cache->db_argc * sizeof(char *));
+ }
+
+ if (cache->db_type != NULL) {
+ isc_mem_free(cache->mctx, cache->db_type);
+ }
+
+ if (cache->name != NULL) {
+ isc_mem_free(cache->mctx, cache->name);
+ }
+
+ if (cache->stats != NULL) {
+ isc_stats_detach(&cache->stats);
+ }
+
+ isc_mutex_destroy(&cache->lock);
+
+ cache->magic = 0;
+ isc_mem_detach(&cache->hmctx);
+ isc_mem_putanddetach(&cache->mctx, cache, sizeof(*cache));
+}
+
+void
+dns_cache_attach(dns_cache_t *cache, dns_cache_t **targetp) {
+ REQUIRE(VALID_CACHE(cache));
+ REQUIRE(targetp != NULL && *targetp == NULL);
+
+ isc_refcount_increment(&cache->references);
+
+ *targetp = cache;
+}
+
+void
+dns_cache_detach(dns_cache_t **cachep) {
+ dns_cache_t *cache;
+
+ REQUIRE(cachep != NULL);
+ cache = *cachep;
+ *cachep = NULL;
+ REQUIRE(VALID_CACHE(cache));
+
+ if (isc_refcount_decrement(&cache->references) == 1) {
+ cache->cleaner.overmem = false;
+
+ /*
+ * If the cleaner task exists, let it free the cache.
+ */
+ if (isc_refcount_decrement(&cache->live_tasks) > 1) {
+ isc_task_shutdown(cache->cleaner.task);
+ } else {
+ cache_free(cache);
+ }
+ }
+}
+
+void
+dns_cache_attachdb(dns_cache_t *cache, dns_db_t **dbp) {
+ REQUIRE(VALID_CACHE(cache));
+ REQUIRE(dbp != NULL && *dbp == NULL);
+ REQUIRE(cache->db != NULL);
+
+ LOCK(&cache->lock);
+ dns_db_attach(cache->db, dbp);
+ UNLOCK(&cache->lock);
+}
+
+const char *
+dns_cache_getname(dns_cache_t *cache) {
+ REQUIRE(VALID_CACHE(cache));
+
+ return (cache->name);
+}
+
+/*
+ * Initialize the cache cleaner object at *cleaner.
+ * Space for the object must be allocated by the caller.
+ */
+
+static isc_result_t
+cache_cleaner_init(dns_cache_t *cache, isc_taskmgr_t *taskmgr,
+ isc_timermgr_t *timermgr, cache_cleaner_t *cleaner) {
+ isc_result_t result;
+
+ isc_mutex_init(&cleaner->lock);
+
+ cleaner->increment = DNS_CACHE_CLEANERINCREMENT;
+ cleaner->state = cleaner_s_idle;
+ cleaner->cache = cache;
+ cleaner->iterator = NULL;
+ cleaner->overmem = false;
+ cleaner->replaceiterator = false;
+
+ cleaner->task = NULL;
+ cleaner->resched_event = NULL;
+ cleaner->overmem_event = NULL;
+
+ result = dns_db_createiterator(cleaner->cache->db, false,
+ &cleaner->iterator);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+
+ if (taskmgr != NULL && timermgr != NULL) {
+ result = isc_task_create(taskmgr, 1, &cleaner->task);
+ if (result != ISC_R_SUCCESS) {
+ UNEXPECTED_ERROR("isc_task_create() failed: %s",
+ isc_result_totext(result));
+ result = ISC_R_UNEXPECTED;
+ goto cleanup;
+ }
+ isc_refcount_increment(&cleaner->cache->live_tasks);
+ isc_task_setname(cleaner->task, "cachecleaner", cleaner);
+
+ result = isc_task_onshutdown(cleaner->task,
+ cleaner_shutdown_action, cache);
+ if (result != ISC_R_SUCCESS) {
+ isc_refcount_decrement0(&cleaner->cache->live_tasks);
+ UNEXPECTED_ERROR("cache cleaner: "
+ "isc_task_onshutdown() failed: %s",
+ isc_result_totext(result));
+ goto cleanup;
+ }
+
+ cleaner->resched_event = isc_event_allocate(
+ cache->mctx, cleaner, DNS_EVENT_CACHECLEAN,
+ incremental_cleaning_action, cleaner,
+ sizeof(isc_event_t));
+
+ cleaner->overmem_event = isc_event_allocate(
+ cache->mctx, cleaner, DNS_EVENT_CACHEOVERMEM,
+ overmem_cleaning_action, cleaner, sizeof(isc_event_t));
+ }
+
+ return (ISC_R_SUCCESS);
+
+cleanup:
+ if (cleaner->overmem_event != NULL) {
+ isc_event_free(&cleaner->overmem_event);
+ }
+ if (cleaner->resched_event != NULL) {
+ isc_event_free(&cleaner->resched_event);
+ }
+ if (cleaner->task != NULL) {
+ isc_task_detach(&cleaner->task);
+ }
+ if (cleaner->iterator != NULL) {
+ dns_dbiterator_destroy(&cleaner->iterator);
+ }
+ isc_mutex_destroy(&cleaner->lock);
+
+ return (result);
+}
+
+static void
+begin_cleaning(cache_cleaner_t *cleaner) {
+ isc_result_t result = ISC_R_SUCCESS;
+
+ REQUIRE(CLEANER_IDLE(cleaner));
+
+ /*
+ * Create an iterator, if it does not already exist, and
+ * position it at the beginning of the cache.
+ */
+ if (cleaner->iterator == NULL) {
+ result = dns_db_createiterator(cleaner->cache->db, false,
+ &cleaner->iterator);
+ }
+ if (result != ISC_R_SUCCESS) {
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE,
+ DNS_LOGMODULE_CACHE, ISC_LOG_WARNING,
+ "cache cleaner could not create "
+ "iterator: %s",
+ isc_result_totext(result));
+ } else {
+ dns_dbiterator_setcleanmode(cleaner->iterator, true);
+ result = dns_dbiterator_first(cleaner->iterator);
+ }
+ if (result != ISC_R_SUCCESS) {
+ /*
+ * If the result is ISC_R_NOMORE, the database is empty,
+ * so there is nothing to be cleaned.
+ */
+ if (result != ISC_R_NOMORE && cleaner->iterator != NULL) {
+ UNEXPECTED_ERROR("cache cleaner: "
+ "dns_dbiterator_first() failed: %s",
+ isc_result_totext(result));
+ dns_dbiterator_destroy(&cleaner->iterator);
+ } else if (cleaner->iterator != NULL) {
+ result = dns_dbiterator_pause(cleaner->iterator);
+ RUNTIME_CHECK(result == ISC_R_SUCCESS);
+ }
+ } else {
+ /*
+ * Pause the iterator to free its lock.
+ */
+ result = dns_dbiterator_pause(cleaner->iterator);
+ RUNTIME_CHECK(result == ISC_R_SUCCESS);
+
+ isc_log_write(
+ dns_lctx, DNS_LOGCATEGORY_DATABASE, DNS_LOGMODULE_CACHE,
+ ISC_LOG_DEBUG(1), "begin cache cleaning, mem inuse %lu",
+ (unsigned long)isc_mem_inuse(cleaner->cache->mctx));
+ cleaner->state = cleaner_s_busy;
+ isc_task_send(cleaner->task, &cleaner->resched_event);
+ }
+
+ return;
+}
+
+static void
+end_cleaning(cache_cleaner_t *cleaner, isc_event_t *event) {
+ isc_result_t result;
+
+ REQUIRE(CLEANER_BUSY(cleaner));
+ REQUIRE(event != NULL);
+
+ result = dns_dbiterator_pause(cleaner->iterator);
+ if (result != ISC_R_SUCCESS) {
+ dns_dbiterator_destroy(&cleaner->iterator);
+ }
+
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, DNS_LOGMODULE_CACHE,
+ ISC_LOG_DEBUG(1), "end cache cleaning, mem inuse %lu",
+ (unsigned long)isc_mem_inuse(cleaner->cache->mctx));
+
+ cleaner->state = cleaner_s_idle;
+ cleaner->resched_event = event;
+}
+
+/*
+ * This is called when the cache either surpasses its upper limit
+ * or shrinks beyond its lower limit.
+ */
+static void
+overmem_cleaning_action(isc_task_t *task, isc_event_t *event) {
+ cache_cleaner_t *cleaner = event->ev_arg;
+ bool want_cleaning = false;
+
+ UNUSED(task);
+
+ INSIST(task == cleaner->task);
+ INSIST(event->ev_type == DNS_EVENT_CACHEOVERMEM);
+ INSIST(cleaner->overmem_event == NULL);
+
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, DNS_LOGMODULE_CACHE,
+ ISC_LOG_DEBUG(1),
+ "overmem_cleaning_action called, "
+ "overmem = %d, state = %d",
+ cleaner->overmem, cleaner->state);
+
+ LOCK(&cleaner->lock);
+
+ if (cleaner->overmem) {
+ if (cleaner->state == cleaner_s_idle) {
+ want_cleaning = true;
+ }
+ } else {
+ if (cleaner->state == cleaner_s_busy) {
+ /*
+ * end_cleaning() can't be called here because
+ * then both cleaner->overmem_event and
+ * cleaner->resched_event will point to this
+ * event. Set the state to done, and then
+ * when the incremental_cleaning_action() event
+ * is posted, it will handle the end_cleaning.
+ */
+ cleaner->state = cleaner_s_done;
+ }
+ }
+
+ cleaner->overmem_event = event;
+
+ UNLOCK(&cleaner->lock);
+
+ if (want_cleaning) {
+ begin_cleaning(cleaner);
+ }
+}
+
+/*
+ * Do incremental cleaning.
+ */
+static void
+incremental_cleaning_action(isc_task_t *task, isc_event_t *event) {
+ cache_cleaner_t *cleaner = event->ev_arg;
+ isc_result_t result;
+ unsigned int n_names;
+ isc_time_t start;
+
+ UNUSED(task);
+
+ INSIST(task == cleaner->task);
+ INSIST(event->ev_type == DNS_EVENT_CACHECLEAN);
+
+ if (cleaner->state == cleaner_s_done) {
+ cleaner->state = cleaner_s_busy;
+ end_cleaning(cleaner, event);
+ LOCK(&cleaner->cache->lock);
+ LOCK(&cleaner->lock);
+ if (cleaner->replaceiterator) {
+ dns_dbiterator_destroy(&cleaner->iterator);
+ (void)dns_db_createiterator(cleaner->cache->db, false,
+ &cleaner->iterator);
+ cleaner->replaceiterator = false;
+ }
+ UNLOCK(&cleaner->lock);
+ UNLOCK(&cleaner->cache->lock);
+ return;
+ }
+
+ INSIST(CLEANER_BUSY(cleaner));
+
+ n_names = cleaner->increment;
+
+ REQUIRE(DNS_DBITERATOR_VALID(cleaner->iterator));
+
+ isc_time_now(&start);
+ while (n_names-- > 0) {
+ dns_dbnode_t *node = NULL;
+
+ result = dns_dbiterator_current(cleaner->iterator, &node, NULL);
+ if (result != ISC_R_SUCCESS) {
+ UNEXPECTED_ERROR("cache cleaner: "
+ "dns_dbiterator_current() failed: %s",
+ isc_result_totext(result));
+
+ end_cleaning(cleaner, event);
+ return;
+ }
+
+ /*
+ * The node was not needed, but was required by
+ * dns_dbiterator_current(). Give up its reference.
+ */
+ dns_db_detachnode(cleaner->cache->db, &node);
+
+ /*
+ * Step to the next node.
+ */
+ result = dns_dbiterator_next(cleaner->iterator);
+
+ if (result != ISC_R_SUCCESS) {
+ /*
+ * Either the end was reached (ISC_R_NOMORE) or
+ * some error was signaled. If the cache is still
+ * overmem and no error was encountered,
+ * keep trying to clean it, otherwise stop cleaning.
+ */
+ if (result != ISC_R_NOMORE) {
+ UNEXPECTED_ERROR("cache cleaner: "
+ "dns_dbiterator_next() "
+ "failed: %s",
+ isc_result_totext(result));
+ } else if (cleaner->overmem) {
+ result =
+ dns_dbiterator_first(cleaner->iterator);
+ if (result == ISC_R_SUCCESS) {
+ isc_log_write(dns_lctx,
+ DNS_LOGCATEGORY_DATABASE,
+ DNS_LOGMODULE_CACHE,
+ ISC_LOG_DEBUG(1),
+ "cache cleaner: "
+ "still overmem, "
+ "reset and try again");
+ continue;
+ }
+ }
+
+ end_cleaning(cleaner, event);
+ return;
+ }
+ }
+
+ /*
+ * We have successfully performed a cleaning increment but have
+ * not gone through the entire cache. Free the iterator locks
+ * and reschedule another batch. If it fails, just try to continue
+ * anyway.
+ */
+ result = dns_dbiterator_pause(cleaner->iterator);
+ RUNTIME_CHECK(result == ISC_R_SUCCESS);
+
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, DNS_LOGMODULE_CACHE,
+ ISC_LOG_DEBUG(1),
+ "cache cleaner: checked %u nodes, "
+ "mem inuse %lu, sleeping",
+ cleaner->increment,
+ (unsigned long)isc_mem_inuse(cleaner->cache->mctx));
+
+ isc_task_send(task, &event);
+ INSIST(CLEANER_BUSY(cleaner));
+ return;
+}
+
+/*
+ * Do immediate cleaning.
+ */
+isc_result_t
+dns_cache_clean(dns_cache_t *cache, isc_stdtime_t now) {
+ isc_result_t result;
+ dns_dbiterator_t *iterator = NULL;
+
+ REQUIRE(VALID_CACHE(cache));
+
+ result = dns_db_createiterator(cache->db, 0, &iterator);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+
+ result = dns_dbiterator_first(iterator);
+
+ while (result == ISC_R_SUCCESS) {
+ dns_dbnode_t *node = NULL;
+ result = dns_dbiterator_current(iterator, &node,
+ (dns_name_t *)NULL);
+ if (result != ISC_R_SUCCESS) {
+ break;
+ }
+
+ /*
+ * Check TTLs, mark expired rdatasets stale.
+ */
+ result = dns_db_expirenode(cache->db, node, now);
+ if (result != ISC_R_SUCCESS) {
+ UNEXPECTED_ERROR("cache cleaner: dns_db_expirenode() "
+ "failed: %s",
+ isc_result_totext(result));
+ /*
+ * Continue anyway.
+ */
+ }
+
+ /*
+ * This is where the actual freeing takes place.
+ */
+ dns_db_detachnode(cache->db, &node);
+
+ result = dns_dbiterator_next(iterator);
+ }
+
+ dns_dbiterator_destroy(&iterator);
+
+ if (result == ISC_R_NOMORE) {
+ result = ISC_R_SUCCESS;
+ }
+
+ return (result);
+}
+
+static void
+water(void *arg, int mark) {
+ dns_cache_t *cache = arg;
+ bool overmem = (mark == ISC_MEM_HIWATER);
+
+ REQUIRE(VALID_CACHE(cache));
+
+ LOCK(&cache->cleaner.lock);
+
+ if (overmem != cache->cleaner.overmem) {
+ dns_db_overmem(cache->db, overmem);
+ cache->cleaner.overmem = overmem;
+ isc_mem_waterack(cache->mctx, mark);
+ }
+
+ if (cache->cleaner.overmem_event != NULL) {
+ isc_task_send(cache->cleaner.task,
+ &cache->cleaner.overmem_event);
+ }
+
+ UNLOCK(&cache->cleaner.lock);
+}
+
+void
+dns_cache_setcachesize(dns_cache_t *cache, size_t size) {
+ size_t hiwater, lowater;
+
+ REQUIRE(VALID_CACHE(cache));
+
+ /*
+ * Impose a minimum cache size; pathological things happen if there
+ * is too little room.
+ */
+ if (size != 0U && size < DNS_CACHE_MINSIZE) {
+ size = DNS_CACHE_MINSIZE;
+ }
+
+ LOCK(&cache->lock);
+ cache->size = size;
+ UNLOCK(&cache->lock);
+
+ hiwater = size - (size >> 3); /* Approximately 7/8ths. */
+ lowater = size - (size >> 2); /* Approximately 3/4ths. */
+
+ /*
+ * If the cache was overmem and cleaning, but now with the new limits
+ * it is no longer in an overmem condition, then the next
+ * isc_mem_put for cache memory will do the right thing and trigger
+ * water().
+ */
+
+ if (size == 0U || hiwater == 0U || lowater == 0U) {
+ /*
+ * Disable cache memory limiting.
+ */
+ isc_mem_clearwater(cache->mctx);
+ } else {
+ /*
+ * Establish new cache memory limits (either for the first
+ * time, or replacing other limits).
+ */
+ isc_mem_setwater(cache->mctx, water, cache, hiwater, lowater);
+ }
+}
+
+size_t
+dns_cache_getcachesize(dns_cache_t *cache) {
+ size_t size;
+
+ REQUIRE(VALID_CACHE(cache));
+
+ LOCK(&cache->lock);
+ size = cache->size;
+ UNLOCK(&cache->lock);
+
+ return (size);
+}
+
+void
+dns_cache_setservestalettl(dns_cache_t *cache, dns_ttl_t ttl) {
+ REQUIRE(VALID_CACHE(cache));
+
+ LOCK(&cache->lock);
+ cache->serve_stale_ttl = ttl;
+ UNLOCK(&cache->lock);
+
+ (void)dns_db_setservestalettl(cache->db, ttl);
+}
+
+dns_ttl_t
+dns_cache_getservestalettl(dns_cache_t *cache) {
+ dns_ttl_t ttl;
+ isc_result_t result;
+
+ REQUIRE(VALID_CACHE(cache));
+
+ /*
+ * Could get it straight from the dns_cache_t, but use db
+ * to confirm the value that the db is really using.
+ */
+ result = dns_db_getservestalettl(cache->db, &ttl);
+ return (result == ISC_R_SUCCESS ? ttl : 0);
+}
+
+void
+dns_cache_setservestalerefresh(dns_cache_t *cache, dns_ttl_t interval) {
+ REQUIRE(VALID_CACHE(cache));
+
+ LOCK(&cache->lock);
+ cache->serve_stale_refresh = interval;
+ UNLOCK(&cache->lock);
+
+ (void)dns_db_setservestalerefresh(cache->db, interval);
+}
+
+dns_ttl_t
+dns_cache_getservestalerefresh(dns_cache_t *cache) {
+ isc_result_t result;
+ dns_ttl_t interval;
+
+ REQUIRE(VALID_CACHE(cache));
+
+ result = dns_db_getservestalerefresh(cache->db, &interval);
+ return (result == ISC_R_SUCCESS ? interval : 0);
+}
+
+/*
+ * The cleaner task is shutting down; do the necessary cleanup.
+ */
+static void
+cleaner_shutdown_action(isc_task_t *task, isc_event_t *event) {
+ dns_cache_t *cache = event->ev_arg;
+
+ UNUSED(task);
+
+ INSIST(task == cache->cleaner.task);
+ INSIST(event->ev_type == ISC_TASKEVENT_SHUTDOWN);
+
+ if (CLEANER_BUSY(&cache->cleaner)) {
+ end_cleaning(&cache->cleaner, event);
+ } else {
+ isc_event_free(&event);
+ }
+
+ /* Make sure we don't reschedule anymore. */
+ (void)isc_task_purge(task, NULL, DNS_EVENT_CACHECLEAN, NULL);
+
+ isc_refcount_decrementz(&cache->live_tasks);
+
+ cache_free(cache);
+}
+
+isc_result_t
+dns_cache_flush(dns_cache_t *cache) {
+ dns_db_t *db = NULL, *olddb;
+ dns_dbiterator_t *dbiterator = NULL, *olddbiterator = NULL;
+ isc_result_t result;
+
+ result = cache_create_db(cache, &db);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+
+ result = dns_db_createiterator(db, false, &dbiterator);
+ if (result != ISC_R_SUCCESS) {
+ dns_db_detach(&db);
+ return (result);
+ }
+
+ LOCK(&cache->lock);
+ LOCK(&cache->cleaner.lock);
+ if (cache->cleaner.state == cleaner_s_idle) {
+ olddbiterator = cache->cleaner.iterator;
+ cache->cleaner.iterator = dbiterator;
+ dbiterator = NULL;
+ } else {
+ if (cache->cleaner.state == cleaner_s_busy) {
+ cache->cleaner.state = cleaner_s_done;
+ }
+ cache->cleaner.replaceiterator = true;
+ }
+ olddb = cache->db;
+ cache->db = db;
+ dns_db_setcachestats(cache->db, cache->stats);
+ UNLOCK(&cache->cleaner.lock);
+ UNLOCK(&cache->lock);
+
+ if (dbiterator != NULL) {
+ dns_dbiterator_destroy(&dbiterator);
+ }
+ if (olddbiterator != NULL) {
+ dns_dbiterator_destroy(&olddbiterator);
+ }
+ dns_db_detach(&olddb);
+
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+clearnode(dns_db_t *db, dns_dbnode_t *node) {
+ isc_result_t result;
+ dns_rdatasetiter_t *iter = NULL;
+
+ result = dns_db_allrdatasets(db, node, NULL, DNS_DB_STALEOK,
+ (isc_stdtime_t)0, &iter);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+
+ for (result = dns_rdatasetiter_first(iter); result == ISC_R_SUCCESS;
+ result = dns_rdatasetiter_next(iter))
+ {
+ dns_rdataset_t rdataset;
+ dns_rdataset_init(&rdataset);
+
+ dns_rdatasetiter_current(iter, &rdataset);
+ result = dns_db_deleterdataset(db, node, NULL, rdataset.type,
+ rdataset.covers);
+ dns_rdataset_disassociate(&rdataset);
+ if (result != ISC_R_SUCCESS && result != DNS_R_UNCHANGED) {
+ break;
+ }
+ }
+
+ if (result == ISC_R_NOMORE) {
+ result = ISC_R_SUCCESS;
+ }
+
+ dns_rdatasetiter_destroy(&iter);
+ return (result);
+}
+
+static isc_result_t
+cleartree(dns_db_t *db, const dns_name_t *name) {
+ isc_result_t result, answer = ISC_R_SUCCESS;
+ dns_dbiterator_t *iter = NULL;
+ dns_dbnode_t *node = NULL, *top = NULL;
+ dns_fixedname_t fnodename;
+ dns_name_t *nodename;
+
+ /*
+ * Create the node if it doesn't exist so dns_dbiterator_seek()
+ * can find it. We will continue even if this fails.
+ */
+ (void)dns_db_findnode(db, name, true, &top);
+
+ nodename = dns_fixedname_initname(&fnodename);
+
+ result = dns_db_createiterator(db, 0, &iter);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+
+ result = dns_dbiterator_seek(iter, name);
+ if (result == DNS_R_PARTIALMATCH) {
+ result = dns_dbiterator_next(iter);
+ }
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+
+ while (result == ISC_R_SUCCESS) {
+ result = dns_dbiterator_current(iter, &node, nodename);
+ if (result == DNS_R_NEWORIGIN) {
+ result = ISC_R_SUCCESS;
+ }
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+ /*
+ * Are we done?
+ */
+ if (!dns_name_issubdomain(nodename, name)) {
+ goto cleanup;
+ }
+
+ /*
+ * If clearnode fails record and move onto the next node.
+ */
+ result = clearnode(db, node);
+ if (result != ISC_R_SUCCESS && answer == ISC_R_SUCCESS) {
+ answer = result;
+ }
+ dns_db_detachnode(db, &node);
+ result = dns_dbiterator_next(iter);
+ }
+
+cleanup:
+ if (result == ISC_R_NOMORE || result == ISC_R_NOTFOUND) {
+ result = ISC_R_SUCCESS;
+ }
+ if (result != ISC_R_SUCCESS && answer == ISC_R_SUCCESS) {
+ answer = result;
+ }
+ if (node != NULL) {
+ dns_db_detachnode(db, &node);
+ }
+ if (iter != NULL) {
+ dns_dbiterator_destroy(&iter);
+ }
+ if (top != NULL) {
+ dns_db_detachnode(db, &top);
+ }
+
+ return (answer);
+}
+
+isc_result_t
+dns_cache_flushname(dns_cache_t *cache, const dns_name_t *name) {
+ return (dns_cache_flushnode(cache, name, false));
+}
+
+isc_result_t
+dns_cache_flushnode(dns_cache_t *cache, const dns_name_t *name, bool tree) {
+ isc_result_t result;
+ dns_dbnode_t *node = NULL;
+ dns_db_t *db = NULL;
+
+ if (tree && dns_name_equal(name, dns_rootname)) {
+ return (dns_cache_flush(cache));
+ }
+
+ LOCK(&cache->lock);
+ if (cache->db != NULL) {
+ dns_db_attach(cache->db, &db);
+ }
+ UNLOCK(&cache->lock);
+ if (db == NULL) {
+ return (ISC_R_SUCCESS);
+ }
+
+ if (tree) {
+ result = cleartree(cache->db, name);
+ } else {
+ result = dns_db_findnode(cache->db, name, false, &node);
+ if (result == ISC_R_NOTFOUND) {
+ result = ISC_R_SUCCESS;
+ goto cleanup_db;
+ }
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup_db;
+ }
+ result = clearnode(cache->db, node);
+ dns_db_detachnode(cache->db, &node);
+ }
+
+cleanup_db:
+ dns_db_detach(&db);
+ return (result);
+}
+
+isc_stats_t *
+dns_cache_getstats(dns_cache_t *cache) {
+ REQUIRE(VALID_CACHE(cache));
+ return (cache->stats);
+}
+
+void
+dns_cache_updatestats(dns_cache_t *cache, isc_result_t result) {
+ REQUIRE(VALID_CACHE(cache));
+ if (cache->stats == NULL) {
+ return;
+ }
+
+ switch (result) {
+ case ISC_R_SUCCESS:
+ case DNS_R_NCACHENXDOMAIN:
+ case DNS_R_NCACHENXRRSET:
+ case DNS_R_CNAME:
+ case DNS_R_DNAME:
+ case DNS_R_GLUE:
+ case DNS_R_ZONECUT:
+ case DNS_R_COVERINGNSEC:
+ isc_stats_increment(cache->stats,
+ dns_cachestatscounter_queryhits);
+ break;
+ default:
+ isc_stats_increment(cache->stats,
+ dns_cachestatscounter_querymisses);
+ }
+}
+
+/*
+ * XXX: Much of the following code has been copied in from statschannel.c.
+ * We should refactor this into a generic function in stats.c that can be
+ * called from both places.
+ */
+typedef struct cache_dumparg {
+ isc_statsformat_t type;
+ void *arg; /* type dependent argument */
+ int ncounters; /* for general statistics */
+ int *counterindices; /* for general statistics */
+ uint64_t *countervalues; /* for general statistics */
+ isc_result_t result;
+} cache_dumparg_t;
+
+static void
+getcounter(isc_statscounter_t counter, uint64_t val, void *arg) {
+ cache_dumparg_t *dumparg = arg;
+
+ REQUIRE(counter < dumparg->ncounters);
+ dumparg->countervalues[counter] = val;
+}
+
+static void
+getcounters(isc_stats_t *stats, isc_statsformat_t type, int ncounters,
+ int *indices, uint64_t *values) {
+ cache_dumparg_t dumparg;
+
+ memset(values, 0, sizeof(values[0]) * ncounters);
+
+ dumparg.type = type;
+ dumparg.ncounters = ncounters;
+ dumparg.counterindices = indices;
+ dumparg.countervalues = values;
+
+ isc_stats_dump(stats, getcounter, &dumparg, ISC_STATSDUMP_VERBOSE);
+}
+
+void
+dns_cache_dumpstats(dns_cache_t *cache, FILE *fp) {
+ int indices[dns_cachestatscounter_max];
+ uint64_t values[dns_cachestatscounter_max];
+
+ REQUIRE(VALID_CACHE(cache));
+
+ getcounters(cache->stats, isc_statsformat_file,
+ dns_cachestatscounter_max, indices, values);
+
+ fprintf(fp, "%20" PRIu64 " %s\n", values[dns_cachestatscounter_hits],
+ "cache hits");
+ fprintf(fp, "%20" PRIu64 " %s\n", values[dns_cachestatscounter_misses],
+ "cache misses");
+ fprintf(fp, "%20" PRIu64 " %s\n",
+ values[dns_cachestatscounter_queryhits],
+ "cache hits (from query)");
+ fprintf(fp, "%20" PRIu64 " %s\n",
+ values[dns_cachestatscounter_querymisses],
+ "cache misses (from query)");
+ fprintf(fp, "%20" PRIu64 " %s\n",
+ values[dns_cachestatscounter_deletelru],
+ "cache records deleted due to memory exhaustion");
+ fprintf(fp, "%20" PRIu64 " %s\n",
+ values[dns_cachestatscounter_deletettl],
+ "cache records deleted due to TTL expiration");
+ fprintf(fp, "%20" PRIu64 " %s\n",
+ values[dns_cachestatscounter_coveringnsec],
+ "covering nsec returned");
+ fprintf(fp, "%20u %s\n", dns_db_nodecount(cache->db, dns_dbtree_main),
+ "cache database nodes");
+ fprintf(fp, "%20u %s\n", dns_db_nodecount(cache->db, dns_dbtree_nsec),
+ "cache NSEC auxiliary database nodes");
+ fprintf(fp, "%20" PRIu64 " %s\n", (uint64_t)dns_db_hashsize(cache->db),
+ "cache database hash buckets");
+
+ fprintf(fp, "%20" PRIu64 " %s\n", (uint64_t)isc_mem_total(cache->mctx),
+ "cache tree memory total");
+ fprintf(fp, "%20" PRIu64 " %s\n", (uint64_t)isc_mem_inuse(cache->mctx),
+ "cache tree memory in use");
+ fprintf(fp, "%20" PRIu64 " %s\n",
+ (uint64_t)isc_mem_maxinuse(cache->mctx),
+ "cache tree highest memory in use");
+
+ fprintf(fp, "%20" PRIu64 " %s\n", (uint64_t)isc_mem_total(cache->hmctx),
+ "cache heap memory total");
+ fprintf(fp, "%20" PRIu64 " %s\n", (uint64_t)isc_mem_inuse(cache->hmctx),
+ "cache heap memory in use");
+ fprintf(fp, "%20" PRIu64 " %s\n",
+ (uint64_t)isc_mem_maxinuse(cache->hmctx),
+ "cache heap highest memory in use");
+}
+
+#ifdef HAVE_LIBXML2
+#define TRY0(a) \
+ do { \
+ xmlrc = (a); \
+ if (xmlrc < 0) \
+ goto error; \
+ } while (0)
+static int
+renderstat(const char *name, uint64_t value, xmlTextWriterPtr writer) {
+ int xmlrc;
+
+ TRY0(xmlTextWriterStartElement(writer, ISC_XMLCHAR "counter"));
+ TRY0(xmlTextWriterWriteAttribute(writer, ISC_XMLCHAR "name",
+ ISC_XMLCHAR name));
+ TRY0(xmlTextWriterWriteFormatString(writer, "%" PRIu64 "", value));
+ TRY0(xmlTextWriterEndElement(writer)); /* counter */
+
+error:
+ return (xmlrc);
+}
+
+int
+dns_cache_renderxml(dns_cache_t *cache, void *writer0) {
+ int indices[dns_cachestatscounter_max];
+ uint64_t values[dns_cachestatscounter_max];
+ int xmlrc;
+ xmlTextWriterPtr writer = (xmlTextWriterPtr)writer0;
+
+ REQUIRE(VALID_CACHE(cache));
+
+ getcounters(cache->stats, isc_statsformat_file,
+ dns_cachestatscounter_max, indices, values);
+ TRY0(renderstat("CacheHits", values[dns_cachestatscounter_hits],
+ writer));
+ TRY0(renderstat("CacheMisses", values[dns_cachestatscounter_misses],
+ writer));
+ TRY0(renderstat("QueryHits", values[dns_cachestatscounter_queryhits],
+ writer));
+ TRY0(renderstat("QueryMisses",
+ values[dns_cachestatscounter_querymisses], writer));
+ TRY0(renderstat("DeleteLRU", values[dns_cachestatscounter_deletelru],
+ writer));
+ TRY0(renderstat("DeleteTTL", values[dns_cachestatscounter_deletettl],
+ writer));
+ TRY0(renderstat("CoveringNSEC",
+ values[dns_cachestatscounter_coveringnsec], writer));
+
+ TRY0(renderstat("CacheNodes",
+ dns_db_nodecount(cache->db, dns_dbtree_main), writer));
+ TRY0(renderstat("CacheNSECNodes",
+ dns_db_nodecount(cache->db, dns_dbtree_nsec), writer));
+ TRY0(renderstat("CacheBuckets", dns_db_hashsize(cache->db), writer));
+
+ TRY0(renderstat("TreeMemTotal", isc_mem_total(cache->mctx), writer));
+ TRY0(renderstat("TreeMemInUse", isc_mem_inuse(cache->mctx), writer));
+ TRY0(renderstat("TreeMemMax", isc_mem_maxinuse(cache->mctx), writer));
+
+ TRY0(renderstat("HeapMemTotal", isc_mem_total(cache->hmctx), writer));
+ TRY0(renderstat("HeapMemInUse", isc_mem_inuse(cache->hmctx), writer));
+ TRY0(renderstat("HeapMemMax", isc_mem_maxinuse(cache->hmctx), writer));
+error:
+ return (xmlrc);
+}
+#endif /* ifdef HAVE_LIBXML2 */
+
+#ifdef HAVE_JSON_C
+#define CHECKMEM(m) \
+ do { \
+ if (m == NULL) { \
+ result = ISC_R_NOMEMORY; \
+ goto error; \
+ } \
+ } while (0)
+
+isc_result_t
+dns_cache_renderjson(dns_cache_t *cache, void *cstats0) {
+ isc_result_t result = ISC_R_SUCCESS;
+ int indices[dns_cachestatscounter_max];
+ uint64_t values[dns_cachestatscounter_max];
+ json_object *obj;
+ json_object *cstats = (json_object *)cstats0;
+
+ REQUIRE(VALID_CACHE(cache));
+
+ getcounters(cache->stats, isc_statsformat_file,
+ dns_cachestatscounter_max, indices, values);
+
+ obj = json_object_new_int64(values[dns_cachestatscounter_hits]);
+ CHECKMEM(obj);
+ json_object_object_add(cstats, "CacheHits", obj);
+
+ obj = json_object_new_int64(values[dns_cachestatscounter_misses]);
+ CHECKMEM(obj);
+ json_object_object_add(cstats, "CacheMisses", obj);
+
+ obj = json_object_new_int64(values[dns_cachestatscounter_queryhits]);
+ CHECKMEM(obj);
+ json_object_object_add(cstats, "QueryHits", obj);
+
+ obj = json_object_new_int64(values[dns_cachestatscounter_querymisses]);
+ CHECKMEM(obj);
+ json_object_object_add(cstats, "QueryMisses", obj);
+
+ obj = json_object_new_int64(values[dns_cachestatscounter_deletelru]);
+ CHECKMEM(obj);
+ json_object_object_add(cstats, "DeleteLRU", obj);
+
+ obj = json_object_new_int64(values[dns_cachestatscounter_deletettl]);
+ CHECKMEM(obj);
+ json_object_object_add(cstats, "DeleteTTL", obj);
+
+ obj = json_object_new_int64(values[dns_cachestatscounter_coveringnsec]);
+ CHECKMEM(obj);
+ json_object_object_add(cstats, "CoveringNSEC", obj);
+
+ obj = json_object_new_int64(
+ dns_db_nodecount(cache->db, dns_dbtree_main));
+ CHECKMEM(obj);
+ json_object_object_add(cstats, "CacheNodes", obj);
+
+ obj = json_object_new_int64(
+ dns_db_nodecount(cache->db, dns_dbtree_nsec));
+ CHECKMEM(obj);
+ json_object_object_add(cstats, "CacheNSECNodes", obj);
+
+ obj = json_object_new_int64(dns_db_hashsize(cache->db));
+ CHECKMEM(obj);
+ json_object_object_add(cstats, "CacheBuckets", obj);
+
+ obj = json_object_new_int64(isc_mem_total(cache->mctx));
+ CHECKMEM(obj);
+ json_object_object_add(cstats, "TreeMemTotal", obj);
+
+ obj = json_object_new_int64(isc_mem_inuse(cache->mctx));
+ CHECKMEM(obj);
+ json_object_object_add(cstats, "TreeMemInUse", obj);
+
+ obj = json_object_new_int64(isc_mem_maxinuse(cache->mctx));
+ CHECKMEM(obj);
+ json_object_object_add(cstats, "TreeMemMax", obj);
+
+ obj = json_object_new_int64(isc_mem_total(cache->hmctx));
+ CHECKMEM(obj);
+ json_object_object_add(cstats, "HeapMemTotal", obj);
+
+ obj = json_object_new_int64(isc_mem_inuse(cache->hmctx));
+ CHECKMEM(obj);
+ json_object_object_add(cstats, "HeapMemInUse", obj);
+
+ obj = json_object_new_int64(isc_mem_maxinuse(cache->hmctx));
+ CHECKMEM(obj);
+ json_object_object_add(cstats, "HeapMemMax", obj);
+
+ result = ISC_R_SUCCESS;
+error:
+ return (result);
+}
+#endif /* ifdef HAVE_JSON_C */
diff --git a/lib/dns/callbacks.c b/lib/dns/callbacks.c
new file mode 100644
index 0000000..5200b72
--- /dev/null
+++ b/lib/dns/callbacks.c
@@ -0,0 +1,107 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/*! \file */
+
+#include <isc/print.h>
+#include <isc/util.h>
+
+#include <dns/callbacks.h>
+#include <dns/log.h>
+
+static void
+stdio_error_warn_callback(dns_rdatacallbacks_t *, const char *, ...)
+ ISC_FORMAT_PRINTF(2, 3);
+
+static void
+isclog_error_callback(dns_rdatacallbacks_t *callbacks, const char *fmt, ...)
+ ISC_FORMAT_PRINTF(2, 3);
+
+static void
+isclog_warn_callback(dns_rdatacallbacks_t *callbacks, const char *fmt, ...)
+ ISC_FORMAT_PRINTF(2, 3);
+
+/*
+ * Private
+ */
+
+static void
+stdio_error_warn_callback(dns_rdatacallbacks_t *callbacks, const char *fmt,
+ ...) {
+ va_list ap;
+
+ UNUSED(callbacks);
+
+ va_start(ap, fmt);
+ vfprintf(stderr, fmt, ap);
+ va_end(ap);
+ fprintf(stderr, "\n");
+}
+
+static void
+isclog_error_callback(dns_rdatacallbacks_t *callbacks, const char *fmt, ...) {
+ va_list ap;
+
+ UNUSED(callbacks);
+
+ va_start(ap, fmt);
+ isc_log_vwrite(dns_lctx, DNS_LOGCATEGORY_GENERAL,
+ DNS_LOGMODULE_MASTER, /* XXX */
+ ISC_LOG_ERROR, fmt, ap);
+ va_end(ap);
+}
+
+static void
+isclog_warn_callback(dns_rdatacallbacks_t *callbacks, const char *fmt, ...) {
+ va_list ap;
+
+ UNUSED(callbacks);
+
+ va_start(ap, fmt);
+
+ isc_log_vwrite(dns_lctx, DNS_LOGCATEGORY_GENERAL,
+ DNS_LOGMODULE_MASTER, /* XXX */
+ ISC_LOG_WARNING, fmt, ap);
+ va_end(ap);
+}
+
+static void
+dns_rdatacallbacks_initcommon(dns_rdatacallbacks_t *callbacks) {
+ REQUIRE(callbacks != NULL);
+
+ callbacks->magic = DNS_CALLBACK_MAGIC;
+ callbacks->add = NULL;
+ callbacks->rawdata = NULL;
+ callbacks->zone = NULL;
+ callbacks->add_private = NULL;
+ callbacks->error_private = NULL;
+ callbacks->warn_private = NULL;
+}
+
+/*
+ * Public.
+ */
+
+void
+dns_rdatacallbacks_init(dns_rdatacallbacks_t *callbacks) {
+ dns_rdatacallbacks_initcommon(callbacks);
+ callbacks->error = isclog_error_callback;
+ callbacks->warn = isclog_warn_callback;
+}
+
+void
+dns_rdatacallbacks_init_stdio(dns_rdatacallbacks_t *callbacks) {
+ dns_rdatacallbacks_initcommon(callbacks);
+ callbacks->error = stdio_error_warn_callback;
+ callbacks->warn = stdio_error_warn_callback;
+}
diff --git a/lib/dns/catz.c b/lib/dns/catz.c
new file mode 100644
index 0000000..b18459e
--- /dev/null
+++ b/lib/dns/catz.c
@@ -0,0 +1,2698 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/*! \file */
+
+#include <inttypes.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdlib.h>
+
+#include <isc/hex.h>
+#include <isc/md.h>
+#include <isc/mem.h>
+#include <isc/parseint.h>
+#include <isc/print.h>
+#include <isc/result.h>
+#include <isc/task.h>
+#include <isc/thread.h>
+#include <isc/util.h>
+
+#include <dns/catz.h>
+#include <dns/dbiterator.h>
+#include <dns/events.h>
+#include <dns/rdatasetiter.h>
+#include <dns/view.h>
+#include <dns/zone.h>
+
+#define DNS_CATZ_ZONE_MAGIC ISC_MAGIC('c', 'a', 't', 'z')
+#define DNS_CATZ_ZONES_MAGIC ISC_MAGIC('c', 'a', 't', 's')
+#define DNS_CATZ_ENTRY_MAGIC ISC_MAGIC('c', 'a', 't', 'e')
+#define DNS_CATZ_COO_MAGIC ISC_MAGIC('c', 'a', 't', 'c')
+
+#define DNS_CATZ_ZONE_VALID(catz) ISC_MAGIC_VALID(catz, DNS_CATZ_ZONE_MAGIC)
+#define DNS_CATZ_ZONES_VALID(catzs) ISC_MAGIC_VALID(catzs, DNS_CATZ_ZONES_MAGIC)
+#define DNS_CATZ_ENTRY_VALID(entry) ISC_MAGIC_VALID(entry, DNS_CATZ_ENTRY_MAGIC)
+#define DNS_CATZ_COO_VALID(coo) ISC_MAGIC_VALID(coo, DNS_CATZ_COO_MAGIC)
+
+#define DNS_CATZ_VERSION_UNDEFINED ((uint32_t)(-1))
+
+/*%
+ * Change of ownership permissions
+ */
+struct dns_catz_coo {
+ unsigned int magic;
+ dns_name_t name;
+ isc_refcount_t references;
+};
+
+/*%
+ * Single member zone in a catalog
+ */
+struct dns_catz_entry {
+ unsigned int magic;
+ dns_name_t name;
+ dns_catz_options_t opts;
+ isc_refcount_t references;
+};
+
+/*%
+ * Catalog zone
+ */
+struct dns_catz_zone {
+ unsigned int magic;
+ dns_name_t name;
+ dns_catz_zones_t *catzs;
+ dns_rdata_t soa;
+ uint32_t version;
+ /* key in entries is 'mhash', not domain name! */
+ isc_ht_t *entries;
+ /* key in coos is domain name */
+ isc_ht_t *coos;
+
+ /*
+ * defoptions are taken from named.conf
+ * zoneoptions are global options from zone
+ */
+ dns_catz_options_t defoptions;
+ dns_catz_options_t zoneoptions;
+ isc_time_t lastupdated;
+
+ bool updatepending; /* there is an update pending */
+ bool updaterunning; /* there is an update running */
+ isc_result_t updateresult; /* result from the offloaded work */
+ dns_db_t *db; /* zones database */
+ dns_dbversion_t *dbversion; /* version we will be updating to */
+ dns_db_t *updb; /* zones database we're working on */
+ dns_dbversion_t *updbversion; /* version we're working on */
+
+ isc_timer_t *updatetimer;
+ isc_event_t updateevent;
+
+ bool active;
+ bool db_registered;
+ bool broken;
+
+ isc_refcount_t references;
+ isc_mutex_t lock;
+};
+
+static void
+dns__catz_timer_cb(isc_task_t *task, isc_event_t *event);
+
+static void
+dns__catz_update_cb(void *data);
+static void
+dns__catz_done_cb(void *data, isc_result_t result);
+
+static isc_result_t
+catz_process_zones_entry(dns_catz_zone_t *catz, dns_rdataset_t *value,
+ dns_label_t *mhash);
+static isc_result_t
+catz_process_zones_suboption(dns_catz_zone_t *catz, dns_rdataset_t *value,
+ dns_label_t *mhash, dns_name_t *name);
+static void
+catz_entry_add_or_mod(dns_catz_zone_t *catz, isc_ht_t *ht, unsigned char *key,
+ size_t keysize, dns_catz_entry_t *nentry,
+ dns_catz_entry_t *oentry, const char *msg,
+ const char *zname, const char *czname);
+
+/*%
+ * Collection of catalog zones for a view
+ */
+struct dns_catz_zones {
+ unsigned int magic;
+ isc_ht_t *zones;
+ isc_mem_t *mctx;
+ isc_refcount_t references;
+ isc_mutex_t lock;
+ dns_catz_zonemodmethods_t *zmm;
+ isc_taskmgr_t *taskmgr;
+ isc_timermgr_t *timermgr;
+ dns_view_t *view;
+ isc_task_t *updater;
+ atomic_bool shuttingdown;
+};
+
+void
+dns_catz_options_init(dns_catz_options_t *options) {
+ REQUIRE(options != NULL);
+
+ dns_ipkeylist_init(&options->masters);
+
+ options->allow_query = NULL;
+ options->allow_transfer = NULL;
+
+ options->allow_query = NULL;
+ options->allow_transfer = NULL;
+
+ options->in_memory = false;
+ options->min_update_interval = 5;
+ options->zonedir = NULL;
+}
+
+void
+dns_catz_options_free(dns_catz_options_t *options, isc_mem_t *mctx) {
+ REQUIRE(options != NULL);
+ REQUIRE(mctx != NULL);
+
+ if (options->masters.count != 0) {
+ dns_ipkeylist_clear(mctx, &options->masters);
+ }
+ if (options->zonedir != NULL) {
+ isc_mem_free(mctx, options->zonedir);
+ options->zonedir = NULL;
+ }
+ if (options->allow_query != NULL) {
+ isc_buffer_free(&options->allow_query);
+ }
+ if (options->allow_transfer != NULL) {
+ isc_buffer_free(&options->allow_transfer);
+ }
+}
+
+void
+dns_catz_options_copy(isc_mem_t *mctx, const dns_catz_options_t *src,
+ dns_catz_options_t *dst) {
+ REQUIRE(mctx != NULL);
+ REQUIRE(src != NULL);
+ REQUIRE(dst != NULL);
+ REQUIRE(dst->masters.count == 0);
+ REQUIRE(dst->allow_query == NULL);
+ REQUIRE(dst->allow_transfer == NULL);
+
+ if (src->masters.count != 0) {
+ dns_ipkeylist_copy(mctx, &src->masters, &dst->masters);
+ }
+
+ if (dst->zonedir != NULL) {
+ isc_mem_free(mctx, dst->zonedir);
+ dst->zonedir = NULL;
+ }
+
+ if (src->zonedir != NULL) {
+ dst->zonedir = isc_mem_strdup(mctx, src->zonedir);
+ }
+
+ if (src->allow_query != NULL) {
+ isc_buffer_dup(mctx, &dst->allow_query, src->allow_query);
+ }
+
+ if (src->allow_transfer != NULL) {
+ isc_buffer_dup(mctx, &dst->allow_transfer, src->allow_transfer);
+ }
+}
+
+void
+dns_catz_options_setdefault(isc_mem_t *mctx, const dns_catz_options_t *defaults,
+ dns_catz_options_t *opts) {
+ REQUIRE(mctx != NULL);
+ REQUIRE(defaults != NULL);
+ REQUIRE(opts != NULL);
+
+ if (opts->masters.count == 0 && defaults->masters.count != 0) {
+ dns_ipkeylist_copy(mctx, &defaults->masters, &opts->masters);
+ }
+
+ if (defaults->zonedir != NULL) {
+ opts->zonedir = isc_mem_strdup(mctx, defaults->zonedir);
+ }
+
+ if (opts->allow_query == NULL && defaults->allow_query != NULL) {
+ isc_buffer_dup(mctx, &opts->allow_query, defaults->allow_query);
+ }
+ if (opts->allow_transfer == NULL && defaults->allow_transfer != NULL) {
+ isc_buffer_dup(mctx, &opts->allow_transfer,
+ defaults->allow_transfer);
+ }
+
+ /* This option is always taken from config, so it's always 'default' */
+ opts->in_memory = defaults->in_memory;
+}
+
+static void
+catz_coo_new(isc_mem_t *mctx, const dns_name_t *domain,
+ dns_catz_coo_t **ncoop) {
+ dns_catz_coo_t *ncoo;
+
+ REQUIRE(mctx != NULL);
+ REQUIRE(domain != NULL);
+ REQUIRE(ncoop != NULL && *ncoop == NULL);
+
+ ncoo = isc_mem_get(mctx, sizeof(*ncoo));
+ dns_name_init(&ncoo->name, NULL);
+ dns_name_dup(domain, mctx, &ncoo->name);
+ isc_refcount_init(&ncoo->references, 1);
+ ncoo->magic = DNS_CATZ_COO_MAGIC;
+ *ncoop = ncoo;
+}
+
+static void
+catz_coo_detach(dns_catz_zone_t *catz, dns_catz_coo_t **coop) {
+ dns_catz_coo_t *coo;
+
+ REQUIRE(DNS_CATZ_ZONE_VALID(catz));
+ REQUIRE(coop != NULL && DNS_CATZ_COO_VALID(*coop));
+ coo = *coop;
+ *coop = NULL;
+
+ if (isc_refcount_decrement(&coo->references) == 1) {
+ isc_mem_t *mctx = catz->catzs->mctx;
+ coo->magic = 0;
+ isc_refcount_destroy(&coo->references);
+ if (dns_name_dynamic(&coo->name)) {
+ dns_name_free(&coo->name, mctx);
+ }
+ isc_mem_put(mctx, coo, sizeof(*coo));
+ }
+}
+
+void
+dns_catz_entry_new(isc_mem_t *mctx, const dns_name_t *domain,
+ dns_catz_entry_t **nentryp) {
+ dns_catz_entry_t *nentry;
+
+ REQUIRE(mctx != NULL);
+ REQUIRE(nentryp != NULL && *nentryp == NULL);
+
+ nentry = isc_mem_get(mctx, sizeof(*nentry));
+
+ dns_name_init(&nentry->name, NULL);
+ if (domain != NULL) {
+ dns_name_dup(domain, mctx, &nentry->name);
+ }
+
+ dns_catz_options_init(&nentry->opts);
+ isc_refcount_init(&nentry->references, 1);
+ nentry->magic = DNS_CATZ_ENTRY_MAGIC;
+ *nentryp = nentry;
+}
+
+dns_name_t *
+dns_catz_entry_getname(dns_catz_entry_t *entry) {
+ REQUIRE(DNS_CATZ_ENTRY_VALID(entry));
+ return (&entry->name);
+}
+
+void
+dns_catz_entry_copy(dns_catz_zone_t *catz, const dns_catz_entry_t *entry,
+ dns_catz_entry_t **nentryp) {
+ dns_catz_entry_t *nentry = NULL;
+
+ REQUIRE(DNS_CATZ_ZONE_VALID(catz));
+ REQUIRE(DNS_CATZ_ENTRY_VALID(entry));
+ REQUIRE(nentryp != NULL && *nentryp == NULL);
+
+ dns_catz_entry_new(catz->catzs->mctx, &entry->name, &nentry);
+
+ dns_catz_options_copy(catz->catzs->mctx, &entry->opts, &nentry->opts);
+ *nentryp = nentry;
+}
+
+void
+dns_catz_entry_attach(dns_catz_entry_t *entry, dns_catz_entry_t **entryp) {
+ REQUIRE(DNS_CATZ_ENTRY_VALID(entry));
+ REQUIRE(entryp != NULL && *entryp == NULL);
+
+ isc_refcount_increment(&entry->references);
+ *entryp = entry;
+}
+
+void
+dns_catz_entry_detach(dns_catz_zone_t *catz, dns_catz_entry_t **entryp) {
+ dns_catz_entry_t *entry;
+
+ REQUIRE(DNS_CATZ_ZONE_VALID(catz));
+ REQUIRE(entryp != NULL && DNS_CATZ_ENTRY_VALID(*entryp));
+ entry = *entryp;
+ *entryp = NULL;
+
+ if (isc_refcount_decrement(&entry->references) == 1) {
+ isc_mem_t *mctx = catz->catzs->mctx;
+ entry->magic = 0;
+ isc_refcount_destroy(&entry->references);
+ dns_catz_options_free(&entry->opts, mctx);
+ if (dns_name_dynamic(&entry->name)) {
+ dns_name_free(&entry->name, mctx);
+ }
+ isc_mem_put(mctx, entry, sizeof(*entry));
+ }
+}
+
+bool
+dns_catz_entry_validate(const dns_catz_entry_t *entry) {
+ REQUIRE(DNS_CATZ_ENTRY_VALID(entry));
+ UNUSED(entry);
+
+ return (true);
+}
+
+bool
+dns_catz_entry_cmp(const dns_catz_entry_t *ea, const dns_catz_entry_t *eb) {
+ isc_region_t ra, rb;
+
+ REQUIRE(DNS_CATZ_ENTRY_VALID(ea));
+ REQUIRE(DNS_CATZ_ENTRY_VALID(eb));
+
+ if (ea == eb) {
+ return (true);
+ }
+
+ if (ea->opts.masters.count != eb->opts.masters.count) {
+ return (false);
+ }
+
+ if (memcmp(ea->opts.masters.addrs, eb->opts.masters.addrs,
+ ea->opts.masters.count * sizeof(isc_sockaddr_t)))
+ {
+ return (false);
+ }
+
+ for (size_t i = 0; i < eb->opts.masters.count; i++) {
+ if ((ea->opts.masters.keys[i] == NULL) !=
+ (eb->opts.masters.keys[i] == NULL))
+ {
+ return (false);
+ }
+ if (ea->opts.masters.keys[i] == NULL) {
+ continue;
+ }
+ if (!dns_name_equal(ea->opts.masters.keys[i],
+ eb->opts.masters.keys[i]))
+ {
+ return (false);
+ }
+ }
+
+ for (size_t i = 0; i < eb->opts.masters.count; i++) {
+ if ((ea->opts.masters.tlss[i] == NULL) !=
+ (eb->opts.masters.tlss[i] == NULL))
+ {
+ return (false);
+ }
+ if (ea->opts.masters.tlss[i] == NULL) {
+ continue;
+ }
+ if (!dns_name_equal(ea->opts.masters.tlss[i],
+ eb->opts.masters.tlss[i]))
+ {
+ return (false);
+ }
+ }
+
+ /* If one is NULL and the other isn't, the entries don't match */
+ if ((ea->opts.allow_query == NULL) != (eb->opts.allow_query == NULL)) {
+ return (false);
+ }
+
+ /* If one is non-NULL, then they both are */
+ if (ea->opts.allow_query != NULL) {
+ isc_buffer_usedregion(ea->opts.allow_query, &ra);
+ isc_buffer_usedregion(eb->opts.allow_query, &rb);
+ if (isc_region_compare(&ra, &rb)) {
+ return (false);
+ }
+ }
+
+ /* Repeat the above checks with allow_transfer */
+ if ((ea->opts.allow_transfer == NULL) !=
+ (eb->opts.allow_transfer == NULL))
+ {
+ return (false);
+ }
+
+ if (ea->opts.allow_transfer != NULL) {
+ isc_buffer_usedregion(ea->opts.allow_transfer, &ra);
+ isc_buffer_usedregion(eb->opts.allow_transfer, &rb);
+ if (isc_region_compare(&ra, &rb)) {
+ return (false);
+ }
+ }
+
+ return (true);
+}
+
+dns_name_t *
+dns_catz_zone_getname(dns_catz_zone_t *catz) {
+ REQUIRE(DNS_CATZ_ZONE_VALID(catz));
+
+ return (&catz->name);
+}
+
+dns_catz_options_t *
+dns_catz_zone_getdefoptions(dns_catz_zone_t *catz) {
+ REQUIRE(DNS_CATZ_ZONE_VALID(catz));
+
+ return (&catz->defoptions);
+}
+
+void
+dns_catz_zone_resetdefoptions(dns_catz_zone_t *catz) {
+ REQUIRE(DNS_CATZ_ZONE_VALID(catz));
+
+ dns_catz_options_free(&catz->defoptions, catz->catzs->mctx);
+ dns_catz_options_init(&catz->defoptions);
+}
+
+/*%<
+ * Merge 'newcatz' into 'catz', calling addzone/delzone/modzone
+ * (from catz->catzs->zmm) for appropriate member zones.
+ *
+ * Requires:
+ * \li 'catz' is a valid dns_catz_zone_t.
+ * \li 'newcatz' is a valid dns_catz_zone_t.
+ *
+ */
+static isc_result_t
+dns__catz_zones_merge(dns_catz_zone_t *catz, dns_catz_zone_t *newcatz) {
+ isc_result_t result;
+ isc_ht_iter_t *iter1 = NULL, *iter2 = NULL;
+ isc_ht_iter_t *iteradd = NULL, *itermod = NULL;
+ isc_ht_t *toadd = NULL, *tomod = NULL;
+ bool delcur = false;
+ char czname[DNS_NAME_FORMATSIZE];
+ char zname[DNS_NAME_FORMATSIZE];
+ dns_catz_zoneop_fn_t addzone, modzone, delzone;
+
+ REQUIRE(DNS_CATZ_ZONE_VALID(catz));
+ REQUIRE(DNS_CATZ_ZONE_VALID(newcatz));
+
+ LOCK(&catz->lock);
+
+ /* TODO verify the new zone first! */
+
+ addzone = catz->catzs->zmm->addzone;
+ modzone = catz->catzs->zmm->modzone;
+ delzone = catz->catzs->zmm->delzone;
+
+ /* Copy zoneoptions from newcatz into catz. */
+
+ dns_catz_options_free(&catz->zoneoptions, catz->catzs->mctx);
+ dns_catz_options_copy(catz->catzs->mctx, &newcatz->zoneoptions,
+ &catz->zoneoptions);
+ dns_catz_options_setdefault(catz->catzs->mctx, &catz->defoptions,
+ &catz->zoneoptions);
+
+ dns_name_format(&catz->name, czname, DNS_NAME_FORMATSIZE);
+
+ isc_ht_init(&toadd, catz->catzs->mctx, 16, ISC_HT_CASE_SENSITIVE);
+ isc_ht_init(&tomod, catz->catzs->mctx, 16, ISC_HT_CASE_SENSITIVE);
+ isc_ht_iter_create(newcatz->entries, &iter1);
+ isc_ht_iter_create(catz->entries, &iter2);
+
+ /*
+ * We can create those iterators now, even though toadd and tomod are
+ * empty
+ */
+ isc_ht_iter_create(toadd, &iteradd);
+ isc_ht_iter_create(tomod, &itermod);
+
+ /*
+ * First - walk the new zone and find all nodes that are not in the
+ * old zone, or are in both zones and are modified.
+ */
+ for (result = isc_ht_iter_first(iter1); result == ISC_R_SUCCESS;
+ result = delcur ? isc_ht_iter_delcurrent_next(iter1)
+ : isc_ht_iter_next(iter1))
+ {
+ isc_result_t zt_find_result;
+ dns_catz_zone_t *parentcatz = NULL;
+ dns_catz_entry_t *nentry = NULL;
+ dns_catz_entry_t *oentry = NULL;
+ dns_zone_t *zone = NULL;
+ unsigned char *key = NULL;
+ size_t keysize;
+ delcur = false;
+
+ isc_ht_iter_current(iter1, (void **)&nentry);
+ isc_ht_iter_currentkey(iter1, &key, &keysize);
+
+ /*
+ * Spurious record that came from suboption without main
+ * record, removed.
+ * xxxwpk: make it a separate verification phase?
+ */
+ if (dns_name_countlabels(&nentry->name) == 0) {
+ dns_catz_entry_detach(newcatz, &nentry);
+ delcur = true;
+ continue;
+ }
+
+ dns_name_format(&nentry->name, zname, DNS_NAME_FORMATSIZE);
+
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL,
+ DNS_LOGMODULE_MASTER, ISC_LOG_DEBUG(3),
+ "catz: iterating over '%s' from catalog '%s'",
+ zname, czname);
+ dns_catz_options_setdefault(catz->catzs->mctx,
+ &catz->zoneoptions, &nentry->opts);
+
+ /* Try to find the zone in the view */
+ zt_find_result = dns_zt_find(catz->catzs->view->zonetable,
+ dns_catz_entry_getname(nentry), 0,
+ NULL, &zone);
+ if (zt_find_result == ISC_R_SUCCESS) {
+ dns_catz_coo_t *coo = NULL;
+ char pczname[DNS_NAME_FORMATSIZE];
+ bool parentcatz_locked = false;
+
+ /*
+ * Change of ownership (coo) processing, if required
+ */
+ parentcatz = dns_zone_get_parentcatz(zone);
+ if (parentcatz != NULL && parentcatz != catz) {
+ UNLOCK(&catz->lock);
+ LOCK(&parentcatz->lock);
+ parentcatz_locked = true;
+ }
+ if (parentcatz_locked &&
+ isc_ht_find(parentcatz->coos, nentry->name.ndata,
+ nentry->name.length,
+ (void **)&coo) == ISC_R_SUCCESS &&
+ dns_name_equal(&coo->name, &catz->name))
+ {
+ dns_name_format(&parentcatz->name, pczname,
+ DNS_NAME_FORMATSIZE);
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL,
+ DNS_LOGMODULE_MASTER,
+ ISC_LOG_DEBUG(3),
+ "catz: zone '%s' "
+ "change of ownership from "
+ "'%s' to '%s'",
+ zname, pczname, czname);
+ result = delzone(nentry, parentcatz,
+ parentcatz->catzs->view,
+ parentcatz->catzs->taskmgr,
+ parentcatz->catzs->zmm->udata);
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL,
+ DNS_LOGMODULE_MASTER,
+ ISC_LOG_INFO,
+ "catz: deleting zone '%s' "
+ "from catalog '%s' - %s",
+ zname, pczname,
+ isc_result_totext(result));
+ }
+ if (parentcatz_locked) {
+ UNLOCK(&parentcatz->lock);
+ LOCK(&catz->lock);
+ }
+ }
+ if (zt_find_result == ISC_R_SUCCESS ||
+ zt_find_result == DNS_R_PARTIALMATCH)
+ {
+ dns_zone_detach(&zone);
+ }
+
+ /* Try to find the zone in the old catalog zone */
+ result = isc_ht_find(catz->entries, key, (uint32_t)keysize,
+ (void **)&oentry);
+ if (result != ISC_R_SUCCESS) {
+ if (zt_find_result == ISC_R_SUCCESS &&
+ parentcatz == catz)
+ {
+ /*
+ * This means that the zone's unique label
+ * has been changed, in that case we must
+ * reset the zone's internal state by removing
+ * and re-adding it.
+ *
+ * Scheduling the addition now, the removal will
+ * be scheduled below, when walking the old
+ * zone for remaining entries, and then we will
+ * perform deletions earlier than additions and
+ * modifications.
+ */
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL,
+ DNS_LOGMODULE_MASTER,
+ ISC_LOG_INFO,
+ "catz: zone '%s' unique label "
+ "has changed, reset state",
+ zname);
+ }
+
+ catz_entry_add_or_mod(catz, toadd, key, keysize, nentry,
+ NULL, "adding", zname, czname);
+ continue;
+ }
+
+ if (zt_find_result != ISC_R_SUCCESS) {
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL,
+ DNS_LOGMODULE_MASTER, ISC_LOG_DEBUG(3),
+ "catz: zone '%s' was expected to exist "
+ "but can not be found, will be restored",
+ zname);
+ catz_entry_add_or_mod(catz, toadd, key, keysize, nentry,
+ oentry, "adding", zname, czname);
+ continue;
+ }
+
+ if (dns_catz_entry_cmp(oentry, nentry) != true) {
+ catz_entry_add_or_mod(catz, tomod, key, keysize, nentry,
+ oentry, "modifying", zname,
+ czname);
+ continue;
+ }
+
+ /*
+ * Delete the old entry so that it won't accidentally be
+ * removed as a non-existing entry below.
+ */
+ dns_catz_entry_detach(catz, &oentry);
+ result = isc_ht_delete(catz->entries, key, (uint32_t)keysize);
+ RUNTIME_CHECK(result == ISC_R_SUCCESS);
+ }
+ RUNTIME_CHECK(result == ISC_R_NOMORE);
+ isc_ht_iter_destroy(&iter1);
+
+ /*
+ * Then - walk the old zone; only deleted entries should remain.
+ */
+ for (result = isc_ht_iter_first(iter2); result == ISC_R_SUCCESS;
+ result = isc_ht_iter_delcurrent_next(iter2))
+ {
+ dns_catz_entry_t *entry = NULL;
+ isc_ht_iter_current(iter2, (void **)&entry);
+
+ dns_name_format(&entry->name, zname, DNS_NAME_FORMATSIZE);
+ result = delzone(entry, catz, catz->catzs->view,
+ catz->catzs->taskmgr, catz->catzs->zmm->udata);
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL,
+ DNS_LOGMODULE_MASTER, ISC_LOG_INFO,
+ "catz: deleting zone '%s' from catalog '%s' - %s",
+ zname, czname, isc_result_totext(result));
+ dns_catz_entry_detach(catz, &entry);
+ }
+ RUNTIME_CHECK(result == ISC_R_NOMORE);
+ isc_ht_iter_destroy(&iter2);
+ /* At this moment catz->entries has to be be empty. */
+ INSIST(isc_ht_count(catz->entries) == 0);
+ isc_ht_destroy(&catz->entries);
+
+ for (result = isc_ht_iter_first(iteradd); result == ISC_R_SUCCESS;
+ result = isc_ht_iter_delcurrent_next(iteradd))
+ {
+ dns_catz_entry_t *entry = NULL;
+ isc_ht_iter_current(iteradd, (void **)&entry);
+
+ dns_name_format(&entry->name, zname, DNS_NAME_FORMATSIZE);
+ result = addzone(entry, catz, catz->catzs->view,
+ catz->catzs->taskmgr, catz->catzs->zmm->udata);
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL,
+ DNS_LOGMODULE_MASTER, ISC_LOG_INFO,
+ "catz: adding zone '%s' from catalog "
+ "'%s' - %s",
+ zname, czname, isc_result_totext(result));
+ }
+
+ for (result = isc_ht_iter_first(itermod); result == ISC_R_SUCCESS;
+ result = isc_ht_iter_delcurrent_next(itermod))
+ {
+ dns_catz_entry_t *entry = NULL;
+ isc_ht_iter_current(itermod, (void **)&entry);
+
+ dns_name_format(&entry->name, zname, DNS_NAME_FORMATSIZE);
+ result = modzone(entry, catz, catz->catzs->view,
+ catz->catzs->taskmgr, catz->catzs->zmm->udata);
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL,
+ DNS_LOGMODULE_MASTER, ISC_LOG_INFO,
+ "catz: modifying zone '%s' from catalog "
+ "'%s' - %s",
+ zname, czname, isc_result_totext(result));
+ }
+
+ catz->entries = newcatz->entries;
+ newcatz->entries = NULL;
+
+ /*
+ * We do not need to merge old coo (change of ownership) permission
+ * records with the new ones, just replace them.
+ */
+ if (catz->coos != NULL && newcatz->coos != NULL) {
+ isc_ht_iter_t *iter = NULL;
+
+ isc_ht_iter_create(catz->coos, &iter);
+ for (result = isc_ht_iter_first(iter); result == ISC_R_SUCCESS;
+ result = isc_ht_iter_delcurrent_next(iter))
+ {
+ dns_catz_coo_t *coo = NULL;
+
+ isc_ht_iter_current(iter, (void **)&coo);
+ catz_coo_detach(catz, &coo);
+ }
+ INSIST(result == ISC_R_NOMORE);
+ isc_ht_iter_destroy(&iter);
+
+ /* The hashtable has to be empty now. */
+ INSIST(isc_ht_count(catz->coos) == 0);
+ isc_ht_destroy(&catz->coos);
+
+ catz->coos = newcatz->coos;
+ newcatz->coos = NULL;
+ }
+
+ result = ISC_R_SUCCESS;
+
+ isc_ht_iter_destroy(&iteradd);
+ isc_ht_iter_destroy(&itermod);
+ isc_ht_destroy(&toadd);
+ isc_ht_destroy(&tomod);
+
+ UNLOCK(&catz->lock);
+
+ return (result);
+}
+
+isc_result_t
+dns_catz_new_zones(isc_mem_t *mctx, isc_taskmgr_t *taskmgr,
+ isc_timermgr_t *timermgr, dns_catz_zones_t **catzsp,
+ dns_catz_zonemodmethods_t *zmm) {
+ isc_result_t result;
+ dns_catz_zones_t *catzs = NULL;
+
+ REQUIRE(mctx != NULL);
+ REQUIRE(taskmgr != NULL);
+ REQUIRE(timermgr != NULL);
+ REQUIRE(catzsp != NULL && *catzsp == NULL);
+ REQUIRE(zmm != NULL);
+
+ catzs = isc_mem_get(mctx, sizeof(*catzs));
+ *catzs = (dns_catz_zones_t){ .taskmgr = taskmgr,
+ .timermgr = timermgr,
+ .zmm = zmm,
+ .magic = DNS_CATZ_ZONES_MAGIC };
+
+ result = isc_taskmgr_excltask(taskmgr, &catzs->updater);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup_task;
+ }
+
+ isc_mutex_init(&catzs->lock);
+ isc_refcount_init(&catzs->references, 1);
+ isc_ht_init(&catzs->zones, mctx, 4, ISC_HT_CASE_SENSITIVE);
+ isc_mem_attach(mctx, &catzs->mctx);
+
+ *catzsp = catzs;
+
+ return (ISC_R_SUCCESS);
+
+cleanup_task:
+ isc_mem_put(mctx, catzs, sizeof(*catzs));
+
+ return (result);
+}
+
+void
+dns_catz_catzs_set_view(dns_catz_zones_t *catzs, dns_view_t *view) {
+ REQUIRE(DNS_CATZ_ZONES_VALID(catzs));
+ REQUIRE(DNS_VIEW_VALID(view));
+ /* Either it's a new one or it's being reconfigured. */
+ REQUIRE(catzs->view == NULL || !strcmp(catzs->view->name, view->name));
+
+ catzs->view = view;
+}
+
+isc_result_t
+dns_catz_new_zone(dns_catz_zones_t *catzs, dns_catz_zone_t **catzp,
+ const dns_name_t *name) {
+ isc_result_t result;
+ dns_catz_zone_t *catz = NULL;
+
+ REQUIRE(DNS_CATZ_ZONES_VALID(catzs));
+ REQUIRE(catzp != NULL && *catzp == NULL);
+ REQUIRE(ISC_MAGIC_VALID(name, DNS_NAME_MAGIC));
+
+ catz = isc_mem_get(catzs->mctx, sizeof(*catz));
+ *catz = (dns_catz_zone_t){ .active = true,
+ .version = DNS_CATZ_VERSION_UNDEFINED,
+ .magic = DNS_CATZ_ZONE_MAGIC };
+
+ result = isc_timer_create(catzs->timermgr, isc_timertype_inactive, NULL,
+ NULL, catzs->updater, dns__catz_timer_cb,
+ catz, &catz->updatetimer);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup_timer;
+ }
+
+ dns_catz_zones_attach(catzs, &catz->catzs);
+ isc_mutex_init(&catz->lock);
+ isc_refcount_init(&catz->references, 1);
+ isc_ht_init(&catz->entries, catzs->mctx, 4, ISC_HT_CASE_SENSITIVE);
+ isc_ht_init(&catz->coos, catzs->mctx, 4, ISC_HT_CASE_INSENSITIVE);
+ isc_time_settoepoch(&catz->lastupdated);
+ dns_catz_options_init(&catz->defoptions);
+ dns_catz_options_init(&catz->zoneoptions);
+ dns_name_init(&catz->name, NULL);
+ dns_name_dup(name, catzs->mctx, &catz->name);
+
+ *catzp = catz;
+
+ return (ISC_R_SUCCESS);
+
+cleanup_timer:
+ isc_mem_put(catzs->mctx, catz, sizeof(*catz));
+
+ return (result);
+}
+
+isc_result_t
+dns_catz_add_zone(dns_catz_zones_t *catzs, const dns_name_t *name,
+ dns_catz_zone_t **catzp) {
+ dns_catz_zone_t *catz = NULL;
+ isc_result_t result, tresult;
+ char zname[DNS_NAME_FORMATSIZE];
+
+ REQUIRE(DNS_CATZ_ZONES_VALID(catzs));
+ REQUIRE(ISC_MAGIC_VALID(name, DNS_NAME_MAGIC));
+ REQUIRE(catzp != NULL && *catzp == NULL);
+
+ dns_name_format(name, zname, DNS_NAME_FORMATSIZE);
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL, DNS_LOGMODULE_MASTER,
+ ISC_LOG_DEBUG(3), "catz: dns_catz_add_zone %s", zname);
+
+ LOCK(&catzs->lock);
+
+ result = dns_catz_new_zone(catzs, &catz, name);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+
+ result = isc_ht_add(catzs->zones, catz->name.ndata, catz->name.length,
+ catz);
+ if (result != ISC_R_SUCCESS) {
+ dns_catz_detach_catz(&catz);
+ if (result != ISC_R_EXISTS) {
+ goto cleanup;
+ }
+ }
+
+ if (result == ISC_R_EXISTS) {
+ tresult = isc_ht_find(catzs->zones, name->ndata, name->length,
+ (void **)&catz);
+ INSIST(tresult == ISC_R_SUCCESS && !catz->active);
+ catz->active = true;
+ }
+
+ *catzp = catz;
+
+cleanup:
+ UNLOCK(&catzs->lock);
+
+ return (result);
+}
+
+dns_catz_zone_t *
+dns_catz_get_zone(dns_catz_zones_t *catzs, const dns_name_t *name) {
+ isc_result_t result;
+ dns_catz_zone_t *found = NULL;
+
+ REQUIRE(DNS_CATZ_ZONES_VALID(catzs));
+ REQUIRE(ISC_MAGIC_VALID(name, DNS_NAME_MAGIC));
+
+ LOCK(&catzs->lock);
+ result = isc_ht_find(catzs->zones, name->ndata, name->length,
+ (void **)&found);
+ UNLOCK(&catzs->lock);
+ if (result != ISC_R_SUCCESS) {
+ return (NULL);
+ }
+
+ return (found);
+}
+
+static void
+dns__catz_shutdown(dns_catz_zone_t *catz) {
+ /* lock must be locked */
+ if (catz->updatetimer != NULL) {
+ isc_result_t result;
+
+ /* Don't wait for timer to trigger for shutdown */
+ result = isc_timer_reset(catz->updatetimer,
+ isc_timertype_inactive, NULL, NULL,
+ true);
+ RUNTIME_CHECK(result == ISC_R_SUCCESS);
+ }
+
+ dns_catz_detach_catz(&catz);
+}
+
+static void
+dns__catz_zone_destroy(dns_catz_zone_t *catz) {
+ isc_mem_t *mctx = catz->catzs->mctx;
+
+ if (catz->entries != NULL) {
+ isc_ht_iter_t *iter = NULL;
+ isc_result_t result;
+ isc_ht_iter_create(catz->entries, &iter);
+ for (result = isc_ht_iter_first(iter); result == ISC_R_SUCCESS;
+ result = isc_ht_iter_delcurrent_next(iter))
+ {
+ dns_catz_entry_t *entry = NULL;
+
+ isc_ht_iter_current(iter, (void **)&entry);
+ dns_catz_entry_detach(catz, &entry);
+ }
+ INSIST(result == ISC_R_NOMORE);
+ isc_ht_iter_destroy(&iter);
+
+ /* The hashtable has to be empty now. */
+ INSIST(isc_ht_count(catz->entries) == 0);
+ isc_ht_destroy(&catz->entries);
+ }
+ if (catz->coos != NULL) {
+ isc_ht_iter_t *iter = NULL;
+ isc_result_t result;
+ isc_ht_iter_create(catz->coos, &iter);
+ for (result = isc_ht_iter_first(iter); result == ISC_R_SUCCESS;
+ result = isc_ht_iter_delcurrent_next(iter))
+ {
+ dns_catz_coo_t *coo = NULL;
+
+ isc_ht_iter_current(iter, (void **)&coo);
+ catz_coo_detach(catz, &coo);
+ }
+ INSIST(result == ISC_R_NOMORE);
+ isc_ht_iter_destroy(&iter);
+
+ /* The hashtable has to be empty now. */
+ INSIST(isc_ht_count(catz->coos) == 0);
+ isc_ht_destroy(&catz->coos);
+ }
+ catz->magic = 0;
+
+ isc_mutex_destroy(&catz->lock);
+ isc_timer_destroy(&catz->updatetimer);
+ if (catz->db_registered) {
+ dns_db_updatenotify_unregister(
+ catz->db, dns_catz_dbupdate_callback, catz->catzs);
+ }
+ if (catz->dbversion != NULL) {
+ dns_db_closeversion(catz->db, &catz->dbversion, false);
+ }
+ if (catz->db != NULL) {
+ dns_db_detach(&catz->db);
+ }
+
+ INSIST(!catz->updaterunning);
+
+ dns_name_free(&catz->name, mctx);
+ dns_catz_options_free(&catz->defoptions, mctx);
+ dns_catz_options_free(&catz->zoneoptions, mctx);
+
+ dns_catz_zones_detach(&catz->catzs);
+ isc_refcount_destroy(&catz->references);
+
+ isc_mem_put(mctx, catz, sizeof(*catz));
+}
+
+static void
+dns__catz_zones_destroy(dns_catz_zones_t *catzs) {
+ REQUIRE(atomic_load(&catzs->shuttingdown));
+ REQUIRE(catzs->zones == NULL);
+
+ catzs->magic = 0;
+ isc_task_detach(&catzs->updater);
+ isc_mutex_destroy(&catzs->lock);
+ isc_refcount_destroy(&catzs->references);
+
+ isc_mem_putanddetach(&catzs->mctx, catzs, sizeof(*catzs));
+}
+
+void
+dns_catz_shutdown_catzs(dns_catz_zones_t *catzs) {
+ REQUIRE(DNS_CATZ_ZONES_VALID(catzs));
+
+ if (!atomic_compare_exchange_strong(&catzs->shuttingdown,
+ &(bool){ false }, true))
+ {
+ return;
+ }
+
+ LOCK(&catzs->lock);
+ if (catzs->zones != NULL) {
+ isc_ht_iter_t *iter = NULL;
+ isc_result_t result;
+ isc_ht_iter_create(catzs->zones, &iter);
+ for (result = isc_ht_iter_first(iter); result == ISC_R_SUCCESS;)
+ {
+ dns_catz_zone_t *catz = NULL;
+ isc_ht_iter_current(iter, (void **)&catz);
+ result = isc_ht_iter_delcurrent_next(iter);
+ dns__catz_shutdown(catz);
+ }
+ INSIST(result == ISC_R_NOMORE);
+ isc_ht_iter_destroy(&iter);
+ INSIST(isc_ht_count(catzs->zones) == 0);
+ isc_ht_destroy(&catzs->zones);
+ }
+ UNLOCK(&catzs->lock);
+}
+
+#ifdef DNS_CATZ_TRACE
+ISC_REFCOUNT_TRACE_IMPL(dns_catz_zone, dns__catz_zone_destroy);
+ISC_REFCOUNT_TRACE_IMPL(dns_catz_zones, dns__catz_zones_destroy);
+#else
+ISC_REFCOUNT_IMPL(dns_catz_zone, dns__catz_zone_destroy);
+ISC_REFCOUNT_IMPL(dns_catz_zones, dns__catz_zones_destroy);
+#endif
+
+typedef enum {
+ CATZ_OPT_NONE,
+ CATZ_OPT_ZONES,
+ CATZ_OPT_COO,
+ CATZ_OPT_VERSION,
+ CATZ_OPT_CUSTOM_START, /* CATZ custom properties must go below this */
+ CATZ_OPT_EXT,
+ CATZ_OPT_PRIMARIES,
+ CATZ_OPT_ALLOW_QUERY,
+ CATZ_OPT_ALLOW_TRANSFER,
+} catz_opt_t;
+
+static bool
+catz_opt_cmp(const dns_label_t *option, const char *opt) {
+ size_t len = strlen(opt);
+
+ if (option->length - 1 == len &&
+ memcmp(opt, option->base + 1, len) == 0)
+ {
+ return (true);
+ } else {
+ return (false);
+ }
+}
+
+static catz_opt_t
+catz_get_option(const dns_label_t *option) {
+ if (catz_opt_cmp(option, "ext")) {
+ return (CATZ_OPT_EXT);
+ } else if (catz_opt_cmp(option, "zones")) {
+ return (CATZ_OPT_ZONES);
+ } else if (catz_opt_cmp(option, "masters") ||
+ catz_opt_cmp(option, "primaries"))
+ {
+ return (CATZ_OPT_PRIMARIES);
+ } else if (catz_opt_cmp(option, "allow-query")) {
+ return (CATZ_OPT_ALLOW_QUERY);
+ } else if (catz_opt_cmp(option, "allow-transfer")) {
+ return (CATZ_OPT_ALLOW_TRANSFER);
+ } else if (catz_opt_cmp(option, "coo")) {
+ return (CATZ_OPT_COO);
+ } else if (catz_opt_cmp(option, "version")) {
+ return (CATZ_OPT_VERSION);
+ } else {
+ return (CATZ_OPT_NONE);
+ }
+}
+
+static isc_result_t
+catz_process_zones(dns_catz_zone_t *catz, dns_rdataset_t *value,
+ dns_name_t *name) {
+ dns_label_t mhash;
+ dns_name_t opt;
+
+ REQUIRE(DNS_CATZ_ZONE_VALID(catz));
+ REQUIRE(DNS_RDATASET_VALID(value));
+ REQUIRE(ISC_MAGIC_VALID(name, DNS_NAME_MAGIC));
+
+ if (name->labels == 0) {
+ return (ISC_R_FAILURE);
+ }
+
+ dns_name_getlabel(name, name->labels - 1, &mhash);
+
+ if (name->labels == 1) {
+ return (catz_process_zones_entry(catz, value, &mhash));
+ } else {
+ dns_name_init(&opt, NULL);
+ dns_name_split(name, 1, &opt, NULL);
+ return (catz_process_zones_suboption(catz, value, &mhash,
+ &opt));
+ }
+}
+
+static isc_result_t
+catz_process_coo(dns_catz_zone_t *catz, dns_label_t *mhash,
+ dns_rdataset_t *value) {
+ isc_result_t result;
+ dns_rdata_t rdata;
+ dns_rdata_ptr_t ptr;
+ dns_catz_entry_t *entry = NULL;
+ dns_catz_coo_t *ncoo = NULL;
+ dns_catz_coo_t *ocoo = NULL;
+
+ REQUIRE(DNS_CATZ_ZONE_VALID(catz));
+ REQUIRE(mhash != NULL);
+ REQUIRE(DNS_RDATASET_VALID(value));
+
+ /* Change of Ownership was introduced in version "2" of the schema. */
+ if (catz->version < 2) {
+ return (ISC_R_FAILURE);
+ }
+
+ if (value->type != dns_rdatatype_ptr) {
+ return (ISC_R_FAILURE);
+ }
+
+ if (dns_rdataset_count(value) != 1) {
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL,
+ DNS_LOGMODULE_MASTER, ISC_LOG_WARNING,
+ "catz: 'coo' property PTR RRset contains "
+ "more than one record, which is invalid");
+ catz->broken = true;
+ return (ISC_R_FAILURE);
+ }
+
+ result = dns_rdataset_first(value);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+
+ dns_rdata_init(&rdata);
+ dns_rdataset_current(value, &rdata);
+
+ result = dns_rdata_tostruct(&rdata, &ptr, NULL);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+
+ if (dns_name_countlabels(&ptr.ptr) == 0) {
+ result = ISC_R_FAILURE;
+ goto cleanup;
+ }
+
+ result = isc_ht_find(catz->entries, mhash->base, mhash->length,
+ (void **)&entry);
+ if (result != ISC_R_SUCCESS) {
+ /* The entry was not found .*/
+ goto cleanup;
+ }
+
+ if (dns_name_countlabels(&entry->name) == 0) {
+ result = ISC_R_FAILURE;
+ goto cleanup;
+ }
+
+ result = isc_ht_find(catz->coos, entry->name.ndata, entry->name.length,
+ (void **)&ocoo);
+ if (result == ISC_R_SUCCESS) {
+ /* The change of ownership permission was already registered. */
+ goto cleanup;
+ }
+
+ catz_coo_new(catz->catzs->mctx, &ptr.ptr, &ncoo);
+ result = isc_ht_add(catz->coos, entry->name.ndata, entry->name.length,
+ ncoo);
+ if (result != ISC_R_SUCCESS) {
+ catz_coo_detach(catz, &ncoo);
+ }
+
+cleanup:
+ dns_rdata_freestruct(&ptr);
+
+ return (result);
+}
+
+static isc_result_t
+catz_process_zones_entry(dns_catz_zone_t *catz, dns_rdataset_t *value,
+ dns_label_t *mhash) {
+ isc_result_t result;
+ dns_rdata_t rdata;
+ dns_rdata_ptr_t ptr;
+ dns_catz_entry_t *entry = NULL;
+
+ if (value->type != dns_rdatatype_ptr) {
+ return (ISC_R_FAILURE);
+ }
+
+ if (dns_rdataset_count(value) != 1) {
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL,
+ DNS_LOGMODULE_MASTER, ISC_LOG_WARNING,
+ "catz: member zone PTR RRset contains "
+ "more than one record, which is invalid");
+ catz->broken = true;
+ return (ISC_R_FAILURE);
+ }
+
+ result = dns_rdataset_first(value);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+
+ dns_rdata_init(&rdata);
+ dns_rdataset_current(value, &rdata);
+
+ result = dns_rdata_tostruct(&rdata, &ptr, NULL);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+
+ result = isc_ht_find(catz->entries, mhash->base, mhash->length,
+ (void **)&entry);
+ if (result == ISC_R_SUCCESS) {
+ if (dns_name_countlabels(&entry->name) != 0) {
+ /* We have a duplicate. */
+ dns_rdata_freestruct(&ptr);
+ return (ISC_R_FAILURE);
+ } else {
+ dns_name_dup(&ptr.ptr, catz->catzs->mctx, &entry->name);
+ }
+ } else {
+ dns_catz_entry_new(catz->catzs->mctx, &ptr.ptr, &entry);
+
+ result = isc_ht_add(catz->entries, mhash->base, mhash->length,
+ entry);
+ if (result != ISC_R_SUCCESS) {
+ dns_rdata_freestruct(&ptr);
+ dns_catz_entry_detach(catz, &entry);
+ return (result);
+ }
+ }
+
+ dns_rdata_freestruct(&ptr);
+
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+catz_process_version(dns_catz_zone_t *catz, dns_rdataset_t *value) {
+ isc_result_t result;
+ dns_rdata_t rdata;
+ dns_rdata_txt_t rdatatxt;
+ dns_rdata_txt_string_t rdatastr;
+ uint32_t tversion;
+ char t[16];
+
+ REQUIRE(DNS_CATZ_ZONE_VALID(catz));
+ REQUIRE(DNS_RDATASET_VALID(value));
+
+ if (value->type != dns_rdatatype_txt) {
+ return (ISC_R_FAILURE);
+ }
+
+ if (dns_rdataset_count(value) != 1) {
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL,
+ DNS_LOGMODULE_MASTER, ISC_LOG_WARNING,
+ "catz: 'version' property TXT RRset contains "
+ "more than one record, which is invalid");
+ catz->broken = true;
+ return (ISC_R_FAILURE);
+ }
+
+ result = dns_rdataset_first(value);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+
+ dns_rdata_init(&rdata);
+ dns_rdataset_current(value, &rdata);
+
+ result = dns_rdata_tostruct(&rdata, &rdatatxt, NULL);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+
+ result = dns_rdata_txt_first(&rdatatxt);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+
+ result = dns_rdata_txt_current(&rdatatxt, &rdatastr);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+
+ result = dns_rdata_txt_next(&rdatatxt);
+ if (result != ISC_R_NOMORE) {
+ result = ISC_R_FAILURE;
+ goto cleanup;
+ }
+ if (rdatastr.length > 15) {
+ result = ISC_R_BADNUMBER;
+ goto cleanup;
+ }
+ memmove(t, rdatastr.data, rdatastr.length);
+ t[rdatastr.length] = 0;
+ result = isc_parse_uint32(&tversion, t, 10);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+ catz->version = tversion;
+ result = ISC_R_SUCCESS;
+
+cleanup:
+ dns_rdata_freestruct(&rdatatxt);
+ if (result != ISC_R_SUCCESS) {
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL,
+ DNS_LOGMODULE_MASTER, ISC_LOG_WARNING,
+ "catz: invalid record for the catalog "
+ "zone version property");
+ catz->broken = true;
+ }
+ return (result);
+}
+
+static isc_result_t
+catz_process_primaries(dns_catz_zone_t *catz, dns_ipkeylist_t *ipkl,
+ dns_rdataset_t *value, dns_name_t *name) {
+ isc_result_t result;
+ dns_rdata_t rdata;
+ dns_rdata_in_a_t rdata_a;
+ dns_rdata_in_aaaa_t rdata_aaaa;
+ dns_rdata_txt_t rdata_txt;
+ dns_rdata_txt_string_t rdatastr;
+ dns_name_t *keyname = NULL;
+ isc_mem_t *mctx;
+ char keycbuf[DNS_NAME_FORMATSIZE];
+ isc_buffer_t keybuf;
+ unsigned int rcount;
+
+ REQUIRE(DNS_CATZ_ZONE_VALID(catz));
+ REQUIRE(ipkl != NULL);
+ REQUIRE(DNS_RDATASET_VALID(value));
+ REQUIRE(dns_rdataset_isassociated(value));
+ REQUIRE(ISC_MAGIC_VALID(name, DNS_NAME_MAGIC));
+
+ mctx = catz->catzs->mctx;
+ memset(&rdata_a, 0, sizeof(rdata_a));
+ memset(&rdata_aaaa, 0, sizeof(rdata_aaaa));
+ memset(&rdata_txt, 0, sizeof(rdata_txt));
+ isc_buffer_init(&keybuf, keycbuf, sizeof(keycbuf));
+
+ /*
+ * We have three possibilities here:
+ * - either empty name and IN A/IN AAAA record
+ * - label and IN A/IN AAAA
+ * - label and IN TXT - TSIG key name
+ */
+ if (name->labels > 0) {
+ isc_sockaddr_t sockaddr;
+ size_t i;
+
+ /*
+ * We're pre-preparing the data once, we'll put it into
+ * the right spot in the primaries array once we find it.
+ */
+ result = dns_rdataset_first(value);
+ RUNTIME_CHECK(result == ISC_R_SUCCESS);
+ dns_rdata_init(&rdata);
+ dns_rdataset_current(value, &rdata);
+ switch (value->type) {
+ case dns_rdatatype_a:
+ result = dns_rdata_tostruct(&rdata, &rdata_a, NULL);
+ RUNTIME_CHECK(result == ISC_R_SUCCESS);
+ isc_sockaddr_fromin(&sockaddr, &rdata_a.in_addr, 0);
+ dns_rdata_freestruct(&rdata_a);
+ break;
+ case dns_rdatatype_aaaa:
+ result = dns_rdata_tostruct(&rdata, &rdata_aaaa, NULL);
+ RUNTIME_CHECK(result == ISC_R_SUCCESS);
+ isc_sockaddr_fromin6(&sockaddr, &rdata_aaaa.in6_addr,
+ 0);
+ dns_rdata_freestruct(&rdata_aaaa);
+ break;
+ case dns_rdatatype_txt:
+ result = dns_rdata_tostruct(&rdata, &rdata_txt, NULL);
+ RUNTIME_CHECK(result == ISC_R_SUCCESS);
+
+ result = dns_rdata_txt_first(&rdata_txt);
+ if (result != ISC_R_SUCCESS) {
+ dns_rdata_freestruct(&rdata_txt);
+ return (result);
+ }
+
+ result = dns_rdata_txt_current(&rdata_txt, &rdatastr);
+ if (result != ISC_R_SUCCESS) {
+ dns_rdata_freestruct(&rdata_txt);
+ return (result);
+ }
+
+ result = dns_rdata_txt_next(&rdata_txt);
+ if (result != ISC_R_NOMORE) {
+ dns_rdata_freestruct(&rdata_txt);
+ return (ISC_R_FAILURE);
+ }
+
+ /* rdatastr.length < DNS_NAME_MAXTEXT */
+ keyname = isc_mem_get(mctx, sizeof(*keyname));
+ dns_name_init(keyname, 0);
+ memmove(keycbuf, rdatastr.data, rdatastr.length);
+ keycbuf[rdatastr.length] = 0;
+ dns_rdata_freestruct(&rdata_txt);
+ result = dns_name_fromstring(keyname, keycbuf, 0, mctx);
+ if (result != ISC_R_SUCCESS) {
+ dns_name_free(keyname, mctx);
+ isc_mem_put(mctx, keyname, sizeof(*keyname));
+ return (result);
+ }
+ break;
+ default:
+ return (ISC_R_FAILURE);
+ }
+
+ /*
+ * We have to find the appropriate labeled record in
+ * primaries if it exists. In the common case we'll
+ * have no more than 3-4 records here, so no optimization.
+ */
+ for (i = 0; i < ipkl->count; i++) {
+ if (ipkl->labels[i] != NULL &&
+ !dns_name_compare(name, ipkl->labels[i]))
+ {
+ break;
+ }
+ }
+
+ if (i < ipkl->count) { /* we have this record already */
+ if (value->type == dns_rdatatype_txt) {
+ ipkl->keys[i] = keyname;
+ } else { /* A/AAAA */
+ memmove(&ipkl->addrs[i], &sockaddr,
+ sizeof(sockaddr));
+ }
+ } else {
+ result = dns_ipkeylist_resize(mctx, ipkl, i + 1);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+
+ ipkl->labels[i] = isc_mem_get(mctx,
+ sizeof(*ipkl->labels[0]));
+ dns_name_init(ipkl->labels[i], NULL);
+ dns_name_dup(name, mctx, ipkl->labels[i]);
+
+ if (value->type == dns_rdatatype_txt) {
+ ipkl->keys[i] = keyname;
+ } else { /* A/AAAA */
+ memmove(&ipkl->addrs[i], &sockaddr,
+ sizeof(sockaddr));
+ }
+ ipkl->count++;
+ }
+ return (ISC_R_SUCCESS);
+ }
+ /* else - 'simple' case - without labels */
+
+ if (value->type != dns_rdatatype_a && value->type != dns_rdatatype_aaaa)
+ {
+ return (ISC_R_FAILURE);
+ }
+
+ rcount = dns_rdataset_count(value) + ipkl->count;
+
+ result = dns_ipkeylist_resize(mctx, ipkl, rcount);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+
+ for (result = dns_rdataset_first(value); result == ISC_R_SUCCESS;
+ result = dns_rdataset_next(value))
+ {
+ dns_rdata_init(&rdata);
+ dns_rdataset_current(value, &rdata);
+ /*
+ * port 0 == take the default
+ */
+ if (value->type == dns_rdatatype_a) {
+ result = dns_rdata_tostruct(&rdata, &rdata_a, NULL);
+ RUNTIME_CHECK(result == ISC_R_SUCCESS);
+ isc_sockaddr_fromin(&ipkl->addrs[ipkl->count],
+ &rdata_a.in_addr, 0);
+ dns_rdata_freestruct(&rdata_a);
+ } else {
+ result = dns_rdata_tostruct(&rdata, &rdata_aaaa, NULL);
+ RUNTIME_CHECK(result == ISC_R_SUCCESS);
+ isc_sockaddr_fromin6(&ipkl->addrs[ipkl->count],
+ &rdata_aaaa.in6_addr, 0);
+ dns_rdata_freestruct(&rdata_aaaa);
+ }
+ ipkl->keys[ipkl->count] = NULL;
+ ipkl->labels[ipkl->count] = NULL;
+ ipkl->count++;
+ }
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+catz_process_apl(dns_catz_zone_t *catz, isc_buffer_t **aclbp,
+ dns_rdataset_t *value) {
+ isc_result_t result = ISC_R_SUCCESS;
+ dns_rdata_t rdata;
+ dns_rdata_in_apl_t rdata_apl;
+ dns_rdata_apl_ent_t apl_ent;
+ isc_netaddr_t addr;
+ isc_buffer_t *aclb = NULL;
+ unsigned char buf[256]; /* larger than INET6_ADDRSTRLEN */
+
+ REQUIRE(DNS_CATZ_ZONE_VALID(catz));
+ REQUIRE(aclbp != NULL);
+ REQUIRE(*aclbp == NULL);
+ REQUIRE(DNS_RDATASET_VALID(value));
+ REQUIRE(dns_rdataset_isassociated(value));
+
+ if (value->type != dns_rdatatype_apl) {
+ return (ISC_R_FAILURE);
+ }
+
+ if (dns_rdataset_count(value) > 1) {
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL,
+ DNS_LOGMODULE_MASTER, ISC_LOG_WARNING,
+ "catz: more than one APL entry for member zone, "
+ "result is undefined");
+ }
+ result = dns_rdataset_first(value);
+ RUNTIME_CHECK(result == ISC_R_SUCCESS);
+ dns_rdata_init(&rdata);
+ dns_rdataset_current(value, &rdata);
+ result = dns_rdata_tostruct(&rdata, &rdata_apl, catz->catzs->mctx);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+ isc_buffer_allocate(catz->catzs->mctx, &aclb, 16);
+ isc_buffer_setautorealloc(aclb, true);
+ for (result = dns_rdata_apl_first(&rdata_apl); result == ISC_R_SUCCESS;
+ result = dns_rdata_apl_next(&rdata_apl))
+ {
+ result = dns_rdata_apl_current(&rdata_apl, &apl_ent);
+ RUNTIME_CHECK(result == ISC_R_SUCCESS);
+ memset(buf, 0, sizeof(buf));
+ if (apl_ent.data != NULL && apl_ent.length > 0) {
+ memmove(buf, apl_ent.data, apl_ent.length);
+ }
+ if (apl_ent.family == 1) {
+ isc_netaddr_fromin(&addr, (struct in_addr *)buf);
+ } else if (apl_ent.family == 2) {
+ isc_netaddr_fromin6(&addr, (struct in6_addr *)buf);
+ } else {
+ continue; /* xxxwpk log it or simply ignore? */
+ }
+ if (apl_ent.negative) {
+ isc_buffer_putuint8(aclb, '!');
+ }
+ isc_buffer_reserve(&aclb, INET6_ADDRSTRLEN);
+ result = isc_netaddr_totext(&addr, aclb);
+ RUNTIME_CHECK(result == ISC_R_SUCCESS);
+ if ((apl_ent.family == 1 && apl_ent.prefix < 32) ||
+ (apl_ent.family == 2 && apl_ent.prefix < 128))
+ {
+ isc_buffer_putuint8(aclb, '/');
+ isc_buffer_putdecint(aclb, apl_ent.prefix);
+ }
+ isc_buffer_putstr(aclb, "; ");
+ }
+ if (result == ISC_R_NOMORE) {
+ result = ISC_R_SUCCESS;
+ } else {
+ goto cleanup;
+ }
+ *aclbp = aclb;
+ aclb = NULL;
+cleanup:
+ if (aclb != NULL) {
+ isc_buffer_free(&aclb);
+ }
+ dns_rdata_freestruct(&rdata_apl);
+ return (result);
+}
+
+static isc_result_t
+catz_process_zones_suboption(dns_catz_zone_t *catz, dns_rdataset_t *value,
+ dns_label_t *mhash, dns_name_t *name) {
+ isc_result_t result;
+ dns_catz_entry_t *entry = NULL;
+ dns_label_t option;
+ dns_name_t prefix;
+ catz_opt_t opt;
+ unsigned int suffix_labels = 1;
+
+ REQUIRE(DNS_CATZ_ZONE_VALID(catz));
+ REQUIRE(mhash != NULL);
+ REQUIRE(DNS_RDATASET_VALID(value));
+ REQUIRE(ISC_MAGIC_VALID(name, DNS_NAME_MAGIC));
+
+ if (name->labels < 1) {
+ return (ISC_R_FAILURE);
+ }
+ dns_name_getlabel(name, name->labels - 1, &option);
+ opt = catz_get_option(&option);
+
+ /*
+ * The custom properties in version 2 schema must be placed under the
+ * "ext" label.
+ */
+ if (catz->version >= 2 && opt >= CATZ_OPT_CUSTOM_START) {
+ if (opt != CATZ_OPT_EXT || name->labels < 2) {
+ return (ISC_R_FAILURE);
+ }
+ suffix_labels++;
+ dns_name_getlabel(name, name->labels - 2, &option);
+ opt = catz_get_option(&option);
+ }
+
+ /*
+ * We're adding this entry now, in case the option is invalid we'll get
+ * rid of it in verification phase.
+ */
+ result = isc_ht_find(catz->entries, mhash->base, mhash->length,
+ (void **)&entry);
+ if (result != ISC_R_SUCCESS) {
+ dns_catz_entry_new(catz->catzs->mctx, NULL, &entry);
+ result = isc_ht_add(catz->entries, mhash->base, mhash->length,
+ entry);
+ if (result != ISC_R_SUCCESS) {
+ dns_catz_entry_detach(catz, &entry);
+ return (result);
+ }
+ }
+
+ dns_name_init(&prefix, NULL);
+ dns_name_split(name, suffix_labels, &prefix, NULL);
+ switch (opt) {
+ case CATZ_OPT_COO:
+ return (catz_process_coo(catz, mhash, value));
+ case CATZ_OPT_PRIMARIES:
+ return (catz_process_primaries(catz, &entry->opts.masters,
+ value, &prefix));
+ case CATZ_OPT_ALLOW_QUERY:
+ if (prefix.labels != 0) {
+ return (ISC_R_FAILURE);
+ }
+ return (catz_process_apl(catz, &entry->opts.allow_query,
+ value));
+ case CATZ_OPT_ALLOW_TRANSFER:
+ if (prefix.labels != 0) {
+ return (ISC_R_FAILURE);
+ }
+ return (catz_process_apl(catz, &entry->opts.allow_transfer,
+ value));
+ default:
+ return (ISC_R_FAILURE);
+ }
+
+ return (ISC_R_FAILURE);
+}
+
+static void
+catz_entry_add_or_mod(dns_catz_zone_t *catz, isc_ht_t *ht, unsigned char *key,
+ size_t keysize, dns_catz_entry_t *nentry,
+ dns_catz_entry_t *oentry, const char *msg,
+ const char *zname, const char *czname) {
+ isc_result_t result = isc_ht_add(ht, key, (uint32_t)keysize, nentry);
+
+ if (result != ISC_R_SUCCESS) {
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL,
+ DNS_LOGMODULE_MASTER, ISC_LOG_ERROR,
+ "catz: error %s zone '%s' from catalog '%s' - %s",
+ msg, zname, czname, isc_result_totext(result));
+ }
+ if (oentry != NULL) {
+ dns_catz_entry_detach(catz, &oentry);
+ result = isc_ht_delete(catz->entries, key, (uint32_t)keysize);
+ RUNTIME_CHECK(result == ISC_R_SUCCESS);
+ }
+}
+
+static isc_result_t
+catz_process_value(dns_catz_zone_t *catz, dns_name_t *name,
+ dns_rdataset_t *rdataset) {
+ dns_label_t option;
+ dns_name_t prefix;
+ catz_opt_t opt;
+ unsigned int suffix_labels = 1;
+
+ REQUIRE(DNS_CATZ_ZONE_VALID(catz));
+ REQUIRE(ISC_MAGIC_VALID(name, DNS_NAME_MAGIC));
+ REQUIRE(DNS_RDATASET_VALID(rdataset));
+
+ if (name->labels < 1) {
+ return (ISC_R_FAILURE);
+ }
+ dns_name_getlabel(name, name->labels - 1, &option);
+ opt = catz_get_option(&option);
+
+ /*
+ * The custom properties in version 2 schema must be placed under the
+ * "ext" label.
+ */
+ if (catz->version >= 2 && opt >= CATZ_OPT_CUSTOM_START) {
+ if (opt != CATZ_OPT_EXT || name->labels < 2) {
+ return (ISC_R_FAILURE);
+ }
+ suffix_labels++;
+ dns_name_getlabel(name, name->labels - 2, &option);
+ opt = catz_get_option(&option);
+ }
+
+ dns_name_init(&prefix, NULL);
+ dns_name_split(name, suffix_labels, &prefix, NULL);
+
+ switch (opt) {
+ case CATZ_OPT_ZONES:
+ return (catz_process_zones(catz, rdataset, &prefix));
+ case CATZ_OPT_PRIMARIES:
+ return (catz_process_primaries(catz, &catz->zoneoptions.masters,
+ rdataset, &prefix));
+ case CATZ_OPT_ALLOW_QUERY:
+ if (prefix.labels != 0) {
+ return (ISC_R_FAILURE);
+ }
+ return (catz_process_apl(catz, &catz->zoneoptions.allow_query,
+ rdataset));
+ case CATZ_OPT_ALLOW_TRANSFER:
+ if (prefix.labels != 0) {
+ return (ISC_R_FAILURE);
+ }
+ return (catz_process_apl(
+ catz, &catz->zoneoptions.allow_transfer, rdataset));
+ case CATZ_OPT_VERSION:
+ if (prefix.labels != 0) {
+ return (ISC_R_FAILURE);
+ }
+ return (catz_process_version(catz, rdataset));
+ default:
+ return (ISC_R_FAILURE);
+ }
+}
+
+/*%<
+ * Process a single rdataset from a catalog zone 'catz' update, src_name is the
+ * record name.
+ *
+ * Requires:
+ * \li 'catz' is a valid dns_catz_zone_t.
+ * \li 'src_name' is a valid dns_name_t.
+ * \li 'rdataset' is valid rdataset.
+ */
+static isc_result_t
+dns__catz_update_process(dns_catz_zone_t *catz, const dns_name_t *src_name,
+ dns_rdataset_t *rdataset) {
+ isc_result_t result;
+ int order;
+ unsigned int nlabels;
+ dns_namereln_t nrres;
+ dns_rdata_t rdata = DNS_RDATA_INIT;
+ dns_rdata_soa_t soa;
+ dns_name_t prefix;
+
+ REQUIRE(DNS_CATZ_ZONE_VALID(catz));
+ REQUIRE(ISC_MAGIC_VALID(src_name, DNS_NAME_MAGIC));
+
+ if (rdataset->rdclass != dns_rdataclass_in) {
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL,
+ DNS_LOGMODULE_MASTER, ISC_LOG_ERROR,
+ "catz: RR found which has a non-IN class");
+ catz->broken = true;
+ return (ISC_R_FAILURE);
+ }
+
+ nrres = dns_name_fullcompare(src_name, &catz->name, &order, &nlabels);
+ if (nrres == dns_namereln_equal) {
+ if (rdataset->type == dns_rdatatype_soa) {
+ result = dns_rdataset_first(rdataset);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+
+ dns_rdataset_current(rdataset, &rdata);
+ result = dns_rdata_tostruct(&rdata, &soa, NULL);
+ RUNTIME_CHECK(result == ISC_R_SUCCESS);
+
+ /*
+ * xxxwpk TODO do we want to save something from SOA?
+ */
+ dns_rdata_freestruct(&soa);
+ return (result);
+ } else if (rdataset->type == dns_rdatatype_ns) {
+ return (ISC_R_SUCCESS);
+ } else {
+ return (ISC_R_UNEXPECTED);
+ }
+ } else if (nrres != dns_namereln_subdomain) {
+ return (ISC_R_UNEXPECTED);
+ }
+
+ dns_name_init(&prefix, NULL);
+ dns_name_split(src_name, catz->name.labels, &prefix, NULL);
+ result = catz_process_value(catz, &prefix, rdataset);
+
+ return (result);
+}
+
+static isc_result_t
+digest2hex(unsigned char *digest, unsigned int digestlen, char *hash,
+ size_t hashlen) {
+ unsigned int i;
+ for (i = 0; i < digestlen; i++) {
+ size_t left = hashlen - i * 2;
+ int ret = snprintf(hash + i * 2, left, "%02x", digest[i]);
+ if (ret < 0 || (size_t)ret >= left) {
+ return (ISC_R_NOSPACE);
+ }
+ }
+ return (ISC_R_SUCCESS);
+}
+
+isc_result_t
+dns_catz_generate_masterfilename(dns_catz_zone_t *catz, dns_catz_entry_t *entry,
+ isc_buffer_t **buffer) {
+ isc_buffer_t *tbuf = NULL;
+ isc_region_t r;
+ isc_result_t result;
+ size_t rlen;
+ bool special = false;
+
+ REQUIRE(DNS_CATZ_ZONE_VALID(catz));
+ REQUIRE(DNS_CATZ_ENTRY_VALID(entry));
+ REQUIRE(buffer != NULL && *buffer != NULL);
+
+ isc_buffer_allocate(catz->catzs->mctx, &tbuf,
+ strlen(catz->catzs->view->name) +
+ 2 * DNS_NAME_FORMATSIZE + 2);
+
+ isc_buffer_putstr(tbuf, catz->catzs->view->name);
+ isc_buffer_putstr(tbuf, "_");
+ result = dns_name_totext(&catz->name, true, tbuf);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+
+ isc_buffer_putstr(tbuf, "_");
+ result = dns_name_totext(&entry->name, true, tbuf);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+
+ /*
+ * Search for slash and other special characters in the view and
+ * zone names. Add a null terminator so we can use strpbrk(), then
+ * remove it.
+ */
+ isc_buffer_putuint8(tbuf, 0);
+ if (strpbrk(isc_buffer_base(tbuf), "\\/:") != NULL) {
+ special = true;
+ }
+ isc_buffer_subtract(tbuf, 1);
+
+ /* __catz__<digest>.db */
+ rlen = (isc_md_type_get_size(ISC_MD_SHA256) * 2 + 1) + 12;
+
+ /* optionally prepend with <zonedir>/ */
+ if (entry->opts.zonedir != NULL) {
+ rlen += strlen(entry->opts.zonedir) + 1;
+ }
+
+ result = isc_buffer_reserve(buffer, (unsigned int)rlen);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+
+ if (entry->opts.zonedir != NULL) {
+ isc_buffer_putstr(*buffer, entry->opts.zonedir);
+ isc_buffer_putstr(*buffer, "/");
+ }
+
+ isc_buffer_usedregion(tbuf, &r);
+ isc_buffer_putstr(*buffer, "__catz__");
+ if (special || tbuf->used > ISC_SHA256_DIGESTLENGTH * 2 + 1) {
+ unsigned char digest[ISC_MAX_MD_SIZE];
+ unsigned int digestlen;
+
+ /* we can do that because digest string < 2 * DNS_NAME */
+ result = isc_md(ISC_MD_SHA256, r.base, r.length, digest,
+ &digestlen);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+ result = digest2hex(digest, digestlen, (char *)r.base,
+ ISC_SHA256_DIGESTLENGTH * 2 + 1);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+ isc_buffer_putstr(*buffer, (char *)r.base);
+ } else {
+ isc_buffer_copyregion(*buffer, &r);
+ }
+
+ isc_buffer_putstr(*buffer, ".db");
+ result = ISC_R_SUCCESS;
+
+cleanup:
+ isc_buffer_free(&tbuf);
+ return (result);
+}
+
+/*
+ * We have to generate a text buffer with regular zone config:
+ * zone "foo.bar" {
+ * type secondary;
+ * primaries { ip1 port port1; ip2 port port2; };
+ * }
+ */
+isc_result_t
+dns_catz_generate_zonecfg(dns_catz_zone_t *catz, dns_catz_entry_t *entry,
+ isc_buffer_t **buf) {
+ isc_buffer_t *buffer = NULL;
+ isc_region_t region;
+ isc_result_t result;
+ uint32_t i;
+ isc_netaddr_t netaddr;
+ char pbuf[sizeof("65535")]; /* used for port number */
+ char zname[DNS_NAME_FORMATSIZE];
+
+ REQUIRE(DNS_CATZ_ZONE_VALID(catz));
+ REQUIRE(DNS_CATZ_ENTRY_VALID(entry));
+ REQUIRE(buf != NULL && *buf == NULL);
+
+ /*
+ * The buffer will be reallocated if something won't fit,
+ * ISC_BUFFER_INCR seems like a good start.
+ */
+ isc_buffer_allocate(catz->catzs->mctx, &buffer, ISC_BUFFER_INCR);
+
+ isc_buffer_setautorealloc(buffer, true);
+ isc_buffer_putstr(buffer, "zone \"");
+ dns_name_totext(&entry->name, true, buffer);
+ isc_buffer_putstr(buffer, "\" { type secondary; primaries");
+
+ isc_buffer_putstr(buffer, " { ");
+ for (i = 0; i < entry->opts.masters.count; i++) {
+ /*
+ * Every primary must have an IP address assigned.
+ */
+ switch (entry->opts.masters.addrs[i].type.sa.sa_family) {
+ case AF_INET:
+ case AF_INET6:
+ break;
+ default:
+ dns_name_format(&entry->name, zname,
+ DNS_NAME_FORMATSIZE);
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL,
+ DNS_LOGMODULE_MASTER, ISC_LOG_ERROR,
+ "catz: zone '%s' uses an invalid primary "
+ "(no IP address assigned)",
+ zname);
+ result = ISC_R_FAILURE;
+ goto cleanup;
+ }
+ isc_netaddr_fromsockaddr(&netaddr,
+ &entry->opts.masters.addrs[i]);
+ isc_buffer_reserve(&buffer, INET6_ADDRSTRLEN);
+ result = isc_netaddr_totext(&netaddr, buffer);
+ RUNTIME_CHECK(result == ISC_R_SUCCESS);
+
+ isc_buffer_putstr(buffer, " port ");
+ snprintf(pbuf, sizeof(pbuf), "%u",
+ isc_sockaddr_getport(&entry->opts.masters.addrs[i]));
+ isc_buffer_putstr(buffer, pbuf);
+
+ if (entry->opts.masters.keys[i] != NULL) {
+ isc_buffer_putstr(buffer, " key ");
+ result = dns_name_totext(entry->opts.masters.keys[i],
+ true, buffer);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+ }
+
+ if (entry->opts.masters.tlss[i] != NULL) {
+ isc_buffer_putstr(buffer, " tls ");
+ result = dns_name_totext(entry->opts.masters.tlss[i],
+ true, buffer);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+ }
+ isc_buffer_putstr(buffer, "; ");
+ }
+ isc_buffer_putstr(buffer, "}; ");
+ if (!entry->opts.in_memory) {
+ isc_buffer_putstr(buffer, "file \"");
+ result = dns_catz_generate_masterfilename(catz, entry, &buffer);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+ isc_buffer_putstr(buffer, "\"; ");
+ }
+ if (entry->opts.allow_query != NULL) {
+ isc_buffer_putstr(buffer, "allow-query { ");
+ isc_buffer_usedregion(entry->opts.allow_query, &region);
+ isc_buffer_copyregion(buffer, &region);
+ isc_buffer_putstr(buffer, "}; ");
+ }
+ if (entry->opts.allow_transfer != NULL) {
+ isc_buffer_putstr(buffer, "allow-transfer { ");
+ isc_buffer_usedregion(entry->opts.allow_transfer, &region);
+ isc_buffer_copyregion(buffer, &region);
+ isc_buffer_putstr(buffer, "}; ");
+ }
+
+ isc_buffer_putstr(buffer, "};");
+ *buf = buffer;
+
+ return (ISC_R_SUCCESS);
+
+cleanup:
+ isc_buffer_free(&buffer);
+ return (result);
+}
+
+static void
+dns__catz_timer_cb(isc_task_t *task, isc_event_t *event) {
+ char domain[DNS_NAME_FORMATSIZE];
+ isc_result_t result;
+ dns_catz_zone_t *catz = NULL;
+
+ UNUSED(task);
+ REQUIRE(event != NULL);
+ REQUIRE(event->ev_arg != NULL);
+
+ catz = (dns_catz_zone_t *)event->ev_arg;
+ isc_event_free(&event);
+
+ REQUIRE(isc_nm_tid() >= 0);
+ REQUIRE(DNS_CATZ_ZONE_VALID(catz));
+
+ if (atomic_load(&catz->catzs->shuttingdown)) {
+ return;
+ }
+
+ LOCK(&catz->catzs->lock);
+
+ INSIST(DNS_DB_VALID(catz->db));
+ INSIST(catz->dbversion != NULL);
+ INSIST(catz->updb == NULL);
+ INSIST(catz->updbversion == NULL);
+
+ catz->updatepending = false;
+ catz->updaterunning = true;
+ catz->updateresult = ISC_R_UNSET;
+
+ dns_name_format(&catz->name, domain, DNS_NAME_FORMATSIZE);
+
+ if (!catz->active) {
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL,
+ DNS_LOGMODULE_MASTER, ISC_LOG_INFO,
+ "catz: %s: no longer active, reload is canceled",
+ domain);
+ catz->updaterunning = false;
+ catz->updateresult = ISC_R_CANCELED;
+ goto exit;
+ }
+
+ dns_db_attach(catz->db, &catz->updb);
+ catz->updbversion = catz->dbversion;
+ catz->dbversion = NULL;
+
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL, DNS_LOGMODULE_MASTER,
+ ISC_LOG_INFO, "catz: %s: reload start", domain);
+
+ dns_catz_ref_catz(catz);
+ isc_nm_work_offload(isc_task_getnetmgr(catz->catzs->updater),
+ dns__catz_update_cb, dns__catz_done_cb, catz);
+
+exit:
+ result = isc_time_now(&catz->lastupdated);
+ RUNTIME_CHECK(result == ISC_R_SUCCESS);
+
+ UNLOCK(&catz->catzs->lock);
+}
+
+isc_result_t
+dns_catz_dbupdate_callback(dns_db_t *db, void *fn_arg) {
+ dns_catz_zones_t *catzs = (dns_catz_zones_t *)fn_arg;
+ dns_catz_zone_t *catz = NULL;
+ isc_time_t now;
+ isc_result_t result = ISC_R_SUCCESS;
+ isc_region_t r;
+ char dname[DNS_NAME_FORMATSIZE];
+
+ REQUIRE(DNS_DB_VALID(db));
+ REQUIRE(DNS_CATZ_ZONES_VALID(catzs));
+
+ if (atomic_load(&catzs->shuttingdown)) {
+ return (ISC_R_SHUTTINGDOWN);
+ }
+
+ dns_name_toregion(&db->origin, &r);
+
+ LOCK(&catzs->lock);
+ if (catzs->zones == NULL) {
+ result = ISC_R_SHUTTINGDOWN;
+ goto cleanup;
+ }
+ result = isc_ht_find(catzs->zones, r.base, r.length, (void **)&catz);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+
+ /* New zone came as AXFR */
+ if (catz->db != NULL && catz->db != db) {
+ /* Old db cleanup. */
+ if (catz->dbversion != NULL) {
+ dns_db_closeversion(catz->db, &catz->dbversion, false);
+ }
+ dns_db_updatenotify_unregister(
+ catz->db, dns_catz_dbupdate_callback, catz->catzs);
+ dns_db_detach(&catz->db);
+ catz->db_registered = false;
+ }
+ if (catz->db == NULL) {
+ /* New db registration. */
+ dns_db_attach(db, &catz->db);
+ result = dns_db_updatenotify_register(
+ db, dns_catz_dbupdate_callback, catz->catzs);
+ if (result == ISC_R_SUCCESS) {
+ catz->db_registered = true;
+ }
+ }
+
+ dns_name_format(&catz->name, dname, DNS_NAME_FORMATSIZE);
+
+ if (!catz->updatepending && !catz->updaterunning) {
+ uint64_t tdiff;
+
+ catz->updatepending = true;
+
+ isc_time_now(&now);
+ tdiff = isc_time_microdiff(&now, &catz->lastupdated) / 1000000;
+ if (tdiff < catz->defoptions.min_update_interval) {
+ uint64_t defer = catz->defoptions.min_update_interval -
+ tdiff;
+ isc_interval_t interval;
+
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL,
+ DNS_LOGMODULE_MASTER, ISC_LOG_INFO,
+ "catz: %s: new zone version came "
+ "too soon, deferring update for "
+ "%" PRIu64 " seconds",
+ dname, defer);
+ isc_interval_set(&interval, (unsigned int)defer, 0);
+ dns_db_currentversion(db, &catz->dbversion);
+ result = isc_timer_reset(catz->updatetimer,
+ isc_timertype_once, NULL,
+ &interval, true);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+ } else {
+ isc_event_t *event;
+
+ dns_db_currentversion(db, &catz->dbversion);
+ ISC_EVENT_INIT(
+ &catz->updateevent, sizeof(catz->updateevent),
+ 0, NULL, DNS_EVENT_CATZUPDATED,
+ dns__catz_timer_cb, catz, catz, NULL, NULL);
+ event = &catz->updateevent;
+ isc_task_send(catzs->updater, &event);
+ }
+ } else {
+ catz->updatepending = true;
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL,
+ DNS_LOGMODULE_MASTER, ISC_LOG_DEBUG(3),
+ "catz: %s: update already queued or running",
+ dname);
+ if (catz->dbversion != NULL) {
+ dns_db_closeversion(catz->db, &catz->dbversion, false);
+ }
+ dns_db_currentversion(catz->db, &catz->dbversion);
+ }
+
+cleanup:
+ UNLOCK(&catzs->lock);
+
+ return (result);
+}
+
+void
+dns_catz_dbupdate_unregister(dns_db_t *db, dns_catz_zones_t *catzs) {
+ REQUIRE(DNS_DB_VALID(db));
+ REQUIRE(DNS_CATZ_ZONES_VALID(catzs));
+
+ dns_db_updatenotify_unregister(db, dns_catz_dbupdate_callback, catzs);
+}
+
+void
+dns_catz_dbupdate_register(dns_db_t *db, dns_catz_zones_t *catzs) {
+ REQUIRE(DNS_DB_VALID(db));
+ REQUIRE(DNS_CATZ_ZONES_VALID(catzs));
+
+ dns_db_updatenotify_register(db, dns_catz_dbupdate_callback, catzs);
+}
+
+static bool
+catz_rdatatype_is_processable(const dns_rdatatype_t type) {
+ return (!dns_rdatatype_isdnssec(type) && type != dns_rdatatype_cds &&
+ type != dns_rdatatype_cdnskey && type != dns_rdatatype_zonemd);
+}
+
+/*
+ * Process an updated database for a catalog zone.
+ * It creates a new catz, iterates over database to fill it with content, and
+ * then merges new catz into old catz.
+ */
+static void
+dns__catz_update_cb(void *data) {
+ dns_catz_zone_t *catz = (dns_catz_zone_t *)data;
+ dns_db_t *updb = NULL;
+ dns_catz_zones_t *catzs = NULL;
+ dns_catz_zone_t *oldcatz = NULL, *newcatz = NULL;
+ isc_result_t result;
+ isc_region_t r;
+ dns_dbnode_t *node = NULL;
+ const dns_dbnode_t *vers_node = NULL;
+ dns_dbiterator_t *updbit = NULL;
+ dns_fixedname_t fixname;
+ dns_name_t *name;
+ dns_rdatasetiter_t *rdsiter = NULL;
+ dns_rdataset_t rdataset;
+ char bname[DNS_NAME_FORMATSIZE];
+ char cname[DNS_NAME_FORMATSIZE];
+ bool is_vers_processed = false;
+ bool is_active;
+ uint32_t vers;
+ uint32_t catz_vers;
+
+ REQUIRE(DNS_CATZ_ZONE_VALID(catz));
+ REQUIRE(DNS_DB_VALID(catz->updb));
+ REQUIRE(DNS_CATZ_ZONES_VALID(catz->catzs));
+
+ updb = catz->updb;
+ catzs = catz->catzs;
+
+ if (atomic_load(&catzs->shuttingdown)) {
+ result = ISC_R_SHUTTINGDOWN;
+ goto exit;
+ }
+
+ dns_name_format(&updb->origin, bname, DNS_NAME_FORMATSIZE);
+
+ /*
+ * Create a new catz in the same context as current catz.
+ */
+ dns_name_toregion(&updb->origin, &r);
+ LOCK(&catzs->lock);
+ result = isc_ht_find(catzs->zones, r.base, r.length, (void **)&oldcatz);
+ is_active = (result == ISC_R_SUCCESS && oldcatz->active);
+ UNLOCK(&catzs->lock);
+ if (result != ISC_R_SUCCESS) {
+ /* This can happen if we remove the zone in the meantime. */
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL,
+ DNS_LOGMODULE_MASTER, ISC_LOG_ERROR,
+ "catz: zone '%s' not in config", bname);
+ goto exit;
+ }
+
+ INSIST(catz == oldcatz);
+
+ if (!is_active) {
+ /* This can happen during a reconfiguration. */
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL,
+ DNS_LOGMODULE_MASTER, ISC_LOG_INFO,
+ "catz: zone '%s' is no longer active", bname);
+ result = ISC_R_CANCELED;
+ goto exit;
+ }
+
+ result = dns_db_getsoaserial(updb, oldcatz->updbversion, &vers);
+ if (result != ISC_R_SUCCESS) {
+ /* A zone without SOA record?!? */
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL,
+ DNS_LOGMODULE_MASTER, ISC_LOG_ERROR,
+ "catz: zone '%s' has no SOA record (%s)", bname,
+ isc_result_totext(result));
+ goto exit;
+ }
+
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL, DNS_LOGMODULE_MASTER,
+ ISC_LOG_INFO,
+ "catz: updating catalog zone '%s' with serial %" PRIu32,
+ bname, vers);
+
+ result = dns_catz_new_zone(catzs, &newcatz, &updb->origin);
+ if (result != ISC_R_SUCCESS) {
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL,
+ DNS_LOGMODULE_MASTER, ISC_LOG_ERROR,
+ "catz: failed to create new zone - %s",
+ isc_result_totext(result));
+ goto exit;
+ }
+
+ result = dns_db_createiterator(updb, DNS_DB_NONSEC3, &updbit);
+ if (result != ISC_R_SUCCESS) {
+ dns_catz_detach_catz(&newcatz);
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL,
+ DNS_LOGMODULE_MASTER, ISC_LOG_ERROR,
+ "catz: failed to create DB iterator - %s",
+ isc_result_totext(result));
+ goto exit;
+ }
+
+ name = dns_fixedname_initname(&fixname);
+
+ /*
+ * Take the version record to process first, because the other
+ * records might be processed differently depending on the version of
+ * the catalog zone's schema.
+ */
+ result = dns_name_fromstring2(name, "version", &updb->origin, 0, NULL);
+ if (result != ISC_R_SUCCESS) {
+ dns_dbiterator_destroy(&updbit);
+ dns_catz_detach_catz(&newcatz);
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL,
+ DNS_LOGMODULE_MASTER, ISC_LOG_ERROR,
+ "catz: failed to create name from string - %s",
+ isc_result_totext(result));
+ goto exit;
+ }
+ result = dns_dbiterator_seek(updbit, name);
+ if (result != ISC_R_SUCCESS) {
+ dns_dbiterator_destroy(&updbit);
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL,
+ DNS_LOGMODULE_MASTER, ISC_LOG_ERROR,
+ "catz: zone '%s' has no 'version' record (%s)",
+ bname, isc_result_totext(result));
+ newcatz->broken = true;
+ goto final;
+ }
+
+ name = dns_fixedname_initname(&fixname);
+
+ /*
+ * Iterate over database to fill the new zone.
+ */
+ while (result == ISC_R_SUCCESS) {
+ if (atomic_load(&catzs->shuttingdown)) {
+ result = ISC_R_SHUTTINGDOWN;
+ break;
+ }
+
+ result = dns_dbiterator_current(updbit, &node, name);
+ if (result != ISC_R_SUCCESS) {
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL,
+ DNS_LOGMODULE_MASTER, ISC_LOG_ERROR,
+ "catz: failed to get db iterator - %s",
+ isc_result_totext(result));
+ break;
+ }
+
+ result = dns_dbiterator_pause(updbit);
+ RUNTIME_CHECK(result == ISC_R_SUCCESS);
+
+ if (!is_vers_processed) {
+ /* Keep the version node to skip it later in the loop */
+ vers_node = node;
+ } else if (node == vers_node) {
+ /* Skip the already processed version node */
+ dns_db_detachnode(updb, &node);
+ result = dns_dbiterator_next(updbit);
+ continue;
+ }
+
+ result = dns_db_allrdatasets(updb, node, oldcatz->updbversion,
+ 0, 0, &rdsiter);
+ if (result != ISC_R_SUCCESS) {
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL,
+ DNS_LOGMODULE_MASTER, ISC_LOG_ERROR,
+ "catz: failed to fetch rrdatasets - %s",
+ isc_result_totext(result));
+ dns_db_detachnode(updb, &node);
+ break;
+ }
+
+ dns_rdataset_init(&rdataset);
+ result = dns_rdatasetiter_first(rdsiter);
+ while (result == ISC_R_SUCCESS) {
+ dns_rdatasetiter_current(rdsiter, &rdataset);
+
+ /*
+ * Skip processing DNSSEC-related and ZONEMD types,
+ * because we are not interested in them in the context
+ * of a catalog zone, and processing them will fail
+ * and produce an unnecessary warning message.
+ */
+ if (!catz_rdatatype_is_processable(rdataset.type)) {
+ goto next;
+ }
+
+ /*
+ * Although newcatz->coos is accessed in
+ * catz_process_coo() in the call-chain below, we don't
+ * need to hold the newcatz->lock, because the newcatz
+ * is still local to this thread and function and
+ * newcatz->coos can't be accessed from the outside
+ * until dns__catz_zones_merge() has been called.
+ */
+ result = dns__catz_update_process(newcatz, name,
+ &rdataset);
+ if (result != ISC_R_SUCCESS) {
+ char typebuf[DNS_RDATATYPE_FORMATSIZE];
+ char classbuf[DNS_RDATACLASS_FORMATSIZE];
+
+ dns_name_format(name, cname,
+ DNS_NAME_FORMATSIZE);
+ dns_rdataclass_format(rdataset.rdclass,
+ classbuf,
+ sizeof(classbuf));
+ dns_rdatatype_format(rdataset.type, typebuf,
+ sizeof(typebuf));
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL,
+ DNS_LOGMODULE_MASTER,
+ ISC_LOG_WARNING,
+ "catz: invalid record in catalog "
+ "zone - %s %s %s (%s) - ignoring",
+ cname, classbuf, typebuf,
+ isc_result_totext(result));
+ }
+ next:
+ dns_rdataset_disassociate(&rdataset);
+ result = dns_rdatasetiter_next(rdsiter);
+ }
+
+ dns_rdatasetiter_destroy(&rdsiter);
+
+ dns_db_detachnode(updb, &node);
+
+ if (!is_vers_processed) {
+ is_vers_processed = true;
+ result = dns_dbiterator_first(updbit);
+ } else {
+ result = dns_dbiterator_next(updbit);
+ }
+ }
+
+ dns_dbiterator_destroy(&updbit);
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL, DNS_LOGMODULE_MASTER,
+ ISC_LOG_DEBUG(3),
+ "catz: update_from_db: iteration finished: %s",
+ isc_result_totext(result));
+
+ /*
+ * Check catalog zone version compatibilites.
+ */
+ catz_vers = (newcatz->version == DNS_CATZ_VERSION_UNDEFINED)
+ ? oldcatz->version
+ : newcatz->version;
+ if (catz_vers == DNS_CATZ_VERSION_UNDEFINED) {
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL,
+ DNS_LOGMODULE_MASTER, ISC_LOG_WARNING,
+ "catz: zone '%s' version is not set", bname);
+ newcatz->broken = true;
+ } else if (catz_vers != 1 && catz_vers != 2) {
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL,
+ DNS_LOGMODULE_MASTER, ISC_LOG_WARNING,
+ "catz: zone '%s' unsupported version "
+ "'%" PRIu32 "'",
+ bname, catz_vers);
+ newcatz->broken = true;
+ } else {
+ oldcatz->version = catz_vers;
+ }
+
+final:
+ if (newcatz->broken) {
+ dns_name_format(name, cname, DNS_NAME_FORMATSIZE);
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL,
+ DNS_LOGMODULE_MASTER, ISC_LOG_ERROR,
+ "catz: new catalog zone '%s' is broken and "
+ "will not be processed",
+ bname);
+ dns_catz_detach_catz(&newcatz);
+ result = ISC_R_FAILURE;
+ goto exit;
+ }
+
+ /*
+ * Finally merge new zone into old zone.
+ */
+ result = dns__catz_zones_merge(oldcatz, newcatz);
+ dns_catz_detach_catz(&newcatz);
+ if (result != ISC_R_SUCCESS) {
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL,
+ DNS_LOGMODULE_MASTER, ISC_LOG_ERROR,
+ "catz: failed merging zones: %s",
+ isc_result_totext(result));
+
+ goto exit;
+ }
+
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL, DNS_LOGMODULE_MASTER,
+ ISC_LOG_DEBUG(3),
+ "catz: update_from_db: new zone merged");
+
+exit:
+ catz->updateresult = result;
+}
+
+static void
+dns__catz_done_cb(void *data, isc_result_t result) {
+ dns_catz_zone_t *catz = (dns_catz_zone_t *)data;
+ char dname[DNS_NAME_FORMATSIZE];
+
+ REQUIRE(DNS_CATZ_ZONE_VALID(catz));
+
+ if (result == ISC_R_SUCCESS && catz->updateresult != ISC_R_SUCCESS) {
+ result = catz->updateresult;
+ }
+
+ LOCK(&catz->catzs->lock);
+ catz->updaterunning = false;
+
+ dns_name_format(&catz->name, dname, DNS_NAME_FORMATSIZE);
+
+ /*
+ * When we're doing reconfig and setting a new catalog zone
+ * from an existing zone we won't have a chance to set up
+ * update callback in zone_startload or axfr_makedb, but we will
+ * call onupdate() artificially so we can register the callback
+ * here.
+ */
+ if (result == ISC_R_SUCCESS && !catz->db_registered) {
+ result = dns_db_updatenotify_register(
+ catz->db, dns_catz_dbupdate_callback, catz->catzs);
+ if (result == ISC_R_SUCCESS) {
+ catz->db_registered = true;
+ }
+ }
+
+ /* If there's no update pending, or if shutting down, finish. */
+ if (!catz->updatepending || atomic_load(&catz->catzs->shuttingdown)) {
+ goto done;
+ }
+
+ /* If there's an update pending, schedule it */
+ if (catz->defoptions.min_update_interval > 0) {
+ uint64_t defer = catz->defoptions.min_update_interval;
+ isc_interval_t interval;
+
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL,
+ DNS_LOGMODULE_MASTER, ISC_LOG_INFO,
+ "catz: %s: new zone version came "
+ "too soon, deferring update for "
+ "%" PRIu64 " seconds",
+ dname, defer);
+ isc_interval_set(&interval, (unsigned int)defer, 0);
+ (void)isc_timer_reset(catz->updatetimer, isc_timertype_once,
+ NULL, &interval, true);
+ } else {
+ isc_event_t *event = NULL;
+ INSIST(!ISC_LINK_LINKED(&catz->updateevent, ev_link));
+ ISC_EVENT_INIT(&catz->updateevent, sizeof(catz->updateevent), 0,
+ NULL, DNS_EVENT_CATZUPDATED, dns__catz_timer_cb,
+ catz, catz, NULL, NULL);
+ event = &catz->updateevent;
+ isc_task_send(catz->catzs->updater, &event);
+ }
+
+done:
+ dns_db_closeversion(catz->updb, &catz->updbversion, false);
+ dns_db_detach(&catz->updb);
+
+ UNLOCK(&catz->catzs->lock);
+
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL, DNS_LOGMODULE_MASTER,
+ ISC_LOG_INFO, "catz: %s: reload done: %s", dname,
+ isc_result_totext(result));
+
+ dns_catz_unref_catz(catz);
+}
+
+void
+dns_catz_prereconfig(dns_catz_zones_t *catzs) {
+ isc_result_t result;
+ isc_ht_iter_t *iter = NULL;
+
+ REQUIRE(DNS_CATZ_ZONES_VALID(catzs));
+
+ LOCK(&catzs->lock);
+ isc_ht_iter_create(catzs->zones, &iter);
+ for (result = isc_ht_iter_first(iter); result == ISC_R_SUCCESS;
+ result = isc_ht_iter_next(iter))
+ {
+ dns_catz_zone_t *catz = NULL;
+ isc_ht_iter_current(iter, (void **)&catz);
+ catz->active = false;
+ }
+ UNLOCK(&catzs->lock);
+ INSIST(result == ISC_R_NOMORE);
+ isc_ht_iter_destroy(&iter);
+}
+
+void
+dns_catz_postreconfig(dns_catz_zones_t *catzs) {
+ isc_result_t result;
+ dns_catz_zone_t *newcatz = NULL;
+ isc_ht_iter_t *iter = NULL;
+
+ REQUIRE(DNS_CATZ_ZONES_VALID(catzs));
+
+ LOCK(&catzs->lock);
+ isc_ht_iter_create(catzs->zones, &iter);
+ for (result = isc_ht_iter_first(iter); result == ISC_R_SUCCESS;) {
+ dns_catz_zone_t *catz = NULL;
+
+ isc_ht_iter_current(iter, (void **)&catz);
+ if (!catz->active) {
+ char cname[DNS_NAME_FORMATSIZE];
+ dns_name_format(&catz->name, cname,
+ DNS_NAME_FORMATSIZE);
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL,
+ DNS_LOGMODULE_MASTER, ISC_LOG_WARNING,
+ "catz: removing catalog zone %s", cname);
+
+ /*
+ * Merge the old zone with an empty one to remove
+ * all members.
+ */
+ result = dns_catz_new_zone(catzs, &newcatz,
+ &catz->name);
+ INSIST(result == ISC_R_SUCCESS);
+ dns__catz_zones_merge(catz, newcatz);
+ dns_catz_detach_catz(&newcatz);
+
+ /* Make sure that we have an empty catalog zone. */
+ INSIST(isc_ht_count(catz->entries) == 0);
+ result = isc_ht_iter_delcurrent_next(iter);
+ dns_catz_detach_catz(&catz);
+ } else {
+ result = isc_ht_iter_next(iter);
+ }
+ }
+ UNLOCK(&catzs->lock);
+ RUNTIME_CHECK(result == ISC_R_NOMORE);
+ isc_ht_iter_destroy(&iter);
+}
+
+void
+dns_catz_get_iterator(dns_catz_zone_t *catz, isc_ht_iter_t **itp) {
+ REQUIRE(DNS_CATZ_ZONE_VALID(catz));
+
+ isc_ht_iter_create(catz->entries, itp);
+}
diff --git a/lib/dns/client.c b/lib/dns/client.c
new file mode 100644
index 0000000..9d8eb87
--- /dev/null
+++ b/lib/dns/client.c
@@ -0,0 +1,1326 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#include <stdbool.h>
+#include <stddef.h>
+
+#include <isc/app.h>
+#include <isc/buffer.h>
+#include <isc/md.h>
+#include <isc/mem.h>
+#include <isc/mutex.h>
+#include <isc/portset.h>
+#include <isc/refcount.h>
+#include <isc/result.h>
+#include <isc/safe.h>
+#include <isc/sockaddr.h>
+#include <isc/task.h>
+#include <isc/timer.h>
+#include <isc/util.h>
+
+#include <dns/adb.h>
+#include <dns/client.h>
+#include <dns/db.h>
+#include <dns/dispatch.h>
+#include <dns/events.h>
+#include <dns/forward.h>
+#include <dns/keytable.h>
+#include <dns/message.h>
+#include <dns/name.h>
+#include <dns/rdata.h>
+#include <dns/rdatalist.h>
+#include <dns/rdataset.h>
+#include <dns/rdatasetiter.h>
+#include <dns/rdatastruct.h>
+#include <dns/rdatatype.h>
+#include <dns/request.h>
+#include <dns/resolver.h>
+#include <dns/tsec.h>
+#include <dns/tsig.h>
+#include <dns/view.h>
+
+#include <dst/dst.h>
+
+#define DNS_CLIENT_MAGIC ISC_MAGIC('D', 'N', 'S', 'c')
+#define DNS_CLIENT_VALID(c) ISC_MAGIC_VALID(c, DNS_CLIENT_MAGIC)
+
+#define RCTX_MAGIC ISC_MAGIC('R', 'c', 't', 'x')
+#define RCTX_VALID(c) ISC_MAGIC_VALID(c, RCTX_MAGIC)
+
+#define UCTX_MAGIC ISC_MAGIC('U', 'c', 't', 'x')
+#define UCTX_VALID(c) ISC_MAGIC_VALID(c, UCTX_MAGIC)
+
+#define MAX_RESTARTS 16
+
+#ifdef TUNE_LARGE
+#define RESOLVER_NTASKS 523
+#else /* ifdef TUNE_LARGE */
+#define RESOLVER_NTASKS 31
+#endif /* TUNE_LARGE */
+
+#define CHECK(r) \
+ do { \
+ result = (r); \
+ if (result != ISC_R_SUCCESS) \
+ goto cleanup; \
+ } while (0)
+
+/*%
+ * DNS client object
+ */
+struct dns_client {
+ /* Unlocked */
+ unsigned int magic;
+ unsigned int attributes;
+ isc_mutex_t lock;
+ isc_mem_t *mctx;
+ isc_appctx_t *actx;
+ isc_taskmgr_t *taskmgr;
+ isc_task_t *task;
+ isc_nm_t *nm;
+ isc_timermgr_t *timermgr;
+ dns_dispatchmgr_t *dispatchmgr;
+ dns_dispatch_t *dispatchv4;
+ dns_dispatch_t *dispatchv6;
+
+ unsigned int find_timeout;
+ unsigned int find_udpretries;
+
+ isc_refcount_t references;
+
+ /* Locked */
+ dns_viewlist_t viewlist;
+ ISC_LIST(struct resctx) resctxs;
+};
+
+#define DEF_FIND_TIMEOUT 5
+#define DEF_FIND_UDPRETRIES 3
+
+/*%
+ * Internal state for a single name resolution procedure
+ */
+typedef struct resctx {
+ /* Unlocked */
+ unsigned int magic;
+ isc_mutex_t lock;
+ dns_client_t *client;
+ bool want_dnssec;
+ bool want_validation;
+ bool want_cdflag;
+ bool want_tcp;
+
+ /* Locked */
+ ISC_LINK(struct resctx) link;
+ isc_task_t *task;
+ dns_view_t *view;
+ unsigned int restarts;
+ dns_fixedname_t name;
+ dns_rdatatype_t type;
+ dns_fetch_t *fetch;
+ dns_namelist_t namelist;
+ isc_result_t result;
+ dns_clientresevent_t *event;
+ bool canceled;
+ dns_rdataset_t *rdataset;
+ dns_rdataset_t *sigrdataset;
+} resctx_t;
+
+/*%
+ * Argument of an internal event for synchronous name resolution.
+ */
+typedef struct resarg {
+ /* Unlocked */
+ isc_appctx_t *actx;
+ dns_client_t *client;
+ isc_mutex_t lock;
+
+ /* Locked */
+ isc_result_t result;
+ isc_result_t vresult;
+ dns_namelist_t *namelist;
+ dns_clientrestrans_t *trans;
+ bool canceled;
+} resarg_t;
+
+static void
+client_resfind(resctx_t *rctx, dns_fetchevent_t *event);
+static void
+cancelresolve(dns_clientrestrans_t *trans);
+static void
+destroyrestrans(dns_clientrestrans_t **transp);
+
+/*
+ * Try honoring the operating system's preferred ephemeral port range.
+ */
+static isc_result_t
+setsourceports(isc_mem_t *mctx, dns_dispatchmgr_t *manager) {
+ isc_portset_t *v4portset = NULL, *v6portset = NULL;
+ in_port_t udpport_low, udpport_high;
+ isc_result_t result;
+
+ result = isc_portset_create(mctx, &v4portset);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+ result = isc_net_getudpportrange(AF_INET, &udpport_low, &udpport_high);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+ isc_portset_addrange(v4portset, udpport_low, udpport_high);
+
+ result = isc_portset_create(mctx, &v6portset);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+ result = isc_net_getudpportrange(AF_INET6, &udpport_low, &udpport_high);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+ isc_portset_addrange(v6portset, udpport_low, udpport_high);
+
+ result = dns_dispatchmgr_setavailports(manager, v4portset, v6portset);
+
+cleanup:
+ if (v4portset != NULL) {
+ isc_portset_destroy(mctx, &v4portset);
+ }
+ if (v6portset != NULL) {
+ isc_portset_destroy(mctx, &v6portset);
+ }
+
+ return (result);
+}
+
+static isc_result_t
+getudpdispatch(int family, dns_dispatchmgr_t *dispatchmgr,
+ dns_dispatch_t **dispp, const isc_sockaddr_t *localaddr) {
+ dns_dispatch_t *disp = NULL;
+ isc_result_t result;
+ isc_sockaddr_t anyaddr;
+
+ if (localaddr == NULL) {
+ isc_sockaddr_anyofpf(&anyaddr, family);
+ localaddr = &anyaddr;
+ }
+
+ result = dns_dispatch_createudp(dispatchmgr, localaddr, &disp);
+ if (result == ISC_R_SUCCESS) {
+ *dispp = disp;
+ }
+
+ return (result);
+}
+
+static isc_result_t
+createview(isc_mem_t *mctx, dns_rdataclass_t rdclass, isc_taskmgr_t *taskmgr,
+ unsigned int ntasks, isc_nm_t *nm, isc_timermgr_t *timermgr,
+ dns_dispatchmgr_t *dispatchmgr, dns_dispatch_t *dispatchv4,
+ dns_dispatch_t *dispatchv6, dns_view_t **viewp) {
+ isc_result_t result;
+ dns_view_t *view = NULL;
+
+ result = dns_view_create(mctx, rdclass, DNS_CLIENTVIEW_NAME, &view);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+
+ /* Initialize view security roots */
+ result = dns_view_initsecroots(view, mctx);
+ if (result != ISC_R_SUCCESS) {
+ dns_view_detach(&view);
+ return (result);
+ }
+
+ result = dns_view_createresolver(view, taskmgr, ntasks, 1, nm, timermgr,
+ 0, dispatchmgr, dispatchv4,
+ dispatchv6);
+ if (result != ISC_R_SUCCESS) {
+ dns_view_detach(&view);
+ return (result);
+ }
+
+ result = dns_db_create(mctx, "rbt", dns_rootname, dns_dbtype_cache,
+ rdclass, 0, NULL, &view->cachedb);
+ if (result != ISC_R_SUCCESS) {
+ dns_view_detach(&view);
+ return (result);
+ }
+
+ *viewp = view;
+ return (ISC_R_SUCCESS);
+}
+
+isc_result_t
+dns_client_create(isc_mem_t *mctx, isc_appctx_t *actx, isc_taskmgr_t *taskmgr,
+ isc_nm_t *nm, isc_timermgr_t *timermgr, unsigned int options,
+ dns_client_t **clientp, const isc_sockaddr_t *localaddr4,
+ const isc_sockaddr_t *localaddr6) {
+ isc_result_t result;
+ dns_client_t *client = NULL;
+ dns_dispatch_t *dispatchv4 = NULL;
+ dns_dispatch_t *dispatchv6 = NULL;
+ dns_view_t *view = NULL;
+
+ REQUIRE(mctx != NULL);
+ REQUIRE(taskmgr != NULL);
+ REQUIRE(timermgr != NULL);
+ REQUIRE(nm != NULL);
+ REQUIRE(clientp != NULL && *clientp == NULL);
+
+ UNUSED(options);
+
+ client = isc_mem_get(mctx, sizeof(*client));
+ *client = (dns_client_t){
+ .actx = actx, .taskmgr = taskmgr, .timermgr = timermgr, .nm = nm
+ };
+
+ isc_mutex_init(&client->lock);
+
+ result = isc_task_create(client->taskmgr, 0, &client->task);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup_lock;
+ }
+
+ result = dns_dispatchmgr_create(mctx, nm, &client->dispatchmgr);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup_task;
+ }
+ (void)setsourceports(mctx, client->dispatchmgr);
+
+ /*
+ * If only one address family is specified, use it.
+ * If neither family is specified, or if both are, use both.
+ */
+ client->dispatchv4 = NULL;
+ if (localaddr4 != NULL || localaddr6 == NULL) {
+ result = getudpdispatch(AF_INET, client->dispatchmgr,
+ &dispatchv4, localaddr4);
+ if (result == ISC_R_SUCCESS) {
+ client->dispatchv4 = dispatchv4;
+ }
+ }
+
+ client->dispatchv6 = NULL;
+ if (localaddr6 != NULL || localaddr4 == NULL) {
+ result = getudpdispatch(AF_INET6, client->dispatchmgr,
+ &dispatchv6, localaddr6);
+ if (result == ISC_R_SUCCESS) {
+ client->dispatchv6 = dispatchv6;
+ }
+ }
+
+ /* We need at least one of the dispatchers */
+ if (dispatchv4 == NULL && dispatchv6 == NULL) {
+ INSIST(result != ISC_R_SUCCESS);
+ goto cleanup_dispatchmgr;
+ }
+
+ isc_refcount_init(&client->references, 1);
+
+ /* Create the default view for class IN */
+ result = createview(mctx, dns_rdataclass_in, taskmgr, RESOLVER_NTASKS,
+ nm, timermgr, client->dispatchmgr, dispatchv4,
+ dispatchv6, &view);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup_references;
+ }
+
+ ISC_LIST_INIT(client->viewlist);
+ ISC_LIST_APPEND(client->viewlist, view, link);
+
+ dns_view_freeze(view); /* too early? */
+
+ ISC_LIST_INIT(client->resctxs);
+
+ isc_mem_attach(mctx, &client->mctx);
+
+ client->find_timeout = DEF_FIND_TIMEOUT;
+ client->find_udpretries = DEF_FIND_UDPRETRIES;
+
+ client->magic = DNS_CLIENT_MAGIC;
+
+ *clientp = client;
+
+ return (ISC_R_SUCCESS);
+
+cleanup_references:
+ isc_refcount_decrementz(&client->references);
+ isc_refcount_destroy(&client->references);
+cleanup_dispatchmgr:
+ if (dispatchv4 != NULL) {
+ dns_dispatch_detach(&dispatchv4);
+ }
+ if (dispatchv6 != NULL) {
+ dns_dispatch_detach(&dispatchv6);
+ }
+ dns_dispatchmgr_detach(&client->dispatchmgr);
+cleanup_task:
+ isc_task_detach(&client->task);
+cleanup_lock:
+ isc_mutex_destroy(&client->lock);
+ isc_mem_put(mctx, client, sizeof(*client));
+
+ return (result);
+}
+
+static void
+destroyclient(dns_client_t *client) {
+ dns_view_t *view = NULL;
+
+ isc_refcount_destroy(&client->references);
+
+ while ((view = ISC_LIST_HEAD(client->viewlist)) != NULL) {
+ ISC_LIST_UNLINK(client->viewlist, view, link);
+ dns_view_detach(&view);
+ }
+
+ if (client->dispatchv4 != NULL) {
+ dns_dispatch_detach(&client->dispatchv4);
+ }
+ if (client->dispatchv6 != NULL) {
+ dns_dispatch_detach(&client->dispatchv6);
+ }
+
+ dns_dispatchmgr_detach(&client->dispatchmgr);
+
+ isc_task_detach(&client->task);
+
+ isc_mutex_destroy(&client->lock);
+ client->magic = 0;
+
+ isc_mem_putanddetach(&client->mctx, client, sizeof(*client));
+}
+
+void
+dns_client_detach(dns_client_t **clientp) {
+ dns_client_t *client = NULL;
+
+ REQUIRE(clientp != NULL);
+ REQUIRE(DNS_CLIENT_VALID(*clientp));
+
+ client = *clientp;
+ *clientp = NULL;
+
+ if (isc_refcount_decrement(&client->references) == 1) {
+ destroyclient(client);
+ }
+}
+
+isc_result_t
+dns_client_setservers(dns_client_t *client, dns_rdataclass_t rdclass,
+ const dns_name_t *name_space, isc_sockaddrlist_t *addrs) {
+ isc_result_t result;
+ dns_view_t *view = NULL;
+
+ REQUIRE(DNS_CLIENT_VALID(client));
+ REQUIRE(addrs != NULL);
+
+ if (name_space == NULL) {
+ name_space = dns_rootname;
+ }
+
+ LOCK(&client->lock);
+ result = dns_viewlist_find(&client->viewlist, DNS_CLIENTVIEW_NAME,
+ rdclass, &view);
+ if (result != ISC_R_SUCCESS) {
+ UNLOCK(&client->lock);
+ return (result);
+ }
+ UNLOCK(&client->lock);
+
+ result = dns_fwdtable_add(view->fwdtable, name_space, addrs,
+ dns_fwdpolicy_only);
+
+ dns_view_detach(&view);
+
+ return (result);
+}
+
+isc_result_t
+dns_client_clearservers(dns_client_t *client, dns_rdataclass_t rdclass,
+ const dns_name_t *name_space) {
+ isc_result_t result;
+ dns_view_t *view = NULL;
+
+ REQUIRE(DNS_CLIENT_VALID(client));
+
+ if (name_space == NULL) {
+ name_space = dns_rootname;
+ }
+
+ LOCK(&client->lock);
+ result = dns_viewlist_find(&client->viewlist, DNS_CLIENTVIEW_NAME,
+ rdclass, &view);
+ if (result != ISC_R_SUCCESS) {
+ UNLOCK(&client->lock);
+ return (result);
+ }
+ UNLOCK(&client->lock);
+
+ result = dns_fwdtable_delete(view->fwdtable, name_space);
+
+ dns_view_detach(&view);
+
+ return (result);
+}
+
+static isc_result_t
+getrdataset(isc_mem_t *mctx, dns_rdataset_t **rdatasetp) {
+ dns_rdataset_t *rdataset;
+
+ REQUIRE(mctx != NULL);
+ REQUIRE(rdatasetp != NULL && *rdatasetp == NULL);
+
+ rdataset = isc_mem_get(mctx, sizeof(*rdataset));
+
+ dns_rdataset_init(rdataset);
+
+ *rdatasetp = rdataset;
+
+ return (ISC_R_SUCCESS);
+}
+
+static void
+putrdataset(isc_mem_t *mctx, dns_rdataset_t **rdatasetp) {
+ dns_rdataset_t *rdataset;
+
+ REQUIRE(rdatasetp != NULL);
+ rdataset = *rdatasetp;
+ *rdatasetp = NULL;
+ REQUIRE(rdataset != NULL);
+
+ if (dns_rdataset_isassociated(rdataset)) {
+ dns_rdataset_disassociate(rdataset);
+ }
+
+ isc_mem_put(mctx, rdataset, sizeof(*rdataset));
+}
+
+static void
+fetch_done(isc_task_t *task, isc_event_t *event) {
+ resctx_t *rctx = event->ev_arg;
+ dns_fetchevent_t *fevent;
+
+ REQUIRE(event->ev_type == DNS_EVENT_FETCHDONE);
+ REQUIRE(RCTX_VALID(rctx));
+ REQUIRE(rctx->task == task);
+ fevent = (dns_fetchevent_t *)event;
+
+ client_resfind(rctx, fevent);
+}
+
+static isc_result_t
+start_fetch(resctx_t *rctx) {
+ isc_result_t result;
+ int fopts = 0;
+
+ /*
+ * The caller must be holding the rctx's lock.
+ */
+
+ REQUIRE(rctx->fetch == NULL);
+
+ if (!rctx->want_cdflag) {
+ fopts |= DNS_FETCHOPT_NOCDFLAG;
+ }
+ if (!rctx->want_validation) {
+ fopts |= DNS_FETCHOPT_NOVALIDATE;
+ }
+ if (rctx->want_tcp) {
+ fopts |= DNS_FETCHOPT_TCP;
+ }
+
+ result = dns_resolver_createfetch(
+ rctx->view->resolver, dns_fixedname_name(&rctx->name),
+ rctx->type, NULL, NULL, NULL, NULL, 0, fopts, 0, NULL,
+ rctx->task, fetch_done, rctx, rctx->rdataset, rctx->sigrdataset,
+ &rctx->fetch);
+
+ return (result);
+}
+
+static isc_result_t
+view_find(resctx_t *rctx, dns_db_t **dbp, dns_dbnode_t **nodep,
+ dns_name_t *foundname) {
+ isc_result_t result;
+ dns_name_t *name = dns_fixedname_name(&rctx->name);
+ dns_rdatatype_t type;
+
+ if (rctx->type == dns_rdatatype_rrsig) {
+ type = dns_rdatatype_any;
+ } else {
+ type = rctx->type;
+ }
+
+ result = dns_view_find(rctx->view, name, type, 0, 0, false, false, dbp,
+ nodep, foundname, rctx->rdataset,
+ rctx->sigrdataset);
+
+ return (result);
+}
+
+static void
+client_resfind(resctx_t *rctx, dns_fetchevent_t *event) {
+ isc_mem_t *mctx;
+ isc_result_t tresult, result = ISC_R_SUCCESS;
+ isc_result_t vresult = ISC_R_SUCCESS;
+ bool want_restart;
+ bool send_event = false;
+ dns_name_t *name = NULL, *prefix = NULL;
+ dns_fixedname_t foundname, fixed;
+ dns_rdataset_t *trdataset = NULL;
+ dns_rdata_t rdata = DNS_RDATA_INIT;
+ unsigned int nlabels;
+ int order;
+ dns_namereln_t namereln;
+ dns_rdata_cname_t cname;
+ dns_rdata_dname_t dname;
+
+ REQUIRE(RCTX_VALID(rctx));
+
+ LOCK(&rctx->lock);
+
+ mctx = rctx->view->mctx;
+
+ name = dns_fixedname_name(&rctx->name);
+
+ do {
+ dns_name_t *fname = NULL;
+ dns_name_t *ansname = NULL;
+ dns_db_t *db = NULL;
+ dns_dbnode_t *node = NULL;
+
+ rctx->restarts++;
+ want_restart = false;
+
+ if (event == NULL && !rctx->canceled) {
+ fname = dns_fixedname_initname(&foundname);
+ INSIST(!dns_rdataset_isassociated(rctx->rdataset));
+ INSIST(rctx->sigrdataset == NULL ||
+ !dns_rdataset_isassociated(rctx->sigrdataset));
+ result = view_find(rctx, &db, &node, fname);
+ if (result == ISC_R_NOTFOUND) {
+ /*
+ * We don't know anything about the name.
+ * Launch a fetch.
+ */
+ if (node != NULL) {
+ INSIST(db != NULL);
+ dns_db_detachnode(db, &node);
+ }
+ if (db != NULL) {
+ dns_db_detach(&db);
+ }
+ result = start_fetch(rctx);
+ if (result != ISC_R_SUCCESS) {
+ putrdataset(mctx, &rctx->rdataset);
+ if (rctx->sigrdataset != NULL) {
+ putrdataset(mctx,
+ &rctx->sigrdataset);
+ }
+ send_event = true;
+ }
+ goto done;
+ }
+ } else {
+ INSIST(event != NULL);
+ INSIST(event->fetch == rctx->fetch);
+ dns_resolver_destroyfetch(&rctx->fetch);
+ db = event->db;
+ node = event->node;
+ result = event->result;
+ vresult = event->vresult;
+ fname = event->foundname;
+ INSIST(event->rdataset == rctx->rdataset);
+ INSIST(event->sigrdataset == rctx->sigrdataset);
+ }
+
+ /*
+ * If we've been canceled, forget about the result.
+ */
+ if (rctx->canceled) {
+ result = ISC_R_CANCELED;
+ } else {
+ /*
+ * Otherwise, get some resource for copying the
+ * result.
+ */
+ dns_name_t *aname = dns_fixedname_name(&rctx->name);
+
+ ansname = isc_mem_get(mctx, sizeof(*ansname));
+ dns_name_init(ansname, NULL);
+
+ dns_name_dup(aname, mctx, ansname);
+ }
+
+ switch (result) {
+ case ISC_R_SUCCESS:
+ send_event = true;
+ /*
+ * This case is handled in the main line below.
+ */
+ break;
+ case DNS_R_CNAME:
+ /*
+ * Add the CNAME to the answer list.
+ */
+ trdataset = rctx->rdataset;
+ ISC_LIST_APPEND(ansname->list, rctx->rdataset, link);
+ rctx->rdataset = NULL;
+ if (rctx->sigrdataset != NULL) {
+ ISC_LIST_APPEND(ansname->list,
+ rctx->sigrdataset, link);
+ rctx->sigrdataset = NULL;
+ }
+ ISC_LIST_APPEND(rctx->namelist, ansname, link);
+ ansname = NULL;
+
+ /*
+ * Copy the CNAME's target into the lookup's
+ * query name and start over.
+ */
+ tresult = dns_rdataset_first(trdataset);
+ if (tresult != ISC_R_SUCCESS) {
+ goto done;
+ }
+ dns_rdataset_current(trdataset, &rdata);
+ tresult = dns_rdata_tostruct(&rdata, &cname, NULL);
+ dns_rdata_reset(&rdata);
+ if (tresult != ISC_R_SUCCESS) {
+ goto done;
+ }
+ dns_name_copy(&cname.cname, name);
+ dns_rdata_freestruct(&cname);
+ want_restart = true;
+ goto done;
+ case DNS_R_DNAME:
+ /*
+ * Add the DNAME to the answer list.
+ */
+ trdataset = rctx->rdataset;
+ ISC_LIST_APPEND(ansname->list, rctx->rdataset, link);
+ rctx->rdataset = NULL;
+ if (rctx->sigrdataset != NULL) {
+ ISC_LIST_APPEND(ansname->list,
+ rctx->sigrdataset, link);
+ rctx->sigrdataset = NULL;
+ }
+ ISC_LIST_APPEND(rctx->namelist, ansname, link);
+ ansname = NULL;
+
+ namereln = dns_name_fullcompare(name, fname, &order,
+ &nlabels);
+ INSIST(namereln == dns_namereln_subdomain);
+ /*
+ * Get the target name of the DNAME.
+ */
+ tresult = dns_rdataset_first(trdataset);
+ if (tresult != ISC_R_SUCCESS) {
+ result = tresult;
+ goto done;
+ }
+ dns_rdataset_current(trdataset, &rdata);
+ tresult = dns_rdata_tostruct(&rdata, &dname, NULL);
+ dns_rdata_reset(&rdata);
+ if (tresult != ISC_R_SUCCESS) {
+ result = tresult;
+ goto done;
+ }
+ /*
+ * Construct the new query name and start over.
+ */
+ prefix = dns_fixedname_initname(&fixed);
+ dns_name_split(name, nlabels, prefix, NULL);
+ tresult = dns_name_concatenate(prefix, &dname.dname,
+ name, NULL);
+ dns_rdata_freestruct(&dname);
+ if (tresult == ISC_R_SUCCESS) {
+ want_restart = true;
+ } else {
+ result = tresult;
+ }
+ goto done;
+ case DNS_R_NCACHENXDOMAIN:
+ case DNS_R_NCACHENXRRSET:
+ ISC_LIST_APPEND(ansname->list, rctx->rdataset, link);
+ ISC_LIST_APPEND(rctx->namelist, ansname, link);
+ ansname = NULL;
+ rctx->rdataset = NULL;
+ /* What about sigrdataset? */
+ if (rctx->sigrdataset != NULL) {
+ putrdataset(mctx, &rctx->sigrdataset);
+ }
+ send_event = true;
+ goto done;
+ default:
+ if (rctx->rdataset != NULL) {
+ putrdataset(mctx, &rctx->rdataset);
+ }
+ if (rctx->sigrdataset != NULL) {
+ putrdataset(mctx, &rctx->sigrdataset);
+ }
+ send_event = true;
+ goto done;
+ }
+
+ if (rctx->type == dns_rdatatype_any) {
+ int n = 0;
+ dns_rdatasetiter_t *rdsiter = NULL;
+
+ tresult = dns_db_allrdatasets(db, node, NULL, 0, 0,
+ &rdsiter);
+ if (tresult != ISC_R_SUCCESS) {
+ result = tresult;
+ goto done;
+ }
+
+ tresult = dns_rdatasetiter_first(rdsiter);
+ while (tresult == ISC_R_SUCCESS) {
+ dns_rdatasetiter_current(rdsiter,
+ rctx->rdataset);
+ if (rctx->rdataset->type != 0) {
+ ISC_LIST_APPEND(ansname->list,
+ rctx->rdataset, link);
+ n++;
+ rctx->rdataset = NULL;
+ } else {
+ /*
+ * We're not interested in this
+ * rdataset.
+ */
+ dns_rdataset_disassociate(
+ rctx->rdataset);
+ }
+ tresult = dns_rdatasetiter_next(rdsiter);
+
+ if (tresult == ISC_R_SUCCESS &&
+ rctx->rdataset == NULL)
+ {
+ tresult = getrdataset(mctx,
+ &rctx->rdataset);
+ if (tresult != ISC_R_SUCCESS) {
+ result = tresult;
+ POST(result);
+ break;
+ }
+ }
+ }
+ if (rctx->rdataset != NULL) {
+ putrdataset(mctx, &rctx->rdataset);
+ }
+ if (rctx->sigrdataset != NULL) {
+ putrdataset(mctx, &rctx->sigrdataset);
+ }
+ if (n == 0) {
+ /*
+ * We didn't match any rdatasets (which means
+ * something went wrong in this
+ * implementation).
+ */
+ result = DNS_R_SERVFAIL; /* better code? */
+ POST(result);
+ } else {
+ ISC_LIST_APPEND(rctx->namelist, ansname, link);
+ ansname = NULL;
+ }
+ dns_rdatasetiter_destroy(&rdsiter);
+ if (tresult != ISC_R_NOMORE) {
+ result = DNS_R_SERVFAIL; /* ditto */
+ } else {
+ result = ISC_R_SUCCESS;
+ }
+ goto done;
+ } else {
+ /*
+ * This is the "normal" case -- an ordinary question
+ * to which we've got the answer.
+ */
+ ISC_LIST_APPEND(ansname->list, rctx->rdataset, link);
+ rctx->rdataset = NULL;
+ if (rctx->sigrdataset != NULL) {
+ ISC_LIST_APPEND(ansname->list,
+ rctx->sigrdataset, link);
+ rctx->sigrdataset = NULL;
+ }
+ ISC_LIST_APPEND(rctx->namelist, ansname, link);
+ ansname = NULL;
+ }
+
+ done:
+ /*
+ * Free temporary resources
+ */
+ if (ansname != NULL) {
+ dns_rdataset_t *rdataset;
+
+ while ((rdataset = ISC_LIST_HEAD(ansname->list)) !=
+ NULL)
+ {
+ ISC_LIST_UNLINK(ansname->list, rdataset, link);
+ putrdataset(mctx, &rdataset);
+ }
+ dns_name_free(ansname, mctx);
+ isc_mem_put(mctx, ansname, sizeof(*ansname));
+ }
+
+ if (node != NULL) {
+ dns_db_detachnode(db, &node);
+ }
+ if (db != NULL) {
+ dns_db_detach(&db);
+ }
+ if (event != NULL) {
+ isc_event_free(ISC_EVENT_PTR(&event));
+ }
+
+ /*
+ * Limit the number of restarts.
+ */
+ if (want_restart && rctx->restarts == MAX_RESTARTS) {
+ want_restart = false;
+ result = ISC_R_QUOTA;
+ send_event = true;
+ }
+
+ /*
+ * Prepare further find with new resources
+ */
+ if (want_restart) {
+ INSIST(rctx->rdataset == NULL &&
+ rctx->sigrdataset == NULL);
+
+ result = getrdataset(mctx, &rctx->rdataset);
+ if (result == ISC_R_SUCCESS && rctx->want_dnssec) {
+ result = getrdataset(mctx, &rctx->sigrdataset);
+ if (result != ISC_R_SUCCESS) {
+ putrdataset(mctx, &rctx->rdataset);
+ }
+ }
+
+ if (result != ISC_R_SUCCESS) {
+ want_restart = false;
+ send_event = true;
+ }
+ }
+ } while (want_restart);
+
+ if (send_event) {
+ isc_task_t *task;
+
+ while ((name = ISC_LIST_HEAD(rctx->namelist)) != NULL) {
+ ISC_LIST_UNLINK(rctx->namelist, name, link);
+ ISC_LIST_APPEND(rctx->event->answerlist, name, link);
+ }
+
+ rctx->event->result = result;
+ rctx->event->vresult = vresult;
+ task = rctx->event->ev_sender;
+ rctx->event->ev_sender = rctx;
+ isc_task_sendanddetach(&task, ISC_EVENT_PTR(&rctx->event));
+ }
+
+ UNLOCK(&rctx->lock);
+}
+
+static void
+suspend(isc_task_t *task, isc_event_t *event) {
+ isc_appctx_t *actx = event->ev_arg;
+
+ UNUSED(task);
+
+ isc_app_ctxsuspend(actx);
+ isc_event_free(&event);
+}
+
+static void
+resolve_done(isc_task_t *task, isc_event_t *event) {
+ resarg_t *resarg = event->ev_arg;
+ dns_clientresevent_t *rev = (dns_clientresevent_t *)event;
+ dns_name_t *name = NULL;
+ dns_client_t *client = resarg->client;
+ isc_result_t result;
+
+ UNUSED(task);
+
+ LOCK(&resarg->lock);
+
+ resarg->result = rev->result;
+ resarg->vresult = rev->vresult;
+ while ((name = ISC_LIST_HEAD(rev->answerlist)) != NULL) {
+ ISC_LIST_UNLINK(rev->answerlist, name, link);
+ ISC_LIST_APPEND(*resarg->namelist, name, link);
+ }
+
+ destroyrestrans(&resarg->trans);
+ isc_event_free(&event);
+ resarg->client = NULL;
+
+ if (!resarg->canceled) {
+ UNLOCK(&resarg->lock);
+
+ /*
+ * We may or may not be running. isc__appctx_onrun will
+ * fail if we are currently running otherwise we post a
+ * action to call isc_app_ctxsuspend when we do start
+ * running.
+ */
+ result = isc_app_ctxonrun(resarg->actx, client->mctx, task,
+ suspend, resarg->actx);
+ if (result == ISC_R_ALREADYRUNNING) {
+ isc_app_ctxsuspend(resarg->actx);
+ }
+ } else {
+ /*
+ * We have already exited from the loop (due to some
+ * unexpected event). Just clean the arg up.
+ */
+ UNLOCK(&resarg->lock);
+ isc_mutex_destroy(&resarg->lock);
+ isc_mem_put(client->mctx, resarg, sizeof(*resarg));
+ }
+
+ dns_client_detach(&client);
+}
+
+isc_result_t
+dns_client_resolve(dns_client_t *client, const dns_name_t *name,
+ dns_rdataclass_t rdclass, dns_rdatatype_t type,
+ unsigned int options, dns_namelist_t *namelist) {
+ isc_result_t result;
+ resarg_t *resarg = NULL;
+
+ REQUIRE(DNS_CLIENT_VALID(client));
+ REQUIRE(client->actx != NULL);
+ REQUIRE(namelist != NULL && ISC_LIST_EMPTY(*namelist));
+
+ resarg = isc_mem_get(client->mctx, sizeof(*resarg));
+
+ *resarg = (resarg_t){
+ .actx = client->actx,
+ .client = client,
+ .result = DNS_R_SERVFAIL,
+ .namelist = namelist,
+ };
+
+ isc_mutex_init(&resarg->lock);
+
+ result = dns_client_startresolve(client, name, rdclass, type, options,
+ client->task, resolve_done, resarg,
+ &resarg->trans);
+ if (result != ISC_R_SUCCESS) {
+ isc_mutex_destroy(&resarg->lock);
+ isc_mem_put(client->mctx, resarg, sizeof(*resarg));
+ return (result);
+ }
+
+ /*
+ * Start internal event loop. It blocks until the entire process
+ * is completed.
+ */
+ result = isc_app_ctxrun(client->actx);
+
+ LOCK(&resarg->lock);
+ if (result == ISC_R_SUCCESS || result == ISC_R_SUSPEND) {
+ result = resarg->result;
+ }
+ if (result != ISC_R_SUCCESS && resarg->vresult != ISC_R_SUCCESS) {
+ /*
+ * If this lookup failed due to some error in DNSSEC
+ * validation, return the validation error code.
+ * XXX: or should we pass the validation result separately?
+ */
+ result = resarg->vresult;
+ }
+ if (resarg->trans != NULL) {
+ /*
+ * Unusual termination (perhaps due to signal). We need some
+ * tricky cleanup process.
+ */
+ resarg->canceled = true;
+ cancelresolve(resarg->trans);
+
+ UNLOCK(&resarg->lock);
+
+ /* resarg will be freed in the event handler. */
+ } else {
+ UNLOCK(&resarg->lock);
+
+ isc_mutex_destroy(&resarg->lock);
+ isc_mem_put(client->mctx, resarg, sizeof(*resarg));
+ }
+
+ return (result);
+}
+
+isc_result_t
+dns_client_startresolve(dns_client_t *client, const dns_name_t *name,
+ dns_rdataclass_t rdclass, dns_rdatatype_t type,
+ unsigned int options, isc_task_t *task,
+ isc_taskaction_t action, void *arg,
+ dns_clientrestrans_t **transp) {
+ dns_view_t *view = NULL;
+ dns_clientresevent_t *event = NULL;
+ resctx_t *rctx = NULL;
+ isc_task_t *tclone = NULL;
+ isc_mem_t *mctx;
+ isc_result_t result;
+ dns_rdataset_t *rdataset, *sigrdataset;
+ bool want_dnssec, want_validation, want_cdflag, want_tcp;
+
+ REQUIRE(DNS_CLIENT_VALID(client));
+ REQUIRE(transp != NULL && *transp == NULL);
+
+ LOCK(&client->lock);
+ result = dns_viewlist_find(&client->viewlist, DNS_CLIENTVIEW_NAME,
+ rdclass, &view);
+ UNLOCK(&client->lock);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+
+ mctx = client->mctx;
+ rdataset = NULL;
+ sigrdataset = NULL;
+ want_dnssec = ((options & DNS_CLIENTRESOPT_NODNSSEC) == 0);
+ want_validation = ((options & DNS_CLIENTRESOPT_NOVALIDATE) == 0);
+ want_cdflag = ((options & DNS_CLIENTRESOPT_NOCDFLAG) == 0);
+ want_tcp = ((options & DNS_CLIENTRESOPT_TCP) != 0);
+
+ /*
+ * Prepare some intermediate resources
+ */
+ tclone = NULL;
+ isc_task_attach(task, &tclone);
+ event = (dns_clientresevent_t *)isc_event_allocate(
+ mctx, tclone, DNS_EVENT_CLIENTRESDONE, action, arg,
+ sizeof(*event));
+ event->result = DNS_R_SERVFAIL;
+ ISC_LIST_INIT(event->answerlist);
+
+ rctx = isc_mem_get(mctx, sizeof(*rctx));
+ isc_mutex_init(&rctx->lock);
+
+ result = getrdataset(mctx, &rdataset);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+ rctx->rdataset = rdataset;
+
+ if (want_dnssec) {
+ result = getrdataset(mctx, &sigrdataset);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+ }
+ rctx->sigrdataset = sigrdataset;
+
+ dns_fixedname_init(&rctx->name);
+ dns_name_copy(name, dns_fixedname_name(&rctx->name));
+
+ rctx->client = client;
+ ISC_LINK_INIT(rctx, link);
+ rctx->canceled = false;
+ rctx->task = client->task;
+ rctx->type = type;
+ rctx->view = view;
+ rctx->restarts = 0;
+ rctx->fetch = NULL;
+ rctx->want_dnssec = want_dnssec;
+ rctx->want_validation = want_validation;
+ rctx->want_cdflag = want_cdflag;
+ rctx->want_tcp = want_tcp;
+ ISC_LIST_INIT(rctx->namelist);
+ rctx->event = event;
+
+ rctx->magic = RCTX_MAGIC;
+ isc_refcount_increment(&client->references);
+
+ LOCK(&client->lock);
+ ISC_LIST_APPEND(client->resctxs, rctx, link);
+ UNLOCK(&client->lock);
+
+ *transp = (dns_clientrestrans_t *)rctx;
+ client_resfind(rctx, NULL);
+
+ return (ISC_R_SUCCESS);
+
+cleanup:
+ if (rdataset != NULL) {
+ putrdataset(client->mctx, &rdataset);
+ }
+ if (sigrdataset != NULL) {
+ putrdataset(client->mctx, &sigrdataset);
+ }
+ isc_mutex_destroy(&rctx->lock);
+ isc_mem_put(mctx, rctx, sizeof(*rctx));
+ isc_event_free(ISC_EVENT_PTR(&event));
+ isc_task_detach(&tclone);
+ dns_view_detach(&view);
+
+ return (result);
+}
+
+/*%<
+ * Cancel an ongoing resolution procedure started via
+ * dns_client_startresolve().
+ *
+ * If the resolution procedure has not completed, post its CLIENTRESDONE
+ * event with a result code of #ISC_R_CANCELED.
+ */
+static void
+cancelresolve(dns_clientrestrans_t *trans) {
+ resctx_t *rctx = NULL;
+
+ REQUIRE(trans != NULL);
+ rctx = (resctx_t *)trans;
+ REQUIRE(RCTX_VALID(rctx));
+
+ LOCK(&rctx->lock);
+
+ if (!rctx->canceled) {
+ rctx->canceled = true;
+ if (rctx->fetch != NULL) {
+ dns_resolver_cancelfetch(rctx->fetch);
+ }
+ }
+
+ UNLOCK(&rctx->lock);
+}
+
+void
+dns_client_freeresanswer(dns_client_t *client, dns_namelist_t *namelist) {
+ dns_name_t *name;
+ dns_rdataset_t *rdataset;
+
+ REQUIRE(DNS_CLIENT_VALID(client));
+ REQUIRE(namelist != NULL);
+
+ while ((name = ISC_LIST_HEAD(*namelist)) != NULL) {
+ ISC_LIST_UNLINK(*namelist, name, link);
+ while ((rdataset = ISC_LIST_HEAD(name->list)) != NULL) {
+ ISC_LIST_UNLINK(name->list, rdataset, link);
+ putrdataset(client->mctx, &rdataset);
+ }
+ dns_name_free(name, client->mctx);
+ isc_mem_put(client->mctx, name, sizeof(*name));
+ }
+}
+
+/*%
+ * Destroy name resolution transaction state identified by '*transp'.
+ *
+ * The caller must have received the CLIENTRESDONE event (either because the
+ * resolution completed or because cancelresolve() was called).
+ */
+static void
+destroyrestrans(dns_clientrestrans_t **transp) {
+ resctx_t *rctx = NULL;
+ isc_mem_t *mctx = NULL;
+ dns_client_t *client = NULL;
+
+ REQUIRE(transp != NULL);
+
+ rctx = (resctx_t *)*transp;
+ *transp = NULL;
+
+ REQUIRE(RCTX_VALID(rctx));
+ REQUIRE(rctx->fetch == NULL);
+ REQUIRE(rctx->event == NULL);
+
+ client = rctx->client;
+
+ REQUIRE(DNS_CLIENT_VALID(client));
+
+ mctx = client->mctx;
+ dns_view_detach(&rctx->view);
+
+ /*
+ * Wait for the lock in client_resfind to be released before
+ * destroying the lock.
+ */
+ LOCK(&rctx->lock);
+ UNLOCK(&rctx->lock);
+
+ LOCK(&client->lock);
+
+ INSIST(ISC_LINK_LINKED(rctx, link));
+ ISC_LIST_UNLINK(client->resctxs, rctx, link);
+
+ UNLOCK(&client->lock);
+
+ INSIST(ISC_LIST_EMPTY(rctx->namelist));
+
+ isc_mutex_destroy(&rctx->lock);
+ rctx->magic = 0;
+
+ isc_mem_put(mctx, rctx, sizeof(*rctx));
+}
+
+isc_result_t
+dns_client_addtrustedkey(dns_client_t *client, dns_rdataclass_t rdclass,
+ dns_rdatatype_t rdtype, const dns_name_t *keyname,
+ isc_buffer_t *databuf) {
+ isc_result_t result;
+ dns_view_t *view = NULL;
+ dns_keytable_t *secroots = NULL;
+ dns_name_t *name = NULL;
+ char rdatabuf[DST_KEY_MAXSIZE];
+ unsigned char digest[ISC_MAX_MD_SIZE];
+ dns_rdata_ds_t ds;
+ dns_decompress_t dctx;
+ dns_rdata_t rdata;
+ isc_buffer_t b;
+
+ REQUIRE(DNS_CLIENT_VALID(client));
+
+ LOCK(&client->lock);
+ result = dns_viewlist_find(&client->viewlist, DNS_CLIENTVIEW_NAME,
+ rdclass, &view);
+ UNLOCK(&client->lock);
+ CHECK(result);
+
+ CHECK(dns_view_getsecroots(view, &secroots));
+
+ DE_CONST(keyname, name);
+
+ if (rdtype != dns_rdatatype_dnskey && rdtype != dns_rdatatype_ds) {
+ result = ISC_R_NOTIMPLEMENTED;
+ goto cleanup;
+ }
+
+ isc_buffer_init(&b, rdatabuf, sizeof(rdatabuf));
+ dns_decompress_init(&dctx, -1, DNS_DECOMPRESS_NONE);
+ dns_rdata_init(&rdata);
+ isc_buffer_setactive(databuf, isc_buffer_usedlength(databuf));
+ CHECK(dns_rdata_fromwire(&rdata, rdclass, rdtype, databuf, &dctx, 0,
+ &b));
+ dns_decompress_invalidate(&dctx);
+
+ if (rdtype == dns_rdatatype_ds) {
+ CHECK(dns_rdata_tostruct(&rdata, &ds, NULL));
+ } else {
+ CHECK(dns_ds_fromkeyrdata(name, &rdata, DNS_DSDIGEST_SHA256,
+ digest, &ds));
+ }
+
+ CHECK(dns_keytable_add(secroots, false, false, name, &ds, NULL, NULL));
+
+cleanup:
+ if (view != NULL) {
+ dns_view_detach(&view);
+ }
+ if (secroots != NULL) {
+ dns_keytable_detach(&secroots);
+ }
+ return (result);
+}
diff --git a/lib/dns/clientinfo.c b/lib/dns/clientinfo.c
new file mode 100644
index 0000000..1ea5e7d
--- /dev/null
+++ b/lib/dns/clientinfo.c
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/*! \file */
+
+#include <dns/clientinfo.h>
+#include <dns/ecs.h>
+
+void
+dns_clientinfomethods_init(dns_clientinfomethods_t *methods,
+ dns_clientinfo_sourceip_t sourceip) {
+ methods->version = DNS_CLIENTINFOMETHODS_VERSION;
+ methods->age = DNS_CLIENTINFOMETHODS_AGE;
+ methods->sourceip = sourceip;
+}
+
+void
+dns_clientinfo_init(dns_clientinfo_t *ci, void *data, void *versionp) {
+ ci->version = DNS_CLIENTINFO_VERSION;
+ ci->data = data;
+ ci->dbversion = versionp;
+ dns_ecs_init(&ci->ecs);
+}
+
+void
+dns_clientinfo_setecs(dns_clientinfo_t *ci, dns_ecs_t *ecs) {
+ if (ecs != NULL) {
+ ci->ecs = *ecs;
+ } else {
+ dns_ecs_init(&ci->ecs);
+ }
+}
diff --git a/lib/dns/compress.c b/lib/dns/compress.c
new file mode 100644
index 0000000..1460e0c
--- /dev/null
+++ b/lib/dns/compress.c
@@ -0,0 +1,580 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/*! \file */
+
+#define DNS_NAME_USEINLINE 1
+
+#include <inttypes.h>
+#include <stdbool.h>
+
+#include <isc/mem.h>
+#include <isc/result.h>
+#include <isc/string.h>
+#include <isc/util.h>
+
+#include <dns/compress.h>
+#include <dns/fixedname.h>
+#include <dns/rbt.h>
+
+#define CCTX_MAGIC ISC_MAGIC('C', 'C', 'T', 'X')
+#define VALID_CCTX(x) ISC_MAGIC_VALID(x, CCTX_MAGIC)
+
+#define DCTX_MAGIC ISC_MAGIC('D', 'C', 'T', 'X')
+#define VALID_DCTX(x) ISC_MAGIC_VALID(x, DCTX_MAGIC)
+
+static unsigned char maptolower[] = {
+ 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b,
+ 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17,
+ 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20, 0x21, 0x22, 0x23,
+ 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f,
+ 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b,
+ 0x3c, 0x3d, 0x3e, 0x3f, 0x40, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67,
+ 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, 0x70, 0x71, 0x72, 0x73,
+ 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f,
+ 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6a, 0x6b,
+ 0x6c, 0x6d, 0x6e, 0x6f, 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77,
+ 0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f, 0x80, 0x81, 0x82, 0x83,
+ 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a, 0x8b, 0x8c, 0x8d, 0x8e, 0x8f,
+ 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9a, 0x9b,
+ 0x9c, 0x9d, 0x9e, 0x9f, 0xa0, 0xa1, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7,
+ 0xa8, 0xa9, 0xaa, 0xab, 0xac, 0xad, 0xae, 0xaf, 0xb0, 0xb1, 0xb2, 0xb3,
+ 0xb4, 0xb5, 0xb6, 0xb7, 0xb8, 0xb9, 0xba, 0xbb, 0xbc, 0xbd, 0xbe, 0xbf,
+ 0xc0, 0xc1, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7, 0xc8, 0xc9, 0xca, 0xcb,
+ 0xcc, 0xcd, 0xce, 0xcf, 0xd0, 0xd1, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7,
+ 0xd8, 0xd9, 0xda, 0xdb, 0xdc, 0xdd, 0xde, 0xdf, 0xe0, 0xe1, 0xe2, 0xe3,
+ 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea, 0xeb, 0xec, 0xed, 0xee, 0xef,
+ 0xf0, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9, 0xfa, 0xfb,
+ 0xfc, 0xfd, 0xfe, 0xff
+};
+
+/*
+ * The tableindex array below is of size 256, one entry for each
+ * unsigned char value. The tableindex array elements are dependent on
+ * DNS_COMPRESS_TABLESIZE. The table was created using the following
+ * function.
+ *
+ * static void
+ * gentable(unsigned char *table) {
+ * unsigned int i;
+ * const unsigned int left = DNS_COMPRESS_TABLESIZE - 38;
+ * long r;
+ *
+ * for (i = 0; i < 26; i++) {
+ * table['A' + i] = i;
+ * table['a' + i] = i;
+ * }
+ *
+ * for (i = 0; i <= 9; i++)
+ * table['0' + i] = i + 26;
+ *
+ * table['-'] = 36;
+ * table['_'] = 37;
+ *
+ * for (i = 0; i < 256; i++) {
+ * if ((i >= 'a' && i <= 'z') ||
+ * (i >= 'A' && i <= 'Z') ||
+ * (i >= '0' && i <= '9') ||
+ * (i == '-') ||
+ * (i == '_'))
+ * continue;
+ * r = random() % left;
+ * table[i] = 38 + r;
+ * }
+ * }
+ */
+static unsigned char tableindex[256] = {
+ 0x3e, 0x3e, 0x33, 0x2d, 0x30, 0x38, 0x31, 0x3c, 0x2b, 0x33, 0x30, 0x3f,
+ 0x2d, 0x3c, 0x36, 0x3a, 0x28, 0x2c, 0x2a, 0x37, 0x3d, 0x34, 0x35, 0x2d,
+ 0x39, 0x2b, 0x2f, 0x2c, 0x3b, 0x32, 0x2b, 0x39, 0x30, 0x38, 0x28, 0x3c,
+ 0x32, 0x33, 0x39, 0x38, 0x27, 0x2b, 0x39, 0x30, 0x27, 0x24, 0x2f, 0x2b,
+ 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20, 0x21, 0x22, 0x3a, 0x29, 0x36,
+ 0x31, 0x3c, 0x35, 0x26, 0x31, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06,
+ 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12,
+ 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x3e, 0x3b, 0x39, 0x2f, 0x25,
+ 0x27, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a,
+ 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16,
+ 0x17, 0x18, 0x19, 0x36, 0x3b, 0x2f, 0x2f, 0x2e, 0x29, 0x33, 0x2a, 0x36,
+ 0x28, 0x3f, 0x2e, 0x29, 0x2c, 0x29, 0x36, 0x2d, 0x32, 0x3d, 0x33, 0x2a,
+ 0x2e, 0x2f, 0x3b, 0x30, 0x3d, 0x39, 0x2b, 0x36, 0x2a, 0x2f, 0x2c, 0x26,
+ 0x3a, 0x37, 0x30, 0x3d, 0x2a, 0x36, 0x33, 0x2c, 0x38, 0x3d, 0x32, 0x3e,
+ 0x26, 0x2a, 0x2c, 0x35, 0x27, 0x39, 0x3b, 0x31, 0x2a, 0x37, 0x3c, 0x27,
+ 0x32, 0x29, 0x39, 0x37, 0x34, 0x3f, 0x39, 0x2e, 0x38, 0x2b, 0x2c, 0x3e,
+ 0x3b, 0x3b, 0x2d, 0x33, 0x3b, 0x3b, 0x32, 0x3d, 0x3f, 0x3a, 0x34, 0x26,
+ 0x35, 0x30, 0x31, 0x39, 0x27, 0x2f, 0x3d, 0x35, 0x35, 0x36, 0x2e, 0x29,
+ 0x38, 0x27, 0x34, 0x32, 0x2c, 0x3c, 0x31, 0x28, 0x37, 0x38, 0x37, 0x34,
+ 0x33, 0x29, 0x32, 0x34, 0x3f, 0x26, 0x34, 0x34, 0x32, 0x27, 0x30, 0x33,
+ 0x33, 0x2d, 0x2b, 0x28, 0x3f, 0x33, 0x2b, 0x39, 0x37, 0x39, 0x2c, 0x3d,
+ 0x35, 0x39, 0x27, 0x2f
+};
+
+/***
+ *** Compression
+ ***/
+
+isc_result_t
+dns_compress_init(dns_compress_t *cctx, int edns, isc_mem_t *mctx) {
+ REQUIRE(cctx != NULL);
+ REQUIRE(mctx != NULL); /* See: rdataset.c:towiresorted(). */
+
+ cctx->edns = edns;
+ cctx->mctx = mctx;
+ cctx->count = 0;
+ cctx->allowed = DNS_COMPRESS_ENABLED;
+ cctx->arena_off = 0;
+
+ memset(&cctx->table[0], 0, sizeof(cctx->table));
+
+ cctx->magic = CCTX_MAGIC;
+
+ return (ISC_R_SUCCESS);
+}
+
+void
+dns_compress_invalidate(dns_compress_t *cctx) {
+ dns_compressnode_t *node;
+ unsigned int i;
+
+ REQUIRE(VALID_CCTX(cctx));
+
+ for (i = 0; i < DNS_COMPRESS_TABLESIZE; i++) {
+ while (cctx->table[i] != NULL) {
+ node = cctx->table[i];
+ cctx->table[i] = cctx->table[i]->next;
+ if ((node->offset & 0x8000) != 0) {
+ isc_mem_put(cctx->mctx, node->r.base,
+ node->r.length);
+ }
+ if (node->count < DNS_COMPRESS_INITIALNODES) {
+ continue;
+ }
+ isc_mem_put(cctx->mctx, node, sizeof(*node));
+ }
+ }
+
+ cctx->magic = 0;
+ cctx->allowed = 0;
+ cctx->edns = -1;
+}
+
+void
+dns_compress_setmethods(dns_compress_t *cctx, unsigned int allowed) {
+ REQUIRE(VALID_CCTX(cctx));
+
+ cctx->allowed &= ~DNS_COMPRESS_ALL;
+ cctx->allowed |= (allowed & DNS_COMPRESS_ALL);
+}
+
+unsigned int
+dns_compress_getmethods(dns_compress_t *cctx) {
+ REQUIRE(VALID_CCTX(cctx));
+ return (cctx->allowed & DNS_COMPRESS_ALL);
+}
+
+void
+dns_compress_disable(dns_compress_t *cctx) {
+ REQUIRE(VALID_CCTX(cctx));
+ cctx->allowed &= ~DNS_COMPRESS_ENABLED;
+}
+
+void
+dns_compress_setsensitive(dns_compress_t *cctx, bool sensitive) {
+ REQUIRE(VALID_CCTX(cctx));
+
+ if (sensitive) {
+ cctx->allowed |= DNS_COMPRESS_CASESENSITIVE;
+ } else {
+ cctx->allowed &= ~DNS_COMPRESS_CASESENSITIVE;
+ }
+}
+
+bool
+dns_compress_getsensitive(dns_compress_t *cctx) {
+ REQUIRE(VALID_CCTX(cctx));
+
+ return (cctx->allowed & DNS_COMPRESS_CASESENSITIVE);
+}
+
+int
+dns_compress_getedns(dns_compress_t *cctx) {
+ REQUIRE(VALID_CCTX(cctx));
+ return (cctx->edns);
+}
+
+/*
+ * Find the longest match of name in the table.
+ * If match is found return true. prefix, suffix and offset are updated.
+ * If no match is found return false.
+ */
+bool
+dns_compress_findglobal(dns_compress_t *cctx, const dns_name_t *name,
+ dns_name_t *prefix, uint16_t *offset) {
+ dns_name_t tname;
+ dns_compressnode_t *node = NULL;
+ unsigned int labels, i, n;
+ unsigned int numlabels;
+ unsigned char *p;
+
+ REQUIRE(VALID_CCTX(cctx));
+ REQUIRE(dns_name_isabsolute(name));
+ REQUIRE(offset != NULL);
+
+ if ((cctx->allowed & DNS_COMPRESS_ENABLED) == 0) {
+ return (false);
+ }
+
+ if (cctx->count == 0) {
+ return (false);
+ }
+
+ labels = dns_name_countlabels(name);
+ INSIST(labels > 0);
+
+ dns_name_init(&tname, NULL);
+
+ numlabels = labels > 3U ? 3U : labels;
+ p = name->ndata;
+
+ for (n = 0; n < numlabels - 1; n++) {
+ unsigned char ch, llen;
+ unsigned int firstoffset, length;
+
+ firstoffset = (unsigned int)(p - name->ndata);
+ length = name->length - firstoffset;
+
+ /*
+ * We calculate the table index using the first
+ * character in the first label of the suffix name.
+ */
+ ch = p[1];
+ i = tableindex[ch];
+ if ((cctx->allowed & DNS_COMPRESS_CASESENSITIVE) != 0) {
+ for (node = cctx->table[i]; node != NULL;
+ node = node->next)
+ {
+ if (node->name.length != length) {
+ continue;
+ }
+
+ if (memcmp(node->name.ndata, p, length) == 0) {
+ goto found;
+ }
+ }
+ } else {
+ for (node = cctx->table[i]; node != NULL;
+ node = node->next)
+ {
+ unsigned int l, count;
+ unsigned char c;
+ unsigned char *label1, *label2;
+
+ if (node->name.length != length) {
+ continue;
+ }
+
+ l = labels - n;
+ if (node->name.labels != l) {
+ continue;
+ }
+
+ label1 = node->name.ndata;
+ label2 = p;
+ while (l-- > 0) {
+ count = *label1++;
+ if (count != *label2++) {
+ goto cont1;
+ }
+
+ /* no bitstring support */
+ INSIST(count <= 63);
+
+ /* Loop unrolled for performance */
+ while (count > 3) {
+ c = maptolower[label1[0]];
+ if (c != maptolower[label2[0]])
+ {
+ goto cont1;
+ }
+ c = maptolower[label1[1]];
+ if (c != maptolower[label2[1]])
+ {
+ goto cont1;
+ }
+ c = maptolower[label1[2]];
+ if (c != maptolower[label2[2]])
+ {
+ goto cont1;
+ }
+ c = maptolower[label1[3]];
+ if (c != maptolower[label2[3]])
+ {
+ goto cont1;
+ }
+ count -= 4;
+ label1 += 4;
+ label2 += 4;
+ }
+ while (count-- > 0) {
+ c = maptolower[*label1++];
+ if (c != maptolower[*label2++])
+ {
+ goto cont1;
+ }
+ }
+ }
+ break;
+ cont1:
+ continue;
+ }
+ }
+
+ if (node != NULL) {
+ break;
+ }
+
+ llen = *p;
+ p += llen + 1;
+ }
+
+found:
+ /*
+ * If node == NULL, we found no match at all.
+ */
+ if (node == NULL) {
+ return (false);
+ }
+
+ if (n == 0) {
+ dns_name_reset(prefix);
+ } else {
+ dns_name_getlabelsequence(name, 0, n, prefix);
+ }
+
+ *offset = (node->offset & 0x7fff);
+ return (true);
+}
+
+static unsigned int
+name_length(const dns_name_t *name) {
+ isc_region_t r;
+ dns_name_toregion(name, &r);
+ return (r.length);
+}
+
+void
+dns_compress_add(dns_compress_t *cctx, const dns_name_t *name,
+ const dns_name_t *prefix, uint16_t offset) {
+ dns_name_t tname, xname;
+ unsigned int start;
+ unsigned int n;
+ unsigned int count;
+ unsigned int i;
+ dns_compressnode_t *node;
+ unsigned int length;
+ unsigned int tlength;
+ uint16_t toffset;
+ unsigned char *tmp;
+ isc_region_t r;
+ bool allocated = false;
+
+ REQUIRE(VALID_CCTX(cctx));
+ REQUIRE(dns_name_isabsolute(name));
+
+ if ((cctx->allowed & DNS_COMPRESS_ENABLED) == 0) {
+ return;
+ }
+
+ if (offset >= 0x4000) {
+ return;
+ }
+ dns_name_init(&tname, NULL);
+ dns_name_init(&xname, NULL);
+
+ n = dns_name_countlabels(name);
+ count = dns_name_countlabels(prefix);
+ if (dns_name_isabsolute(prefix)) {
+ count--;
+ }
+ if (count == 0) {
+ return;
+ }
+ start = 0;
+ dns_name_toregion(name, &r);
+ length = r.length;
+ if (cctx->arena_off + length < DNS_COMPRESS_ARENA_SIZE) {
+ tmp = &cctx->arena[cctx->arena_off];
+ cctx->arena_off += length;
+ } else {
+ allocated = true;
+ tmp = isc_mem_get(cctx->mctx, length);
+ }
+ /*
+ * Copy name data to 'tmp' and make 'r' use 'tmp'.
+ */
+ memmove(tmp, r.base, r.length);
+ r.base = tmp;
+ dns_name_fromregion(&xname, &r);
+
+ if (count > 2U) {
+ count = 2U;
+ }
+
+ while (count > 0) {
+ unsigned char ch;
+
+ dns_name_getlabelsequence(&xname, start, n, &tname);
+ /*
+ * We calculate the table index using the first
+ * character in the first label of tname.
+ */
+ ch = tname.ndata[1];
+ i = tableindex[ch];
+ tlength = name_length(&tname);
+ toffset = (uint16_t)(offset + (length - tlength));
+ if (toffset >= 0x4000) {
+ break;
+ }
+ /*
+ * Create a new node and add it.
+ */
+ if (cctx->count < DNS_COMPRESS_INITIALNODES) {
+ node = &cctx->initialnodes[cctx->count];
+ } else {
+ node = isc_mem_get(cctx->mctx,
+ sizeof(dns_compressnode_t));
+ }
+ node->count = cctx->count++;
+ /*
+ * 'node->r.base' becomes 'tmp' when start == 0.
+ * Record this by setting 0x8000 so it can be freed later.
+ */
+ if (start == 0 && allocated) {
+ toffset |= 0x8000;
+ }
+ node->offset = toffset;
+ dns_name_toregion(&tname, &node->r);
+ dns_name_init(&node->name, NULL);
+ node->name.length = node->r.length;
+ node->name.ndata = node->r.base;
+ node->name.labels = tname.labels;
+ node->name.attributes = DNS_NAMEATTR_ABSOLUTE;
+ node->next = cctx->table[i];
+ cctx->table[i] = node;
+ start++;
+ n--;
+ count--;
+ }
+
+ if (start == 0) {
+ if (!allocated) {
+ cctx->arena_off -= length;
+ } else {
+ isc_mem_put(cctx->mctx, tmp, length);
+ }
+ }
+}
+
+void
+dns_compress_rollback(dns_compress_t *cctx, uint16_t offset) {
+ unsigned int i;
+ dns_compressnode_t *node;
+
+ REQUIRE(VALID_CCTX(cctx));
+
+ if ((cctx->allowed & DNS_COMPRESS_ENABLED) == 0) {
+ return;
+ }
+
+ for (i = 0; i < DNS_COMPRESS_TABLESIZE; i++) {
+ node = cctx->table[i];
+ /*
+ * This relies on nodes with greater offsets being
+ * closer to the beginning of the list, and the
+ * items with the greatest offsets being at the end
+ * of the initialnodes[] array.
+ */
+ while (node != NULL && (node->offset & 0x7fff) >= offset) {
+ cctx->table[i] = node->next;
+ if ((node->offset & 0x8000) != 0) {
+ isc_mem_put(cctx->mctx, node->r.base,
+ node->r.length);
+ }
+ if (node->count >= DNS_COMPRESS_INITIALNODES) {
+ isc_mem_put(cctx->mctx, node, sizeof(*node));
+ }
+ cctx->count--;
+ node = cctx->table[i];
+ }
+ }
+}
+
+/***
+ *** Decompression
+ ***/
+
+void
+dns_decompress_init(dns_decompress_t *dctx, int edns,
+ dns_decompresstype_t type) {
+ REQUIRE(dctx != NULL);
+ REQUIRE(edns >= -1 && edns <= 255);
+
+ dctx->allowed = DNS_COMPRESS_NONE;
+ dctx->edns = edns;
+ dctx->type = type;
+ dctx->magic = DCTX_MAGIC;
+}
+
+void
+dns_decompress_invalidate(dns_decompress_t *dctx) {
+ REQUIRE(VALID_DCTX(dctx));
+
+ dctx->magic = 0;
+}
+
+void
+dns_decompress_setmethods(dns_decompress_t *dctx, unsigned int allowed) {
+ REQUIRE(VALID_DCTX(dctx));
+
+ switch (dctx->type) {
+ case DNS_DECOMPRESS_ANY:
+ dctx->allowed = DNS_COMPRESS_ALL;
+ break;
+ case DNS_DECOMPRESS_NONE:
+ dctx->allowed = DNS_COMPRESS_NONE;
+ break;
+ case DNS_DECOMPRESS_STRICT:
+ dctx->allowed = allowed;
+ break;
+ }
+}
+
+unsigned int
+dns_decompress_getmethods(dns_decompress_t *dctx) {
+ REQUIRE(VALID_DCTX(dctx));
+
+ return (dctx->allowed);
+}
+
+int
+dns_decompress_edns(dns_decompress_t *dctx) {
+ REQUIRE(VALID_DCTX(dctx));
+
+ return (dctx->edns);
+}
+
+dns_decompresstype_t
+dns_decompress_type(dns_decompress_t *dctx) {
+ REQUIRE(VALID_DCTX(dctx));
+
+ return (dctx->type);
+}
diff --git a/lib/dns/db.c b/lib/dns/db.c
new file mode 100644
index 0000000..c95d19a
--- /dev/null
+++ b/lib/dns/db.c
@@ -0,0 +1,1121 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/*! \file */
+
+/***
+ *** Imports
+ ***/
+
+#include <inttypes.h>
+#include <stdbool.h>
+
+#include <isc/buffer.h>
+#include <isc/mem.h>
+#include <isc/once.h>
+#include <isc/result.h>
+#include <isc/rwlock.h>
+#include <isc/string.h>
+#include <isc/util.h>
+
+#include <dns/callbacks.h>
+#include <dns/clientinfo.h>
+#include <dns/db.h>
+#include <dns/dbiterator.h>
+#include <dns/log.h>
+#include <dns/master.h>
+#include <dns/rdata.h>
+#include <dns/rdataset.h>
+#include <dns/rdatasetiter.h>
+
+/***
+ *** Private Types
+ ***/
+
+struct dns_dbimplementation {
+ const char *name;
+ dns_dbcreatefunc_t create;
+ isc_mem_t *mctx;
+ void *driverarg;
+ ISC_LINK(dns_dbimplementation_t) link;
+};
+
+/***
+ *** Supported DB Implementations Registry
+ ***/
+
+/*
+ * Built in database implementations are registered here.
+ */
+
+#include "rbtdb.h"
+
+unsigned int dns_pps = 0U;
+
+static ISC_LIST(dns_dbimplementation_t) implementations;
+static isc_rwlock_t implock;
+static isc_once_t once = ISC_ONCE_INIT;
+
+static dns_dbimplementation_t rbtimp;
+
+static void
+initialize(void) {
+ isc_rwlock_init(&implock, 0, 0);
+
+ rbtimp.name = "rbt";
+ rbtimp.create = dns_rbtdb_create;
+ rbtimp.mctx = NULL;
+ rbtimp.driverarg = NULL;
+ ISC_LINK_INIT(&rbtimp, link);
+
+ ISC_LIST_INIT(implementations);
+ ISC_LIST_APPEND(implementations, &rbtimp, link);
+}
+
+static dns_dbimplementation_t *
+impfind(const char *name) {
+ dns_dbimplementation_t *imp;
+
+ for (imp = ISC_LIST_HEAD(implementations); imp != NULL;
+ imp = ISC_LIST_NEXT(imp, link))
+ {
+ if (strcasecmp(name, imp->name) == 0) {
+ return (imp);
+ }
+ }
+ return (NULL);
+}
+
+/***
+ *** Basic DB Methods
+ ***/
+
+isc_result_t
+dns_db_create(isc_mem_t *mctx, const char *db_type, const dns_name_t *origin,
+ dns_dbtype_t type, dns_rdataclass_t rdclass, unsigned int argc,
+ char *argv[], dns_db_t **dbp) {
+ dns_dbimplementation_t *impinfo;
+
+ RUNTIME_CHECK(isc_once_do(&once, initialize) == ISC_R_SUCCESS);
+
+ /*
+ * Create a new database using implementation 'db_type'.
+ */
+
+ REQUIRE(dbp != NULL && *dbp == NULL);
+ REQUIRE(dns_name_isabsolute(origin));
+
+ RWLOCK(&implock, isc_rwlocktype_read);
+ impinfo = impfind(db_type);
+ if (impinfo != NULL) {
+ isc_result_t result;
+ result = ((impinfo->create)(mctx, origin, type, rdclass, argc,
+ argv, impinfo->driverarg, dbp));
+ RWUNLOCK(&implock, isc_rwlocktype_read);
+ return (result);
+ }
+
+ RWUNLOCK(&implock, isc_rwlocktype_read);
+
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, DNS_LOGMODULE_DB,
+ ISC_LOG_ERROR, "unsupported database type '%s'", db_type);
+
+ return (ISC_R_NOTFOUND);
+}
+
+void
+dns_db_attach(dns_db_t *source, dns_db_t **targetp) {
+ /*
+ * Attach *targetp to source.
+ */
+
+ REQUIRE(DNS_DB_VALID(source));
+ REQUIRE(targetp != NULL && *targetp == NULL);
+
+ (source->methods->attach)(source, targetp);
+
+ ENSURE(*targetp == source);
+}
+
+void
+dns_db_detach(dns_db_t **dbp) {
+ /*
+ * Detach *dbp from its database.
+ */
+
+ REQUIRE(dbp != NULL);
+ REQUIRE(DNS_DB_VALID(*dbp));
+
+ ((*dbp)->methods->detach)(dbp);
+
+ ENSURE(*dbp == NULL);
+}
+
+bool
+dns_db_iscache(dns_db_t *db) {
+ /*
+ * Does 'db' have cache semantics?
+ */
+
+ REQUIRE(DNS_DB_VALID(db));
+
+ if ((db->attributes & DNS_DBATTR_CACHE) != 0) {
+ return (true);
+ }
+
+ return (false);
+}
+
+bool
+dns_db_iszone(dns_db_t *db) {
+ /*
+ * Does 'db' have zone semantics?
+ */
+
+ REQUIRE(DNS_DB_VALID(db));
+
+ if ((db->attributes & (DNS_DBATTR_CACHE | DNS_DBATTR_STUB)) == 0) {
+ return (true);
+ }
+
+ return (false);
+}
+
+bool
+dns_db_isstub(dns_db_t *db) {
+ /*
+ * Does 'db' have stub semantics?
+ */
+
+ REQUIRE(DNS_DB_VALID(db));
+
+ if ((db->attributes & DNS_DBATTR_STUB) != 0) {
+ return (true);
+ }
+
+ return (false);
+}
+
+bool
+dns_db_isdnssec(dns_db_t *db) {
+ /*
+ * Is 'db' secure or partially secure?
+ */
+
+ REQUIRE(DNS_DB_VALID(db));
+ REQUIRE((db->attributes & DNS_DBATTR_CACHE) == 0);
+
+ if (db->methods->isdnssec != NULL) {
+ return ((db->methods->isdnssec)(db));
+ }
+ return ((db->methods->issecure)(db));
+}
+
+bool
+dns_db_issecure(dns_db_t *db) {
+ /*
+ * Is 'db' secure?
+ */
+
+ REQUIRE(DNS_DB_VALID(db));
+ REQUIRE((db->attributes & DNS_DBATTR_CACHE) == 0);
+
+ return ((db->methods->issecure)(db));
+}
+
+bool
+dns_db_ispersistent(dns_db_t *db) {
+ /*
+ * Is 'db' persistent?
+ */
+
+ REQUIRE(DNS_DB_VALID(db));
+
+ return ((db->methods->ispersistent)(db));
+}
+
+dns_name_t *
+dns_db_origin(dns_db_t *db) {
+ /*
+ * The origin of the database.
+ */
+
+ REQUIRE(DNS_DB_VALID(db));
+
+ return (&db->origin);
+}
+
+dns_rdataclass_t
+dns_db_class(dns_db_t *db) {
+ /*
+ * The class of the database.
+ */
+
+ REQUIRE(DNS_DB_VALID(db));
+
+ return (db->rdclass);
+}
+
+isc_result_t
+dns_db_beginload(dns_db_t *db, dns_rdatacallbacks_t *callbacks) {
+ /*
+ * Begin loading 'db'.
+ */
+
+ REQUIRE(DNS_DB_VALID(db));
+ REQUIRE(DNS_CALLBACK_VALID(callbacks));
+
+ return ((db->methods->beginload)(db, callbacks));
+}
+
+isc_result_t
+dns_db_endload(dns_db_t *db, dns_rdatacallbacks_t *callbacks) {
+ dns_dbonupdatelistener_t *listener;
+
+ /*
+ * Finish loading 'db'.
+ */
+
+ REQUIRE(DNS_DB_VALID(db));
+ REQUIRE(DNS_CALLBACK_VALID(callbacks));
+ REQUIRE(callbacks->add_private != NULL);
+
+ for (listener = ISC_LIST_HEAD(db->update_listeners); listener != NULL;
+ listener = ISC_LIST_NEXT(listener, link))
+ {
+ listener->onupdate(db, listener->onupdate_arg);
+ }
+
+ return ((db->methods->endload)(db, callbacks));
+}
+
+isc_result_t
+dns_db_load(dns_db_t *db, const char *filename, dns_masterformat_t format,
+ unsigned int options) {
+ isc_result_t result, eresult;
+ dns_rdatacallbacks_t callbacks;
+
+ /*
+ * Load master file 'filename' into 'db'.
+ */
+
+ REQUIRE(DNS_DB_VALID(db));
+
+ if ((db->attributes & DNS_DBATTR_CACHE) != 0) {
+ options |= DNS_MASTER_AGETTL;
+ }
+
+ dns_rdatacallbacks_init(&callbacks);
+ result = dns_db_beginload(db, &callbacks);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+ result = dns_master_loadfile(filename, &db->origin, &db->origin,
+ db->rdclass, options, 0, &callbacks, NULL,
+ NULL, db->mctx, format, 0);
+ eresult = dns_db_endload(db, &callbacks);
+ /*
+ * We always call dns_db_endload(), but we only want to return its
+ * result if dns_master_loadfile() succeeded. If dns_master_loadfile()
+ * failed, we want to return the result code it gave us.
+ */
+ if (eresult != ISC_R_SUCCESS &&
+ (result == ISC_R_SUCCESS || result == DNS_R_SEENINCLUDE))
+ {
+ result = eresult;
+ }
+
+ return (result);
+}
+
+isc_result_t
+dns_db_dump(dns_db_t *db, dns_dbversion_t *version, const char *filename) {
+ return ((db->methods->dump)(db, version, filename,
+ dns_masterformat_text));
+}
+
+/***
+ *** Version Methods
+ ***/
+
+void
+dns_db_currentversion(dns_db_t *db, dns_dbversion_t **versionp) {
+ /*
+ * Open the current version for reading.
+ */
+
+ REQUIRE(DNS_DB_VALID(db));
+ REQUIRE((db->attributes & DNS_DBATTR_CACHE) == 0);
+ REQUIRE(versionp != NULL && *versionp == NULL);
+
+ (db->methods->currentversion)(db, versionp);
+}
+
+isc_result_t
+dns_db_newversion(dns_db_t *db, dns_dbversion_t **versionp) {
+ /*
+ * Open a new version for reading and writing.
+ */
+
+ REQUIRE(DNS_DB_VALID(db));
+ REQUIRE((db->attributes & DNS_DBATTR_CACHE) == 0);
+ REQUIRE(versionp != NULL && *versionp == NULL);
+
+ return ((db->methods->newversion)(db, versionp));
+}
+
+void
+dns_db_attachversion(dns_db_t *db, dns_dbversion_t *source,
+ dns_dbversion_t **targetp) {
+ /*
+ * Attach '*targetp' to 'source'.
+ */
+
+ REQUIRE(DNS_DB_VALID(db));
+ REQUIRE((db->attributes & DNS_DBATTR_CACHE) == 0);
+ REQUIRE(source != NULL);
+ REQUIRE(targetp != NULL && *targetp == NULL);
+
+ (db->methods->attachversion)(db, source, targetp);
+
+ ENSURE(*targetp != NULL);
+}
+
+void
+dns_db_closeversion(dns_db_t *db, dns_dbversion_t **versionp, bool commit) {
+ dns_dbonupdatelistener_t *listener;
+
+ /*
+ * Close version '*versionp'.
+ */
+
+ REQUIRE(DNS_DB_VALID(db));
+ REQUIRE((db->attributes & DNS_DBATTR_CACHE) == 0);
+ REQUIRE(versionp != NULL && *versionp != NULL);
+
+ (db->methods->closeversion)(db, versionp, commit);
+
+ if (commit) {
+ for (listener = ISC_LIST_HEAD(db->update_listeners);
+ listener != NULL; listener = ISC_LIST_NEXT(listener, link))
+ {
+ listener->onupdate(db, listener->onupdate_arg);
+ }
+ }
+
+ ENSURE(*versionp == NULL);
+}
+
+/***
+ *** Node Methods
+ ***/
+
+isc_result_t
+dns_db_findnode(dns_db_t *db, const dns_name_t *name, bool create,
+ dns_dbnode_t **nodep) {
+ /*
+ * Find the node with name 'name'.
+ */
+
+ REQUIRE(DNS_DB_VALID(db));
+ REQUIRE(nodep != NULL && *nodep == NULL);
+
+ if (db->methods->findnode != NULL) {
+ return ((db->methods->findnode)(db, name, create, nodep));
+ } else {
+ return ((db->methods->findnodeext)(db, name, create, NULL, NULL,
+ nodep));
+ }
+}
+
+isc_result_t
+dns_db_findnodeext(dns_db_t *db, const dns_name_t *name, bool create,
+ dns_clientinfomethods_t *methods,
+ dns_clientinfo_t *clientinfo, dns_dbnode_t **nodep) {
+ /*
+ * Find the node with name 'name', passing 'arg' to the database
+ * implementation.
+ */
+
+ REQUIRE(DNS_DB_VALID(db));
+ REQUIRE(nodep != NULL && *nodep == NULL);
+
+ if (db->methods->findnodeext != NULL) {
+ return ((db->methods->findnodeext)(db, name, create, methods,
+ clientinfo, nodep));
+ } else {
+ return ((db->methods->findnode)(db, name, create, nodep));
+ }
+}
+
+isc_result_t
+dns_db_findnsec3node(dns_db_t *db, const dns_name_t *name, bool create,
+ dns_dbnode_t **nodep) {
+ /*
+ * Find the node with name 'name'.
+ */
+
+ REQUIRE(DNS_DB_VALID(db));
+ REQUIRE(nodep != NULL && *nodep == NULL);
+
+ return ((db->methods->findnsec3node)(db, name, create, nodep));
+}
+
+isc_result_t
+dns_db_find(dns_db_t *db, const dns_name_t *name, dns_dbversion_t *version,
+ dns_rdatatype_t type, unsigned int options, isc_stdtime_t now,
+ dns_dbnode_t **nodep, dns_name_t *foundname,
+ dns_rdataset_t *rdataset, dns_rdataset_t *sigrdataset) {
+ /*
+ * Find the best match for 'name' and 'type' in version 'version'
+ * of 'db'.
+ */
+
+ REQUIRE(DNS_DB_VALID(db));
+ REQUIRE(type != dns_rdatatype_rrsig);
+ REQUIRE(nodep == NULL || *nodep == NULL);
+ REQUIRE(dns_name_hasbuffer(foundname));
+ REQUIRE(rdataset == NULL || (DNS_RDATASET_VALID(rdataset) &&
+ !dns_rdataset_isassociated(rdataset)));
+ REQUIRE(sigrdataset == NULL ||
+ (DNS_RDATASET_VALID(sigrdataset) &&
+ !dns_rdataset_isassociated(sigrdataset)));
+
+ if (db->methods->find != NULL) {
+ return ((db->methods->find)(db, name, version, type, options,
+ now, nodep, foundname, rdataset,
+ sigrdataset));
+ } else {
+ return ((db->methods->findext)(db, name, version, type, options,
+ now, nodep, foundname, NULL,
+ NULL, rdataset, sigrdataset));
+ }
+}
+
+isc_result_t
+dns_db_findext(dns_db_t *db, const dns_name_t *name, dns_dbversion_t *version,
+ dns_rdatatype_t type, unsigned int options, isc_stdtime_t now,
+ dns_dbnode_t **nodep, dns_name_t *foundname,
+ dns_clientinfomethods_t *methods, dns_clientinfo_t *clientinfo,
+ dns_rdataset_t *rdataset, dns_rdataset_t *sigrdataset) {
+ /*
+ * Find the best match for 'name' and 'type' in version 'version'
+ * of 'db', passing in 'arg'.
+ */
+
+ REQUIRE(DNS_DB_VALID(db));
+ REQUIRE(type != dns_rdatatype_rrsig);
+ REQUIRE(nodep == NULL || *nodep == NULL);
+ REQUIRE(dns_name_hasbuffer(foundname));
+ REQUIRE(rdataset == NULL || (DNS_RDATASET_VALID(rdataset) &&
+ !dns_rdataset_isassociated(rdataset)));
+ REQUIRE(sigrdataset == NULL ||
+ (DNS_RDATASET_VALID(sigrdataset) &&
+ !dns_rdataset_isassociated(sigrdataset)));
+
+ if (db->methods->findext != NULL) {
+ return ((db->methods->findext)(
+ db, name, version, type, options, now, nodep, foundname,
+ methods, clientinfo, rdataset, sigrdataset));
+ } else {
+ return ((db->methods->find)(db, name, version, type, options,
+ now, nodep, foundname, rdataset,
+ sigrdataset));
+ }
+}
+
+isc_result_t
+dns_db_findzonecut(dns_db_t *db, const dns_name_t *name, unsigned int options,
+ isc_stdtime_t now, dns_dbnode_t **nodep,
+ dns_name_t *foundname, dns_name_t *dcname,
+ dns_rdataset_t *rdataset, dns_rdataset_t *sigrdataset) {
+ /*
+ * Find the deepest known zonecut which encloses 'name' in 'db'.
+ * foundname is the zonecut, dcname is the deepest name we have
+ * in database that is part of queried name.
+ */
+
+ REQUIRE(DNS_DB_VALID(db));
+ REQUIRE((db->attributes & DNS_DBATTR_CACHE) != 0);
+ REQUIRE(nodep == NULL || *nodep == NULL);
+ REQUIRE(dns_name_hasbuffer(foundname));
+ REQUIRE(sigrdataset == NULL ||
+ (DNS_RDATASET_VALID(sigrdataset) &&
+ !dns_rdataset_isassociated(sigrdataset)));
+
+ return ((db->methods->findzonecut)(db, name, options, now, nodep,
+ foundname, dcname, rdataset,
+ sigrdataset));
+}
+
+void
+dns_db_attachnode(dns_db_t *db, dns_dbnode_t *source, dns_dbnode_t **targetp) {
+ /*
+ * Attach *targetp to source.
+ */
+
+ REQUIRE(DNS_DB_VALID(db));
+ REQUIRE(source != NULL);
+ REQUIRE(targetp != NULL && *targetp == NULL);
+
+ (db->methods->attachnode)(db, source, targetp);
+}
+
+void
+dns_db_detachnode(dns_db_t *db, dns_dbnode_t **nodep) {
+ /*
+ * Detach *nodep from its node.
+ */
+
+ REQUIRE(DNS_DB_VALID(db));
+ REQUIRE(nodep != NULL && *nodep != NULL);
+
+ (db->methods->detachnode)(db, nodep);
+
+ ENSURE(*nodep == NULL);
+}
+
+void
+dns_db_transfernode(dns_db_t *db, dns_dbnode_t **sourcep,
+ dns_dbnode_t **targetp) {
+ REQUIRE(DNS_DB_VALID(db));
+ REQUIRE(targetp != NULL && *targetp == NULL);
+ /*
+ * This doesn't check the implementation magic. If we find that
+ * we need such checks in future then this will be done in the
+ * method.
+ */
+ REQUIRE(sourcep != NULL && *sourcep != NULL);
+
+ UNUSED(db);
+
+ if (db->methods->transfernode == NULL) {
+ *targetp = *sourcep;
+ *sourcep = NULL;
+ } else {
+ (db->methods->transfernode)(db, sourcep, targetp);
+ }
+
+ ENSURE(*sourcep == NULL);
+}
+
+isc_result_t
+dns_db_expirenode(dns_db_t *db, dns_dbnode_t *node, isc_stdtime_t now) {
+ /*
+ * Mark as stale all records at 'node' which expire at or before 'now'.
+ */
+
+ REQUIRE(DNS_DB_VALID(db));
+ REQUIRE((db->attributes & DNS_DBATTR_CACHE) != 0);
+ REQUIRE(node != NULL);
+
+ return ((db->methods->expirenode)(db, node, now));
+}
+
+void
+dns_db_printnode(dns_db_t *db, dns_dbnode_t *node, FILE *out) {
+ /*
+ * Print a textual representation of the contents of the node to
+ * 'out'.
+ */
+
+ REQUIRE(DNS_DB_VALID(db));
+ REQUIRE(node != NULL);
+
+ (db->methods->printnode)(db, node, out);
+}
+
+/***
+ *** DB Iterator Creation
+ ***/
+
+isc_result_t
+dns_db_createiterator(dns_db_t *db, unsigned int flags,
+ dns_dbiterator_t **iteratorp) {
+ /*
+ * Create an iterator for version 'version' of 'db'.
+ */
+
+ REQUIRE(DNS_DB_VALID(db));
+ REQUIRE(iteratorp != NULL && *iteratorp == NULL);
+
+ return (db->methods->createiterator(db, flags, iteratorp));
+}
+
+/***
+ *** Rdataset Methods
+ ***/
+
+isc_result_t
+dns_db_findrdataset(dns_db_t *db, dns_dbnode_t *node, dns_dbversion_t *version,
+ dns_rdatatype_t type, dns_rdatatype_t covers,
+ isc_stdtime_t now, dns_rdataset_t *rdataset,
+ dns_rdataset_t *sigrdataset) {
+ REQUIRE(DNS_DB_VALID(db));
+ REQUIRE(node != NULL);
+ REQUIRE(DNS_RDATASET_VALID(rdataset));
+ REQUIRE(!dns_rdataset_isassociated(rdataset));
+ REQUIRE(covers == 0 || type == dns_rdatatype_rrsig);
+ REQUIRE(type != dns_rdatatype_any);
+ REQUIRE(sigrdataset == NULL ||
+ (DNS_RDATASET_VALID(sigrdataset) &&
+ !dns_rdataset_isassociated(sigrdataset)));
+
+ return ((db->methods->findrdataset)(db, node, version, type, covers,
+ now, rdataset, sigrdataset));
+}
+
+isc_result_t
+dns_db_allrdatasets(dns_db_t *db, dns_dbnode_t *node, dns_dbversion_t *version,
+ unsigned int options, isc_stdtime_t now,
+ dns_rdatasetiter_t **iteratorp) {
+ /*
+ * Make '*iteratorp' an rdataset iteratator for all rdatasets at
+ * 'node' in version 'version' of 'db'.
+ */
+
+ REQUIRE(DNS_DB_VALID(db));
+ REQUIRE(iteratorp != NULL && *iteratorp == NULL);
+
+ return ((db->methods->allrdatasets)(db, node, version, options, now,
+ iteratorp));
+}
+
+isc_result_t
+dns_db_addrdataset(dns_db_t *db, dns_dbnode_t *node, dns_dbversion_t *version,
+ isc_stdtime_t now, dns_rdataset_t *rdataset,
+ unsigned int options, dns_rdataset_t *addedrdataset) {
+ /*
+ * Add 'rdataset' to 'node' in version 'version' of 'db'.
+ */
+
+ REQUIRE(DNS_DB_VALID(db));
+ REQUIRE(node != NULL);
+ REQUIRE(((db->attributes & DNS_DBATTR_CACHE) == 0 && version != NULL) ||
+ ((db->attributes & DNS_DBATTR_CACHE) != 0 && version == NULL &&
+ (options & DNS_DBADD_MERGE) == 0));
+ REQUIRE((options & DNS_DBADD_EXACT) == 0 ||
+ (options & DNS_DBADD_MERGE) != 0);
+ REQUIRE(DNS_RDATASET_VALID(rdataset));
+ REQUIRE(dns_rdataset_isassociated(rdataset));
+ REQUIRE(rdataset->rdclass == db->rdclass);
+ REQUIRE(addedrdataset == NULL ||
+ (DNS_RDATASET_VALID(addedrdataset) &&
+ !dns_rdataset_isassociated(addedrdataset)));
+
+ return ((db->methods->addrdataset)(db, node, version, now, rdataset,
+ options, addedrdataset));
+}
+
+isc_result_t
+dns_db_subtractrdataset(dns_db_t *db, dns_dbnode_t *node,
+ dns_dbversion_t *version, dns_rdataset_t *rdataset,
+ unsigned int options, dns_rdataset_t *newrdataset) {
+ /*
+ * Remove any rdata in 'rdataset' from 'node' in version 'version' of
+ * 'db'.
+ */
+
+ REQUIRE(DNS_DB_VALID(db));
+ REQUIRE(node != NULL);
+ REQUIRE((db->attributes & DNS_DBATTR_CACHE) == 0 && version != NULL);
+ REQUIRE(DNS_RDATASET_VALID(rdataset));
+ REQUIRE(dns_rdataset_isassociated(rdataset));
+ REQUIRE(rdataset->rdclass == db->rdclass);
+ REQUIRE(newrdataset == NULL ||
+ (DNS_RDATASET_VALID(newrdataset) &&
+ !dns_rdataset_isassociated(newrdataset)));
+
+ return ((db->methods->subtractrdataset)(db, node, version, rdataset,
+ options, newrdataset));
+}
+
+isc_result_t
+dns_db_deleterdataset(dns_db_t *db, dns_dbnode_t *node,
+ dns_dbversion_t *version, dns_rdatatype_t type,
+ dns_rdatatype_t covers) {
+ /*
+ * Make it so that no rdataset of type 'type' exists at 'node' in
+ * version version 'version' of 'db'.
+ */
+
+ REQUIRE(DNS_DB_VALID(db));
+ REQUIRE(node != NULL);
+ REQUIRE(((db->attributes & DNS_DBATTR_CACHE) == 0 && version != NULL) ||
+ ((db->attributes & DNS_DBATTR_CACHE) != 0 && version == NULL));
+
+ return ((db->methods->deleterdataset)(db, node, version, type, covers));
+}
+
+void
+dns_db_overmem(dns_db_t *db, bool overmem) {
+ REQUIRE(DNS_DB_VALID(db));
+
+ (db->methods->overmem)(db, overmem);
+}
+
+isc_result_t
+dns_db_getsoaserial(dns_db_t *db, dns_dbversion_t *ver, uint32_t *serialp) {
+ isc_result_t result;
+ dns_dbnode_t *node = NULL;
+ dns_rdataset_t rdataset;
+ dns_rdata_t rdata = DNS_RDATA_INIT;
+ isc_buffer_t buffer;
+
+ REQUIRE(dns_db_iszone(db) || dns_db_isstub(db));
+
+ result = dns_db_findnode(db, dns_db_origin(db), false, &node);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+
+ dns_rdataset_init(&rdataset);
+ result = dns_db_findrdataset(db, node, ver, dns_rdatatype_soa, 0,
+ (isc_stdtime_t)0, &rdataset, NULL);
+ if (result != ISC_R_SUCCESS) {
+ goto freenode;
+ }
+
+ result = dns_rdataset_first(&rdataset);
+ if (result != ISC_R_SUCCESS) {
+ goto freerdataset;
+ }
+ dns_rdataset_current(&rdataset, &rdata);
+ result = dns_rdataset_next(&rdataset);
+ INSIST(result == ISC_R_NOMORE);
+
+ INSIST(rdata.length > 20);
+ isc_buffer_init(&buffer, rdata.data, rdata.length);
+ isc_buffer_add(&buffer, rdata.length);
+ isc_buffer_forward(&buffer, rdata.length - 20);
+ *serialp = isc_buffer_getuint32(&buffer);
+
+ result = ISC_R_SUCCESS;
+
+freerdataset:
+ dns_rdataset_disassociate(&rdataset);
+
+freenode:
+ dns_db_detachnode(db, &node);
+ return (result);
+}
+
+unsigned int
+dns_db_nodecount(dns_db_t *db, dns_dbtree_t tree) {
+ REQUIRE(DNS_DB_VALID(db));
+
+ return ((db->methods->nodecount)(db, tree));
+}
+
+size_t
+dns_db_hashsize(dns_db_t *db) {
+ REQUIRE(DNS_DB_VALID(db));
+
+ if (db->methods->hashsize == NULL) {
+ return (0);
+ }
+
+ return ((db->methods->hashsize)(db));
+}
+
+void
+dns_db_settask(dns_db_t *db, isc_task_t *task) {
+ REQUIRE(DNS_DB_VALID(db));
+
+ (db->methods->settask)(db, task);
+}
+
+isc_result_t
+dns_db_register(const char *name, dns_dbcreatefunc_t create, void *driverarg,
+ isc_mem_t *mctx, dns_dbimplementation_t **dbimp) {
+ dns_dbimplementation_t *imp;
+
+ REQUIRE(name != NULL);
+ REQUIRE(dbimp != NULL && *dbimp == NULL);
+
+ RUNTIME_CHECK(isc_once_do(&once, initialize) == ISC_R_SUCCESS);
+
+ RWLOCK(&implock, isc_rwlocktype_write);
+ imp = impfind(name);
+ if (imp != NULL) {
+ RWUNLOCK(&implock, isc_rwlocktype_write);
+ return (ISC_R_EXISTS);
+ }
+
+ imp = isc_mem_get(mctx, sizeof(dns_dbimplementation_t));
+ imp->name = name;
+ imp->create = create;
+ imp->mctx = NULL;
+ imp->driverarg = driverarg;
+ isc_mem_attach(mctx, &imp->mctx);
+ ISC_LINK_INIT(imp, link);
+ ISC_LIST_APPEND(implementations, imp, link);
+ RWUNLOCK(&implock, isc_rwlocktype_write);
+
+ *dbimp = imp;
+
+ return (ISC_R_SUCCESS);
+}
+
+void
+dns_db_unregister(dns_dbimplementation_t **dbimp) {
+ dns_dbimplementation_t *imp;
+
+ REQUIRE(dbimp != NULL && *dbimp != NULL);
+
+ RUNTIME_CHECK(isc_once_do(&once, initialize) == ISC_R_SUCCESS);
+
+ imp = *dbimp;
+ *dbimp = NULL;
+ RWLOCK(&implock, isc_rwlocktype_write);
+ ISC_LIST_UNLINK(implementations, imp, link);
+ isc_mem_putanddetach(&imp->mctx, imp, sizeof(dns_dbimplementation_t));
+ RWUNLOCK(&implock, isc_rwlocktype_write);
+ ENSURE(*dbimp == NULL);
+}
+
+isc_result_t
+dns_db_getoriginnode(dns_db_t *db, dns_dbnode_t **nodep) {
+ REQUIRE(DNS_DB_VALID(db));
+ REQUIRE(dns_db_iszone(db));
+ REQUIRE(nodep != NULL && *nodep == NULL);
+
+ if (db->methods->getoriginnode != NULL) {
+ return ((db->methods->getoriginnode)(db, nodep));
+ }
+
+ return (ISC_R_NOTFOUND);
+}
+
+dns_stats_t *
+dns_db_getrrsetstats(dns_db_t *db) {
+ REQUIRE(DNS_DB_VALID(db));
+
+ if (db->methods->getrrsetstats != NULL) {
+ return ((db->methods->getrrsetstats)(db));
+ }
+
+ return (NULL);
+}
+
+isc_result_t
+dns_db_setcachestats(dns_db_t *db, isc_stats_t *stats) {
+ REQUIRE(DNS_DB_VALID(db));
+
+ if (db->methods->setcachestats != NULL) {
+ return ((db->methods->setcachestats)(db, stats));
+ }
+
+ return (ISC_R_NOTIMPLEMENTED);
+}
+
+isc_result_t
+dns_db_getnsec3parameters(dns_db_t *db, dns_dbversion_t *version,
+ dns_hash_t *hash, uint8_t *flags,
+ uint16_t *iterations, unsigned char *salt,
+ size_t *salt_length) {
+ REQUIRE(DNS_DB_VALID(db));
+ REQUIRE(dns_db_iszone(db));
+
+ if (db->methods->getnsec3parameters != NULL) {
+ return ((db->methods->getnsec3parameters)(db, version, hash,
+ flags, iterations,
+ salt, salt_length));
+ }
+
+ return (ISC_R_NOTFOUND);
+}
+
+isc_result_t
+dns_db_getsize(dns_db_t *db, dns_dbversion_t *version, uint64_t *records,
+ uint64_t *bytes) {
+ REQUIRE(DNS_DB_VALID(db));
+ REQUIRE(dns_db_iszone(db));
+
+ if (db->methods->getsize != NULL) {
+ return ((db->methods->getsize)(db, version, records, bytes));
+ }
+
+ return (ISC_R_NOTFOUND);
+}
+
+isc_result_t
+dns_db_setsigningtime(dns_db_t *db, dns_rdataset_t *rdataset,
+ isc_stdtime_t resign) {
+ if (db->methods->setsigningtime != NULL) {
+ return ((db->methods->setsigningtime)(db, rdataset, resign));
+ }
+ return (ISC_R_NOTIMPLEMENTED);
+}
+
+isc_result_t
+dns_db_getsigningtime(dns_db_t *db, dns_rdataset_t *rdataset,
+ dns_name_t *name) {
+ if (db->methods->getsigningtime != NULL) {
+ return ((db->methods->getsigningtime)(db, rdataset, name));
+ }
+ return (ISC_R_NOTFOUND);
+}
+
+void
+dns_db_resigned(dns_db_t *db, dns_rdataset_t *rdataset,
+ dns_dbversion_t *version) {
+ if (db->methods->resigned != NULL) {
+ (db->methods->resigned)(db, rdataset, version);
+ }
+}
+
+/*
+ * Attach a database to policy zone databases.
+ * This should only happen when the caller has already ensured that
+ * it is dealing with a database that understands response policy zones.
+ */
+void
+dns_db_rpz_attach(dns_db_t *db, void *rpzs, uint8_t rpz_num) {
+ REQUIRE(db->methods->rpz_attach != NULL);
+ (db->methods->rpz_attach)(db, rpzs, rpz_num);
+}
+
+/*
+ * Finish loading a response policy zone.
+ */
+isc_result_t
+dns_db_rpz_ready(dns_db_t *db) {
+ if (db->methods->rpz_ready == NULL) {
+ return (ISC_R_SUCCESS);
+ }
+ return ((db->methods->rpz_ready)(db));
+}
+
+/*
+ * Attach a notify-on-update function the database
+ */
+isc_result_t
+dns_db_updatenotify_register(dns_db_t *db, dns_dbupdate_callback_t fn,
+ void *fn_arg) {
+ dns_dbonupdatelistener_t *listener;
+
+ REQUIRE(db != NULL);
+ REQUIRE(fn != NULL);
+
+ for (listener = ISC_LIST_HEAD(db->update_listeners); listener != NULL;
+ listener = ISC_LIST_NEXT(listener, link))
+ {
+ if ((listener->onupdate == fn) &&
+ (listener->onupdate_arg == fn_arg))
+ {
+ return (ISC_R_SUCCESS);
+ }
+ }
+
+ listener = isc_mem_get(db->mctx, sizeof(dns_dbonupdatelistener_t));
+
+ listener->onupdate = fn;
+ listener->onupdate_arg = fn_arg;
+
+ ISC_LINK_INIT(listener, link);
+ ISC_LIST_APPEND(db->update_listeners, listener, link);
+
+ return (ISC_R_SUCCESS);
+}
+
+isc_result_t
+dns_db_updatenotify_unregister(dns_db_t *db, dns_dbupdate_callback_t fn,
+ void *fn_arg) {
+ dns_dbonupdatelistener_t *listener;
+
+ REQUIRE(db != NULL);
+
+ for (listener = ISC_LIST_HEAD(db->update_listeners); listener != NULL;
+ listener = ISC_LIST_NEXT(listener, link))
+ {
+ if ((listener->onupdate == fn) &&
+ (listener->onupdate_arg == fn_arg))
+ {
+ ISC_LIST_UNLINK(db->update_listeners, listener, link);
+ isc_mem_put(db->mctx, listener,
+ sizeof(dns_dbonupdatelistener_t));
+ return (ISC_R_SUCCESS);
+ }
+ }
+
+ return (ISC_R_NOTFOUND);
+}
+
+isc_result_t
+dns_db_nodefullname(dns_db_t *db, dns_dbnode_t *node, dns_name_t *name) {
+ REQUIRE(db != NULL);
+ REQUIRE(node != NULL);
+ REQUIRE(name != NULL);
+
+ if (db->methods->nodefullname == NULL) {
+ return (ISC_R_NOTIMPLEMENTED);
+ }
+ return ((db->methods->nodefullname)(db, node, name));
+}
+
+isc_result_t
+dns_db_setservestalettl(dns_db_t *db, dns_ttl_t ttl) {
+ REQUIRE(DNS_DB_VALID(db));
+ REQUIRE((db->attributes & DNS_DBATTR_CACHE) != 0);
+
+ if (db->methods->setservestalettl != NULL) {
+ return ((db->methods->setservestalettl)(db, ttl));
+ }
+ return (ISC_R_NOTIMPLEMENTED);
+}
+
+isc_result_t
+dns_db_getservestalettl(dns_db_t *db, dns_ttl_t *ttl) {
+ REQUIRE(DNS_DB_VALID(db));
+ REQUIRE((db->attributes & DNS_DBATTR_CACHE) != 0);
+
+ if (db->methods->getservestalettl != NULL) {
+ return ((db->methods->getservestalettl)(db, ttl));
+ }
+ return (ISC_R_NOTIMPLEMENTED);
+}
+
+isc_result_t
+dns_db_setservestalerefresh(dns_db_t *db, uint32_t interval) {
+ REQUIRE(DNS_DB_VALID(db));
+ REQUIRE((db->attributes & DNS_DBATTR_CACHE) != 0);
+
+ if (db->methods->setservestalerefresh != NULL) {
+ return ((db->methods->setservestalerefresh)(db, interval));
+ }
+ return (ISC_R_NOTIMPLEMENTED);
+}
+
+isc_result_t
+dns_db_getservestalerefresh(dns_db_t *db, uint32_t *interval) {
+ REQUIRE(DNS_DB_VALID(db));
+ REQUIRE((db->attributes & DNS_DBATTR_CACHE) != 0);
+
+ if (db->methods->getservestalerefresh != NULL) {
+ return ((db->methods->getservestalerefresh)(db, interval));
+ }
+ return (ISC_R_NOTIMPLEMENTED);
+}
+
+isc_result_t
+dns_db_setgluecachestats(dns_db_t *db, isc_stats_t *stats) {
+ REQUIRE(dns_db_iszone(db));
+ REQUIRE(stats != NULL);
+
+ if (db->methods->setgluecachestats != NULL) {
+ return ((db->methods->setgluecachestats)(db, stats));
+ }
+
+ return (ISC_R_NOTIMPLEMENTED);
+}
diff --git a/lib/dns/dbiterator.c b/lib/dns/dbiterator.c
new file mode 100644
index 0000000..39d9471
--- /dev/null
+++ b/lib/dns/dbiterator.c
@@ -0,0 +1,135 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/*! \file */
+
+#include <stdbool.h>
+
+#include <isc/util.h>
+
+#include <dns/dbiterator.h>
+#include <dns/name.h>
+
+void
+dns_dbiterator_destroy(dns_dbiterator_t **iteratorp) {
+ /*
+ * Destroy '*iteratorp'.
+ */
+
+ REQUIRE(iteratorp != NULL);
+ REQUIRE(DNS_DBITERATOR_VALID(*iteratorp));
+
+ (*iteratorp)->methods->destroy(iteratorp);
+
+ ENSURE(*iteratorp == NULL);
+}
+
+isc_result_t
+dns_dbiterator_first(dns_dbiterator_t *iterator) {
+ /*
+ * Move the node cursor to the first node in the database (if any).
+ */
+
+ REQUIRE(DNS_DBITERATOR_VALID(iterator));
+
+ return (iterator->methods->first(iterator));
+}
+
+isc_result_t
+dns_dbiterator_last(dns_dbiterator_t *iterator) {
+ /*
+ * Move the node cursor to the first node in the database (if any).
+ */
+
+ REQUIRE(DNS_DBITERATOR_VALID(iterator));
+
+ return (iterator->methods->last(iterator));
+}
+
+isc_result_t
+dns_dbiterator_seek(dns_dbiterator_t *iterator, const dns_name_t *name) {
+ /*
+ * Move the node cursor to the node with name 'name'.
+ */
+
+ REQUIRE(DNS_DBITERATOR_VALID(iterator));
+
+ return (iterator->methods->seek(iterator, name));
+}
+
+isc_result_t
+dns_dbiterator_prev(dns_dbiterator_t *iterator) {
+ /*
+ * Move the node cursor to the previous node in the database (if any).
+ */
+
+ REQUIRE(DNS_DBITERATOR_VALID(iterator));
+
+ return (iterator->methods->prev(iterator));
+}
+
+isc_result_t
+dns_dbiterator_next(dns_dbiterator_t *iterator) {
+ /*
+ * Move the node cursor to the next node in the database (if any).
+ */
+
+ REQUIRE(DNS_DBITERATOR_VALID(iterator));
+
+ return (iterator->methods->next(iterator));
+}
+
+isc_result_t
+dns_dbiterator_current(dns_dbiterator_t *iterator, dns_dbnode_t **nodep,
+ dns_name_t *name) {
+ /*
+ * Return the current node.
+ */
+
+ REQUIRE(DNS_DBITERATOR_VALID(iterator));
+ REQUIRE(nodep != NULL && *nodep == NULL);
+ REQUIRE(name == NULL || dns_name_hasbuffer(name));
+
+ return (iterator->methods->current(iterator, nodep, name));
+}
+
+isc_result_t
+dns_dbiterator_pause(dns_dbiterator_t *iterator) {
+ /*
+ * Pause iteration.
+ */
+
+ REQUIRE(DNS_DBITERATOR_VALID(iterator));
+
+ return (iterator->methods->pause(iterator));
+}
+
+isc_result_t
+dns_dbiterator_origin(dns_dbiterator_t *iterator, dns_name_t *name) {
+ /*
+ * Return the origin to which returned node names are relative.
+ */
+
+ REQUIRE(DNS_DBITERATOR_VALID(iterator));
+ REQUIRE(iterator->relative_names);
+ REQUIRE(dns_name_hasbuffer(name));
+
+ return (iterator->methods->origin(iterator, name));
+}
+
+void
+dns_dbiterator_setcleanmode(dns_dbiterator_t *iterator, bool mode) {
+ REQUIRE(DNS_DBITERATOR_VALID(iterator));
+
+ iterator->cleaning = mode;
+}
diff --git a/lib/dns/diff.c b/lib/dns/diff.c
new file mode 100644
index 0000000..52f5aca
--- /dev/null
+++ b/lib/dns/diff.c
@@ -0,0 +1,686 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/*! \file */
+
+#include <inttypes.h>
+#include <stdbool.h>
+#include <stdlib.h>
+
+#include <isc/buffer.h>
+#include <isc/file.h>
+#include <isc/mem.h>
+#include <isc/print.h>
+#include <isc/result.h>
+#include <isc/string.h>
+#include <isc/util.h>
+
+#include <dns/db.h>
+#include <dns/diff.h>
+#include <dns/log.h>
+#include <dns/rdataclass.h>
+#include <dns/rdatalist.h>
+#include <dns/rdataset.h>
+#include <dns/rdatastruct.h>
+#include <dns/rdatatype.h>
+#include <dns/time.h>
+
+#define CHECK(op) \
+ do { \
+ result = (op); \
+ if (result != ISC_R_SUCCESS) \
+ goto failure; \
+ } while (0)
+
+#define DIFF_COMMON_LOGARGS \
+ dns_lctx, DNS_LOGCATEGORY_GENERAL, DNS_LOGMODULE_DIFF
+
+static dns_rdatatype_t
+rdata_covers(dns_rdata_t *rdata) {
+ return (rdata->type == dns_rdatatype_rrsig ? dns_rdata_covers(rdata)
+ : 0);
+}
+
+isc_result_t
+dns_difftuple_create(isc_mem_t *mctx, dns_diffop_t op, const dns_name_t *name,
+ dns_ttl_t ttl, dns_rdata_t *rdata, dns_difftuple_t **tp) {
+ dns_difftuple_t *t;
+ unsigned int size;
+ unsigned char *datap;
+
+ REQUIRE(tp != NULL && *tp == NULL);
+
+ /*
+ * Create a new tuple. The variable-size wire-format name data and
+ * rdata immediately follow the dns_difftuple_t structure
+ * in memory.
+ */
+ size = sizeof(*t) + name->length + rdata->length;
+ t = isc_mem_allocate(mctx, size);
+ t->mctx = NULL;
+ isc_mem_attach(mctx, &t->mctx);
+ t->op = op;
+
+ datap = (unsigned char *)(t + 1);
+
+ memmove(datap, name->ndata, name->length);
+ dns_name_init(&t->name, NULL);
+ dns_name_clone(name, &t->name);
+ t->name.ndata = datap;
+ datap += name->length;
+
+ t->ttl = ttl;
+
+ dns_rdata_init(&t->rdata);
+ dns_rdata_clone(rdata, &t->rdata);
+ if (rdata->data != NULL) {
+ memmove(datap, rdata->data, rdata->length);
+ t->rdata.data = datap;
+ datap += rdata->length;
+ } else {
+ t->rdata.data = NULL;
+ INSIST(rdata->length == 0);
+ }
+
+ ISC_LINK_INIT(&t->rdata, link);
+ ISC_LINK_INIT(t, link);
+ t->magic = DNS_DIFFTUPLE_MAGIC;
+
+ INSIST(datap == (unsigned char *)t + size);
+
+ *tp = t;
+ return (ISC_R_SUCCESS);
+}
+
+void
+dns_difftuple_free(dns_difftuple_t **tp) {
+ dns_difftuple_t *t = *tp;
+ *tp = NULL;
+ isc_mem_t *mctx;
+
+ REQUIRE(DNS_DIFFTUPLE_VALID(t));
+
+ dns_name_invalidate(&t->name);
+ t->magic = 0;
+ mctx = t->mctx;
+ isc_mem_free(mctx, t);
+ isc_mem_detach(&mctx);
+}
+
+isc_result_t
+dns_difftuple_copy(dns_difftuple_t *orig, dns_difftuple_t **copyp) {
+ return (dns_difftuple_create(orig->mctx, orig->op, &orig->name,
+ orig->ttl, &orig->rdata, copyp));
+}
+
+void
+dns_diff_init(isc_mem_t *mctx, dns_diff_t *diff) {
+ diff->mctx = mctx;
+ ISC_LIST_INIT(diff->tuples);
+ diff->magic = DNS_DIFF_MAGIC;
+}
+
+void
+dns_diff_clear(dns_diff_t *diff) {
+ dns_difftuple_t *t;
+ REQUIRE(DNS_DIFF_VALID(diff));
+ while ((t = ISC_LIST_HEAD(diff->tuples)) != NULL) {
+ ISC_LIST_UNLINK(diff->tuples, t, link);
+ dns_difftuple_free(&t);
+ }
+ ENSURE(ISC_LIST_EMPTY(diff->tuples));
+}
+
+void
+dns_diff_append(dns_diff_t *diff, dns_difftuple_t **tuplep) {
+ ISC_LIST_APPEND(diff->tuples, *tuplep, link);
+ *tuplep = NULL;
+}
+
+/* XXX this is O(N) */
+
+void
+dns_diff_appendminimal(dns_diff_t *diff, dns_difftuple_t **tuplep) {
+ dns_difftuple_t *ot, *next_ot;
+
+ REQUIRE(DNS_DIFF_VALID(diff));
+ REQUIRE(DNS_DIFFTUPLE_VALID(*tuplep));
+
+ /*
+ * Look for an existing tuple with the same owner name,
+ * rdata, and TTL. If we are doing an addition and find a
+ * deletion or vice versa, remove both the old and the
+ * new tuple since they cancel each other out (assuming
+ * that we never delete nonexistent data or add existing
+ * data).
+ *
+ * If we find an old update of the same kind as
+ * the one we are doing, there must be a programming
+ * error. We report it but try to continue anyway.
+ */
+ for (ot = ISC_LIST_HEAD(diff->tuples); ot != NULL; ot = next_ot) {
+ next_ot = ISC_LIST_NEXT(ot, link);
+ if (dns_name_caseequal(&ot->name, &(*tuplep)->name) &&
+ dns_rdata_compare(&ot->rdata, &(*tuplep)->rdata) == 0 &&
+ ot->ttl == (*tuplep)->ttl)
+ {
+ ISC_LIST_UNLINK(diff->tuples, ot, link);
+ if ((*tuplep)->op == ot->op) {
+ UNEXPECTED_ERROR("unexpected non-minimal diff");
+ } else {
+ dns_difftuple_free(tuplep);
+ }
+ dns_difftuple_free(&ot);
+ break;
+ }
+ }
+
+ if (*tuplep != NULL) {
+ ISC_LIST_APPEND(diff->tuples, *tuplep, link);
+ *tuplep = NULL;
+ }
+}
+
+static isc_stdtime_t
+setresign(dns_rdataset_t *modified) {
+ dns_rdata_t rdata = DNS_RDATA_INIT;
+ dns_rdata_rrsig_t sig;
+ int64_t when;
+ isc_result_t result;
+
+ result = dns_rdataset_first(modified);
+ INSIST(result == ISC_R_SUCCESS);
+ dns_rdataset_current(modified, &rdata);
+ (void)dns_rdata_tostruct(&rdata, &sig, NULL);
+ if ((rdata.flags & DNS_RDATA_OFFLINE) != 0) {
+ when = 0;
+ } else {
+ when = dns_time64_from32(sig.timeexpire);
+ }
+ dns_rdata_reset(&rdata);
+
+ result = dns_rdataset_next(modified);
+ while (result == ISC_R_SUCCESS) {
+ dns_rdataset_current(modified, &rdata);
+ (void)dns_rdata_tostruct(&rdata, &sig, NULL);
+ if ((rdata.flags & DNS_RDATA_OFFLINE) != 0) {
+ goto next_rr;
+ }
+ if (when == 0 || dns_time64_from32(sig.timeexpire) < when) {
+ when = dns_time64_from32(sig.timeexpire);
+ }
+ next_rr:
+ dns_rdata_reset(&rdata);
+ result = dns_rdataset_next(modified);
+ }
+ INSIST(result == ISC_R_NOMORE);
+ return ((isc_stdtime_t)when);
+}
+
+static void
+getownercase(dns_rdataset_t *rdataset, dns_name_t *name) {
+ if (dns_rdataset_isassociated(rdataset)) {
+ dns_rdataset_getownercase(rdataset, name);
+ }
+}
+
+static void
+setownercase(dns_rdataset_t *rdataset, const dns_name_t *name) {
+ if (dns_rdataset_isassociated(rdataset)) {
+ dns_rdataset_setownercase(rdataset, name);
+ }
+}
+
+static isc_result_t
+diff_apply(dns_diff_t *diff, dns_db_t *db, dns_dbversion_t *ver, bool warn) {
+ dns_difftuple_t *t;
+ dns_dbnode_t *node = NULL;
+ isc_result_t result;
+ char namebuf[DNS_NAME_FORMATSIZE];
+ char typebuf[DNS_RDATATYPE_FORMATSIZE];
+ char classbuf[DNS_RDATACLASS_FORMATSIZE];
+
+ REQUIRE(DNS_DIFF_VALID(diff));
+ REQUIRE(DNS_DB_VALID(db));
+
+ t = ISC_LIST_HEAD(diff->tuples);
+ while (t != NULL) {
+ dns_name_t *name;
+
+ INSIST(node == NULL);
+ name = &t->name;
+ /*
+ * Find the node.
+ * We create the node if it does not exist.
+ * This will cause an empty node to be created if the diff
+ * contains a deletion of an RR at a nonexistent name,
+ * but such diffs should never be created in the first
+ * place.
+ */
+
+ while (t != NULL && dns_name_equal(&t->name, name)) {
+ dns_rdatatype_t type, covers;
+ dns_diffop_t op;
+ dns_rdatalist_t rdl;
+ dns_rdataset_t rds;
+ dns_rdataset_t ardataset;
+ unsigned int options;
+
+ op = t->op;
+ type = t->rdata.type;
+ covers = rdata_covers(&t->rdata);
+
+ /*
+ * Collect a contiguous set of updates with
+ * the same operation (add/delete) and RR type
+ * into a single rdatalist so that the
+ * database rrset merging/subtraction code
+ * can work more efficiently than if each
+ * RR were merged into / subtracted from
+ * the database separately.
+ *
+ * This is done by linking rdata structures from the
+ * diff into "rdatalist". This uses the rdata link
+ * field, not the diff link field, so the structure
+ * of the diff itself is not affected.
+ */
+
+ dns_rdatalist_init(&rdl);
+ rdl.type = type;
+ rdl.covers = covers;
+ rdl.rdclass = t->rdata.rdclass;
+ rdl.ttl = t->ttl;
+
+ node = NULL;
+ if (type != dns_rdatatype_nsec3 &&
+ covers != dns_rdatatype_nsec3)
+ {
+ CHECK(dns_db_findnode(db, name, true, &node));
+ } else {
+ CHECK(dns_db_findnsec3node(db, name, true,
+ &node));
+ }
+
+ while (t != NULL && dns_name_equal(&t->name, name) &&
+ t->op == op && t->rdata.type == type &&
+ rdata_covers(&t->rdata) == covers)
+ {
+ /*
+ * Remember the add name for
+ * dns_rdataset_setownercase.
+ */
+ name = &t->name;
+ if (t->ttl != rdl.ttl && warn) {
+ dns_name_format(name, namebuf,
+ sizeof(namebuf));
+ dns_rdatatype_format(t->rdata.type,
+ typebuf,
+ sizeof(typebuf));
+ dns_rdataclass_format(t->rdata.rdclass,
+ classbuf,
+ sizeof(classbuf));
+ isc_log_write(DIFF_COMMON_LOGARGS,
+ ISC_LOG_WARNING,
+ "'%s/%s/%s': TTL differs "
+ "in "
+ "rdataset, adjusting "
+ "%lu -> %lu",
+ namebuf, typebuf,
+ classbuf,
+ (unsigned long)t->ttl,
+ (unsigned long)rdl.ttl);
+ }
+ ISC_LIST_APPEND(rdl.rdata, &t->rdata, link);
+ t = ISC_LIST_NEXT(t, link);
+ }
+
+ /*
+ * Convert the rdatalist into a rdataset.
+ */
+ dns_rdataset_init(&rds);
+ dns_rdataset_init(&ardataset);
+ CHECK(dns_rdatalist_tordataset(&rdl, &rds));
+ rds.trust = dns_trust_ultimate;
+
+ /*
+ * Merge the rdataset into the database.
+ */
+ switch (op) {
+ case DNS_DIFFOP_ADD:
+ case DNS_DIFFOP_ADDRESIGN:
+ options = DNS_DBADD_MERGE | DNS_DBADD_EXACT |
+ DNS_DBADD_EXACTTTL;
+ result = dns_db_addrdataset(db, node, ver, 0,
+ &rds, options,
+ &ardataset);
+ break;
+ case DNS_DIFFOP_DEL:
+ case DNS_DIFFOP_DELRESIGN:
+ options = DNS_DBSUB_EXACT | DNS_DBSUB_WANTOLD;
+ result = dns_db_subtractrdataset(db, node, ver,
+ &rds, options,
+ &ardataset);
+ break;
+ default:
+ UNREACHABLE();
+ }
+
+ if (result == ISC_R_SUCCESS) {
+ if (rds.type == dns_rdatatype_rrsig &&
+ (op == DNS_DIFFOP_DELRESIGN ||
+ op == DNS_DIFFOP_ADDRESIGN))
+ {
+ isc_stdtime_t resign;
+ resign = setresign(&ardataset);
+ dns_db_setsigningtime(db, &ardataset,
+ resign);
+ }
+ if (op == DNS_DIFFOP_ADD ||
+ op == DNS_DIFFOP_ADDRESIGN)
+ {
+ setownercase(&ardataset, name);
+ }
+ if (op == DNS_DIFFOP_DEL ||
+ op == DNS_DIFFOP_DELRESIGN)
+ {
+ getownercase(&ardataset, name);
+ }
+ } else if (result == DNS_R_UNCHANGED) {
+ /*
+ * This will not happen when executing a
+ * dynamic update, because that code will
+ * generate strictly minimal diffs.
+ * It may happen when receiving an IXFR
+ * from a server that is not as careful.
+ * Issue a warning and continue.
+ */
+ if (warn) {
+ dns_name_format(dns_db_origin(db),
+ namebuf,
+ sizeof(namebuf));
+ dns_rdataclass_format(dns_db_class(db),
+ classbuf,
+ sizeof(classbuf));
+ isc_log_write(DIFF_COMMON_LOGARGS,
+ ISC_LOG_WARNING,
+ "%s/%s: dns_diff_apply: "
+ "update with no effect",
+ namebuf, classbuf);
+ }
+ if (op == DNS_DIFFOP_ADD ||
+ op == DNS_DIFFOP_ADDRESIGN)
+ {
+ setownercase(&ardataset, name);
+ }
+ if (op == DNS_DIFFOP_DEL ||
+ op == DNS_DIFFOP_DELRESIGN)
+ {
+ getownercase(&ardataset, name);
+ }
+ } else if (result == DNS_R_NXRRSET) {
+ /*
+ * OK.
+ */
+ if (op == DNS_DIFFOP_DEL ||
+ op == DNS_DIFFOP_DELRESIGN)
+ {
+ getownercase(&ardataset, name);
+ }
+ if (dns_rdataset_isassociated(&ardataset)) {
+ dns_rdataset_disassociate(&ardataset);
+ }
+ } else {
+ if (dns_rdataset_isassociated(&ardataset)) {
+ dns_rdataset_disassociate(&ardataset);
+ }
+ CHECK(result);
+ }
+ dns_db_detachnode(db, &node);
+ if (dns_rdataset_isassociated(&ardataset)) {
+ dns_rdataset_disassociate(&ardataset);
+ }
+ }
+ }
+ return (ISC_R_SUCCESS);
+
+failure:
+ if (node != NULL) {
+ dns_db_detachnode(db, &node);
+ }
+ return (result);
+}
+
+isc_result_t
+dns_diff_apply(dns_diff_t *diff, dns_db_t *db, dns_dbversion_t *ver) {
+ return (diff_apply(diff, db, ver, true));
+}
+
+isc_result_t
+dns_diff_applysilently(dns_diff_t *diff, dns_db_t *db, dns_dbversion_t *ver) {
+ return (diff_apply(diff, db, ver, false));
+}
+
+/* XXX this duplicates lots of code in diff_apply(). */
+
+isc_result_t
+dns_diff_load(dns_diff_t *diff, dns_addrdatasetfunc_t addfunc,
+ void *add_private) {
+ dns_difftuple_t *t;
+ isc_result_t result;
+
+ REQUIRE(DNS_DIFF_VALID(diff));
+
+ t = ISC_LIST_HEAD(diff->tuples);
+ while (t != NULL) {
+ dns_name_t *name;
+
+ name = &t->name;
+ while (t != NULL && dns_name_caseequal(&t->name, name)) {
+ dns_rdatatype_t type, covers;
+ dns_diffop_t op;
+ dns_rdatalist_t rdl;
+ dns_rdataset_t rds;
+
+ op = t->op;
+ type = t->rdata.type;
+ covers = rdata_covers(&t->rdata);
+
+ dns_rdatalist_init(&rdl);
+ rdl.type = type;
+ rdl.covers = covers;
+ rdl.rdclass = t->rdata.rdclass;
+ rdl.ttl = t->ttl;
+
+ while (t != NULL &&
+ dns_name_caseequal(&t->name, name) &&
+ t->op == op && t->rdata.type == type &&
+ rdata_covers(&t->rdata) == covers)
+ {
+ ISC_LIST_APPEND(rdl.rdata, &t->rdata, link);
+ t = ISC_LIST_NEXT(t, link);
+ }
+
+ /*
+ * Convert the rdatalist into a rdataset.
+ */
+ dns_rdataset_init(&rds);
+ CHECK(dns_rdatalist_tordataset(&rdl, &rds));
+ rds.trust = dns_trust_ultimate;
+
+ INSIST(op == DNS_DIFFOP_ADD);
+ result = (*addfunc)(add_private, name, &rds);
+ if (result == DNS_R_UNCHANGED) {
+ isc_log_write(DIFF_COMMON_LOGARGS,
+ ISC_LOG_WARNING,
+ "dns_diff_load: "
+ "update with no effect");
+ } else if (result == ISC_R_SUCCESS ||
+ result == DNS_R_NXRRSET)
+ {
+ /*
+ * OK.
+ */
+ } else {
+ CHECK(result);
+ }
+ }
+ }
+ result = ISC_R_SUCCESS;
+failure:
+ return (result);
+}
+
+/*
+ * XXX uses qsort(); a merge sort would be more natural for lists,
+ * and perhaps safer wrt thread stack overflow.
+ */
+isc_result_t
+dns_diff_sort(dns_diff_t *diff, dns_diff_compare_func *compare) {
+ unsigned int length = 0;
+ unsigned int i;
+ dns_difftuple_t **v;
+ dns_difftuple_t *p;
+ REQUIRE(DNS_DIFF_VALID(diff));
+
+ for (p = ISC_LIST_HEAD(diff->tuples); p != NULL;
+ p = ISC_LIST_NEXT(p, link))
+ {
+ length++;
+ }
+ if (length == 0) {
+ return (ISC_R_SUCCESS);
+ }
+ v = isc_mem_get(diff->mctx, length * sizeof(dns_difftuple_t *));
+ for (i = 0; i < length; i++) {
+ p = ISC_LIST_HEAD(diff->tuples);
+ v[i] = p;
+ ISC_LIST_UNLINK(diff->tuples, p, link);
+ }
+ INSIST(ISC_LIST_HEAD(diff->tuples) == NULL);
+ qsort(v, length, sizeof(v[0]), compare);
+ for (i = 0; i < length; i++) {
+ ISC_LIST_APPEND(diff->tuples, v[i], link);
+ }
+ isc_mem_put(diff->mctx, v, length * sizeof(dns_difftuple_t *));
+ return (ISC_R_SUCCESS);
+}
+
+/*
+ * Create an rdataset containing the single RR of the given
+ * tuple. The caller must allocate the rdata, rdataset and
+ * an rdatalist structure for it to refer to.
+ */
+
+static isc_result_t
+diff_tuple_tordataset(dns_difftuple_t *t, dns_rdata_t *rdata,
+ dns_rdatalist_t *rdl, dns_rdataset_t *rds) {
+ REQUIRE(DNS_DIFFTUPLE_VALID(t));
+ REQUIRE(rdl != NULL);
+ REQUIRE(rds != NULL);
+
+ dns_rdatalist_init(rdl);
+ rdl->type = t->rdata.type;
+ rdl->rdclass = t->rdata.rdclass;
+ rdl->ttl = t->ttl;
+ dns_rdataset_init(rds);
+ ISC_LINK_INIT(rdata, link);
+ dns_rdata_clone(&t->rdata, rdata);
+ ISC_LIST_APPEND(rdl->rdata, rdata, link);
+ return (dns_rdatalist_tordataset(rdl, rds));
+}
+
+isc_result_t
+dns_diff_print(dns_diff_t *diff, FILE *file) {
+ isc_result_t result;
+ dns_difftuple_t *t;
+ char *mem = NULL;
+ unsigned int size = 2048;
+ const char *op = NULL;
+
+ REQUIRE(DNS_DIFF_VALID(diff));
+
+ mem = isc_mem_get(diff->mctx, size);
+
+ for (t = ISC_LIST_HEAD(diff->tuples); t != NULL;
+ t = ISC_LIST_NEXT(t, link))
+ {
+ isc_buffer_t buf;
+ isc_region_t r;
+
+ dns_rdatalist_t rdl;
+ dns_rdataset_t rds;
+ dns_rdata_t rd = DNS_RDATA_INIT;
+
+ result = diff_tuple_tordataset(t, &rd, &rdl, &rds);
+ if (result != ISC_R_SUCCESS) {
+ UNEXPECTED_ERROR("diff_tuple_tordataset failed: %s",
+ isc_result_totext(result));
+ result = ISC_R_UNEXPECTED;
+ goto cleanup;
+ }
+ again:
+ isc_buffer_init(&buf, mem, size);
+ result = dns_rdataset_totext(&rds, &t->name, false, false,
+ &buf);
+
+ if (result == ISC_R_NOSPACE) {
+ isc_mem_put(diff->mctx, mem, size);
+ size += 1024;
+ mem = isc_mem_get(diff->mctx, size);
+ goto again;
+ }
+
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+ /*
+ * Get rid of final newline.
+ */
+ INSIST(buf.used >= 1 &&
+ ((char *)buf.base)[buf.used - 1] == '\n');
+ buf.used--;
+
+ isc_buffer_usedregion(&buf, &r);
+ switch (t->op) {
+ case DNS_DIFFOP_EXISTS:
+ op = "exists";
+ break;
+ case DNS_DIFFOP_ADD:
+ op = "add";
+ break;
+ case DNS_DIFFOP_DEL:
+ op = "del";
+ break;
+ case DNS_DIFFOP_ADDRESIGN:
+ op = "add re-sign";
+ break;
+ case DNS_DIFFOP_DELRESIGN:
+ op = "del re-sign";
+ break;
+ }
+ if (file != NULL) {
+ fprintf(file, "%s %.*s\n", op, (int)r.length,
+ (char *)r.base);
+ } else {
+ isc_log_write(DIFF_COMMON_LOGARGS, ISC_LOG_DEBUG(7),
+ "%s %.*s", op, (int)r.length,
+ (char *)r.base);
+ }
+ }
+ result = ISC_R_SUCCESS;
+cleanup:
+ if (mem != NULL) {
+ isc_mem_put(diff->mctx, mem, size);
+ }
+ return (result);
+}
diff --git a/lib/dns/dispatch.c b/lib/dns/dispatch.c
new file mode 100644
index 0000000..d737363
--- /dev/null
+++ b/lib/dns/dispatch.c
@@ -0,0 +1,2296 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/*! \file */
+
+#include <inttypes.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include <isc/atomic.h>
+#include <isc/mem.h>
+#include <isc/mutex.h>
+#include <isc/net.h>
+#include <isc/netmgr.h>
+#include <isc/portset.h>
+#include <isc/print.h>
+#include <isc/random.h>
+#include <isc/stats.h>
+#include <isc/string.h>
+#include <isc/time.h>
+#include <isc/util.h>
+
+#include <dns/acl.h>
+#include <dns/dispatch.h>
+#include <dns/log.h>
+#include <dns/message.h>
+#include <dns/stats.h>
+#include <dns/types.h>
+
+typedef ISC_LIST(dns_dispentry_t) dns_displist_t;
+
+typedef struct dns_qid {
+ unsigned int magic;
+ isc_mutex_t lock;
+ unsigned int qid_nbuckets; /*%< hash table size */
+ unsigned int qid_increment; /*%< id increment on collision */
+ dns_displist_t *qid_table; /*%< the table itself */
+} dns_qid_t;
+
+struct dns_dispatchmgr {
+ /* Unlocked. */
+ unsigned int magic;
+ isc_refcount_t references;
+ isc_mem_t *mctx;
+ dns_acl_t *blackhole;
+ isc_stats_t *stats;
+ isc_nm_t *nm;
+
+ /* Locked by "lock". */
+ isc_mutex_t lock;
+ ISC_LIST(dns_dispatch_t) list;
+
+ dns_qid_t *qid;
+
+ in_port_t *v4ports; /*%< available ports for IPv4 */
+ unsigned int nv4ports; /*%< # of available ports for IPv4 */
+ in_port_t *v6ports; /*%< available ports for IPv4 */
+ unsigned int nv6ports; /*%< # of available ports for IPv4 */
+};
+
+typedef enum {
+ DNS_DISPATCHSTATE_NONE = 0UL,
+ DNS_DISPATCHSTATE_CONNECTING,
+ DNS_DISPATCHSTATE_CONNECTED,
+ DNS_DISPATCHSTATE_CANCELED,
+} dns_dispatchstate_t;
+
+struct dns_dispentry {
+ unsigned int magic;
+ isc_refcount_t references;
+ dns_dispatch_t *disp;
+ isc_nmhandle_t *handle; /*%< netmgr handle for UDP connection */
+ dns_dispatchstate_t state;
+ unsigned int bucket;
+ unsigned int retries;
+ unsigned int timeout;
+ isc_time_t start;
+ isc_sockaddr_t local;
+ isc_sockaddr_t peer;
+ in_port_t port;
+ dns_messageid_t id;
+ dispatch_cb_t connected;
+ dispatch_cb_t sent;
+ dispatch_cb_t response;
+ void *arg;
+ bool reading;
+ isc_result_t result;
+ ISC_LINK(dns_dispentry_t) link;
+ ISC_LINK(dns_dispentry_t) alink;
+ ISC_LINK(dns_dispentry_t) plink;
+ ISC_LINK(dns_dispentry_t) rlink;
+};
+
+struct dns_dispatch {
+ /* Unlocked. */
+ unsigned int magic; /*%< magic */
+ int tid;
+ dns_dispatchmgr_t *mgr; /*%< dispatch manager */
+ isc_nmhandle_t *handle; /*%< netmgr handle for TCP connection */
+ isc_sockaddr_t local; /*%< local address */
+ in_port_t localport; /*%< local UDP port */
+ isc_sockaddr_t peer; /*%< peer address (TCP) */
+
+ /*% Locked by mgr->lock. */
+ ISC_LINK(dns_dispatch_t) link;
+
+ /* Locked by "lock". */
+ isc_mutex_t lock; /*%< locks all below */
+ isc_socktype_t socktype;
+ dns_dispatchstate_t state;
+ isc_refcount_t references;
+
+ bool reading;
+
+ dns_displist_t pending;
+ dns_displist_t active;
+
+ unsigned int requests; /*%< how many requests we have */
+
+ unsigned int timedout;
+};
+
+#define QID_MAGIC ISC_MAGIC('Q', 'i', 'd', ' ')
+#define VALID_QID(e) ISC_MAGIC_VALID((e), QID_MAGIC)
+
+#define RESPONSE_MAGIC ISC_MAGIC('D', 'r', 's', 'p')
+#define VALID_RESPONSE(e) ISC_MAGIC_VALID((e), RESPONSE_MAGIC)
+
+#define DISPSOCK_MAGIC ISC_MAGIC('D', 's', 'o', 'c')
+#define VALID_DISPSOCK(e) ISC_MAGIC_VALID((e), DISPSOCK_MAGIC)
+
+#define DISPATCH_MAGIC ISC_MAGIC('D', 'i', 's', 'p')
+#define VALID_DISPATCH(e) ISC_MAGIC_VALID((e), DISPATCH_MAGIC)
+
+#define DNS_DISPATCHMGR_MAGIC ISC_MAGIC('D', 'M', 'g', 'r')
+#define VALID_DISPATCHMGR(e) ISC_MAGIC_VALID((e), DNS_DISPATCHMGR_MAGIC)
+
+/*%
+ * Number of buckets in the QID hash table, and the value to
+ * increment the QID by when attempting to avoid collisions.
+ * The number of buckets should be prime, and the increment
+ * should be the next higher prime number.
+ */
+#ifndef DNS_QID_BUCKETS
+#define DNS_QID_BUCKETS 16411
+#endif /* ifndef DNS_QID_BUCKETS */
+#ifndef DNS_QID_INCREMENT
+#define DNS_QID_INCREMENT 16433
+#endif /* ifndef DNS_QID_INCREMENT */
+
+#if DNS_DISPATCH_TRACE
+#define dns_dispentry_ref(ptr) \
+ dns_dispentry__ref(ptr, __func__, __FILE__, __LINE__)
+#define dns_dispentry_unref(ptr) \
+ dns_dispentry__unref(ptr, __func__, __FILE__, __LINE__)
+#define dns_dispentry_attach(ptr, ptrp) \
+ dns_dispentry__attach(ptr, ptrp, __func__, __FILE__, __LINE__)
+#define dns_dispentry_detach(ptrp) \
+ dns_dispentry__detach(ptrp, __func__, __FILE__, __LINE__)
+ISC_REFCOUNT_TRACE_DECL(dns_dispentry);
+#else
+ISC_REFCOUNT_DECL(dns_dispentry);
+#endif
+
+/*
+ * Statics.
+ */
+static void
+dispatchmgr_destroy(dns_dispatchmgr_t *mgr);
+
+static dns_dispentry_t *
+entry_search(dns_qid_t *, const isc_sockaddr_t *, dns_messageid_t, in_port_t,
+ unsigned int);
+static void
+udp_recv(isc_nmhandle_t *handle, isc_result_t eresult, isc_region_t *region,
+ void *arg);
+static void
+tcp_recv(isc_nmhandle_t *handle, isc_result_t eresult, isc_region_t *region,
+ void *arg);
+static void
+tcp_recv_done(dns_dispentry_t *resp, isc_result_t eresult,
+ isc_region_t *region);
+static uint32_t
+dns_hash(dns_qid_t *, const isc_sockaddr_t *, dns_messageid_t, in_port_t);
+static void
+dispentry_cancel(dns_dispentry_t *resp, isc_result_t result);
+static isc_result_t
+dispatch_createudp(dns_dispatchmgr_t *mgr, const isc_sockaddr_t *localaddr,
+ dns_dispatch_t **dispp);
+static void
+qid_allocate(dns_dispatchmgr_t *mgr, dns_qid_t **qidp);
+static void
+qid_destroy(isc_mem_t *mctx, dns_qid_t **qidp);
+static void
+udp_startrecv(isc_nmhandle_t *handle, dns_dispentry_t *resp);
+static void
+udp_dispatch_connect(dns_dispatch_t *disp, dns_dispentry_t *resp);
+static void
+tcp_startrecv(isc_nmhandle_t *handle, dns_dispatch_t *disp,
+ dns_dispentry_t *resp);
+static void
+tcp_dispatch_getnext(dns_dispatch_t *disp, dns_dispentry_t *resp,
+ int32_t timeout);
+static void
+udp_dispatch_getnext(dns_dispentry_t *resp, int32_t timeout);
+
+#define LVL(x) ISC_LOG_DEBUG(x)
+
+static const char *
+socktype2str(dns_dispentry_t *resp) {
+ dns_dispatch_t *disp = resp->disp;
+
+ switch (disp->socktype) {
+ case isc_socktype_udp:
+ return ("UDP");
+ case isc_socktype_tcp:
+ return ("TCP");
+ default:
+ return ("<unexpected>");
+ }
+}
+
+static const char *
+state2str(dns_dispatchstate_t state) {
+ switch (state) {
+ case DNS_DISPATCHSTATE_NONE:
+ return ("none");
+ case DNS_DISPATCHSTATE_CONNECTING:
+ return ("connecting");
+ case DNS_DISPATCHSTATE_CONNECTED:
+ return ("connected");
+ case DNS_DISPATCHSTATE_CANCELED:
+ return ("canceled");
+ default:
+ return ("<unexpected>");
+ }
+}
+
+static void
+mgr_log(dns_dispatchmgr_t *mgr, int level, const char *fmt, ...)
+ ISC_FORMAT_PRINTF(3, 4);
+
+static void
+mgr_log(dns_dispatchmgr_t *mgr, int level, const char *fmt, ...) {
+ char msgbuf[2048];
+ va_list ap;
+
+ if (!isc_log_wouldlog(dns_lctx, level)) {
+ return;
+ }
+
+ va_start(ap, fmt);
+ vsnprintf(msgbuf, sizeof(msgbuf), fmt, ap);
+ va_end(ap);
+
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_DISPATCH,
+ DNS_LOGMODULE_DISPATCH, level, "dispatchmgr %p: %s", mgr,
+ msgbuf);
+}
+
+static void
+inc_stats(dns_dispatchmgr_t *mgr, isc_statscounter_t counter) {
+ if (mgr->stats != NULL) {
+ isc_stats_increment(mgr->stats, counter);
+ }
+}
+
+static void
+dec_stats(dns_dispatchmgr_t *mgr, isc_statscounter_t counter) {
+ if (mgr->stats != NULL) {
+ isc_stats_decrement(mgr->stats, counter);
+ }
+}
+
+static void
+dispatch_log(dns_dispatch_t *disp, int level, const char *fmt, ...)
+ ISC_FORMAT_PRINTF(3, 4);
+
+static void
+dispatch_log(dns_dispatch_t *disp, int level, const char *fmt, ...) {
+ char msgbuf[2048];
+ va_list ap;
+ int r;
+
+ if (!isc_log_wouldlog(dns_lctx, level)) {
+ return;
+ }
+
+ va_start(ap, fmt);
+ r = vsnprintf(msgbuf, sizeof(msgbuf), fmt, ap);
+ if (r < 0) {
+ msgbuf[0] = '\0';
+ } else if ((unsigned int)r >= sizeof(msgbuf)) {
+ /* Truncated */
+ msgbuf[sizeof(msgbuf) - 1] = '\0';
+ }
+ va_end(ap);
+
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_DISPATCH,
+ DNS_LOGMODULE_DISPATCH, level, "dispatch %p: %s", disp,
+ msgbuf);
+}
+
+static void
+dispentry_log(dns_dispentry_t *resp, int level, const char *fmt, ...)
+ ISC_FORMAT_PRINTF(3, 4);
+
+static void
+dispentry_log(dns_dispentry_t *resp, int level, const char *fmt, ...) {
+ char msgbuf[2048];
+ va_list ap;
+ int r;
+
+ if (!isc_log_wouldlog(dns_lctx, level)) {
+ return;
+ }
+
+ va_start(ap, fmt);
+ r = vsnprintf(msgbuf, sizeof(msgbuf), fmt, ap);
+ if (r < 0) {
+ msgbuf[0] = '\0';
+ } else if ((unsigned int)r >= sizeof(msgbuf)) {
+ /* Truncated */
+ msgbuf[sizeof(msgbuf) - 1] = '\0';
+ }
+ va_end(ap);
+
+ dispatch_log(resp->disp, level, "%s response %p: %s",
+ socktype2str(resp), resp, msgbuf);
+}
+
+/*
+ * Return a hash of the destination and message id.
+ */
+static uint32_t
+dns_hash(dns_qid_t *qid, const isc_sockaddr_t *dest, dns_messageid_t id,
+ in_port_t port) {
+ uint32_t ret;
+
+ ret = isc_sockaddr_hash(dest, true);
+ ret ^= ((uint32_t)id << 16) | port;
+ ret %= qid->qid_nbuckets;
+
+ INSIST(ret < qid->qid_nbuckets);
+
+ return (ret);
+}
+
+/*%
+ * Choose a random port number for a dispatch entry.
+ * The caller must hold the disp->lock
+ */
+static isc_result_t
+setup_socket(dns_dispatch_t *disp, dns_dispentry_t *resp,
+ const isc_sockaddr_t *dest, in_port_t *portp) {
+ dns_dispatchmgr_t *mgr = disp->mgr;
+ unsigned int nports;
+ in_port_t *ports = NULL;
+ in_port_t port = *portp;
+
+ if (resp->retries++ > 5) {
+ return (ISC_R_FAILURE);
+ }
+
+ if (isc_sockaddr_pf(&disp->local) == AF_INET) {
+ nports = mgr->nv4ports;
+ ports = mgr->v4ports;
+ } else {
+ nports = mgr->nv6ports;
+ ports = mgr->v6ports;
+ }
+ if (nports == 0) {
+ return (ISC_R_ADDRNOTAVAIL);
+ }
+
+ resp->local = disp->local;
+ resp->peer = *dest;
+
+ if (port == 0) {
+ port = ports[isc_random_uniform(nports)];
+ isc_sockaddr_setport(&resp->local, port);
+ *portp = port;
+ }
+ resp->port = port;
+
+ return (ISC_R_SUCCESS);
+}
+
+/*
+ * Find an entry for query ID 'id', socket address 'dest', and port number
+ * 'port'.
+ * Return NULL if no such entry exists.
+ */
+static dns_dispentry_t *
+entry_search(dns_qid_t *qid, const isc_sockaddr_t *dest, dns_messageid_t id,
+ in_port_t port, unsigned int bucket) {
+ dns_dispentry_t *res = NULL;
+
+ REQUIRE(VALID_QID(qid));
+ REQUIRE(bucket < qid->qid_nbuckets);
+
+ res = ISC_LIST_HEAD(qid->qid_table[bucket]);
+
+ while (res != NULL) {
+ if (res->id == id && isc_sockaddr_equal(dest, &res->peer) &&
+ res->port == port)
+ {
+ return (res);
+ }
+ res = ISC_LIST_NEXT(res, link);
+ }
+
+ return (NULL);
+}
+
+static void
+dispentry_destroy(dns_dispentry_t *resp) {
+ dns_dispatch_t *disp = resp->disp;
+
+ /*
+ * We need to call this from here in case there's an external event that
+ * shuts down our dispatch (like ISC_R_SHUTTINGDOWN).
+ */
+ dispentry_cancel(resp, ISC_R_CANCELED);
+
+ LOCK(&disp->lock);
+ INSIST(disp->requests > 0);
+ disp->requests--;
+ UNLOCK(&disp->lock);
+
+ isc_refcount_destroy(&resp->references);
+
+ resp->magic = 0;
+
+ INSIST(!ISC_LINK_LINKED(resp, link));
+ INSIST(!ISC_LINK_LINKED(resp, plink));
+ INSIST(!ISC_LINK_LINKED(resp, alink));
+ INSIST(!ISC_LINK_LINKED(resp, rlink));
+
+ dispentry_log(resp, LVL(90), "destroying");
+
+ if (resp->handle != NULL) {
+ dispentry_log(resp, LVL(90), "detaching handle %p from %p",
+ resp->handle, &resp->handle);
+ isc_nmhandle_detach(&resp->handle);
+ }
+
+ isc_mem_put(disp->mgr->mctx, resp, sizeof(*resp));
+
+ dns_dispatch_detach(&disp); /* DISPATCH001 */
+}
+
+#if DNS_DISPATCH_TRACE
+ISC_REFCOUNT_TRACE_IMPL(dns_dispentry, dispentry_destroy);
+#else
+ISC_REFCOUNT_IMPL(dns_dispentry, dispentry_destroy);
+#endif
+
+/*
+ * How long in milliseconds has it been since this dispentry
+ * started reading?
+ */
+static unsigned int
+dispentry_runtime(dns_dispentry_t *resp, const isc_time_t *now) {
+ if (isc_time_isepoch(&resp->start)) {
+ return (0);
+ }
+
+ return (isc_time_microdiff(now, &resp->start) / 1000);
+}
+
+/*
+ * General flow:
+ *
+ * If I/O result == CANCELED or error, free the buffer.
+ *
+ * If query, free the buffer, restart.
+ *
+ * If response:
+ * Allocate event, fill in details.
+ * If cannot allocate, free buffer, restart.
+ * find target. If not found, free buffer, restart.
+ * if event queue is not empty, queue. else, send.
+ * restart.
+ */
+static void
+udp_recv(isc_nmhandle_t *handle, isc_result_t eresult, isc_region_t *region,
+ void *arg) {
+ dns_dispentry_t *resp = (dns_dispentry_t *)arg;
+ dns_dispatch_t *disp = NULL;
+ dns_messageid_t id;
+ isc_result_t dres;
+ isc_buffer_t source;
+ unsigned int flags;
+ isc_sockaddr_t peer;
+ isc_netaddr_t netaddr;
+ int match, timeout = 0;
+ dispatch_cb_t response = NULL;
+ isc_time_t now;
+
+ REQUIRE(VALID_RESPONSE(resp));
+ REQUIRE(VALID_DISPATCH(resp->disp));
+
+ disp = resp->disp;
+
+ LOCK(&disp->lock);
+ INSIST(resp->reading);
+ resp->reading = false;
+
+ response = resp->response;
+
+ if (resp->state == DNS_DISPATCHSTATE_CANCELED) {
+ /*
+ * Nobody is interested in the callback if the response
+ * has been canceled already. Detach from the response
+ * and the handle.
+ */
+ response = NULL;
+ eresult = ISC_R_CANCELED;
+ }
+
+ dispentry_log(resp, LVL(90), "read callback:%s, requests %d",
+ isc_result_totext(eresult), disp->requests);
+
+ if (eresult != ISC_R_SUCCESS) {
+ /*
+ * This is most likely a network error on a connected
+ * socket, a timeout, or the query has been canceled.
+ * It makes no sense to check the address or parse the
+ * packet, but we can return the error to the caller.
+ */
+ goto done;
+ }
+
+ peer = isc_nmhandle_peeraddr(handle);
+ isc_netaddr_fromsockaddr(&netaddr, &peer);
+
+ /*
+ * If this is from a blackholed address, drop it.
+ */
+ if (disp->mgr->blackhole != NULL &&
+ dns_acl_match(&netaddr, NULL, disp->mgr->blackhole, NULL, &match,
+ NULL) == ISC_R_SUCCESS &&
+ match > 0)
+ {
+ if (isc_log_wouldlog(dns_lctx, LVL(10))) {
+ char netaddrstr[ISC_NETADDR_FORMATSIZE];
+ isc_netaddr_format(&netaddr, netaddrstr,
+ sizeof(netaddrstr));
+ dispentry_log(resp, LVL(10),
+ "blackholed packet from %s", netaddrstr);
+ }
+ goto next;
+ }
+
+ /*
+ * Peek into the buffer to see what we can see.
+ */
+ id = resp->id;
+ isc_buffer_init(&source, region->base, region->length);
+ isc_buffer_add(&source, region->length);
+ dres = dns_message_peekheader(&source, &id, &flags);
+ if (dres != ISC_R_SUCCESS) {
+ char netaddrstr[ISC_NETADDR_FORMATSIZE];
+ isc_netaddr_format(&netaddr, netaddrstr, sizeof(netaddrstr));
+ dispentry_log(resp, LVL(10), "got garbage packet from %s",
+ netaddrstr);
+ goto next;
+ }
+
+ dispentry_log(resp, LVL(92),
+ "got valid DNS message header, /QR %c, id %u",
+ (((flags & DNS_MESSAGEFLAG_QR) != 0) ? '1' : '0'), id);
+
+ /*
+ * Look at the message flags. If it's a query, ignore it.
+ */
+ if ((flags & DNS_MESSAGEFLAG_QR) == 0) {
+ goto next;
+ }
+
+ /*
+ * The QID and the address must match the expected ones.
+ */
+ if (resp->id != id || !isc_sockaddr_equal(&peer, &resp->peer)) {
+ dispentry_log(resp, LVL(90), "response doesn't match");
+ inc_stats(disp->mgr, dns_resstatscounter_mismatch);
+ goto next;
+ }
+
+ /*
+ * We have the right resp, so call the caller back.
+ */
+ goto done;
+
+next:
+ /*
+ * This is the wrong response. Check whether there is still enough
+ * time to wait for the correct one to arrive before the timeout fires.
+ */
+ TIME_NOW(&now);
+ timeout = resp->timeout - dispentry_runtime(resp, &now);
+ if (timeout <= 0) {
+ /*
+ * The time window for receiving the correct response is
+ * already closed, libuv has just not processed the socket
+ * timer yet. Invoke the read callback, indicating a timeout.
+ */
+ eresult = ISC_R_TIMEDOUT;
+ goto done;
+ }
+
+ /*
+ * Do not invoke the read callback just yet and instead wait for the
+ * proper response to arrive until the original timeout fires.
+ */
+ response = NULL;
+ udp_dispatch_getnext(resp, timeout);
+
+done:
+ UNLOCK(&disp->lock);
+
+ if (response != NULL) {
+ dispentry_log(resp, LVL(90), "UDP read callback on %p: %s",
+ handle, isc_result_totext(eresult));
+ response(eresult, region, resp->arg);
+ }
+
+ dns_dispentry_detach(&resp); /* DISPENTRY003 */
+}
+
+static isc_result_t
+tcp_recv_oldest(dns_dispatch_t *disp, dns_dispentry_t **respp) {
+ dns_dispentry_t *resp = NULL;
+ resp = ISC_LIST_HEAD(disp->active);
+ if (resp != NULL) {
+ disp->timedout++;
+
+ *respp = resp;
+ return (ISC_R_TIMEDOUT);
+ }
+
+ return (ISC_R_NOTFOUND);
+}
+
+static isc_result_t
+tcp_recv_success(dns_dispatch_t *disp, isc_region_t *region, dns_qid_t *qid,
+ isc_sockaddr_t *peer, dns_dispentry_t **respp) {
+ isc_buffer_t source;
+ dns_messageid_t id;
+ unsigned int flags;
+ unsigned int bucket;
+ isc_result_t result = ISC_R_SUCCESS;
+ dns_dispentry_t *resp = NULL;
+
+ dispatch_log(disp, LVL(90), "TCP read success, length == %d, addr = %p",
+ region->length, region->base);
+
+ /*
+ * Peek into the buffer to see what we can see.
+ */
+ isc_buffer_init(&source, region->base, region->length);
+ isc_buffer_add(&source, region->length);
+ result = dns_message_peekheader(&source, &id, &flags);
+ if (result != ISC_R_SUCCESS) {
+ dispatch_log(disp, LVL(10), "got garbage packet");
+ return (ISC_R_UNEXPECTED);
+ }
+
+ dispatch_log(disp, LVL(92),
+ "got valid DNS message header, /QR %c, id %u",
+ (((flags & DNS_MESSAGEFLAG_QR) != 0) ? '1' : '0'), id);
+
+ /*
+ * Look at the message flags. If it's a query, ignore it and keep
+ * reading.
+ */
+ if ((flags & DNS_MESSAGEFLAG_QR) == 0) {
+ dispatch_log(disp, LVL(10), "got DNS query instead of answer");
+ return (ISC_R_UNEXPECTED);
+ }
+
+ /*
+ * We have a valid response; find the associated dispentry object
+ * and call the caller back.
+ */
+ bucket = dns_hash(qid, peer, id, disp->localport);
+ LOCK(&qid->lock);
+ resp = entry_search(qid, peer, id, disp->localport, bucket);
+ if (resp != NULL) {
+ if (resp->reading) {
+ *respp = resp;
+ } else {
+ /* We already got our DNS message. */
+ result = ISC_R_UNEXPECTED;
+ }
+ } else {
+ /* We are not expecting this DNS message */
+ result = ISC_R_NOTFOUND;
+ }
+ dispatch_log(disp, LVL(90), "search for response in bucket %d: %s",
+ bucket, isc_result_totext(result));
+ UNLOCK(&qid->lock);
+
+ return (result);
+}
+
+static void
+tcp_recv_add(dns_displist_t *resps, dns_dispentry_t *resp,
+ isc_result_t result) {
+ dns_dispentry_ref(resp); /* DISPENTRY009 */
+ ISC_LIST_UNLINK(resp->disp->active, resp, alink);
+ ISC_LIST_APPEND(*resps, resp, rlink);
+ INSIST(resp->reading);
+ resp->reading = false;
+ resp->result = result;
+}
+
+static void
+tcp_recv_shutdown(dns_dispatch_t *disp, dns_displist_t *resps,
+ isc_result_t result) {
+ dns_dispentry_t *resp = NULL, *next = NULL;
+
+ /*
+ * If there are any active responses, shut them all down.
+ */
+ for (resp = ISC_LIST_HEAD(disp->active); resp != NULL; resp = next) {
+ next = ISC_LIST_NEXT(resp, alink);
+ tcp_recv_add(resps, resp, result);
+ }
+ disp->state = DNS_DISPATCHSTATE_CANCELED;
+}
+
+static void
+tcp_recv_done(dns_dispentry_t *resp, isc_result_t eresult,
+ isc_region_t *region) {
+ dispentry_log(resp, LVL(90), "read callback: %s",
+ isc_result_totext(eresult));
+
+ resp->response(eresult, region, resp->arg);
+ dns_dispentry_detach(&resp); /* DISPENTRY009 */
+}
+
+static void
+tcp_recv_processall(dns_displist_t *resps, isc_region_t *region) {
+ dns_dispentry_t *resp = NULL, *next = NULL;
+
+ for (resp = ISC_LIST_HEAD(*resps); resp != NULL; resp = next) {
+ next = ISC_LIST_NEXT(resp, rlink);
+ ISC_LIST_UNLINK(*resps, resp, rlink);
+ tcp_recv_done(resp, resp->result, region);
+ }
+}
+
+/*
+ * General flow:
+ *
+ * If I/O result == CANCELED, EOF, or error, notify everyone as the
+ * various queues drain.
+ *
+ * If response:
+ * Allocate event, fill in details.
+ * If cannot allocate, restart.
+ * find target. If not found, restart.
+ * if event queue is not empty, queue. else, send.
+ * restart.
+ */
+static void
+tcp_recv(isc_nmhandle_t *handle, isc_result_t result, isc_region_t *region,
+ void *arg) {
+ dns_dispatch_t *disp = (dns_dispatch_t *)arg;
+ dns_dispentry_t *resp = NULL;
+ dns_qid_t *qid = NULL;
+ char buf[ISC_SOCKADDR_FORMATSIZE];
+ isc_sockaddr_t peer;
+ dns_displist_t resps = ISC_LIST_INITIALIZER;
+ isc_time_t now;
+ int timeout;
+
+ REQUIRE(VALID_DISPATCH(disp));
+
+ qid = disp->mgr->qid;
+
+ TIME_NOW(&now);
+
+ LOCK(&disp->lock);
+ INSIST(disp->reading);
+ disp->reading = false;
+
+ dispatch_log(disp, LVL(90), "TCP read:%s:requests %u",
+ isc_result_totext(result), disp->requests);
+
+ peer = isc_nmhandle_peeraddr(handle);
+
+ /*
+ * Phase 1: Process timeout and success.
+ */
+ switch (result) {
+ case ISC_R_TIMEDOUT:
+ /*
+ * Time out the oldest response in the active queue.
+ */
+ result = tcp_recv_oldest(disp, &resp);
+ break;
+ case ISC_R_SUCCESS:
+ /* We got an answer */
+ result = tcp_recv_success(disp, region, qid, &peer, &resp);
+ break;
+
+ default:
+ break;
+ }
+
+ if (resp != NULL) {
+ tcp_recv_add(&resps, resp, result);
+ }
+
+ /*
+ * Phase 2: Look if we timed out before.
+ */
+
+ if (result == ISC_R_NOTFOUND) {
+ if (disp->timedout > 0) {
+ /* There was active query that timed-out before */
+ disp->timedout--;
+ } else {
+ result = ISC_R_UNEXPECTED;
+ }
+ }
+
+ /*
+ * Phase 3: Trigger timeouts. It's possible that the responses would
+ * have been timedout out already, but non-matching TCP reads have
+ * prevented this.
+ */
+ dns_dispentry_t *next = NULL;
+ for (resp = ISC_LIST_HEAD(disp->active); resp != NULL; resp = next) {
+ next = ISC_LIST_NEXT(resp, alink);
+
+ timeout = resp->timeout - dispentry_runtime(resp, &now);
+ if (timeout <= 0) {
+ tcp_recv_add(&resps, resp, ISC_R_TIMEDOUT);
+ }
+ }
+
+ /*
+ * Phase 4: log if we errored out.
+ */
+ switch (result) {
+ case ISC_R_SUCCESS:
+ case ISC_R_TIMEDOUT:
+ case ISC_R_NOTFOUND:
+ break;
+
+ case ISC_R_SHUTTINGDOWN:
+ case ISC_R_CANCELED:
+ case ISC_R_EOF:
+ case ISC_R_CONNECTIONRESET:
+ isc_sockaddr_format(&peer, buf, sizeof(buf));
+ dispatch_log(disp, LVL(90), "shutting down TCP: %s: %s", buf,
+ isc_result_totext(result));
+ tcp_recv_shutdown(disp, &resps, result);
+ break;
+ default:
+ isc_sockaddr_format(&peer, buf, sizeof(buf));
+ dispatch_log(disp, ISC_LOG_ERROR,
+ "shutting down due to TCP "
+ "receive error: %s: %s",
+ buf, isc_result_totext(result));
+ tcp_recv_shutdown(disp, &resps, result);
+ break;
+ }
+
+ /*
+ * Phase 5: Resume reading if there are still active responses
+ */
+ resp = ISC_LIST_HEAD(disp->active);
+ if (resp != NULL) {
+ timeout = resp->timeout - dispentry_runtime(resp, &now);
+ INSIST(timeout > 0);
+ tcp_startrecv(NULL, disp, resp);
+ isc_nmhandle_settimeout(handle, timeout);
+ }
+
+ UNLOCK(&disp->lock);
+
+ /*
+ * Phase 6: Process all scheduled callbacks.
+ */
+ tcp_recv_processall(&resps, region);
+
+ dns_dispatch_detach(&disp); /* DISPATCH002 */
+}
+
+/*%
+ * Create a temporary port list to set the initial default set of dispatch
+ * ephemeral ports. This is almost meaningless as the application will
+ * normally set the ports explicitly, but is provided to fill some minor corner
+ * cases.
+ */
+static void
+create_default_portset(isc_mem_t *mctx, int family, isc_portset_t **portsetp) {
+ in_port_t low, high;
+
+ isc_net_getudpportrange(family, &low, &high);
+
+ isc_portset_create(mctx, portsetp);
+ isc_portset_addrange(*portsetp, low, high);
+}
+
+static isc_result_t
+setavailports(dns_dispatchmgr_t *mgr, isc_portset_t *v4portset,
+ isc_portset_t *v6portset) {
+ in_port_t *v4ports, *v6ports, p = 0;
+ unsigned int nv4ports, nv6ports, i4 = 0, i6 = 0;
+
+ nv4ports = isc_portset_nports(v4portset);
+ nv6ports = isc_portset_nports(v6portset);
+
+ v4ports = NULL;
+ if (nv4ports != 0) {
+ v4ports = isc_mem_get(mgr->mctx, sizeof(in_port_t) * nv4ports);
+ }
+ v6ports = NULL;
+ if (nv6ports != 0) {
+ v6ports = isc_mem_get(mgr->mctx, sizeof(in_port_t) * nv6ports);
+ }
+
+ do {
+ if (isc_portset_isset(v4portset, p)) {
+ INSIST(i4 < nv4ports);
+ v4ports[i4++] = p;
+ }
+ if (isc_portset_isset(v6portset, p)) {
+ INSIST(i6 < nv6ports);
+ v6ports[i6++] = p;
+ }
+ } while (p++ < 65535);
+ INSIST(i4 == nv4ports && i6 == nv6ports);
+
+ if (mgr->v4ports != NULL) {
+ isc_mem_put(mgr->mctx, mgr->v4ports,
+ mgr->nv4ports * sizeof(in_port_t));
+ }
+ mgr->v4ports = v4ports;
+ mgr->nv4ports = nv4ports;
+
+ if (mgr->v6ports != NULL) {
+ isc_mem_put(mgr->mctx, mgr->v6ports,
+ mgr->nv6ports * sizeof(in_port_t));
+ }
+ mgr->v6ports = v6ports;
+ mgr->nv6ports = nv6ports;
+
+ return (ISC_R_SUCCESS);
+}
+
+/*
+ * Publics.
+ */
+
+isc_result_t
+dns_dispatchmgr_create(isc_mem_t *mctx, isc_nm_t *nm,
+ dns_dispatchmgr_t **mgrp) {
+ dns_dispatchmgr_t *mgr = NULL;
+ isc_portset_t *v4portset = NULL;
+ isc_portset_t *v6portset = NULL;
+
+ REQUIRE(mctx != NULL);
+ REQUIRE(mgrp != NULL && *mgrp == NULL);
+
+ mgr = isc_mem_get(mctx, sizeof(dns_dispatchmgr_t));
+ *mgr = (dns_dispatchmgr_t){ .magic = 0 };
+
+#if DNS_DISPATCH_TRACE
+ fprintf(stderr, "dns_dispatchmgr__init:%s:%s:%d:%p->references = 1\n",
+ __func__, __FILE__, __LINE__, mgr);
+#endif
+ isc_refcount_init(&mgr->references, 1);
+
+ isc_mem_attach(mctx, &mgr->mctx);
+ isc_nm_attach(nm, &mgr->nm);
+
+ isc_mutex_init(&mgr->lock);
+
+ ISC_LIST_INIT(mgr->list);
+
+ create_default_portset(mctx, AF_INET, &v4portset);
+ create_default_portset(mctx, AF_INET6, &v6portset);
+
+ setavailports(mgr, v4portset, v6portset);
+
+ isc_portset_destroy(mctx, &v4portset);
+ isc_portset_destroy(mctx, &v6portset);
+
+ qid_allocate(mgr, &mgr->qid);
+ mgr->magic = DNS_DISPATCHMGR_MAGIC;
+
+ *mgrp = mgr;
+ return (ISC_R_SUCCESS);
+}
+
+#if DNS_DISPATCH_TRACE
+ISC_REFCOUNT_TRACE_IMPL(dns_dispatchmgr, dispatchmgr_destroy);
+#else
+ISC_REFCOUNT_IMPL(dns_dispatchmgr, dispatchmgr_destroy);
+#endif
+
+void
+dns_dispatchmgr_setblackhole(dns_dispatchmgr_t *mgr, dns_acl_t *blackhole) {
+ REQUIRE(VALID_DISPATCHMGR(mgr));
+ if (mgr->blackhole != NULL) {
+ dns_acl_detach(&mgr->blackhole);
+ }
+ dns_acl_attach(blackhole, &mgr->blackhole);
+}
+
+dns_acl_t *
+dns_dispatchmgr_getblackhole(dns_dispatchmgr_t *mgr) {
+ REQUIRE(VALID_DISPATCHMGR(mgr));
+ return (mgr->blackhole);
+}
+
+isc_result_t
+dns_dispatchmgr_setavailports(dns_dispatchmgr_t *mgr, isc_portset_t *v4portset,
+ isc_portset_t *v6portset) {
+ REQUIRE(VALID_DISPATCHMGR(mgr));
+ return (setavailports(mgr, v4portset, v6portset));
+}
+
+static void
+dispatchmgr_destroy(dns_dispatchmgr_t *mgr) {
+ REQUIRE(VALID_DISPATCHMGR(mgr));
+
+ isc_refcount_destroy(&mgr->references);
+
+ mgr->magic = 0;
+ isc_mutex_destroy(&mgr->lock);
+
+ qid_destroy(mgr->mctx, &mgr->qid);
+
+ if (mgr->blackhole != NULL) {
+ dns_acl_detach(&mgr->blackhole);
+ }
+
+ if (mgr->stats != NULL) {
+ isc_stats_detach(&mgr->stats);
+ }
+
+ if (mgr->v4ports != NULL) {
+ isc_mem_put(mgr->mctx, mgr->v4ports,
+ mgr->nv4ports * sizeof(in_port_t));
+ }
+ if (mgr->v6ports != NULL) {
+ isc_mem_put(mgr->mctx, mgr->v6ports,
+ mgr->nv6ports * sizeof(in_port_t));
+ }
+
+ isc_nm_detach(&mgr->nm);
+
+ isc_mem_putanddetach(&mgr->mctx, mgr, sizeof(dns_dispatchmgr_t));
+}
+
+void
+dns_dispatchmgr_setstats(dns_dispatchmgr_t *mgr, isc_stats_t *stats) {
+ REQUIRE(VALID_DISPATCHMGR(mgr));
+ REQUIRE(ISC_LIST_EMPTY(mgr->list));
+ REQUIRE(mgr->stats == NULL);
+
+ isc_stats_attach(stats, &mgr->stats);
+}
+
+static void
+qid_allocate(dns_dispatchmgr_t *mgr, dns_qid_t **qidp) {
+ dns_qid_t *qid = NULL;
+ unsigned int i;
+
+ REQUIRE(qidp != NULL && *qidp == NULL);
+
+ qid = isc_mem_get(mgr->mctx, sizeof(*qid));
+ *qid = (dns_qid_t){ .qid_nbuckets = DNS_QID_BUCKETS,
+ .qid_increment = DNS_QID_INCREMENT };
+
+ qid->qid_table = isc_mem_get(mgr->mctx,
+ DNS_QID_BUCKETS * sizeof(dns_displist_t));
+ for (i = 0; i < qid->qid_nbuckets; i++) {
+ ISC_LIST_INIT(qid->qid_table[i]);
+ }
+
+ isc_mutex_init(&qid->lock);
+ qid->magic = QID_MAGIC;
+ *qidp = qid;
+}
+
+static void
+qid_destroy(isc_mem_t *mctx, dns_qid_t **qidp) {
+ dns_qid_t *qid = NULL;
+
+ REQUIRE(qidp != NULL);
+ qid = *qidp;
+ *qidp = NULL;
+
+ REQUIRE(VALID_QID(qid));
+
+ qid->magic = 0;
+ isc_mem_put(mctx, qid->qid_table,
+ qid->qid_nbuckets * sizeof(dns_displist_t));
+ isc_mutex_destroy(&qid->lock);
+ isc_mem_put(mctx, qid, sizeof(*qid));
+}
+
+/*
+ * Allocate and set important limits.
+ */
+static void
+dispatch_allocate(dns_dispatchmgr_t *mgr, isc_socktype_t type,
+ dns_dispatch_t **dispp) {
+ dns_dispatch_t *disp = NULL;
+
+ REQUIRE(VALID_DISPATCHMGR(mgr));
+ REQUIRE(dispp != NULL && *dispp == NULL);
+
+ /*
+ * Set up the dispatcher, mostly. Don't bother setting some of
+ * the options that are controlled by tcp vs. udp, etc.
+ */
+
+ disp = isc_mem_get(mgr->mctx, sizeof(*disp));
+ *disp = (dns_dispatch_t){
+ .socktype = type,
+ .link = ISC_LINK_INITIALIZER,
+ .active = ISC_LIST_INITIALIZER,
+ .pending = ISC_LIST_INITIALIZER,
+ .tid = isc_nm_tid(),
+ .magic = DISPATCH_MAGIC,
+ };
+
+ dns_dispatchmgr_attach(mgr, &disp->mgr);
+#if DNS_DISPATCH_TRACE
+ fprintf(stderr, "dns_dispatch__init:%s:%s:%d:%p->references = 1\n",
+ __func__, __FILE__, __LINE__, disp);
+#endif
+ isc_refcount_init(&disp->references, 1); /* DISPATCH000 */
+ isc_mutex_init(&disp->lock);
+
+ *dispp = disp;
+}
+
+isc_result_t
+dns_dispatch_createtcp(dns_dispatchmgr_t *mgr, const isc_sockaddr_t *localaddr,
+ const isc_sockaddr_t *destaddr, dns_dispatch_t **dispp) {
+ dns_dispatch_t *disp = NULL;
+
+ REQUIRE(VALID_DISPATCHMGR(mgr));
+ REQUIRE(destaddr != NULL);
+
+ LOCK(&mgr->lock);
+
+ dispatch_allocate(mgr, isc_socktype_tcp, &disp);
+
+ disp->peer = *destaddr;
+
+ if (localaddr != NULL) {
+ disp->local = *localaddr;
+ } else {
+ int pf;
+ pf = isc_sockaddr_pf(destaddr);
+ isc_sockaddr_anyofpf(&disp->local, pf);
+ isc_sockaddr_setport(&disp->local, 0);
+ }
+
+ /*
+ * Append it to the dispatcher list.
+ */
+
+ /* FIXME: There should be a lookup hashtable here */
+ ISC_LIST_APPEND(mgr->list, disp, link);
+ UNLOCK(&mgr->lock);
+
+ if (isc_log_wouldlog(dns_lctx, 90)) {
+ char addrbuf[ISC_SOCKADDR_FORMATSIZE];
+
+ isc_sockaddr_format(&disp->local, addrbuf,
+ ISC_SOCKADDR_FORMATSIZE);
+
+ mgr_log(mgr, LVL(90),
+ "dns_dispatch_createtcp: created TCP dispatch %p for "
+ "%s",
+ disp, addrbuf);
+ }
+ *dispp = disp;
+
+ return (ISC_R_SUCCESS);
+}
+
+isc_result_t
+dns_dispatch_gettcp(dns_dispatchmgr_t *mgr, const isc_sockaddr_t *destaddr,
+ const isc_sockaddr_t *localaddr, dns_dispatch_t **dispp) {
+ dns_dispatch_t *disp_connected = NULL;
+ dns_dispatch_t *disp_fallback = NULL;
+ isc_result_t result = ISC_R_NOTFOUND;
+
+ REQUIRE(VALID_DISPATCHMGR(mgr));
+ REQUIRE(destaddr != NULL);
+ REQUIRE(dispp != NULL && *dispp == NULL);
+
+ LOCK(&mgr->lock);
+
+ for (dns_dispatch_t *disp = ISC_LIST_HEAD(mgr->list); disp != NULL;
+ disp = ISC_LIST_NEXT(disp, link))
+ {
+ isc_sockaddr_t sockname;
+ isc_sockaddr_t peeraddr;
+
+ LOCK(&disp->lock);
+
+ if (disp->tid != isc_nm_tid()) {
+ UNLOCK(&disp->lock);
+ continue;
+ }
+
+ if (disp->handle != NULL) {
+ sockname = isc_nmhandle_localaddr(disp->handle);
+ peeraddr = isc_nmhandle_peeraddr(disp->handle);
+ } else {
+ sockname = disp->local;
+ peeraddr = disp->peer;
+ }
+
+ /*
+ * The conditions match:
+ * 1. socktype is TCP
+ * 2. destination address is same
+ * 3. local address is either NULL or same
+ */
+ if (disp->socktype != isc_socktype_tcp ||
+ !isc_sockaddr_equal(destaddr, &peeraddr) ||
+ (localaddr != NULL &&
+ !isc_sockaddr_eqaddr(localaddr, &sockname)))
+ {
+ UNLOCK(&disp->lock);
+ continue;
+ }
+
+ switch (disp->state) {
+ case DNS_DISPATCHSTATE_NONE:
+ /* A dispatch in indeterminate state, skip it */
+ break;
+ case DNS_DISPATCHSTATE_CONNECTED:
+ if (ISC_LIST_EMPTY(disp->active)) {
+ /* Ignore dispatch with no responses */
+ break;
+ }
+ /* We found a connected dispatch */
+ dns_dispatch_attach(disp, &disp_connected);
+ break;
+ case DNS_DISPATCHSTATE_CONNECTING:
+ if (ISC_LIST_EMPTY(disp->pending)) {
+ /* Ignore dispatch with no responses */
+ break;
+ }
+ /* We found "a" dispatch, store it for later */
+ if (disp_fallback == NULL) {
+ dns_dispatch_attach(disp, &disp_fallback);
+ }
+ break;
+ case DNS_DISPATCHSTATE_CANCELED:
+ /* A canceled dispatch, skip it. */
+ break;
+ default:
+ UNREACHABLE();
+ }
+
+ UNLOCK(&disp->lock);
+
+ if (disp_connected != NULL) {
+ break;
+ }
+ }
+
+ if (disp_connected != NULL) {
+ /* We found connected dispatch */
+ INSIST(disp_connected->handle != NULL);
+
+ *dispp = disp_connected;
+ disp_connected = NULL;
+
+ result = ISC_R_SUCCESS;
+
+ if (disp_fallback != NULL) {
+ dns_dispatch_detach(&disp_fallback);
+ }
+ } else if (disp_fallback != NULL) {
+ *dispp = disp_fallback;
+
+ result = ISC_R_SUCCESS;
+ }
+
+ UNLOCK(&mgr->lock);
+
+ return (result);
+}
+
+isc_result_t
+dns_dispatch_createudp(dns_dispatchmgr_t *mgr, const isc_sockaddr_t *localaddr,
+ dns_dispatch_t **dispp) {
+ isc_result_t result;
+ dns_dispatch_t *disp = NULL;
+
+ REQUIRE(VALID_DISPATCHMGR(mgr));
+ REQUIRE(localaddr != NULL);
+ REQUIRE(dispp != NULL && *dispp == NULL);
+
+ LOCK(&mgr->lock);
+ result = dispatch_createudp(mgr, localaddr, &disp);
+ if (result == ISC_R_SUCCESS) {
+ *dispp = disp;
+ }
+ UNLOCK(&mgr->lock);
+
+ return (result);
+}
+
+static isc_result_t
+dispatch_createudp(dns_dispatchmgr_t *mgr, const isc_sockaddr_t *localaddr,
+ dns_dispatch_t **dispp) {
+ isc_result_t result = ISC_R_SUCCESS;
+ dns_dispatch_t *disp = NULL;
+ isc_sockaddr_t sa_any;
+
+ /*
+ * Check whether this address/port is available locally.
+ */
+ isc_sockaddr_anyofpf(&sa_any, isc_sockaddr_pf(localaddr));
+ if (!isc_sockaddr_eqaddr(&sa_any, localaddr)) {
+ result = isc_nm_checkaddr(localaddr, isc_socktype_udp);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+ }
+
+ dispatch_allocate(mgr, isc_socktype_udp, &disp);
+
+ if (isc_log_wouldlog(dns_lctx, 90)) {
+ char addrbuf[ISC_SOCKADDR_FORMATSIZE];
+
+ isc_sockaddr_format(localaddr, addrbuf,
+ ISC_SOCKADDR_FORMATSIZE);
+ mgr_log(mgr, LVL(90),
+ "dispatch_createudp: created UDP dispatch %p for %s",
+ disp, addrbuf);
+ }
+
+ disp->local = *localaddr;
+
+ /*
+ * Don't append it to the dispatcher list, we don't care about UDP, only
+ * TCP should be searched
+ *
+ * ISC_LIST_APPEND(mgr->list, disp, link);
+ */
+
+ *dispp = disp;
+
+ return (result);
+}
+
+static void
+dispatch_destroy(dns_dispatch_t *disp) {
+ dns_dispatchmgr_t *mgr = disp->mgr;
+
+ isc_refcount_destroy(&disp->references);
+ disp->magic = 0;
+
+ LOCK(&mgr->lock);
+ if (ISC_LINK_LINKED(disp, link)) {
+ ISC_LIST_UNLINK(disp->mgr->list, disp, link);
+ }
+ UNLOCK(&mgr->lock);
+
+ INSIST(disp->requests == 0);
+ INSIST(ISC_LIST_EMPTY(disp->pending));
+ INSIST(ISC_LIST_EMPTY(disp->active));
+
+ INSIST(!ISC_LINK_LINKED(disp, link));
+
+ dispatch_log(disp, LVL(90), "destroying dispatch %p", disp);
+
+ if (disp->handle) {
+ dispatch_log(disp, LVL(90), "detaching TCP handle %p from %p",
+ disp->handle, &disp->handle);
+ isc_nmhandle_detach(&disp->handle);
+ }
+
+ isc_mutex_destroy(&disp->lock);
+
+ isc_mem_put(mgr->mctx, disp, sizeof(*disp));
+
+ /*
+ * Because dispatch uses mgr->mctx, we must detach after freeing
+ * dispatch, not before.
+ */
+ dns_dispatchmgr_detach(&mgr);
+}
+
+#if DNS_DISPATCH_TRACE
+ISC_REFCOUNT_TRACE_IMPL(dns_dispatch, dispatch_destroy);
+#else
+ISC_REFCOUNT_IMPL(dns_dispatch, dispatch_destroy);
+#endif
+
+isc_result_t
+dns_dispatch_add(dns_dispatch_t *disp, unsigned int options,
+ unsigned int timeout, const isc_sockaddr_t *dest,
+ dispatch_cb_t connected, dispatch_cb_t sent,
+ dispatch_cb_t response, void *arg, dns_messageid_t *idp,
+ dns_dispentry_t **respp) {
+ dns_dispentry_t *resp = NULL;
+ dns_qid_t *qid = NULL;
+ in_port_t localport;
+ dns_messageid_t id;
+ unsigned int bucket;
+ bool ok = false;
+ int i = 0;
+
+ REQUIRE(VALID_DISPATCH(disp));
+ REQUIRE(dest != NULL);
+ REQUIRE(respp != NULL && *respp == NULL);
+ REQUIRE(idp != NULL);
+ REQUIRE(disp->socktype == isc_socktype_tcp ||
+ disp->socktype == isc_socktype_udp);
+ REQUIRE(connected != NULL);
+ REQUIRE(response != NULL);
+ REQUIRE(sent != NULL);
+
+ LOCK(&disp->lock);
+
+ if (disp->state == DNS_DISPATCHSTATE_CANCELED) {
+ UNLOCK(&disp->lock);
+ return (ISC_R_CANCELED);
+ }
+
+ qid = disp->mgr->qid;
+
+ localport = isc_sockaddr_getport(&disp->local);
+
+ resp = isc_mem_get(disp->mgr->mctx, sizeof(*resp));
+ *resp = (dns_dispentry_t){
+ .port = localport,
+ .timeout = timeout,
+ .peer = *dest,
+ .connected = connected,
+ .sent = sent,
+ .response = response,
+ .arg = arg,
+ .link = ISC_LINK_INITIALIZER,
+ .alink = ISC_LINK_INITIALIZER,
+ .plink = ISC_LINK_INITIALIZER,
+ .rlink = ISC_LINK_INITIALIZER,
+ .magic = RESPONSE_MAGIC,
+ };
+
+#if DNS_DISPATCH_TRACE
+ fprintf(stderr, "dns_dispentry__init:%s:%s:%d:%p->references = 1\n",
+ __func__, __FILE__, __LINE__, resp);
+#endif
+ isc_refcount_init(&resp->references, 1); /* DISPENTRY000 */
+
+ if (disp->socktype == isc_socktype_udp) {
+ isc_result_t result = setup_socket(disp, resp, dest,
+ &localport);
+ if (result != ISC_R_SUCCESS) {
+ isc_mem_put(disp->mgr->mctx, resp, sizeof(*resp));
+ UNLOCK(&disp->lock);
+ inc_stats(disp->mgr, dns_resstatscounter_dispsockfail);
+ return (result);
+ }
+ }
+
+ /*
+ * Try somewhat hard to find a unique ID. Start with
+ * a random number unless DNS_DISPATCHOPT_FIXEDID is set,
+ * in which case we start with the ID passed in via *idp.
+ */
+ if ((options & DNS_DISPATCHOPT_FIXEDID) != 0) {
+ id = *idp;
+ } else {
+ id = (dns_messageid_t)isc_random16();
+ }
+
+ LOCK(&qid->lock);
+ do {
+ dns_dispentry_t *entry = NULL;
+ bucket = dns_hash(qid, dest, id, localport);
+ entry = entry_search(qid, dest, id, localport, bucket);
+ if (entry == NULL) {
+ ok = true;
+ break;
+ }
+ if ((options & DNS_DISPATCHOPT_FIXEDID) != 0) {
+ /* When using fixed ID, we either must use it or fail */
+ break;
+ }
+ id += qid->qid_increment;
+ id &= 0x0000ffff;
+ } while (i++ < 64);
+
+ if (ok) {
+ resp->id = id;
+ resp->bucket = bucket;
+ ISC_LIST_APPEND(qid->qid_table[bucket], resp, link);
+ }
+ UNLOCK(&qid->lock);
+
+ if (!ok) {
+ isc_mem_put(disp->mgr->mctx, resp, sizeof(*resp));
+ UNLOCK(&disp->lock);
+ return (ISC_R_NOMORE);
+ }
+
+ dns_dispatch_attach(disp, &resp->disp); /* DISPATCH001 */
+
+ disp->requests++;
+
+ inc_stats(disp->mgr, (disp->socktype == isc_socktype_udp)
+ ? dns_resstatscounter_disprequdp
+ : dns_resstatscounter_dispreqtcp);
+
+ UNLOCK(&disp->lock);
+
+ *idp = id;
+ *respp = resp;
+
+ return (ISC_R_SUCCESS);
+}
+
+isc_result_t
+dns_dispatch_getnext(dns_dispentry_t *resp) {
+ isc_time_t now;
+
+ REQUIRE(VALID_RESPONSE(resp));
+ REQUIRE(VALID_DISPATCH(resp->disp));
+
+ dns_dispatch_t *disp = resp->disp;
+ isc_result_t result = ISC_R_SUCCESS;
+ int32_t timeout = -1;
+
+ dispentry_log(resp, LVL(90), "getnext for QID %d", resp->id);
+
+ TIME_NOW(&now);
+ timeout = resp->timeout - dispentry_runtime(resp, &now);
+ if (timeout <= 0) {
+ return (ISC_R_TIMEDOUT);
+ }
+
+ LOCK(&disp->lock);
+ switch (disp->socktype) {
+ case isc_socktype_udp:
+ udp_dispatch_getnext(resp, timeout);
+ break;
+ case isc_socktype_tcp:
+ tcp_dispatch_getnext(disp, resp, timeout);
+ break;
+ default:
+ UNREACHABLE();
+ }
+ UNLOCK(&disp->lock);
+
+ return (result);
+}
+
+static void
+udp_dispentry_cancel(dns_dispentry_t *resp, isc_result_t result) {
+ REQUIRE(VALID_RESPONSE(resp));
+ REQUIRE(VALID_DISPATCH(resp->disp));
+ REQUIRE(VALID_DISPATCHMGR(resp->disp->mgr));
+
+ dns_dispatch_t *disp = resp->disp;
+ dns_dispatchmgr_t *mgr = disp->mgr;
+ dns_qid_t *qid = mgr->qid;
+ dispatch_cb_t response = NULL;
+
+ LOCK(&disp->lock);
+ dispentry_log(resp, LVL(90),
+ "canceling response: %s, %s/%s (%s/%s), "
+ "requests %u",
+ isc_result_totext(result), state2str(resp->state),
+ resp->reading ? "reading" : "not reading",
+ state2str(disp->state),
+ disp->reading ? "reading" : "not reading",
+ disp->requests);
+
+ if (ISC_LINK_LINKED(resp, alink)) {
+ ISC_LIST_UNLINK(disp->active, resp, alink);
+ }
+
+ switch (resp->state) {
+ case DNS_DISPATCHSTATE_NONE:
+ break;
+
+ case DNS_DISPATCHSTATE_CONNECTING:
+ break;
+
+ case DNS_DISPATCHSTATE_CONNECTED:
+ if (resp->reading) {
+ dns_dispentry_ref(resp); /* DISPENTRY003 */
+ response = resp->response;
+
+ dispentry_log(resp, LVL(90), "canceling read on %p",
+ resp->handle);
+ isc_nm_cancelread(resp->handle);
+ }
+ break;
+
+ case DNS_DISPATCHSTATE_CANCELED:
+ goto unlock;
+
+ default:
+ UNREACHABLE();
+ }
+
+ dec_stats(disp->mgr, dns_resstatscounter_disprequdp);
+
+ LOCK(&qid->lock);
+ ISC_LIST_UNLINK(qid->qid_table[resp->bucket], resp, link);
+ UNLOCK(&qid->lock);
+ resp->state = DNS_DISPATCHSTATE_CANCELED;
+
+unlock:
+ UNLOCK(&disp->lock);
+
+ if (response) {
+ dispentry_log(resp, LVL(90), "read callback: %s",
+ isc_result_totext(result));
+ response(result, NULL, resp->arg);
+ dns_dispentry_detach(&resp); /* DISPENTRY003 */
+ }
+}
+
+static void
+tcp_dispentry_cancel(dns_dispentry_t *resp, isc_result_t result) {
+ REQUIRE(VALID_RESPONSE(resp));
+ REQUIRE(VALID_DISPATCH(resp->disp));
+ REQUIRE(VALID_DISPATCHMGR(resp->disp->mgr));
+
+ dns_dispatch_t *disp = resp->disp;
+ dns_dispatchmgr_t *mgr = disp->mgr;
+ dns_qid_t *qid = mgr->qid;
+ dns_displist_t resps = ISC_LIST_INITIALIZER;
+
+ LOCK(&disp->lock);
+ dispentry_log(resp, LVL(90),
+ "canceling response: %s, %s/%s (%s/%s), "
+ "requests %u",
+ isc_result_totext(result), state2str(resp->state),
+ resp->reading ? "reading" : "not reading",
+ state2str(disp->state),
+ disp->reading ? "reading" : "not reading",
+ disp->requests);
+
+ switch (resp->state) {
+ case DNS_DISPATCHSTATE_NONE:
+ break;
+
+ case DNS_DISPATCHSTATE_CONNECTING:
+ break;
+
+ case DNS_DISPATCHSTATE_CONNECTED:
+ if (resp->reading) {
+ tcp_recv_add(&resps, resp, ISC_R_CANCELED);
+ }
+
+ INSIST(!ISC_LINK_LINKED(resp, alink));
+
+ if (ISC_LIST_EMPTY(disp->active)) {
+ INSIST(disp->handle != NULL);
+
+#if DISPATCH_TCP_KEEPALIVE
+ /*
+ * This is an experimental code that keeps the TCP
+ * connection open for 1 second before it is finally
+ * closed. By keeping the TCP connection open, it can
+ * be reused by dns_request that uses
+ * dns_dispatch_gettcp() to join existing TCP
+ * connections.
+ *
+ * It is disabled for now, because it changes the
+ * behaviour, but I am keeping the code here for future
+ * reference when we improve the dns_dispatch to reuse
+ * the TCP connections also in the resolver.
+ *
+ * The TCP connection reuse should be seamless and not
+ * require any extra handling on the client side though.
+ */
+ isc_nmhandle_cleartimeout(disp->handle);
+ isc_nmhandle_settimeout(disp->handle, 1000);
+
+ if (!disp->reading) {
+ dispentry_log(resp, LVL(90),
+ "final 1 second timeout on %p",
+ disp->handle);
+ tcp_startrecv(NULL, disp, NULL);
+ }
+#else
+ if (disp->reading) {
+ dispentry_log(resp, LVL(90),
+ "canceling read on %p",
+ disp->handle);
+ isc_nm_cancelread(disp->handle);
+ }
+#endif
+ }
+ break;
+
+ case DNS_DISPATCHSTATE_CANCELED:
+ goto unlock;
+
+ default:
+ UNREACHABLE();
+ }
+
+ dec_stats(disp->mgr, dns_resstatscounter_dispreqtcp);
+
+ LOCK(&qid->lock);
+ ISC_LIST_UNLINK(qid->qid_table[resp->bucket], resp, link);
+ UNLOCK(&qid->lock);
+ resp->state = DNS_DISPATCHSTATE_CANCELED;
+
+unlock:
+ UNLOCK(&disp->lock);
+
+ /*
+ * NOTE: Calling the response callback directly from here should be done
+ * asynchronously, as the dns_dispatch_done() is usually called directly
+ * from the response callback, so there's a slight chance that the call
+ * stack will get higher here, but it's mitigated by the ".reading"
+ * flag, so we don't ever go into a loop.
+ */
+
+ tcp_recv_processall(&resps, NULL);
+}
+
+static void
+dispentry_cancel(dns_dispentry_t *resp, isc_result_t result) {
+ REQUIRE(VALID_RESPONSE(resp));
+ REQUIRE(VALID_DISPATCH(resp->disp));
+
+ dns_dispatch_t *disp = resp->disp;
+
+ switch (disp->socktype) {
+ case isc_socktype_udp:
+ udp_dispentry_cancel(resp, result);
+ break;
+ case isc_socktype_tcp:
+ tcp_dispentry_cancel(resp, result);
+ break;
+ default:
+ UNREACHABLE();
+ }
+}
+
+void
+dns_dispatch_done(dns_dispentry_t **respp) {
+ REQUIRE(VALID_RESPONSE(*respp));
+
+ dns_dispentry_t *resp = *respp;
+ *respp = NULL;
+
+ dispentry_cancel(resp, ISC_R_CANCELED);
+ dns_dispentry_detach(&resp); /* DISPENTRY000 */
+}
+
+static void
+udp_startrecv(isc_nmhandle_t *handle, dns_dispentry_t *resp) {
+ REQUIRE(VALID_RESPONSE(resp));
+
+ dispentry_log(resp, LVL(90), "attaching handle %p to %p", handle,
+ &resp->handle);
+ isc_nmhandle_attach(handle, &resp->handle);
+ dns_dispentry_ref(resp); /* DISPENTRY003 */
+ dispentry_log(resp, LVL(90), "reading");
+ isc_nm_read(resp->handle, udp_recv, resp);
+ resp->reading = true;
+}
+
+static void
+tcp_startrecv(isc_nmhandle_t *handle, dns_dispatch_t *disp,
+ dns_dispentry_t *resp) {
+ REQUIRE(VALID_DISPATCH(disp));
+ REQUIRE(disp->socktype == isc_socktype_tcp);
+
+ if (handle != NULL) {
+ isc_nmhandle_attach(handle, &disp->handle);
+ }
+ dns_dispatch_ref(disp); /* DISPATCH002 */
+ if (resp != NULL) {
+ dispentry_log(resp, LVL(90), "reading from %p", disp->handle);
+ INSIST(!isc_time_isepoch(&resp->start));
+ } else {
+ dispatch_log(disp, LVL(90),
+ "TCP reading without response from %p",
+ disp->handle);
+ }
+ isc_nm_read(disp->handle, tcp_recv, disp);
+ disp->reading = true;
+}
+
+static void
+tcp_connected(isc_nmhandle_t *handle, isc_result_t eresult, void *arg) {
+ dns_dispatch_t *disp = (dns_dispatch_t *)arg;
+ dns_dispentry_t *resp = NULL;
+ dns_dispentry_t *next = NULL;
+ dns_displist_t resps = ISC_LIST_INITIALIZER;
+
+ if (isc_log_wouldlog(dns_lctx, 90)) {
+ char localbuf[ISC_SOCKADDR_FORMATSIZE];
+ char peerbuf[ISC_SOCKADDR_FORMATSIZE];
+ if (handle != NULL) {
+ isc_sockaddr_t local = isc_nmhandle_localaddr(handle);
+ isc_sockaddr_t peer = isc_nmhandle_peeraddr(handle);
+
+ isc_sockaddr_format(&local, localbuf,
+ ISC_SOCKADDR_FORMATSIZE);
+ isc_sockaddr_format(&peer, peerbuf,
+ ISC_SOCKADDR_FORMATSIZE);
+ } else {
+ isc_sockaddr_format(&disp->local, localbuf,
+ ISC_SOCKADDR_FORMATSIZE);
+ isc_sockaddr_format(&disp->peer, peerbuf,
+ ISC_SOCKADDR_FORMATSIZE);
+ }
+
+ dispatch_log(disp, LVL(90), "connected from %s to %s: %s",
+ localbuf, peerbuf, isc_result_totext(eresult));
+ }
+
+ LOCK(&disp->lock);
+ INSIST(disp->state == DNS_DISPATCHSTATE_CONNECTING);
+
+ /*
+ * If there are pending responses, call the connect
+ * callbacks for all of them.
+ */
+ for (resp = ISC_LIST_HEAD(disp->pending); resp != NULL; resp = next) {
+ next = ISC_LIST_NEXT(resp, plink);
+ ISC_LIST_UNLINK(disp->pending, resp, plink);
+ ISC_LIST_APPEND(resps, resp, rlink);
+ resp->result = eresult;
+
+ if (resp->state == DNS_DISPATCHSTATE_CANCELED) {
+ resp->result = ISC_R_CANCELED;
+ } else if (eresult == ISC_R_SUCCESS) {
+ resp->state = DNS_DISPATCHSTATE_CONNECTED;
+ ISC_LIST_APPEND(disp->active, resp, alink);
+ resp->reading = true;
+ dispentry_log(resp, LVL(90), "start reading");
+ } else {
+ resp->state = DNS_DISPATCHSTATE_NONE;
+ }
+ }
+
+ if (ISC_LIST_EMPTY(disp->active)) {
+ /* All responses have been canceled */
+ disp->state = DNS_DISPATCHSTATE_CANCELED;
+ } else if (eresult == ISC_R_SUCCESS) {
+ disp->state = DNS_DISPATCHSTATE_CONNECTED;
+ tcp_startrecv(handle, disp, resp);
+ } else {
+ disp->state = DNS_DISPATCHSTATE_NONE;
+ }
+
+ UNLOCK(&disp->lock);
+
+ for (resp = ISC_LIST_HEAD(resps); resp != NULL; resp = next) {
+ next = ISC_LIST_NEXT(resp, rlink);
+ ISC_LIST_UNLINK(resps, resp, rlink);
+
+ dispentry_log(resp, LVL(90), "connect callback: %s",
+ isc_result_totext(resp->result));
+ resp->connected(resp->result, NULL, resp->arg);
+ dns_dispentry_detach(&resp); /* DISPENTRY005 */
+ }
+
+ dns_dispatch_detach(&disp); /* DISPATCH003 */
+}
+
+static void
+udp_connected(isc_nmhandle_t *handle, isc_result_t eresult, void *arg) {
+ dns_dispentry_t *resp = (dns_dispentry_t *)arg;
+ dns_dispatch_t *disp = resp->disp;
+
+ dispentry_log(resp, LVL(90), "connected: %s",
+ isc_result_totext(eresult));
+
+ LOCK(&disp->lock);
+
+ switch (resp->state) {
+ case DNS_DISPATCHSTATE_CANCELED:
+ eresult = ISC_R_CANCELED;
+ ISC_LIST_UNLINK(disp->pending, resp, plink);
+ goto unlock;
+ case DNS_DISPATCHSTATE_CONNECTING:
+ ISC_LIST_UNLINK(disp->pending, resp, plink);
+ break;
+ default:
+ UNREACHABLE();
+ }
+
+ switch (eresult) {
+ case ISC_R_CANCELED:
+ break;
+ case ISC_R_SUCCESS:
+ resp->state = DNS_DISPATCHSTATE_CONNECTED;
+ udp_startrecv(handle, resp);
+ break;
+ case ISC_R_NOPERM:
+ case ISC_R_ADDRINUSE: {
+ in_port_t localport = isc_sockaddr_getport(&disp->local);
+ isc_result_t result;
+
+ /* probably a port collision; try a different one */
+ result = setup_socket(disp, resp, &resp->peer, &localport);
+ if (result == ISC_R_SUCCESS) {
+ UNLOCK(&disp->lock);
+ udp_dispatch_connect(disp, resp);
+ goto detach;
+ }
+ resp->state = DNS_DISPATCHSTATE_NONE;
+ break;
+ }
+ default:
+ resp->state = DNS_DISPATCHSTATE_NONE;
+ break;
+ }
+unlock:
+ UNLOCK(&disp->lock);
+
+ dispentry_log(resp, LVL(90), "connect callback: %s",
+ isc_result_totext(eresult));
+ resp->connected(eresult, NULL, resp->arg);
+
+detach:
+ dns_dispentry_detach(&resp); /* DISPENTRY004 */
+}
+
+static void
+udp_dispatch_connect(dns_dispatch_t *disp, dns_dispentry_t *resp) {
+ LOCK(&disp->lock);
+ resp->state = DNS_DISPATCHSTATE_CONNECTING;
+ TIME_NOW(&resp->start);
+ dns_dispentry_ref(resp); /* DISPENTRY004 */
+ ISC_LIST_APPEND(disp->pending, resp, plink);
+ UNLOCK(&disp->lock);
+
+ isc_nm_udpconnect(disp->mgr->nm, &resp->local, &resp->peer,
+ udp_connected, resp, resp->timeout, 0);
+}
+
+static isc_result_t
+tcp_dispatch_connect(dns_dispatch_t *disp, dns_dispentry_t *resp) {
+ /* Check whether the dispatch is already connecting or connected. */
+ LOCK(&disp->lock);
+ switch (disp->state) {
+ case DNS_DISPATCHSTATE_NONE:
+ /* First connection, continue with connecting */
+ disp->state = DNS_DISPATCHSTATE_CONNECTING;
+ resp->state = DNS_DISPATCHSTATE_CONNECTING;
+ TIME_NOW(&resp->start);
+ dns_dispentry_ref(resp); /* DISPENTRY005 */
+ ISC_LIST_APPEND(disp->pending, resp, plink);
+ UNLOCK(&disp->lock);
+
+ char localbuf[ISC_SOCKADDR_FORMATSIZE];
+ char peerbuf[ISC_SOCKADDR_FORMATSIZE];
+
+ isc_sockaddr_format(&disp->local, localbuf,
+ ISC_SOCKADDR_FORMATSIZE);
+ isc_sockaddr_format(&disp->peer, peerbuf,
+ ISC_SOCKADDR_FORMATSIZE);
+
+ dns_dispatch_ref(disp); /* DISPATCH003 */
+ dispentry_log(resp, LVL(90),
+ "connecting from %s to %s, timeout %u", localbuf,
+ peerbuf, resp->timeout);
+
+ isc_nm_tcpdnsconnect(disp->mgr->nm, &disp->local, &disp->peer,
+ tcp_connected, disp, resp->timeout, 0);
+ break;
+
+ case DNS_DISPATCHSTATE_CONNECTING:
+ /* Connection pending; add resp to the list */
+ resp->state = DNS_DISPATCHSTATE_CONNECTING;
+ TIME_NOW(&resp->start);
+ dns_dispentry_ref(resp); /* DISPENTRY005 */
+ ISC_LIST_APPEND(disp->pending, resp, plink);
+ UNLOCK(&disp->lock);
+ break;
+
+ case DNS_DISPATCHSTATE_CONNECTED:
+ resp->state = DNS_DISPATCHSTATE_CONNECTED;
+ TIME_NOW(&resp->start);
+
+ /* Add the resp to the reading list */
+ ISC_LIST_APPEND(disp->active, resp, alink);
+ dispentry_log(resp, LVL(90), "already connected; attaching");
+ resp->reading = true;
+
+ if (!disp->reading) {
+ /* Restart the reading */
+ tcp_startrecv(NULL, disp, resp);
+ }
+
+ UNLOCK(&disp->lock);
+ /* We are already connected; call the connected cb */
+ dispentry_log(resp, LVL(90), "connect callback: %s",
+ isc_result_totext(ISC_R_SUCCESS));
+ resp->connected(ISC_R_SUCCESS, NULL, resp->arg);
+ break;
+
+ default:
+ UNREACHABLE();
+ }
+
+ return (ISC_R_SUCCESS);
+}
+
+isc_result_t
+dns_dispatch_connect(dns_dispentry_t *resp) {
+ REQUIRE(VALID_RESPONSE(resp));
+ REQUIRE(VALID_DISPATCH(resp->disp));
+
+ dns_dispatch_t *disp = resp->disp;
+
+ switch (disp->socktype) {
+ case isc_socktype_tcp:
+ return (tcp_dispatch_connect(disp, resp));
+
+ case isc_socktype_udp:
+ udp_dispatch_connect(disp, resp);
+ return (ISC_R_SUCCESS);
+
+ default:
+ UNREACHABLE();
+ }
+}
+
+static void
+send_done(isc_nmhandle_t *handle, isc_result_t result, void *cbarg) {
+ dns_dispentry_t *resp = (dns_dispentry_t *)cbarg;
+
+ REQUIRE(VALID_RESPONSE(resp));
+
+ dns_dispatch_t *disp = resp->disp;
+
+ REQUIRE(VALID_DISPATCH(disp));
+
+ dispentry_log(resp, LVL(90), "sent: %s", isc_result_totext(result));
+
+ resp->sent(result, NULL, resp->arg);
+
+ if (result != ISC_R_SUCCESS) {
+ dispentry_cancel(resp, result);
+ }
+
+ dns_dispentry_detach(&resp); /* DISPENTRY007 */
+ isc_nmhandle_detach(&handle);
+}
+
+static void
+tcp_dispatch_getnext(dns_dispatch_t *disp, dns_dispentry_t *resp,
+ int32_t timeout) {
+ REQUIRE(timeout <= INT16_MAX);
+
+ if (disp->reading) {
+ return;
+ }
+
+ if (timeout > 0) {
+ isc_nmhandle_settimeout(disp->handle, timeout);
+ }
+
+ dispentry_log(resp, LVL(90), "continue reading");
+
+ dns_dispatch_ref(disp); /* DISPATCH002 */
+ isc_nm_read(disp->handle, tcp_recv, disp);
+ disp->reading = true;
+
+ ISC_LIST_APPEND(disp->active, resp, alink);
+ resp->reading = true;
+}
+
+static void
+udp_dispatch_getnext(dns_dispentry_t *resp, int32_t timeout) {
+ REQUIRE(timeout <= INT16_MAX);
+
+ if (resp->reading) {
+ return;
+ }
+
+ if (timeout > 0) {
+ isc_nmhandle_settimeout(resp->handle, timeout);
+ }
+
+ dispentry_log(resp, LVL(90), "continue reading");
+
+ dns_dispentry_ref(resp); /* DISPENTRY003 */
+ isc_nm_read(resp->handle, udp_recv, resp);
+ resp->reading = true;
+}
+
+void
+dns_dispatch_resume(dns_dispentry_t *resp, uint16_t timeout) {
+ REQUIRE(VALID_RESPONSE(resp));
+ REQUIRE(VALID_DISPATCH(resp->disp));
+
+ dns_dispatch_t *disp = resp->disp;
+
+ LOCK(&disp->lock);
+ switch (disp->socktype) {
+ case isc_socktype_udp: {
+ udp_dispatch_getnext(resp, timeout);
+ break;
+ }
+ case isc_socktype_tcp:
+ INSIST(disp->timedout > 0);
+ disp->timedout--;
+ tcp_dispatch_getnext(disp, resp, timeout);
+ break;
+ default:
+ UNREACHABLE();
+ }
+
+ UNLOCK(&disp->lock);
+}
+
+void
+dns_dispatch_send(dns_dispentry_t *resp, isc_region_t *r) {
+ REQUIRE(VALID_RESPONSE(resp));
+ REQUIRE(VALID_DISPATCH(resp->disp));
+
+ dns_dispatch_t *disp = resp->disp;
+ isc_nmhandle_t *sendhandle = NULL;
+
+ dispentry_log(resp, LVL(90), "sending");
+ switch (disp->socktype) {
+ case isc_socktype_udp:
+ isc_nmhandle_attach(resp->handle, &sendhandle);
+ break;
+ case isc_socktype_tcp:
+ isc_nmhandle_attach(disp->handle, &sendhandle);
+ break;
+ default:
+ UNREACHABLE();
+ }
+ dns_dispentry_ref(resp); /* DISPENTRY007 */
+ isc_nm_send(sendhandle, r, send_done, resp);
+}
+
+isc_result_t
+dns_dispatch_getlocaladdress(dns_dispatch_t *disp, isc_sockaddr_t *addrp) {
+ REQUIRE(VALID_DISPATCH(disp));
+ REQUIRE(addrp != NULL);
+
+ if (disp->socktype == isc_socktype_udp) {
+ *addrp = disp->local;
+ return (ISC_R_SUCCESS);
+ }
+ return (ISC_R_NOTIMPLEMENTED);
+}
+
+isc_result_t
+dns_dispentry_getlocaladdress(dns_dispentry_t *resp, isc_sockaddr_t *addrp) {
+ REQUIRE(VALID_RESPONSE(resp));
+ REQUIRE(VALID_DISPATCH(resp->disp));
+ REQUIRE(addrp != NULL);
+
+ dns_dispatch_t *disp = resp->disp;
+
+ switch (disp->socktype) {
+ case isc_socktype_tcp:
+ *addrp = disp->local;
+ return (ISC_R_SUCCESS);
+ case isc_socktype_udp:
+ *addrp = isc_nmhandle_localaddr(resp->handle);
+ return (ISC_R_SUCCESS);
+ default:
+ UNREACHABLE();
+ }
+}
+
+dns_dispatch_t *
+dns_dispatchset_get(dns_dispatchset_t *dset) {
+ dns_dispatch_t *disp = NULL;
+
+ /* check that dispatch set is configured */
+ if (dset == NULL || dset->ndisp == 0) {
+ return (NULL);
+ }
+
+ LOCK(&dset->lock);
+ disp = dset->dispatches[dset->cur];
+ dset->cur++;
+ if (dset->cur == dset->ndisp) {
+ dset->cur = 0;
+ }
+ UNLOCK(&dset->lock);
+
+ return (disp);
+}
+
+isc_result_t
+dns_dispatchset_create(isc_mem_t *mctx, dns_dispatch_t *source,
+ dns_dispatchset_t **dsetp, int n) {
+ isc_result_t result;
+ dns_dispatchset_t *dset = NULL;
+ dns_dispatchmgr_t *mgr = NULL;
+ int i, j;
+
+ REQUIRE(VALID_DISPATCH(source));
+ REQUIRE(source->socktype == isc_socktype_udp);
+ REQUIRE(dsetp != NULL && *dsetp == NULL);
+
+ mgr = source->mgr;
+
+ dset = isc_mem_get(mctx, sizeof(dns_dispatchset_t));
+ *dset = (dns_dispatchset_t){ .ndisp = n };
+
+ isc_mutex_init(&dset->lock);
+
+ dset->dispatches = isc_mem_get(mctx, sizeof(dns_dispatch_t *) * n);
+
+ isc_mem_attach(mctx, &dset->mctx);
+
+ dset->dispatches[0] = NULL;
+ dns_dispatch_attach(source, &dset->dispatches[0]); /* DISPATCH004 */
+
+ LOCK(&mgr->lock);
+ for (i = 1; i < n; i++) {
+ dset->dispatches[i] = NULL;
+ result = dispatch_createudp(mgr, &source->local,
+ &dset->dispatches[i]);
+ if (result != ISC_R_SUCCESS) {
+ goto fail;
+ }
+ }
+
+ UNLOCK(&mgr->lock);
+ *dsetp = dset;
+
+ return (ISC_R_SUCCESS);
+
+fail:
+ UNLOCK(&mgr->lock);
+
+ for (j = 0; j < i; j++) {
+ dns_dispatch_detach(&(dset->dispatches[j])); /* DISPATCH004 */
+ }
+ isc_mem_put(mctx, dset->dispatches, sizeof(dns_dispatch_t *) * n);
+ if (dset->mctx == mctx) {
+ isc_mem_detach(&dset->mctx);
+ }
+
+ isc_mutex_destroy(&dset->lock);
+ isc_mem_put(mctx, dset, sizeof(dns_dispatchset_t));
+ return (result);
+}
+
+void
+dns_dispatchset_destroy(dns_dispatchset_t **dsetp) {
+ dns_dispatchset_t *dset = NULL;
+ int i;
+
+ REQUIRE(dsetp != NULL && *dsetp != NULL);
+
+ dset = *dsetp;
+ *dsetp = NULL;
+ for (i = 0; i < dset->ndisp; i++) {
+ dns_dispatch_detach(&(dset->dispatches[i])); /* DISPATCH004 */
+ }
+ isc_mem_put(dset->mctx, dset->dispatches,
+ sizeof(dns_dispatch_t *) * dset->ndisp);
+ isc_mutex_destroy(&dset->lock);
+ isc_mem_putanddetach(&dset->mctx, dset, sizeof(dns_dispatchset_t));
+}
diff --git a/lib/dns/dlz.c b/lib/dns/dlz.c
new file mode 100644
index 0000000..4462a7b
--- /dev/null
+++ b/lib/dns/dlz.c
@@ -0,0 +1,537 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0 AND ISC
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/*
+ * Copyright (C) 2002 Stichting NLnet, Netherlands, stichting@nlnet.nl.
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the
+ * above copyright notice and this permission notice appear in all
+ * copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND STICHTING NLNET
+ * DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
+ * STICHTING NLNET BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR
+ * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
+ * OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+ * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE
+ * USE OR PERFORMANCE OF THIS SOFTWARE.
+ *
+ * The development of Dynamically Loadable Zones (DLZ) for Bind 9 was
+ * conceived and contributed by Rob Butler.
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the
+ * above copyright notice and this permission notice appear in all
+ * copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND ROB BUTLER
+ * DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
+ * ROB BUTLER BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR
+ * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
+ * OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+ * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE
+ * USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+/*! \file */
+
+/***
+ *** Imports
+ ***/
+
+#include <stdbool.h>
+
+#include <isc/buffer.h>
+#include <isc/commandline.h>
+#include <isc/magic.h>
+#include <isc/mem.h>
+#include <isc/once.h>
+#include <isc/rwlock.h>
+#include <isc/string.h>
+#include <isc/util.h>
+
+#include <dns/db.h>
+#include <dns/dlz.h>
+#include <dns/fixedname.h>
+#include <dns/log.h>
+#include <dns/master.h>
+#include <dns/ssu.h>
+#include <dns/zone.h>
+
+/***
+ *** Supported DLZ DB Implementations Registry
+ ***/
+
+static ISC_LIST(dns_dlzimplementation_t) dlz_implementations;
+static isc_rwlock_t dlz_implock;
+static isc_once_t once = ISC_ONCE_INIT;
+
+static void
+dlz_initialize(void) {
+ isc_rwlock_init(&dlz_implock, 0, 0);
+ ISC_LIST_INIT(dlz_implementations);
+}
+
+/*%
+ * Searches the dlz_implementations list for a driver matching name.
+ */
+static dns_dlzimplementation_t *
+dlz_impfind(const char *name) {
+ dns_dlzimplementation_t *imp;
+
+ for (imp = ISC_LIST_HEAD(dlz_implementations); imp != NULL;
+ imp = ISC_LIST_NEXT(imp, link))
+ {
+ if (strcasecmp(name, imp->name) == 0) {
+ return (imp);
+ }
+ }
+ return (NULL);
+}
+
+/***
+ *** Basic DLZ Methods
+ ***/
+
+isc_result_t
+dns_dlzallowzonexfr(dns_view_t *view, const dns_name_t *name,
+ const isc_sockaddr_t *clientaddr, dns_db_t **dbp) {
+ isc_result_t result = ISC_R_NOTFOUND;
+ dns_dlzallowzonexfr_t allowzonexfr;
+ dns_dlzdb_t *dlzdb;
+
+ /*
+ * Performs checks to make sure data is as we expect it to be.
+ */
+ REQUIRE(name != NULL);
+ REQUIRE(dbp != NULL && *dbp == NULL);
+
+ /*
+ * Find a driver in which the zone exists and transfer is supported
+ */
+ for (dlzdb = ISC_LIST_HEAD(view->dlz_searched); dlzdb != NULL;
+ dlzdb = ISC_LIST_NEXT(dlzdb, link))
+ {
+ REQUIRE(DNS_DLZ_VALID(dlzdb));
+
+ allowzonexfr = dlzdb->implementation->methods->allowzonexfr;
+ result = (*allowzonexfr)(dlzdb->implementation->driverarg,
+ dlzdb->dbdata, dlzdb->mctx,
+ view->rdclass, name, clientaddr, dbp);
+
+ /*
+ * In these cases, we found the right database. Non-success
+ * result codes indicate the zone might not transfer.
+ */
+ switch (result) {
+ case ISC_R_SUCCESS:
+ case ISC_R_NOPERM:
+ case ISC_R_DEFAULT:
+ return (result);
+ default:
+ break;
+ }
+ }
+
+ if (result == ISC_R_NOTIMPLEMENTED) {
+ result = ISC_R_NOTFOUND;
+ }
+
+ return (result);
+}
+
+isc_result_t
+dns_dlzcreate(isc_mem_t *mctx, const char *dlzname, const char *drivername,
+ unsigned int argc, char *argv[], dns_dlzdb_t **dbp) {
+ dns_dlzimplementation_t *impinfo;
+ isc_result_t result;
+ dns_dlzdb_t *db = NULL;
+
+ /*
+ * initialize the dlz_implementations list, this is guaranteed
+ * to only really happen once.
+ */
+ RUNTIME_CHECK(isc_once_do(&once, dlz_initialize) == ISC_R_SUCCESS);
+
+ /*
+ * Performs checks to make sure data is as we expect it to be.
+ */
+ REQUIRE(dbp != NULL && *dbp == NULL);
+ REQUIRE(dlzname != NULL);
+ REQUIRE(drivername != NULL);
+ REQUIRE(mctx != NULL);
+
+ /* write log message */
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, DNS_LOGMODULE_DLZ,
+ ISC_LOG_INFO, "Loading '%s' using driver %s", dlzname,
+ drivername);
+
+ /* lock the dlz_implementations list so we can search it. */
+ RWLOCK(&dlz_implock, isc_rwlocktype_read);
+
+ /* search for the driver implementation */
+ impinfo = dlz_impfind(drivername);
+ if (impinfo == NULL) {
+ RWUNLOCK(&dlz_implock, isc_rwlocktype_read);
+
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE,
+ DNS_LOGMODULE_DLZ, ISC_LOG_ERROR,
+ "unsupported DLZ database driver '%s'."
+ " %s not loaded.",
+ drivername, dlzname);
+
+ return (ISC_R_NOTFOUND);
+ }
+
+ /* Allocate memory to hold the DLZ database driver */
+ db = isc_mem_get(mctx, sizeof(dns_dlzdb_t));
+
+ /* Make sure memory region is set to all 0's */
+ memset(db, 0, sizeof(dns_dlzdb_t));
+
+ ISC_LINK_INIT(db, link);
+ db->implementation = impinfo;
+ if (dlzname != NULL) {
+ db->dlzname = isc_mem_strdup(mctx, dlzname);
+ }
+
+ /* Create a new database using implementation 'drivername'. */
+ result = ((impinfo->methods->create)(mctx, dlzname, argc, argv,
+ impinfo->driverarg, &db->dbdata));
+
+ /* mark the DLZ driver as valid */
+ if (result == ISC_R_SUCCESS) {
+ RWUNLOCK(&dlz_implock, isc_rwlocktype_read);
+ db->magic = DNS_DLZ_MAGIC;
+ isc_mem_attach(mctx, &db->mctx);
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE,
+ DNS_LOGMODULE_DLZ, ISC_LOG_DEBUG(2),
+ "DLZ driver loaded successfully.");
+ *dbp = db;
+ return (ISC_R_SUCCESS);
+ } else {
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE,
+ DNS_LOGMODULE_DLZ, ISC_LOG_ERROR,
+ "DLZ driver failed to load.");
+ }
+
+ /* impinfo->methods->create failed. */
+ RWUNLOCK(&dlz_implock, isc_rwlocktype_read);
+ isc_mem_free(mctx, db->dlzname);
+ isc_mem_put(mctx, db, sizeof(dns_dlzdb_t));
+ return (result);
+}
+
+void
+dns_dlzdestroy(dns_dlzdb_t **dbp) {
+ dns_dlzdestroy_t destroy;
+ dns_dlzdb_t *db;
+
+ /* Write debugging message to log */
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, DNS_LOGMODULE_DLZ,
+ ISC_LOG_DEBUG(2), "Unloading DLZ driver.");
+
+ /*
+ * Perform checks to make sure data is as we expect it to be.
+ */
+ REQUIRE(dbp != NULL && DNS_DLZ_VALID(*dbp));
+
+ db = *dbp;
+ *dbp = NULL;
+
+ if (db->ssutable != NULL) {
+ dns_ssutable_detach(&db->ssutable);
+ }
+
+ /* call the drivers destroy method */
+ if (db->dlzname != NULL) {
+ isc_mem_free(db->mctx, db->dlzname);
+ }
+ destroy = db->implementation->methods->destroy;
+ (*destroy)(db->implementation->driverarg, db->dbdata);
+ /* return memory and detach */
+ isc_mem_putanddetach(&db->mctx, db, sizeof(dns_dlzdb_t));
+}
+
+/*%
+ * Registers a DLZ driver. This basically just adds the dlz
+ * driver to the list of available drivers in the dlz_implementations list.
+ */
+isc_result_t
+dns_dlzregister(const char *drivername, const dns_dlzmethods_t *methods,
+ void *driverarg, isc_mem_t *mctx,
+ dns_dlzimplementation_t **dlzimp) {
+ dns_dlzimplementation_t *dlz_imp;
+
+ /* Write debugging message to log */
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, DNS_LOGMODULE_DLZ,
+ ISC_LOG_DEBUG(2), "Registering DLZ driver '%s'",
+ drivername);
+
+ /*
+ * Performs checks to make sure data is as we expect it to be.
+ */
+ REQUIRE(drivername != NULL);
+ REQUIRE(methods != NULL);
+ REQUIRE(methods->create != NULL);
+ REQUIRE(methods->destroy != NULL);
+ REQUIRE(methods->findzone != NULL);
+ REQUIRE(mctx != NULL);
+ REQUIRE(dlzimp != NULL && *dlzimp == NULL);
+
+ /*
+ * initialize the dlz_implementations list, this is guaranteed
+ * to only really happen once.
+ */
+ RUNTIME_CHECK(isc_once_do(&once, dlz_initialize) == ISC_R_SUCCESS);
+
+ /* lock the dlz_implementations list so we can modify it. */
+ RWLOCK(&dlz_implock, isc_rwlocktype_write);
+
+ /*
+ * check that another already registered driver isn't using
+ * the same name
+ */
+ dlz_imp = dlz_impfind(drivername);
+ if (dlz_imp != NULL) {
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE,
+ DNS_LOGMODULE_DLZ, ISC_LOG_DEBUG(2),
+ "DLZ Driver '%s' already registered", drivername);
+ RWUNLOCK(&dlz_implock, isc_rwlocktype_write);
+ return (ISC_R_EXISTS);
+ }
+
+ /*
+ * Allocate memory for a dlz_implementation object. Error if
+ * we cannot.
+ */
+ dlz_imp = isc_mem_get(mctx, sizeof(dns_dlzimplementation_t));
+
+ /* Make sure memory region is set to all 0's */
+ memset(dlz_imp, 0, sizeof(dns_dlzimplementation_t));
+
+ /* Store the data passed into this method */
+ dlz_imp->name = drivername;
+ dlz_imp->methods = methods;
+ dlz_imp->mctx = NULL;
+ dlz_imp->driverarg = driverarg;
+
+ /* attach the new dlz_implementation object to a memory context */
+ isc_mem_attach(mctx, &dlz_imp->mctx);
+
+ /*
+ * prepare the dlz_implementation object to be put in a list,
+ * and append it to the list
+ */
+ ISC_LINK_INIT(dlz_imp, link);
+ ISC_LIST_APPEND(dlz_implementations, dlz_imp, link);
+
+ /* Unlock the dlz_implementations list. */
+ RWUNLOCK(&dlz_implock, isc_rwlocktype_write);
+
+ /* Pass back the dlz_implementation that we created. */
+ *dlzimp = dlz_imp;
+
+ return (ISC_R_SUCCESS);
+}
+
+/*%
+ * Tokenize the string "s" into whitespace-separated words,
+ * return the number of words in '*argcp' and an array
+ * of pointers to the words in '*argvp'. The caller
+ * must free the array using isc_mem_put(). The string
+ * is modified in-place.
+ */
+isc_result_t
+dns_dlzstrtoargv(isc_mem_t *mctx, char *s, unsigned int *argcp, char ***argvp) {
+ return (isc_commandline_strtoargv(mctx, s, argcp, argvp, 0));
+}
+
+/*%
+ * Unregisters a DLZ driver. This basically just removes the dlz
+ * driver from the list of available drivers in the dlz_implementations list.
+ */
+void
+dns_dlzunregister(dns_dlzimplementation_t **dlzimp) {
+ dns_dlzimplementation_t *dlz_imp;
+
+ /* Write debugging message to log */
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, DNS_LOGMODULE_DLZ,
+ ISC_LOG_DEBUG(2), "Unregistering DLZ driver.");
+
+ /*
+ * Performs checks to make sure data is as we expect it to be.
+ */
+ REQUIRE(dlzimp != NULL && *dlzimp != NULL);
+
+ /*
+ * initialize the dlz_implementations list, this is guaranteed
+ * to only really happen once.
+ */
+ RUNTIME_CHECK(isc_once_do(&once, dlz_initialize) == ISC_R_SUCCESS);
+
+ dlz_imp = *dlzimp;
+
+ /* lock the dlz_implementations list so we can modify it. */
+ RWLOCK(&dlz_implock, isc_rwlocktype_write);
+
+ /* remove the dlz_implementation object from the list */
+ ISC_LIST_UNLINK(dlz_implementations, dlz_imp, link);
+
+ /*
+ * Return the memory back to the available memory pool and
+ * remove it from the memory context.
+ */
+ isc_mem_putanddetach(&dlz_imp->mctx, dlz_imp, sizeof(*dlz_imp));
+
+ /* Unlock the dlz_implementations list. */
+ RWUNLOCK(&dlz_implock, isc_rwlocktype_write);
+}
+
+/*
+ * Create a writeable DLZ zone. This can be called by DLZ drivers
+ * during configure() to create a zone that can be updated. The zone
+ * type is set to dns_zone_dlz, which is equivalent to a primary zone
+ *
+ * This function uses a callback setup in dns_dlzconfigure() to call
+ * into the server zone code to setup the remaining pieces of server
+ * specific functionality on the zone
+ */
+isc_result_t
+dns_dlz_writeablezone(dns_view_t *view, dns_dlzdb_t *dlzdb,
+ const char *zone_name) {
+ dns_zone_t *zone = NULL;
+ dns_zone_t *dupzone = NULL;
+ isc_result_t result;
+ isc_buffer_t buffer;
+ dns_fixedname_t fixorigin;
+ dns_name_t *origin;
+
+ REQUIRE(DNS_DLZ_VALID(dlzdb));
+
+ REQUIRE(dlzdb->configure_callback != NULL);
+
+ isc_buffer_constinit(&buffer, zone_name, strlen(zone_name));
+ isc_buffer_add(&buffer, strlen(zone_name));
+ dns_fixedname_init(&fixorigin);
+ result = dns_name_fromtext(dns_fixedname_name(&fixorigin), &buffer,
+ dns_rootname, 0, NULL);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+ origin = dns_fixedname_name(&fixorigin);
+
+ if (!dlzdb->search) {
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE,
+ DNS_LOGMODULE_DLZ, ISC_LOG_WARNING,
+ "DLZ %s has 'search no;', but attempted to "
+ "register writeable zone %s.",
+ dlzdb->dlzname, zone_name);
+ result = ISC_R_SUCCESS;
+ goto cleanup;
+ }
+
+ /* See if the zone already exists */
+ result = dns_view_findzone(view, origin, &dupzone);
+ if (result == ISC_R_SUCCESS) {
+ dns_zone_detach(&dupzone);
+ result = ISC_R_EXISTS;
+ goto cleanup;
+ }
+ INSIST(dupzone == NULL);
+
+ /* Create it */
+ result = dns_zone_create(&zone, view->mctx);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+ result = dns_zone_setorigin(zone, origin);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+ dns_zone_setview(zone, view);
+
+ dns_zone_setadded(zone, true);
+
+ if (dlzdb->ssutable == NULL) {
+ dns_ssutable_createdlz(dlzdb->mctx, &dlzdb->ssutable, dlzdb);
+ }
+ dns_zone_setssutable(zone, dlzdb->ssutable);
+
+ result = dlzdb->configure_callback(view, dlzdb, zone);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+
+ result = dns_view_addzone(view, zone);
+
+cleanup:
+ if (zone != NULL) {
+ dns_zone_detach(&zone);
+ }
+
+ return (result);
+}
+
+/*%
+ * Configure a DLZ driver. This is optional, and if supplied gives
+ * the backend an opportunity to configure parameters related to DLZ.
+ */
+isc_result_t
+dns_dlzconfigure(dns_view_t *view, dns_dlzdb_t *dlzdb,
+ dlzconfigure_callback_t callback) {
+ dns_dlzimplementation_t *impl;
+ isc_result_t result;
+
+ REQUIRE(DNS_DLZ_VALID(dlzdb));
+ REQUIRE(dlzdb->implementation != NULL);
+
+ impl = dlzdb->implementation;
+
+ if (impl->methods->configure == NULL) {
+ return (ISC_R_SUCCESS);
+ }
+
+ dlzdb->configure_callback = callback;
+
+ result = impl->methods->configure(impl->driverarg, dlzdb->dbdata, view,
+ dlzdb);
+ return (result);
+}
+
+bool
+dns_dlz_ssumatch(dns_dlzdb_t *dlzdatabase, const dns_name_t *signer,
+ const dns_name_t *name, const isc_netaddr_t *tcpaddr,
+ dns_rdatatype_t type, const dst_key_t *key) {
+ dns_dlzimplementation_t *impl;
+ bool r;
+
+ REQUIRE(dlzdatabase != NULL);
+ REQUIRE(dlzdatabase->implementation != NULL);
+ REQUIRE(dlzdatabase->implementation->methods != NULL);
+ impl = dlzdatabase->implementation;
+
+ if (impl->methods->ssumatch == NULL) {
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE,
+ DNS_LOGMODULE_DLZ, ISC_LOG_INFO,
+ "No ssumatch method for DLZ database");
+ return (false);
+ }
+
+ r = impl->methods->ssumatch(signer, name, tcpaddr, type, key,
+ impl->driverarg, dlzdatabase->dbdata);
+ return (r);
+}
diff --git a/lib/dns/dns64.c b/lib/dns/dns64.c
new file mode 100644
index 0000000..b575726
--- /dev/null
+++ b/lib/dns/dns64.c
@@ -0,0 +1,484 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#include <stdbool.h>
+#include <string.h>
+
+#include <isc/list.h>
+#include <isc/mem.h>
+#include <isc/netaddr.h>
+#include <isc/result.h>
+#include <isc/string.h>
+#include <isc/util.h>
+
+#include <dns/acl.h>
+#include <dns/dns64.h>
+#include <dns/rdata.h>
+#include <dns/rdataset.h>
+
+struct dns_dns64 {
+ unsigned char bits[16]; /*
+ * Prefix + suffix bits.
+ */
+ dns_acl_t *clients; /*
+ * Which clients get mapped
+ * addresses.
+ */
+ dns_acl_t *mapped; /*
+ * IPv4 addresses to be mapped.
+ */
+ dns_acl_t *excluded; /*
+ * IPv6 addresses that are
+ * treated as not existing.
+ */
+ unsigned int prefixlen; /*
+ * Start of mapped address.
+ */
+ unsigned int flags;
+ isc_mem_t *mctx;
+ ISC_LINK(dns_dns64_t) link;
+};
+
+isc_result_t
+dns_dns64_create(isc_mem_t *mctx, const isc_netaddr_t *prefix,
+ unsigned int prefixlen, const isc_netaddr_t *suffix,
+ dns_acl_t *clients, dns_acl_t *mapped, dns_acl_t *excluded,
+ unsigned int flags, dns_dns64_t **dns64p) {
+ dns_dns64_t *dns64;
+ unsigned int nbytes = 16;
+
+ REQUIRE(prefix != NULL && prefix->family == AF_INET6);
+ /* Legal prefix lengths from rfc6052.txt. */
+ REQUIRE(prefixlen == 32 || prefixlen == 40 || prefixlen == 48 ||
+ prefixlen == 56 || prefixlen == 64 || prefixlen == 96);
+ REQUIRE(isc_netaddr_prefixok(prefix, prefixlen) == ISC_R_SUCCESS);
+ REQUIRE(dns64p != NULL && *dns64p == NULL);
+
+ if (suffix != NULL) {
+ static const unsigned char zeros[16];
+ REQUIRE(prefix->family == AF_INET6);
+ nbytes = prefixlen / 8 + 4;
+ /* Bits 64-71 are zeros. rfc6052.txt */
+ if (prefixlen >= 32 && prefixlen <= 64) {
+ nbytes++;
+ }
+ REQUIRE(memcmp(suffix->type.in6.s6_addr, zeros, nbytes) == 0);
+ }
+
+ dns64 = isc_mem_get(mctx, sizeof(dns_dns64_t));
+ memset(dns64->bits, 0, sizeof(dns64->bits));
+ memmove(dns64->bits, prefix->type.in6.s6_addr, prefixlen / 8);
+ if (suffix != NULL) {
+ memmove(dns64->bits + nbytes, suffix->type.in6.s6_addr + nbytes,
+ 16 - nbytes);
+ }
+ dns64->clients = NULL;
+ if (clients != NULL) {
+ dns_acl_attach(clients, &dns64->clients);
+ }
+ dns64->mapped = NULL;
+ if (mapped != NULL) {
+ dns_acl_attach(mapped, &dns64->mapped);
+ }
+ dns64->excluded = NULL;
+ if (excluded != NULL) {
+ dns_acl_attach(excluded, &dns64->excluded);
+ }
+ dns64->prefixlen = prefixlen;
+ dns64->flags = flags;
+ ISC_LINK_INIT(dns64, link);
+ dns64->mctx = NULL;
+ isc_mem_attach(mctx, &dns64->mctx);
+ *dns64p = dns64;
+ return (ISC_R_SUCCESS);
+}
+
+void
+dns_dns64_destroy(dns_dns64_t **dns64p) {
+ dns_dns64_t *dns64;
+
+ REQUIRE(dns64p != NULL && *dns64p != NULL);
+
+ dns64 = *dns64p;
+ *dns64p = NULL;
+
+ REQUIRE(!ISC_LINK_LINKED(dns64, link));
+
+ if (dns64->clients != NULL) {
+ dns_acl_detach(&dns64->clients);
+ }
+ if (dns64->mapped != NULL) {
+ dns_acl_detach(&dns64->mapped);
+ }
+ if (dns64->excluded != NULL) {
+ dns_acl_detach(&dns64->excluded);
+ }
+ isc_mem_putanddetach(&dns64->mctx, dns64, sizeof(*dns64));
+}
+
+isc_result_t
+dns_dns64_aaaafroma(const dns_dns64_t *dns64, const isc_netaddr_t *reqaddr,
+ const dns_name_t *reqsigner, dns_aclenv_t *env,
+ unsigned int flags, unsigned char *a, unsigned char *aaaa) {
+ unsigned int nbytes, i;
+ isc_result_t result;
+ int match;
+
+ if ((dns64->flags & DNS_DNS64_RECURSIVE_ONLY) != 0 &&
+ (flags & DNS_DNS64_RECURSIVE) == 0)
+ {
+ return (DNS_R_DISALLOWED);
+ }
+
+ if ((dns64->flags & DNS_DNS64_BREAK_DNSSEC) == 0 &&
+ (flags & DNS_DNS64_DNSSEC) != 0)
+ {
+ return (DNS_R_DISALLOWED);
+ }
+
+ if (dns64->clients != NULL) {
+ result = dns_acl_match(reqaddr, reqsigner, dns64->clients, env,
+ &match, NULL);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+ if (match <= 0) {
+ return (DNS_R_DISALLOWED);
+ }
+ }
+
+ if (dns64->mapped != NULL) {
+ struct in_addr ina;
+ isc_netaddr_t netaddr;
+
+ memmove(&ina.s_addr, a, 4);
+ isc_netaddr_fromin(&netaddr, &ina);
+ result = dns_acl_match(&netaddr, NULL, dns64->mapped, env,
+ &match, NULL);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+ if (match <= 0) {
+ return (DNS_R_DISALLOWED);
+ }
+ }
+
+ nbytes = dns64->prefixlen / 8;
+ INSIST(nbytes <= 12);
+ /* Copy prefix. */
+ memmove(aaaa, dns64->bits, nbytes);
+ /* Bits 64-71 are zeros. rfc6052.txt */
+ if (nbytes == 8) {
+ aaaa[nbytes++] = 0;
+ }
+ /* Copy mapped address. */
+ for (i = 0; i < 4U; i++) {
+ aaaa[nbytes++] = a[i];
+ /* Bits 64-71 are zeros. rfc6052.txt */
+ if (nbytes == 8) {
+ aaaa[nbytes++] = 0;
+ }
+ }
+ /* Copy suffix. */
+ memmove(aaaa + nbytes, dns64->bits + nbytes, 16 - nbytes);
+ return (ISC_R_SUCCESS);
+}
+
+dns_dns64_t *
+dns_dns64_next(dns_dns64_t *dns64) {
+ dns64 = ISC_LIST_NEXT(dns64, link);
+ return (dns64);
+}
+
+void
+dns_dns64_append(dns_dns64list_t *list, dns_dns64_t *dns64) {
+ ISC_LIST_APPEND(*list, dns64, link);
+}
+
+void
+dns_dns64_unlink(dns_dns64list_t *list, dns_dns64_t *dns64) {
+ ISC_LIST_UNLINK(*list, dns64, link);
+}
+
+bool
+dns_dns64_aaaaok(const dns_dns64_t *dns64, const isc_netaddr_t *reqaddr,
+ const dns_name_t *reqsigner, dns_aclenv_t *env,
+ unsigned int flags, dns_rdataset_t *rdataset, bool *aaaaok,
+ size_t aaaaoklen) {
+ struct in6_addr in6;
+ isc_netaddr_t netaddr;
+ isc_result_t result;
+ int match;
+ bool answer = false;
+ bool found = false;
+ unsigned int i, ok;
+
+ REQUIRE(rdataset != NULL);
+ REQUIRE(rdataset->type == dns_rdatatype_aaaa);
+ REQUIRE(rdataset->rdclass == dns_rdataclass_in);
+ if (aaaaok != NULL) {
+ REQUIRE(aaaaoklen == dns_rdataset_count(rdataset));
+ }
+
+ for (; dns64 != NULL; dns64 = ISC_LIST_NEXT(dns64, link)) {
+ if ((dns64->flags & DNS_DNS64_RECURSIVE_ONLY) != 0 &&
+ (flags & DNS_DNS64_RECURSIVE) == 0)
+ {
+ continue;
+ }
+
+ if ((dns64->flags & DNS_DNS64_BREAK_DNSSEC) == 0 &&
+ (flags & DNS_DNS64_DNSSEC) != 0)
+ {
+ continue;
+ }
+ /*
+ * Work out if this dns64 structure applies to this client.
+ */
+ if (dns64->clients != NULL) {
+ result = dns_acl_match(reqaddr, reqsigner,
+ dns64->clients, env, &match,
+ NULL);
+ if (result != ISC_R_SUCCESS) {
+ continue;
+ }
+ if (match <= 0) {
+ continue;
+ }
+ }
+
+ if (!found && aaaaok != NULL) {
+ for (i = 0; i < aaaaoklen; i++) {
+ aaaaok[i] = false;
+ }
+ }
+ found = true;
+
+ /*
+ * If we are not excluding any addresses then any AAAA
+ * will do.
+ */
+ if (dns64->excluded == NULL) {
+ answer = true;
+ if (aaaaok == NULL) {
+ goto done;
+ }
+ for (i = 0; i < aaaaoklen; i++) {
+ aaaaok[i] = true;
+ }
+ goto done;
+ }
+
+ i = 0;
+ ok = 0;
+ for (result = dns_rdataset_first(rdataset);
+ result == ISC_R_SUCCESS;
+ result = dns_rdataset_next(rdataset))
+ {
+ dns_rdata_t rdata = DNS_RDATA_INIT;
+ if (aaaaok == NULL || !aaaaok[i]) {
+ dns_rdataset_current(rdataset, &rdata);
+ memmove(&in6.s6_addr, rdata.data, 16);
+ isc_netaddr_fromin6(&netaddr, &in6);
+
+ result = dns_acl_match(&netaddr, NULL,
+ dns64->excluded, env,
+ &match, NULL);
+ if (result == ISC_R_SUCCESS && match <= 0) {
+ answer = true;
+ if (aaaaok == NULL) {
+ goto done;
+ }
+ aaaaok[i] = true;
+ ok++;
+ }
+ } else {
+ ok++;
+ }
+ i++;
+ }
+ /*
+ * Are all addresses ok?
+ */
+ if (aaaaok != NULL && ok == aaaaoklen) {
+ goto done;
+ }
+ }
+
+done:
+ if (!found && aaaaok != NULL) {
+ for (i = 0; i < aaaaoklen; i++) {
+ aaaaok[i] = true;
+ }
+ }
+ return (found ? answer : true);
+}
+
+/*
+ * Posible mapping of IPV4ONLY.ARPA A records into AAAA records
+ * for valid RFC6052 prefixes.
+ */
+static struct {
+ const unsigned char aa[16]; /* mapped version of 192.0.0.170 */
+ const unsigned char ab[16]; /* mapped version of 192.0.0.171 */
+ const unsigned char mask[16];
+ const unsigned int plen;
+} const prefixes[6] = {
+ { { 0, 0, 0, 0, 192, 0, 0, 170, 0, 0, 0, 0, 0, 0, 0, 0 },
+ { 0, 0, 0, 0, 192, 0, 0, 171, 0, 0, 0, 0, 0, 0, 0, 0 },
+ { 0, 0, 0, 0, 255, 255, 255, 255, 255, 0, 0, 0, 0, 0, 0, 0 },
+ 32 },
+ { { 0, 0, 0, 0, 0, 192, 0, 0, 0, 170, 0, 0, 0, 0, 0, 0 },
+ { 0, 0, 0, 0, 0, 192, 0, 0, 0, 171, 0, 0, 0, 0, 0, 0 },
+ { 0, 0, 0, 0, 0, 255, 255, 255, 255, 255, 0, 0, 0, 0, 0, 0 },
+ 40 },
+ { { 0, 0, 0, 0, 0, 0, 192, 0, 0, 0, 170, 0, 0, 0, 0, 0 },
+ { 0, 0, 0, 0, 0, 0, 192, 0, 0, 0, 171, 0, 0, 0, 0, 0 },
+ { 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 255, 0, 0, 0, 0, 0 },
+ 48 },
+ { { 0, 0, 0, 0, 0, 0, 0, 192, 0, 0, 0, 170, 0, 0, 0, 0 },
+ { 0, 0, 0, 0, 0, 0, 0, 192, 0, 0, 0, 171, 0, 0, 0, 0 },
+ { 0, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 255, 0, 0, 0, 0 },
+ 56 },
+ { { 0, 0, 0, 0, 0, 0, 0, 0, 0, 192, 0, 0, 170, 0, 0, 0 },
+ { 0, 0, 0, 0, 0, 0, 0, 0, 0, 192, 0, 0, 171, 0, 0, 0 },
+ { 0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 255, 0, 0, 0 },
+ 64 },
+ { { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 192, 0, 0, 170 },
+ { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 192, 0, 0, 171 },
+ { 0, 0, 0, 0, 0, 0, 0, 0, 255, 0, 0, 0, 255, 255, 255, 255 },
+ 96 }
+};
+
+static unsigned int
+search(const dns_rdata_t *rd1, const dns_rdata_t *rd2, unsigned int plen) {
+ unsigned int i = 0, j;
+ const unsigned char *c, *m;
+
+ /*
+ * Resume looking for another aa match?
+ */
+ if (plen != 0U && rd2 == NULL) {
+ while (i < 6U) {
+ /* Post increment as we resume on next entry. */
+ if (prefixes[i++].plen == plen) {
+ break;
+ }
+ }
+ }
+
+ for (; i < 6U; i++) {
+ j = 0;
+ if (rd2 != NULL) {
+ /* Find the right entry. */
+ if (prefixes[i].plen != plen) {
+ continue;
+ }
+ /* Does the prefix match? */
+ while ((j * 8U) < plen) {
+ if (rd1->data[j] != rd2->data[j]) {
+ return (0);
+ }
+ j++;
+ }
+ }
+
+ /* Match well known mapped addresses. */
+ c = (rd2 == NULL) ? prefixes[i].aa : prefixes[i].ab;
+ m = prefixes[i].mask;
+ for (; j < 16U; j++) {
+ if ((rd1->data[j] & m[j]) != (c[j] & m[j])) {
+ break;
+ }
+ }
+ if (j == 16U) {
+ return (prefixes[i].plen);
+ }
+ if (rd2 != NULL) {
+ return (0);
+ }
+ }
+ return (0);
+}
+
+isc_result_t
+dns_dns64_findprefix(dns_rdataset_t *rdataset, isc_netprefix_t *prefix,
+ size_t *len) {
+ dns_rdataset_t outer, inner;
+ unsigned int oplen, iplen;
+ size_t count = 0;
+ struct in6_addr ina6;
+ isc_result_t result;
+
+ REQUIRE(prefix != NULL && len != NULL && *len != 0U);
+ REQUIRE(rdataset != NULL && rdataset->type == dns_rdatatype_aaaa);
+
+ dns_rdataset_init(&outer);
+ dns_rdataset_init(&inner);
+ dns_rdataset_clone(rdataset, &outer);
+ dns_rdataset_clone(rdataset, &inner);
+
+ for (result = dns_rdataset_first(&outer); result == ISC_R_SUCCESS;
+ result = dns_rdataset_next(&outer))
+ {
+ dns_rdata_t rd1 = DNS_RDATA_INIT;
+ dns_rdataset_current(&outer, &rd1);
+ oplen = 0;
+ resume:
+ /* Look for a 192.0.0.170 match. */
+ oplen = search(&rd1, NULL, oplen);
+ if (oplen == 0) {
+ continue;
+ }
+
+ /* Look for the 192.0.0.171 match. */
+ for (result = dns_rdataset_first(&inner);
+ result == ISC_R_SUCCESS;
+ result = dns_rdataset_next(&inner))
+ {
+ dns_rdata_t rd2 = DNS_RDATA_INIT;
+
+ dns_rdataset_current(&inner, &rd2);
+ iplen = search(&rd2, &rd1, oplen);
+ if (iplen == 0) {
+ continue;
+ }
+ INSIST(iplen == oplen);
+ if (count >= *len) {
+ count++;
+ break;
+ }
+
+ /* We have a prefix. */
+ memset(ina6.s6_addr, 0, sizeof(ina6.s6_addr));
+ memmove(ina6.s6_addr, rd1.data, oplen / 8);
+ isc_netaddr_fromin6(&prefix[count].addr, &ina6);
+ prefix[count].prefixlen = oplen;
+ count++;
+ break;
+ }
+ /* Didn't find a match look for a different prefix length. */
+ if (result == ISC_R_NOMORE) {
+ goto resume;
+ }
+ }
+ if (count == 0U) {
+ return (ISC_R_NOTFOUND);
+ }
+ if (count > *len) {
+ *len = count;
+ return (ISC_R_NOSPACE);
+ }
+ *len = count;
+ return (ISC_R_SUCCESS);
+}
diff --git a/lib/dns/dnsrps.c b/lib/dns/dnsrps.c
new file mode 100644
index 0000000..d4a1c65
--- /dev/null
+++ b/lib/dns/dnsrps.c
@@ -0,0 +1,1004 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/*! \file */
+
+#include <inttypes.h>
+#include <stdbool.h>
+
+#ifdef USE_DNSRPS
+
+#include <stdlib.h>
+
+#include <isc/mem.h>
+#include <isc/string.h>
+#include <isc/util.h>
+
+#include <dns/db.h>
+#define LIBRPZ_LIB_OPEN DNSRPS_LIB_OPEN
+#include <isc/result.h>
+
+#include <dns/dnsrps.h>
+#include <dns/rdataset.h>
+#include <dns/rdatasetiter.h>
+#include <dns/rpz.h>
+
+librpz_t *librpz;
+librpz_emsg_t librpz_lib_open_emsg;
+static void *librpz_handle;
+
+#define RPSDB_MAGIC ISC_MAGIC('R', 'P', 'Z', 'F')
+#define VALID_RPSDB(rpsdb) ((rpsdb)->common.impmagic == RPSDB_MAGIC)
+
+#define RD_DB(r) ((r)->private1)
+#define RD_CUR_RR(r) ((r)->private2)
+#define RD_NEXT_RR(r) ((r)->resign)
+#define RD_COUNT(r) ((r)->privateuint4)
+
+typedef struct {
+ dns_rdatasetiter_t common;
+ dns_rdatatype_t type;
+ dns_rdataclass_t class;
+ uint32_t ttl;
+ uint count;
+ librpz_idx_t next_rr;
+} rpsdb_rdatasetiter_t;
+
+static dns_dbmethods_t rpsdb_db_methods;
+static dns_rdatasetmethods_t rpsdb_rdataset_methods;
+static dns_rdatasetitermethods_t rpsdb_rdatasetiter_methods;
+
+static librpz_clist_t *clist;
+
+static isc_mutex_t dnsrps_mutex;
+
+static void
+dnsrps_lock(void *mutex0) {
+ isc_mutex_t *mutex = mutex0;
+
+ LOCK(mutex);
+}
+
+static void
+dnsrps_unlock(void *mutex0) {
+ isc_mutex_t *mutex = mutex0;
+
+ UNLOCK(mutex);
+}
+
+static void
+dnsrps_mutex_destroy(void *mutex0) {
+ isc_mutex_t *mutex = mutex0;
+
+ isc_mutex_destroy(mutex);
+}
+
+static void
+dnsrps_log_fnc(librpz_log_level_t level, void *ctxt, const char *buf) {
+ int isc_level;
+
+ UNUSED(ctxt);
+
+ /* Setting librpz_log_level in the configuration overrides the
+ * BIND9 logging levels. */
+ if (level > LIBRPZ_LOG_TRACE1 &&
+ level <= librpz->log_level_val(LIBRPZ_LOG_INVALID))
+ {
+ level = LIBRPZ_LOG_TRACE1;
+ }
+
+ switch (level) {
+ case LIBRPZ_LOG_FATAL:
+ case LIBRPZ_LOG_ERROR: /* errors */
+ default:
+ isc_level = DNS_RPZ_ERROR_LEVEL;
+ break;
+
+ case LIBRPZ_LOG_TRACE1: /* big events such as dnsrpzd starts */
+ isc_level = DNS_RPZ_INFO_LEVEL;
+ break;
+
+ case LIBRPZ_LOG_TRACE2: /* smaller dnsrpzd zone transfers */
+ isc_level = DNS_RPZ_DEBUG_LEVEL1;
+ break;
+
+ case LIBRPZ_LOG_TRACE3: /* librpz hits */
+ isc_level = DNS_RPZ_DEBUG_LEVEL2;
+ break;
+
+ case LIBRPZ_LOG_TRACE4: /* librpz lookups */
+ isc_level = DNS_RPZ_DEBUG_LEVEL3;
+ break;
+ }
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_RPZ, DNS_LOGMODULE_RBTDB,
+ isc_level, "dnsrps: %s", buf);
+}
+
+/*
+ * Start dnsrps for the entire server.
+ * This is not thread safe, but it is called by a single thread.
+ */
+isc_result_t
+dns_dnsrps_server_create(void) {
+ librpz_emsg_t emsg;
+
+ INSIST(clist == NULL);
+ INSIST(librpz == NULL);
+ INSIST(librpz_handle == NULL);
+
+ /*
+ * Notice if librpz is available.
+ */
+ librpz = librpz_lib_open(&librpz_lib_open_emsg, &librpz_handle,
+ DNSRPS_LIBRPZ_PATH);
+ /*
+ * Stop now without complaining if librpz is not available.
+ * Complain later if and when librpz is needed for a view with
+ * "dnsrps-enable yes" (including the default view).
+ */
+ if (librpz == NULL) {
+ return (ISC_R_SUCCESS);
+ }
+
+ isc_mutex_init(&dnsrps_mutex);
+
+ librpz->set_log(dnsrps_log_fnc, NULL);
+
+ clist = librpz->clist_create(&emsg, dnsrps_lock, dnsrps_unlock,
+ dnsrps_mutex_destroy, &dnsrps_mutex,
+ dns_lctx);
+ if (clist == NULL) {
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_RPZ,
+ DNS_LOGMODULE_RBTDB, DNS_RPZ_ERROR_LEVEL,
+ "dnsrps: %s", emsg.c);
+ return (ISC_R_NOMEMORY);
+ }
+ return (ISC_R_SUCCESS);
+}
+
+/*
+ * Stop dnsrps for the entire server.
+ * This is not thread safe.
+ */
+void
+dns_dnsrps_server_destroy(void) {
+ if (clist != NULL) {
+ librpz->clist_detach(&clist);
+ }
+
+#ifdef LIBRPZ_USE_DLOPEN
+ if (librpz != NULL) {
+ INSIST(librpz_handle != NULL);
+ if (dlclose(librpz_handle) != 0) {
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_RPZ,
+ DNS_LOGMODULE_RBTDB, DNS_RPZ_ERROR_LEVEL,
+ "dnsrps: dlclose(): %s", dlerror());
+ }
+ librpz_handle = NULL;
+ }
+#endif /* ifdef LIBRPZ_USE_DLOPEN */
+}
+
+/*
+ * Ready dnsrps for a view.
+ */
+isc_result_t
+dns_dnsrps_view_init(dns_rpz_zones_t *new, char *rps_cstr) {
+ librpz_emsg_t emsg;
+
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_RPZ, DNS_LOGMODULE_RBTDB,
+ DNS_RPZ_DEBUG_LEVEL3, "dnsrps configuration \"%s\"",
+ rps_cstr);
+
+ new->rps_client = librpz->client_create(&emsg, clist, rps_cstr, false);
+ if (new->rps_client == NULL) {
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_RPZ,
+ DNS_LOGMODULE_RBTDB, DNS_RPZ_ERROR_LEVEL,
+ "librpz->client_create(): %s", emsg.c);
+ new->p.dnsrps_enabled = false;
+ return (ISC_R_FAILURE);
+ }
+
+ new->p.dnsrps_enabled = true;
+ return (ISC_R_SUCCESS);
+}
+
+/*
+ * Connect to and start the dnsrps daemon, dnsrpzd.
+ */
+isc_result_t
+dns_dnsrps_connect(dns_rpz_zones_t *rpzs) {
+ librpz_emsg_t emsg;
+
+ if (rpzs == NULL || !rpzs->p.dnsrps_enabled) {
+ return (ISC_R_SUCCESS);
+ }
+
+ /*
+ * Fail only if we failed to link to librpz.
+ */
+ if (librpz == NULL) {
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_RPZ,
+ DNS_LOGMODULE_RBTDB, DNS_RPZ_ERROR_LEVEL,
+ "librpz->connect(): %s", librpz_lib_open_emsg.c);
+ return (ISC_R_FAILURE);
+ }
+
+ if (!librpz->connect(&emsg, rpzs->rps_client, true)) {
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_RPZ,
+ DNS_LOGMODULE_RBTDB, DNS_RPZ_ERROR_LEVEL,
+ "librpz->connect(): %s", emsg.c);
+ return (ISC_R_SUCCESS);
+ }
+
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_RPZ, DNS_LOGMODULE_RBTDB,
+ DNS_RPZ_INFO_LEVEL, "dnsrps: librpz version %s",
+ librpz->version);
+
+ return (ISC_R_SUCCESS);
+}
+
+/*
+ * Get ready to try RPZ rewriting.
+ */
+isc_result_t
+dns_dnsrps_rewrite_init(librpz_emsg_t *emsg, dns_rpz_st_t *st,
+ dns_rpz_zones_t *rpzs, const dns_name_t *qname,
+ isc_mem_t *mctx, bool have_rd) {
+ rpsdb_t *rpsdb;
+
+ rpsdb = isc_mem_get(mctx, sizeof(*rpsdb));
+ memset(rpsdb, 0, sizeof(*rpsdb));
+
+ if (!librpz->rsp_create(emsg, &rpsdb->rsp, NULL, rpzs->rps_client,
+ have_rd, false))
+ {
+ isc_mem_put(mctx, rpsdb, sizeof(*rpsdb));
+ return (DNS_R_SERVFAIL);
+ }
+ if (rpsdb->rsp == NULL) {
+ isc_mem_put(mctx, rpsdb, sizeof(*rpsdb));
+ return (DNS_R_DISALLOWED);
+ }
+
+ rpsdb->common.magic = DNS_DB_MAGIC;
+ rpsdb->common.impmagic = RPSDB_MAGIC;
+ rpsdb->common.methods = &rpsdb_db_methods;
+ rpsdb->common.rdclass = dns_rdataclass_in;
+ dns_name_init(&rpsdb->common.origin, NULL);
+ isc_mem_attach(mctx, &rpsdb->common.mctx);
+
+ rpsdb->ref_cnt = 1;
+ rpsdb->qname = qname;
+
+ st->rpsdb = &rpsdb->common;
+ return (ISC_R_SUCCESS);
+}
+
+/*
+ * Convert a dnsrps policy to a classic BIND9 RPZ policy.
+ */
+dns_rpz_policy_t
+dns_dnsrps_2policy(librpz_policy_t rps_policy) {
+ switch (rps_policy) {
+ case LIBRPZ_POLICY_UNDEFINED:
+ return (DNS_RPZ_POLICY_MISS);
+ case LIBRPZ_POLICY_PASSTHRU:
+ return (DNS_RPZ_POLICY_PASSTHRU);
+ case LIBRPZ_POLICY_DROP:
+ return (DNS_RPZ_POLICY_DROP);
+ case LIBRPZ_POLICY_TCP_ONLY:
+ return (DNS_RPZ_POLICY_TCP_ONLY);
+ case LIBRPZ_POLICY_NXDOMAIN:
+ return (DNS_RPZ_POLICY_NXDOMAIN);
+ case LIBRPZ_POLICY_NODATA:
+ return (DNS_RPZ_POLICY_NODATA);
+ case LIBRPZ_POLICY_RECORD:
+ case LIBRPZ_POLICY_CNAME:
+ return (DNS_RPZ_POLICY_RECORD);
+
+ case LIBRPZ_POLICY_DELETED:
+ case LIBRPZ_POLICY_GIVEN:
+ case LIBRPZ_POLICY_DISABLED:
+ default:
+ UNREACHABLE();
+ }
+}
+
+/*
+ * Convert a dnsrps trigger to a classic BIND9 RPZ rewrite or trigger type.
+ */
+dns_rpz_type_t
+dns_dnsrps_trig2type(librpz_trig_t trig) {
+ switch (trig) {
+ case LIBRPZ_TRIG_BAD:
+ default:
+ return (DNS_RPZ_TYPE_BAD);
+ case LIBRPZ_TRIG_CLIENT_IP:
+ return (DNS_RPZ_TYPE_CLIENT_IP);
+ case LIBRPZ_TRIG_QNAME:
+ return (DNS_RPZ_TYPE_QNAME);
+ case LIBRPZ_TRIG_IP:
+ return (DNS_RPZ_TYPE_IP);
+ case LIBRPZ_TRIG_NSDNAME:
+ return (DNS_RPZ_TYPE_NSDNAME);
+ case LIBRPZ_TRIG_NSIP:
+ return (DNS_RPZ_TYPE_NSIP);
+ }
+}
+
+/*
+ * Convert a classic BIND9 RPZ rewrite or trigger type to a librpz trigger type.
+ */
+librpz_trig_t
+dns_dnsrps_type2trig(dns_rpz_type_t type) {
+ switch (type) {
+ case DNS_RPZ_TYPE_BAD:
+ default:
+ return (LIBRPZ_TRIG_BAD);
+ case DNS_RPZ_TYPE_CLIENT_IP:
+ return (LIBRPZ_TRIG_CLIENT_IP);
+ case DNS_RPZ_TYPE_QNAME:
+ return (LIBRPZ_TRIG_QNAME);
+ case DNS_RPZ_TYPE_IP:
+ return (LIBRPZ_TRIG_IP);
+ case DNS_RPZ_TYPE_NSDNAME:
+ return (LIBRPZ_TRIG_NSDNAME);
+ case DNS_RPZ_TYPE_NSIP:
+ return (LIBRPZ_TRIG_NSIP);
+ }
+}
+
+static void
+rpsdb_attach(dns_db_t *source, dns_db_t **targetp) {
+ rpsdb_t *rpsdb = (rpsdb_t *)source;
+
+ REQUIRE(VALID_RPSDB(rpsdb));
+
+ /*
+ * Use a simple count because only one thread uses any single rpsdb_t
+ */
+ ++rpsdb->ref_cnt;
+ *targetp = source;
+}
+
+static void
+rpsdb_detach(dns_db_t **dbp) {
+ rpsdb_t *rpsdb = (rpsdb_t *)*dbp;
+
+ REQUIRE(VALID_RPSDB(rpsdb));
+ REQUIRE(rpsdb->ref_cnt > 0);
+
+ *dbp = NULL;
+
+ /*
+ * Simple count because only one thread uses a rpsdb_t.
+ */
+ if (--rpsdb->ref_cnt != 0) {
+ return;
+ }
+
+ librpz->rsp_detach(&rpsdb->rsp);
+ rpsdb->common.impmagic = 0;
+ isc_mem_putanddetach(&rpsdb->common.mctx, rpsdb, sizeof(*rpsdb));
+}
+
+static void
+rpsdb_attachnode(dns_db_t *db, dns_dbnode_t *source, dns_dbnode_t **targetp) {
+ rpsdb_t *rpsdb = (rpsdb_t *)db;
+
+ REQUIRE(VALID_RPSDB(rpsdb));
+ REQUIRE(targetp != NULL && *targetp == NULL);
+ REQUIRE(source == &rpsdb->origin_node || source == &rpsdb->data_node);
+
+ /*
+ * Simple count because only one thread uses a rpsdb_t.
+ */
+ ++rpsdb->ref_cnt;
+ *targetp = source;
+}
+
+static void
+rpsdb_detachnode(dns_db_t *db, dns_dbnode_t **targetp) {
+ rpsdb_t *rpsdb = (rpsdb_t *)db;
+
+ REQUIRE(VALID_RPSDB(rpsdb));
+ REQUIRE(*targetp == &rpsdb->origin_node ||
+ *targetp == &rpsdb->data_node);
+
+ *targetp = NULL;
+ rpsdb_detach(&db);
+}
+
+static isc_result_t
+rpsdb_findnode(dns_db_t *db, const dns_name_t *name, bool create,
+ dns_dbnode_t **nodep) {
+ rpsdb_t *rpsdb = (rpsdb_t *)db;
+ dns_db_t *dbp;
+
+ REQUIRE(VALID_RPSDB(rpsdb));
+ REQUIRE(nodep != NULL && *nodep == NULL);
+ REQUIRE(!create);
+
+ /*
+ * A fake/shim rpsdb has two nodes.
+ * One is the origin to support query_addsoa() in bin/named/query.c.
+ * The other contains rewritten RRs.
+ */
+ if (dns_name_equal(name, &db->origin)) {
+ *nodep = &rpsdb->origin_node;
+ } else {
+ *nodep = &rpsdb->data_node;
+ }
+ dbp = NULL;
+ rpsdb_attach(db, &dbp);
+
+ return (ISC_R_SUCCESS);
+}
+
+static void
+rpsdb_bind_rdataset(dns_rdataset_t *rdataset, uint count, librpz_idx_t next_rr,
+ dns_rdatatype_t type, uint16_t class, uint32_t ttl,
+ rpsdb_t *rpsdb) {
+ dns_db_t *dbp;
+
+ INSIST(rdataset->methods == NULL); /* We must be disassociated. */
+ REQUIRE(type != dns_rdatatype_none);
+
+ rdataset->methods = &rpsdb_rdataset_methods;
+ rdataset->rdclass = class;
+ rdataset->type = type;
+ rdataset->ttl = ttl;
+ dbp = NULL;
+ dns_db_attach(&rpsdb->common, &dbp);
+ RD_DB(rdataset) = dbp;
+ RD_COUNT(rdataset) = count;
+ RD_NEXT_RR(rdataset) = next_rr;
+ RD_CUR_RR(rdataset) = NULL;
+}
+
+static isc_result_t
+rpsdb_bind_soa(dns_rdataset_t *rdataset, rpsdb_t *rpsdb) {
+ uint32_t ttl;
+ librpz_emsg_t emsg;
+
+ if (!librpz->rsp_soa(&emsg, &ttl, NULL, NULL, &rpsdb->result,
+ rpsdb->rsp))
+ {
+ librpz->log(LIBRPZ_LOG_ERROR, NULL, "%s", emsg.c);
+ return (DNS_R_SERVFAIL);
+ }
+ rpsdb_bind_rdataset(rdataset, 1, LIBRPZ_IDX_BAD, dns_rdatatype_soa,
+ dns_rdataclass_in, ttl, rpsdb);
+ return (ISC_R_SUCCESS);
+}
+
+/*
+ * Forge an rdataset of the desired type from a librpz result.
+ * This is written for simplicity instead of speed, because RPZ rewriting
+ * should be rare compared to normal BIND operations.
+ */
+static isc_result_t
+rpsdb_findrdataset(dns_db_t *db, dns_dbnode_t *node, dns_dbversion_t *version,
+ dns_rdatatype_t type, dns_rdatatype_t covers,
+ isc_stdtime_t now, dns_rdataset_t *rdataset,
+ dns_rdataset_t *sigrdataset) {
+ rpsdb_t *rpsdb = (rpsdb_t *)db;
+ dns_rdatatype_t foundtype;
+ dns_rdataclass_t class;
+ uint32_t ttl;
+ uint count;
+ librpz_emsg_t emsg;
+
+ UNUSED(version);
+ UNUSED(covers);
+ UNUSED(now);
+ UNUSED(sigrdataset);
+
+ REQUIRE(VALID_RPSDB(rpsdb));
+
+ if (node == &rpsdb->origin_node) {
+ if (type == dns_rdatatype_any) {
+ return (ISC_R_SUCCESS);
+ }
+ if (type == dns_rdatatype_soa) {
+ return (rpsdb_bind_soa(rdataset, rpsdb));
+ }
+ return (DNS_R_NXRRSET);
+ }
+
+ REQUIRE(node == &rpsdb->data_node);
+
+ switch (rpsdb->result.policy) {
+ case LIBRPZ_POLICY_UNDEFINED:
+ case LIBRPZ_POLICY_DELETED:
+ case LIBRPZ_POLICY_PASSTHRU:
+ case LIBRPZ_POLICY_DROP:
+ case LIBRPZ_POLICY_TCP_ONLY:
+ case LIBRPZ_POLICY_GIVEN:
+ case LIBRPZ_POLICY_DISABLED:
+ default:
+ librpz->log(LIBRPZ_LOG_ERROR, NULL,
+ "impossible dnsrps policy %d at %s:%d",
+ rpsdb->result.policy, __FILE__, __LINE__);
+ return (DNS_R_SERVFAIL);
+
+ case LIBRPZ_POLICY_NXDOMAIN:
+ return (DNS_R_NXDOMAIN);
+
+ case LIBRPZ_POLICY_NODATA:
+ return (DNS_R_NXRRSET);
+
+ case LIBRPZ_POLICY_RECORD:
+ case LIBRPZ_POLICY_CNAME:
+ break;
+ }
+
+ if (type == dns_rdatatype_soa) {
+ return (rpsdb_bind_soa(rdataset, rpsdb));
+ }
+
+ /*
+ * There is little to do for an ANY query.
+ */
+ if (type == dns_rdatatype_any) {
+ return (ISC_R_SUCCESS);
+ }
+
+ /*
+ * Reset to the start of the RRs.
+ * This function is only used after a policy has been chosen,
+ * and so without caring whether it is after recursion.
+ */
+ if (!librpz->rsp_result(&emsg, &rpsdb->result, true, rpsdb->rsp)) {
+ librpz->log(LIBRPZ_LOG_ERROR, NULL, "%s", emsg.c);
+ return (DNS_R_SERVFAIL);
+ }
+ if (!librpz->rsp_rr(&emsg, &foundtype, &class, &ttl, NULL,
+ &rpsdb->result, rpsdb->qname->ndata,
+ rpsdb->qname->length, rpsdb->rsp))
+ {
+ librpz->log(LIBRPZ_LOG_ERROR, NULL, "%s", emsg.c);
+ return (DNS_R_SERVFAIL);
+ }
+ REQUIRE(foundtype != dns_rdatatype_none);
+
+ /*
+ * Ho many of the target RR type are available?
+ */
+ count = 0;
+ do {
+ if (type == foundtype || type == dns_rdatatype_any) {
+ ++count;
+ }
+
+ if (!librpz->rsp_rr(&emsg, &foundtype, NULL, NULL, NULL,
+ &rpsdb->result, rpsdb->qname->ndata,
+ rpsdb->qname->length, rpsdb->rsp))
+ {
+ librpz->log(LIBRPZ_LOG_ERROR, NULL, "%s", emsg.c);
+ return (DNS_R_SERVFAIL);
+ }
+ } while (foundtype != dns_rdatatype_none);
+ if (count == 0) {
+ return (DNS_R_NXRRSET);
+ }
+ rpsdb_bind_rdataset(rdataset, count, rpsdb->result.next_rr, type, class,
+ ttl, rpsdb);
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+rpsdb_finddb(dns_db_t *db, const dns_name_t *name, dns_dbversion_t *version,
+ dns_rdatatype_t type, unsigned int options, isc_stdtime_t now,
+ dns_dbnode_t **nodep, dns_name_t *foundname,
+ dns_rdataset_t *rdataset, dns_rdataset_t *sigrdataset) {
+ dns_dbnode_t *node;
+
+ UNUSED(version);
+ UNUSED(options);
+ UNUSED(now);
+ UNUSED(sigrdataset);
+
+ if (nodep == NULL) {
+ node = NULL;
+ nodep = &node;
+ }
+ rpsdb_findnode(db, name, false, nodep);
+ dns_name_copy(name, foundname);
+ return (rpsdb_findrdataset(db, *nodep, NULL, type, 0, 0, rdataset,
+ sigrdataset));
+}
+
+static isc_result_t
+rpsdb_allrdatasets(dns_db_t *db, dns_dbnode_t *node, dns_dbversion_t *version,
+ unsigned int options, isc_stdtime_t now,
+ dns_rdatasetiter_t **iteratorp) {
+ rpsdb_t *rpsdb = (rpsdb_t *)db;
+ rpsdb_rdatasetiter_t *rpsdb_iter;
+
+ UNUSED(version);
+ UNUSED(now);
+
+ REQUIRE(VALID_RPSDB(rpsdb));
+ REQUIRE(node == &rpsdb->origin_node || node == &rpsdb->data_node);
+
+ rpsdb_iter = isc_mem_get(rpsdb->common.mctx, sizeof(*rpsdb_iter));
+
+ memset(rpsdb_iter, 0, sizeof(*rpsdb_iter));
+ rpsdb_iter->common.magic = DNS_RDATASETITER_MAGIC;
+ rpsdb_iter->common.methods = &rpsdb_rdatasetiter_methods;
+ rpsdb_iter->common.db = db;
+ rpsdb_iter->common.options = options;
+ rpsdb_attachnode(db, node, &rpsdb_iter->common.node);
+
+ *iteratorp = &rpsdb_iter->common;
+
+ return (ISC_R_SUCCESS);
+}
+
+static bool
+rpsdb_issecure(dns_db_t *db) {
+ UNUSED(db);
+
+ return (false);
+}
+
+static isc_result_t
+rpsdb_getoriginnode(dns_db_t *db, dns_dbnode_t **nodep) {
+ rpsdb_t *rpsdb = (rpsdb_t *)db;
+
+ REQUIRE(VALID_RPSDB(rpsdb));
+ REQUIRE(nodep != NULL && *nodep == NULL);
+
+ rpsdb_attachnode(db, &rpsdb->origin_node, nodep);
+ return (ISC_R_SUCCESS);
+}
+
+static void
+rpsdb_rdataset_disassociate(dns_rdataset_t *rdataset) {
+ dns_db_t *db;
+
+ /*
+ * Detach the last RR delivered.
+ */
+ if (RD_CUR_RR(rdataset) != NULL) {
+ free(RD_CUR_RR(rdataset));
+ RD_CUR_RR(rdataset) = NULL;
+ }
+
+ db = RD_DB(rdataset);
+ RD_DB(rdataset) = NULL;
+ dns_db_detach(&db);
+}
+
+static isc_result_t
+rpsdb_rdataset_next(dns_rdataset_t *rdataset) {
+ rpsdb_t *rpsdb;
+ uint16_t type;
+ dns_rdataclass_t class;
+ librpz_rr_t *rr;
+ librpz_emsg_t emsg;
+
+ rpsdb = RD_DB(rdataset);
+
+ /*
+ * Detach the previous RR.
+ */
+ if (RD_CUR_RR(rdataset) != NULL) {
+ free(RD_CUR_RR(rdataset));
+ RD_CUR_RR(rdataset) = NULL;
+ }
+
+ /*
+ * Get the next RR of the specified type.
+ * SOAs differ.
+ */
+ if (rdataset->type == dns_rdatatype_soa) {
+ if (RD_NEXT_RR(rdataset) == LIBRPZ_IDX_NULL) {
+ return (ISC_R_NOMORE);
+ }
+ RD_NEXT_RR(rdataset) = LIBRPZ_IDX_NULL;
+ if (!librpz->rsp_soa(&emsg, NULL, &rr, NULL, &rpsdb->result,
+ rpsdb->rsp))
+ {
+ librpz->log(LIBRPZ_LOG_ERROR, NULL, "%s", emsg.c);
+ return (DNS_R_SERVFAIL);
+ }
+ RD_CUR_RR(rdataset) = rr;
+ return (ISC_R_SUCCESS);
+ }
+
+ rpsdb->result.next_rr = RD_NEXT_RR(rdataset);
+ for (;;) {
+ if (!librpz->rsp_rr(&emsg, &type, &class, NULL, &rr,
+ &rpsdb->result, rpsdb->qname->ndata,
+ rpsdb->qname->length, rpsdb->rsp))
+ {
+ librpz->log(LIBRPZ_LOG_ERROR, NULL, "%s", emsg.c);
+ return (DNS_R_SERVFAIL);
+ }
+ if (rdataset->type == type && rdataset->rdclass == class) {
+ RD_CUR_RR(rdataset) = rr;
+ RD_NEXT_RR(rdataset) = rpsdb->result.next_rr;
+ return (ISC_R_SUCCESS);
+ }
+ if (type == dns_rdatatype_none) {
+ return (ISC_R_NOMORE);
+ }
+ free(rr);
+ }
+}
+
+static isc_result_t
+rpsdb_rdataset_first(dns_rdataset_t *rdataset) {
+ rpsdb_t *rpsdb;
+ librpz_emsg_t emsg;
+
+ rpsdb = RD_DB(rdataset);
+ REQUIRE(VALID_RPSDB(rpsdb));
+
+ if (RD_CUR_RR(rdataset) != NULL) {
+ free(RD_CUR_RR(rdataset));
+ RD_CUR_RR(rdataset) = NULL;
+ }
+
+ if (!librpz->rsp_result(&emsg, &rpsdb->result, true, rpsdb->rsp)) {
+ librpz->log(LIBRPZ_LOG_ERROR, NULL, "%s", emsg.c);
+ return (DNS_R_SERVFAIL);
+ }
+ if (rdataset->type == dns_rdatatype_soa) {
+ RD_NEXT_RR(rdataset) = LIBRPZ_IDX_BAD;
+ } else {
+ RD_NEXT_RR(rdataset) = rpsdb->result.next_rr;
+ }
+
+ return (rpsdb_rdataset_next(rdataset));
+}
+
+static void
+rpsdb_rdataset_current(dns_rdataset_t *rdataset, dns_rdata_t *rdata) {
+ rpsdb_t *rpsdb;
+ librpz_rr_t *rr;
+ isc_region_t r;
+
+ rpsdb = RD_DB(rdataset);
+ REQUIRE(VALID_RPSDB(rpsdb));
+ rr = RD_CUR_RR(rdataset);
+ REQUIRE(rr != NULL);
+
+ r.length = ntohs(rr->rdlength);
+ r.base = rr->rdata;
+ dns_rdata_fromregion(rdata, ntohs(rr->class), ntohs(rr->type), &r);
+}
+
+static void
+rpsdb_rdataset_clone(dns_rdataset_t *source, dns_rdataset_t *target) {
+ rpsdb_t *rpsdb;
+ dns_db_t *dbp;
+
+ INSIST(!ISC_LINK_LINKED(target, link));
+ *target = *source;
+ ISC_LINK_INIT(target, link);
+ rpsdb = RD_DB(source);
+ REQUIRE(VALID_RPSDB(rpsdb));
+ dbp = NULL;
+ dns_db_attach(&rpsdb->common, &dbp);
+ RD_DB(target) = dbp;
+ RD_CUR_RR(target) = NULL;
+ RD_NEXT_RR(target) = LIBRPZ_IDX_NULL;
+}
+
+static unsigned int
+rpsdb_rdataset_count(dns_rdataset_t *rdataset) {
+ rpsdb_t *rpsdb;
+
+ rpsdb = RD_DB(rdataset);
+ REQUIRE(VALID_RPSDB(rpsdb));
+
+ return (RD_COUNT(rdataset));
+}
+
+static void
+rpsdb_rdatasetiter_destroy(dns_rdatasetiter_t **iteratorp) {
+ rpsdb_t *rpsdb;
+ dns_rdatasetiter_t *iterator;
+ isc_mem_t *mctx;
+
+ iterator = *iteratorp;
+ *iteratorp = NULL;
+ rpsdb = (rpsdb_t *)iterator->db;
+ REQUIRE(VALID_RPSDB(rpsdb));
+
+ mctx = iterator->db->mctx;
+ dns_db_detachnode(iterator->db, &iterator->node);
+ isc_mem_put(mctx, iterator, sizeof(rpsdb_rdatasetiter_t));
+}
+
+static isc_result_t
+rpsdb_rdatasetiter_next(dns_rdatasetiter_t *iter) {
+ rpsdb_t *rpsdb;
+ rpsdb_rdatasetiter_t *rpsdb_iter;
+ dns_rdatatype_t next_type, type;
+ dns_rdataclass_t next_class, class;
+ uint32_t ttl;
+ librpz_emsg_t emsg;
+
+ rpsdb = (rpsdb_t *)iter->db;
+ REQUIRE(VALID_RPSDB(rpsdb));
+ rpsdb_iter = (rpsdb_rdatasetiter_t *)iter;
+
+ /*
+ * This function is only used after a policy has been chosen,
+ * and so without caring whether it is after recursion.
+ */
+ if (!librpz->rsp_result(&emsg, &rpsdb->result, true, rpsdb->rsp)) {
+ librpz->log(LIBRPZ_LOG_ERROR, NULL, "%s", emsg.c);
+ return (DNS_R_SERVFAIL);
+ }
+ /*
+ * Find the next class and type after the current class and type
+ * among the RRs in current result.
+ * As a side effect, count the number of those RRs.
+ */
+ rpsdb_iter->count = 0;
+ next_class = dns_rdataclass_reserved0;
+ next_type = dns_rdatatype_none;
+ for (;;) {
+ if (!librpz->rsp_rr(&emsg, &type, &class, &ttl, NULL,
+ &rpsdb->result, rpsdb->qname->ndata,
+ rpsdb->qname->length, rpsdb->rsp))
+ {
+ librpz->log(LIBRPZ_LOG_ERROR, NULL, "%s", emsg.c);
+ return (DNS_R_SERVFAIL);
+ }
+ if (type == dns_rdatatype_none) {
+ if (next_type == dns_rdatatype_none) {
+ return (ISC_R_NOMORE);
+ }
+ rpsdb_iter->type = next_type;
+ rpsdb_iter->class = next_class;
+ return (ISC_R_SUCCESS);
+ }
+ /*
+ * Skip RRs with the current class and type or before.
+ */
+ if (rpsdb_iter->class > class ||
+ (rpsdb_iter->class = class && rpsdb_iter->type >= type))
+ {
+ continue;
+ }
+ if (next_type == dns_rdatatype_none || next_class > class ||
+ (next_class == class && next_type > type))
+ {
+ /*
+ * This is the first of a subsequent class and type.
+ */
+ next_type = type;
+ next_class = class;
+ rpsdb_iter->ttl = ttl;
+ rpsdb_iter->count = 1;
+ rpsdb_iter->next_rr = rpsdb->result.next_rr;
+ } else if (next_type == type && next_class == class) {
+ ++rpsdb_iter->count;
+ }
+ }
+}
+
+static isc_result_t
+rpsdb_rdatasetiter_first(dns_rdatasetiter_t *iterator) {
+ rpsdb_t *rpsdb;
+ rpsdb_rdatasetiter_t *rpsdb_iter;
+
+ rpsdb = (rpsdb_t *)iterator->db;
+ REQUIRE(VALID_RPSDB(rpsdb));
+ rpsdb_iter = (rpsdb_rdatasetiter_t *)iterator;
+
+ rpsdb_iter->type = dns_rdatatype_none;
+ rpsdb_iter->class = dns_rdataclass_reserved0;
+ return (rpsdb_rdatasetiter_next(iterator));
+}
+
+static void
+rpsdb_rdatasetiter_current(dns_rdatasetiter_t *iterator,
+ dns_rdataset_t *rdataset) {
+ rpsdb_t *rpsdb;
+ rpsdb_rdatasetiter_t *rpsdb_iter;
+
+ rpsdb = (rpsdb_t *)iterator->db;
+ REQUIRE(VALID_RPSDB(rpsdb));
+ rpsdb_iter = (rpsdb_rdatasetiter_t *)iterator;
+ REQUIRE(rpsdb_iter->type != dns_rdatatype_none);
+
+ rpsdb_bind_rdataset(rdataset, rpsdb_iter->count, rpsdb_iter->next_rr,
+ rpsdb_iter->type, rpsdb_iter->class,
+ rpsdb_iter->ttl, rpsdb);
+}
+
+static dns_dbmethods_t rpsdb_db_methods = {
+ rpsdb_attach,
+ rpsdb_detach,
+ NULL, /* beginload */
+ NULL, /* endload */
+ NULL, /* dump */
+ NULL, /* currentversion */
+ NULL, /* newversion */
+ NULL, /* attachversion */
+ NULL, /* closeversion */
+ rpsdb_findnode,
+ rpsdb_finddb,
+ NULL, /* findzonecut*/
+ rpsdb_attachnode,
+ rpsdb_detachnode,
+ NULL, /* expirenode */
+ NULL, /* printnode */
+ NULL, /* createiterator */
+ rpsdb_findrdataset,
+ rpsdb_allrdatasets,
+ NULL, /* addrdataset */
+ NULL, /* subtractrdataset */
+ NULL, /* deleterdataset */
+ rpsdb_issecure,
+ NULL, /* nodecount */
+ NULL, /* ispersistent */
+ NULL, /* overmem */
+ NULL, /* settask */
+ rpsdb_getoriginnode,
+ NULL, /* transfernode */
+ NULL, /* getnsec3parameters */
+ NULL, /* findnsec3node */
+ NULL, /* setsigningtime */
+ NULL, /* getsigningtime */
+ NULL, /* resigned */
+ NULL, /* isdnssec */
+ NULL, /* getrrsetstats */
+ NULL, /* rpz_attach */
+ NULL, /* rpz_ready */
+ NULL, /* findnodeext */
+ NULL, /* findext */
+ NULL, /* setcachestats */
+ NULL, /* hashsize */
+ NULL, /* nodefullname */
+ NULL, /* getsize */
+ NULL, /* setservestalettl */
+ NULL, /* getservestalettl */
+ NULL, /* setservestalerefresh */
+ NULL, /* getservestalerefresh */
+ NULL, /* setgluecachestats */
+};
+
+static dns_rdatasetmethods_t rpsdb_rdataset_methods = {
+ rpsdb_rdataset_disassociate,
+ rpsdb_rdataset_first,
+ rpsdb_rdataset_next,
+ rpsdb_rdataset_current,
+ rpsdb_rdataset_clone,
+ rpsdb_rdataset_count,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL
+};
+
+static dns_rdatasetitermethods_t rpsdb_rdatasetiter_methods = {
+ rpsdb_rdatasetiter_destroy, rpsdb_rdatasetiter_first,
+ rpsdb_rdatasetiter_next, rpsdb_rdatasetiter_current
+};
+
+#endif /* USE_DNSRPS */
diff --git a/lib/dns/dnssec.c b/lib/dns/dnssec.c
new file mode 100644
index 0000000..5678c05
--- /dev/null
+++ b/lib/dns/dnssec.c
@@ -0,0 +1,2533 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/*! \file */
+
+#include <ctype.h>
+#include <inttypes.h>
+#include <stdbool.h>
+#include <stdlib.h>
+
+#include <isc/buffer.h>
+#include <isc/dir.h>
+#include <isc/mem.h>
+#include <isc/print.h>
+#include <isc/result.h>
+#include <isc/serial.h>
+#include <isc/string.h>
+#include <isc/util.h>
+
+#include <dns/db.h>
+#include <dns/diff.h>
+#include <dns/dnssec.h>
+#include <dns/fixedname.h>
+#include <dns/kasp.h>
+#include <dns/keyvalues.h>
+#include <dns/log.h>
+#include <dns/message.h>
+#include <dns/rdata.h>
+#include <dns/rdatalist.h>
+#include <dns/rdataset.h>
+#include <dns/rdatastruct.h>
+#include <dns/stats.h>
+#include <dns/tsig.h> /* for DNS_TSIG_FUDGE */
+
+isc_stats_t *dns_dnssec_stats;
+
+#define is_response(msg) ((msg->flags & DNS_MESSAGEFLAG_QR) != 0)
+
+#define RETERR(x) \
+ do { \
+ result = (x); \
+ if (result != ISC_R_SUCCESS) \
+ goto failure; \
+ } while (0)
+
+#define TYPE_SIGN 0
+#define TYPE_VERIFY 1
+
+static isc_result_t
+digest_callback(void *arg, isc_region_t *data);
+
+static int
+rdata_compare_wrapper(const void *rdata1, const void *rdata2);
+
+static isc_result_t
+rdataset_to_sortedarray(dns_rdataset_t *set, isc_mem_t *mctx,
+ dns_rdata_t **rdata, int *nrdata);
+
+static isc_result_t
+digest_callback(void *arg, isc_region_t *data) {
+ dst_context_t *ctx = arg;
+
+ return (dst_context_adddata(ctx, data));
+}
+
+static void
+inc_stat(isc_statscounter_t counter) {
+ if (dns_dnssec_stats != NULL) {
+ isc_stats_increment(dns_dnssec_stats, counter);
+ }
+}
+
+/*
+ * Make qsort happy.
+ */
+static int
+rdata_compare_wrapper(const void *rdata1, const void *rdata2) {
+ return (dns_rdata_compare((const dns_rdata_t *)rdata1,
+ (const dns_rdata_t *)rdata2));
+}
+
+/*
+ * Sort the rdataset into an array.
+ */
+static isc_result_t
+rdataset_to_sortedarray(dns_rdataset_t *set, isc_mem_t *mctx,
+ dns_rdata_t **rdata, int *nrdata) {
+ isc_result_t ret;
+ int i = 0, n;
+ dns_rdata_t *data;
+ dns_rdataset_t rdataset;
+
+ n = dns_rdataset_count(set);
+
+ data = isc_mem_get(mctx, n * sizeof(dns_rdata_t));
+
+ dns_rdataset_init(&rdataset);
+ dns_rdataset_clone(set, &rdataset);
+ ret = dns_rdataset_first(&rdataset);
+ if (ret != ISC_R_SUCCESS) {
+ dns_rdataset_disassociate(&rdataset);
+ isc_mem_put(mctx, data, n * sizeof(dns_rdata_t));
+ return (ret);
+ }
+
+ /*
+ * Put them in the array.
+ */
+ do {
+ dns_rdata_init(&data[i]);
+ dns_rdataset_current(&rdataset, &data[i++]);
+ } while (dns_rdataset_next(&rdataset) == ISC_R_SUCCESS);
+
+ /*
+ * Sort the array.
+ */
+ qsort(data, n, sizeof(dns_rdata_t), rdata_compare_wrapper);
+ *rdata = data;
+ *nrdata = n;
+ dns_rdataset_disassociate(&rdataset);
+ return (ISC_R_SUCCESS);
+}
+
+isc_result_t
+dns_dnssec_keyfromrdata(const dns_name_t *name, const dns_rdata_t *rdata,
+ isc_mem_t *mctx, dst_key_t **key) {
+ isc_buffer_t b;
+ isc_region_t r;
+
+ INSIST(name != NULL);
+ INSIST(rdata != NULL);
+ INSIST(mctx != NULL);
+ INSIST(key != NULL);
+ INSIST(*key == NULL);
+ REQUIRE(rdata->type == dns_rdatatype_key ||
+ rdata->type == dns_rdatatype_dnskey);
+
+ dns_rdata_toregion(rdata, &r);
+ isc_buffer_init(&b, r.base, r.length);
+ isc_buffer_add(&b, r.length);
+ return (dst_key_fromdns(name, rdata->rdclass, &b, mctx, key));
+}
+
+static isc_result_t
+digest_sig(dst_context_t *ctx, bool downcase, dns_rdata_t *sigrdata,
+ dns_rdata_rrsig_t *rrsig) {
+ isc_region_t r;
+ isc_result_t ret;
+ dns_fixedname_t fname;
+
+ dns_rdata_toregion(sigrdata, &r);
+ INSIST(r.length >= 19);
+
+ r.length = 18;
+ ret = dst_context_adddata(ctx, &r);
+ if (ret != ISC_R_SUCCESS) {
+ return (ret);
+ }
+ if (downcase) {
+ dns_fixedname_init(&fname);
+
+ RUNTIME_CHECK(dns_name_downcase(&rrsig->signer,
+ dns_fixedname_name(&fname),
+ NULL) == ISC_R_SUCCESS);
+ dns_name_toregion(dns_fixedname_name(&fname), &r);
+ } else {
+ dns_name_toregion(&rrsig->signer, &r);
+ }
+
+ return (dst_context_adddata(ctx, &r));
+}
+
+isc_result_t
+dns_dnssec_sign(const dns_name_t *name, dns_rdataset_t *set, dst_key_t *key,
+ isc_stdtime_t *inception, isc_stdtime_t *expire,
+ isc_mem_t *mctx, isc_buffer_t *buffer, dns_rdata_t *sigrdata) {
+ dns_rdata_rrsig_t sig;
+ dns_rdata_t tmpsigrdata;
+ dns_rdata_t *rdatas;
+ int nrdatas, i;
+ isc_buffer_t sigbuf, envbuf;
+ isc_region_t r;
+ dst_context_t *ctx = NULL;
+ isc_result_t ret;
+ isc_buffer_t *databuf = NULL;
+ char data[256 + 8];
+ uint32_t flags;
+ unsigned int sigsize;
+ dns_fixedname_t fnewname;
+ dns_fixedname_t fsigner;
+
+ REQUIRE(name != NULL);
+ REQUIRE(dns_name_countlabels(name) <= 255);
+ REQUIRE(set != NULL);
+ REQUIRE(key != NULL);
+ REQUIRE(inception != NULL);
+ REQUIRE(expire != NULL);
+ REQUIRE(mctx != NULL);
+ REQUIRE(sigrdata != NULL);
+
+ if (*inception >= *expire) {
+ return (DNS_R_INVALIDTIME);
+ }
+
+ /*
+ * Is the key allowed to sign data?
+ */
+ flags = dst_key_flags(key);
+ if ((flags & DNS_KEYTYPE_NOAUTH) != 0) {
+ return (DNS_R_KEYUNAUTHORIZED);
+ }
+ if ((flags & DNS_KEYFLAG_OWNERMASK) != DNS_KEYOWNER_ZONE) {
+ return (DNS_R_KEYUNAUTHORIZED);
+ }
+
+ sig.mctx = mctx;
+ sig.common.rdclass = set->rdclass;
+ sig.common.rdtype = dns_rdatatype_rrsig;
+ ISC_LINK_INIT(&sig.common, link);
+
+ /*
+ * Downcase signer.
+ */
+ dns_name_init(&sig.signer, NULL);
+ dns_fixedname_init(&fsigner);
+ RUNTIME_CHECK(dns_name_downcase(dst_key_name(key),
+ dns_fixedname_name(&fsigner),
+ NULL) == ISC_R_SUCCESS);
+ dns_name_clone(dns_fixedname_name(&fsigner), &sig.signer);
+
+ sig.covered = set->type;
+ sig.algorithm = dst_key_alg(key);
+ sig.labels = dns_name_countlabels(name) - 1;
+ if (dns_name_iswildcard(name)) {
+ sig.labels--;
+ }
+ sig.originalttl = set->ttl;
+ sig.timesigned = *inception;
+ sig.timeexpire = *expire;
+ sig.keyid = dst_key_id(key);
+ ret = dst_key_sigsize(key, &sigsize);
+ if (ret != ISC_R_SUCCESS) {
+ return (ret);
+ }
+ sig.siglen = sigsize;
+ /*
+ * The actual contents of sig.signature are not important yet, since
+ * they're not used in digest_sig().
+ */
+ sig.signature = isc_mem_get(mctx, sig.siglen);
+
+ isc_buffer_allocate(mctx, &databuf, sigsize + 256 + 18);
+
+ dns_rdata_init(&tmpsigrdata);
+ ret = dns_rdata_fromstruct(&tmpsigrdata, sig.common.rdclass,
+ sig.common.rdtype, &sig, databuf);
+ if (ret != ISC_R_SUCCESS) {
+ goto cleanup_databuf;
+ }
+
+ ret = dst_context_create(key, mctx, DNS_LOGCATEGORY_DNSSEC, true, 0,
+ &ctx);
+ if (ret != ISC_R_SUCCESS) {
+ goto cleanup_databuf;
+ }
+
+ /*
+ * Digest the SIG rdata.
+ */
+ ret = digest_sig(ctx, false, &tmpsigrdata, &sig);
+ if (ret != ISC_R_SUCCESS) {
+ goto cleanup_context;
+ }
+
+ dns_fixedname_init(&fnewname);
+ RUNTIME_CHECK(dns_name_downcase(name, dns_fixedname_name(&fnewname),
+ NULL) == ISC_R_SUCCESS);
+ dns_name_toregion(dns_fixedname_name(&fnewname), &r);
+
+ /*
+ * Create an envelope for each rdata: <name|type|class|ttl>.
+ */
+ isc_buffer_init(&envbuf, data, sizeof(data));
+ memmove(data, r.base, r.length);
+ isc_buffer_add(&envbuf, r.length);
+ isc_buffer_putuint16(&envbuf, set->type);
+ isc_buffer_putuint16(&envbuf, set->rdclass);
+ isc_buffer_putuint32(&envbuf, set->ttl);
+
+ ret = rdataset_to_sortedarray(set, mctx, &rdatas, &nrdatas);
+ if (ret != ISC_R_SUCCESS) {
+ goto cleanup_context;
+ }
+ isc_buffer_usedregion(&envbuf, &r);
+
+ for (i = 0; i < nrdatas; i++) {
+ uint16_t len;
+ isc_buffer_t lenbuf;
+ isc_region_t lenr;
+
+ /*
+ * Skip duplicates.
+ */
+ if (i > 0 && dns_rdata_compare(&rdatas[i], &rdatas[i - 1]) == 0)
+ {
+ continue;
+ }
+
+ /*
+ * Digest the envelope.
+ */
+ ret = dst_context_adddata(ctx, &r);
+ if (ret != ISC_R_SUCCESS) {
+ goto cleanup_array;
+ }
+
+ /*
+ * Digest the length of the rdata.
+ */
+ isc_buffer_init(&lenbuf, &len, sizeof(len));
+ INSIST(rdatas[i].length < 65536);
+ isc_buffer_putuint16(&lenbuf, (uint16_t)rdatas[i].length);
+ isc_buffer_usedregion(&lenbuf, &lenr);
+ ret = dst_context_adddata(ctx, &lenr);
+ if (ret != ISC_R_SUCCESS) {
+ goto cleanup_array;
+ }
+
+ /*
+ * Digest the rdata.
+ */
+ ret = dns_rdata_digest(&rdatas[i], digest_callback, ctx);
+ if (ret != ISC_R_SUCCESS) {
+ goto cleanup_array;
+ }
+ }
+
+ isc_buffer_init(&sigbuf, sig.signature, sig.siglen);
+ ret = dst_context_sign(ctx, &sigbuf);
+ if (ret != ISC_R_SUCCESS) {
+ goto cleanup_array;
+ }
+ isc_buffer_usedregion(&sigbuf, &r);
+ if (r.length != sig.siglen) {
+ ret = ISC_R_NOSPACE;
+ goto cleanup_array;
+ }
+
+ ret = dns_rdata_fromstruct(sigrdata, sig.common.rdclass,
+ sig.common.rdtype, &sig, buffer);
+
+cleanup_array:
+ isc_mem_put(mctx, rdatas, nrdatas * sizeof(dns_rdata_t));
+cleanup_context:
+ dst_context_destroy(&ctx);
+cleanup_databuf:
+ isc_buffer_free(&databuf);
+ isc_mem_put(mctx, sig.signature, sig.siglen);
+
+ return (ret);
+}
+
+isc_result_t
+dns_dnssec_verify(const dns_name_t *name, dns_rdataset_t *set, dst_key_t *key,
+ bool ignoretime, unsigned int maxbits, isc_mem_t *mctx,
+ dns_rdata_t *sigrdata, dns_name_t *wild) {
+ dns_rdata_rrsig_t sig;
+ dns_fixedname_t fnewname;
+ isc_region_t r;
+ isc_buffer_t envbuf;
+ dns_rdata_t *rdatas;
+ int nrdatas, i;
+ isc_stdtime_t now;
+ isc_result_t ret;
+ unsigned char data[300];
+ dst_context_t *ctx = NULL;
+ int labels = 0;
+ uint32_t flags;
+ bool downcase = false;
+
+ REQUIRE(name != NULL);
+ REQUIRE(set != NULL);
+ REQUIRE(key != NULL);
+ REQUIRE(mctx != NULL);
+ REQUIRE(sigrdata != NULL && sigrdata->type == dns_rdatatype_rrsig);
+
+ ret = dns_rdata_tostruct(sigrdata, &sig, NULL);
+ if (ret != ISC_R_SUCCESS) {
+ return (ret);
+ }
+
+ if (set->type != sig.covered) {
+ return (DNS_R_SIGINVALID);
+ }
+
+ if (isc_serial_lt(sig.timeexpire, sig.timesigned)) {
+ inc_stat(dns_dnssecstats_fail);
+ return (DNS_R_SIGINVALID);
+ }
+
+ if (!ignoretime) {
+ isc_stdtime_get(&now);
+
+ /*
+ * Is SIG temporally valid?
+ */
+ if (isc_serial_lt((uint32_t)now, sig.timesigned)) {
+ inc_stat(dns_dnssecstats_fail);
+ return (DNS_R_SIGFUTURE);
+ } else if (isc_serial_lt(sig.timeexpire, (uint32_t)now)) {
+ inc_stat(dns_dnssecstats_fail);
+ return (DNS_R_SIGEXPIRED);
+ }
+ }
+
+ /*
+ * NS, SOA and DNSSKEY records are signed by their owner.
+ * DS records are signed by the parent.
+ */
+ switch (set->type) {
+ case dns_rdatatype_ns:
+ case dns_rdatatype_soa:
+ case dns_rdatatype_dnskey:
+ if (!dns_name_equal(name, &sig.signer)) {
+ inc_stat(dns_dnssecstats_fail);
+ return (DNS_R_SIGINVALID);
+ }
+ break;
+ case dns_rdatatype_ds:
+ if (dns_name_equal(name, &sig.signer)) {
+ inc_stat(dns_dnssecstats_fail);
+ return (DNS_R_SIGINVALID);
+ }
+ FALLTHROUGH;
+ default:
+ if (!dns_name_issubdomain(name, &sig.signer)) {
+ inc_stat(dns_dnssecstats_fail);
+ return (DNS_R_SIGINVALID);
+ }
+ break;
+ }
+
+ /*
+ * Is the key allowed to sign data?
+ */
+ flags = dst_key_flags(key);
+ if ((flags & DNS_KEYTYPE_NOAUTH) != 0) {
+ inc_stat(dns_dnssecstats_fail);
+ return (DNS_R_KEYUNAUTHORIZED);
+ }
+ if ((flags & DNS_KEYFLAG_OWNERMASK) != DNS_KEYOWNER_ZONE) {
+ inc_stat(dns_dnssecstats_fail);
+ return (DNS_R_KEYUNAUTHORIZED);
+ }
+
+again:
+ ret = dst_context_create(key, mctx, DNS_LOGCATEGORY_DNSSEC, false,
+ maxbits, &ctx);
+ if (ret != ISC_R_SUCCESS) {
+ goto cleanup_struct;
+ }
+
+ /*
+ * Digest the SIG rdata (not including the signature).
+ */
+ ret = digest_sig(ctx, downcase, sigrdata, &sig);
+ if (ret != ISC_R_SUCCESS) {
+ goto cleanup_context;
+ }
+
+ /*
+ * If the name is an expanded wildcard, use the wildcard name.
+ */
+ dns_fixedname_init(&fnewname);
+ labels = dns_name_countlabels(name) - 1;
+ RUNTIME_CHECK(dns_name_downcase(name, dns_fixedname_name(&fnewname),
+ NULL) == ISC_R_SUCCESS);
+ if (labels - sig.labels > 0) {
+ dns_name_split(dns_fixedname_name(&fnewname), sig.labels + 1,
+ NULL, dns_fixedname_name(&fnewname));
+ }
+
+ dns_name_toregion(dns_fixedname_name(&fnewname), &r);
+
+ /*
+ * Create an envelope for each rdata: <name|type|class|ttl>.
+ */
+ isc_buffer_init(&envbuf, data, sizeof(data));
+ if (labels - sig.labels > 0) {
+ isc_buffer_putuint8(&envbuf, 1);
+ isc_buffer_putuint8(&envbuf, '*');
+ memmove(data + 2, r.base, r.length);
+ } else {
+ memmove(data, r.base, r.length);
+ }
+ isc_buffer_add(&envbuf, r.length);
+ isc_buffer_putuint16(&envbuf, set->type);
+ isc_buffer_putuint16(&envbuf, set->rdclass);
+ isc_buffer_putuint32(&envbuf, sig.originalttl);
+
+ ret = rdataset_to_sortedarray(set, mctx, &rdatas, &nrdatas);
+ if (ret != ISC_R_SUCCESS) {
+ goto cleanup_context;
+ }
+
+ isc_buffer_usedregion(&envbuf, &r);
+
+ for (i = 0; i < nrdatas; i++) {
+ uint16_t len;
+ isc_buffer_t lenbuf;
+ isc_region_t lenr;
+
+ /*
+ * Skip duplicates.
+ */
+ if (i > 0 && dns_rdata_compare(&rdatas[i], &rdatas[i - 1]) == 0)
+ {
+ continue;
+ }
+
+ /*
+ * Digest the envelope.
+ */
+ ret = dst_context_adddata(ctx, &r);
+ if (ret != ISC_R_SUCCESS) {
+ goto cleanup_array;
+ }
+
+ /*
+ * Digest the rdata length.
+ */
+ isc_buffer_init(&lenbuf, &len, sizeof(len));
+ INSIST(rdatas[i].length < 65536);
+ isc_buffer_putuint16(&lenbuf, (uint16_t)rdatas[i].length);
+ isc_buffer_usedregion(&lenbuf, &lenr);
+
+ /*
+ * Digest the rdata.
+ */
+ ret = dst_context_adddata(ctx, &lenr);
+ if (ret != ISC_R_SUCCESS) {
+ goto cleanup_array;
+ }
+ ret = dns_rdata_digest(&rdatas[i], digest_callback, ctx);
+ if (ret != ISC_R_SUCCESS) {
+ goto cleanup_array;
+ }
+ }
+
+ r.base = sig.signature;
+ r.length = sig.siglen;
+ ret = dst_context_verify2(ctx, maxbits, &r);
+ if (ret == ISC_R_SUCCESS && downcase) {
+ char namebuf[DNS_NAME_FORMATSIZE];
+ dns_name_format(&sig.signer, namebuf, sizeof(namebuf));
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_DNSSEC,
+ DNS_LOGMODULE_DNSSEC, ISC_LOG_DEBUG(1),
+ "successfully validated after lower casing "
+ "signer '%s'",
+ namebuf);
+ inc_stat(dns_dnssecstats_downcase);
+ } else if (ret == ISC_R_SUCCESS) {
+ inc_stat(dns_dnssecstats_asis);
+ }
+
+cleanup_array:
+ isc_mem_put(mctx, rdatas, nrdatas * sizeof(dns_rdata_t));
+cleanup_context:
+ dst_context_destroy(&ctx);
+ if (ret == DST_R_VERIFYFAILURE && !downcase) {
+ downcase = true;
+ goto again;
+ }
+cleanup_struct:
+ dns_rdata_freestruct(&sig);
+
+ if (ret == DST_R_VERIFYFAILURE) {
+ ret = DNS_R_SIGINVALID;
+ }
+
+ if (ret != ISC_R_SUCCESS) {
+ inc_stat(dns_dnssecstats_fail);
+ }
+
+ if (ret == ISC_R_SUCCESS && labels - sig.labels > 0) {
+ if (wild != NULL) {
+ RUNTIME_CHECK(dns_name_concatenate(
+ dns_wildcardname,
+ dns_fixedname_name(&fnewname),
+ wild, NULL) == ISC_R_SUCCESS);
+ }
+ inc_stat(dns_dnssecstats_wildcard);
+ ret = DNS_R_FROMWILDCARD;
+ }
+ return (ret);
+}
+
+bool
+dns_dnssec_keyactive(dst_key_t *key, isc_stdtime_t now) {
+ isc_result_t result;
+ isc_stdtime_t publish, active, revoke, remove;
+ bool hint_publish, hint_zsign, hint_ksign, hint_revoke, hint_remove;
+ int major, minor;
+ bool ksk = false, zsk = false;
+ isc_result_t ret;
+
+ /* Is this an old-style key? */
+ result = dst_key_getprivateformat(key, &major, &minor);
+ RUNTIME_CHECK(result == ISC_R_SUCCESS);
+
+ /* Is this a KSK? */
+ ret = dst_key_getbool(key, DST_BOOL_KSK, &ksk);
+ if (ret != ISC_R_SUCCESS) {
+ ksk = ((dst_key_flags(key) & DNS_KEYFLAG_KSK) != 0);
+ }
+ ret = dst_key_getbool(key, DST_BOOL_ZSK, &zsk);
+ if (ret != ISC_R_SUCCESS) {
+ zsk = ((dst_key_flags(key) & DNS_KEYFLAG_KSK) == 0);
+ }
+
+ /*
+ * Smart signing started with key format 1.3; prior to that, all
+ * keys are assumed active.
+ */
+ if (major == 1 && minor <= 2) {
+ return (true);
+ }
+
+ hint_publish = dst_key_is_published(key, now, &publish);
+ hint_zsign = dst_key_is_signing(key, DST_BOOL_ZSK, now, &active);
+ hint_ksign = dst_key_is_signing(key, DST_BOOL_KSK, now, &active);
+ hint_revoke = dst_key_is_revoked(key, now, &revoke);
+ hint_remove = dst_key_is_removed(key, now, &remove);
+
+ if (hint_remove) {
+ return (false);
+ }
+ if (hint_publish && hint_revoke) {
+ return (true);
+ }
+ if (hint_zsign && zsk) {
+ return (true);
+ }
+ if (hint_ksign && ksk) {
+ return (true);
+ }
+ return (false);
+}
+
+/*%<
+ * Indicate whether a key is scheduled to to have CDS/CDNSKEY records
+ * published now.
+ *
+ * Returns true if.
+ * - kasp says the DS record should be published (e.g. the DS state is in
+ * RUMOURED or OMNIPRESENT state).
+ * Or:
+ * - SyncPublish is set and in the past, AND
+ * - SyncDelete is unset or in the future
+ */
+static bool
+syncpublish(dst_key_t *key, isc_stdtime_t now) {
+ isc_result_t result;
+ isc_stdtime_t when;
+ dst_key_state_t state;
+ int major, minor;
+ bool publish;
+
+ /*
+ * Is this an old-style key?
+ */
+ result = dst_key_getprivateformat(key, &major, &minor);
+ RUNTIME_CHECK(result == ISC_R_SUCCESS);
+
+ /*
+ * Smart signing started with key format 1.3
+ */
+ if (major == 1 && minor <= 2) {
+ return (false);
+ }
+
+ /* Check kasp state first. */
+ result = dst_key_getstate(key, DST_KEY_DS, &state);
+ if (result == ISC_R_SUCCESS) {
+ return (state == DST_KEY_STATE_RUMOURED ||
+ state == DST_KEY_STATE_OMNIPRESENT);
+ }
+
+ /* If no kasp state, check timings. */
+ publish = false;
+ result = dst_key_gettime(key, DST_TIME_SYNCPUBLISH, &when);
+ if (result == ISC_R_SUCCESS && when <= now) {
+ publish = true;
+ }
+ result = dst_key_gettime(key, DST_TIME_SYNCDELETE, &when);
+ if (result == ISC_R_SUCCESS && when < now) {
+ publish = false;
+ }
+ return (publish);
+}
+
+/*%<
+ * Indicate whether a key is scheduled to to have CDS/CDNSKEY records
+ * deleted now.
+ *
+ * Returns true if:
+ * - kasp says the DS record should be unpublished (e.g. the DS state is in
+ * UNRETENTIVE or HIDDEN state).
+ * Or:
+ * - SyncDelete is set and in the past.
+ */
+static bool
+syncdelete(dst_key_t *key, isc_stdtime_t now) {
+ isc_result_t result;
+ isc_stdtime_t when;
+ dst_key_state_t state;
+ int major, minor;
+
+ /*
+ * Is this an old-style key?
+ */
+ result = dst_key_getprivateformat(key, &major, &minor);
+ RUNTIME_CHECK(result == ISC_R_SUCCESS);
+
+ /*
+ * Smart signing started with key format 1.3.
+ */
+ if (major == 1 && minor <= 2) {
+ return (false);
+ }
+
+ /* Check kasp state first. */
+ result = dst_key_getstate(key, DST_KEY_DS, &state);
+ if (result == ISC_R_SUCCESS) {
+ return (state == DST_KEY_STATE_UNRETENTIVE ||
+ state == DST_KEY_STATE_HIDDEN);
+ }
+
+ /* If no kasp state, check timings. */
+ result = dst_key_gettime(key, DST_TIME_SYNCDELETE, &when);
+ if (result != ISC_R_SUCCESS) {
+ return (false);
+ }
+ if (when <= now) {
+ return (true);
+ }
+ return (false);
+}
+
+#define is_zone_key(key) \
+ ((dst_key_flags(key) & DNS_KEYFLAG_OWNERMASK) == DNS_KEYOWNER_ZONE)
+
+isc_result_t
+dns_dnssec_findzonekeys(dns_db_t *db, dns_dbversion_t *ver, dns_dbnode_t *node,
+ const dns_name_t *name, const char *directory,
+ isc_stdtime_t now, isc_mem_t *mctx,
+ unsigned int maxkeys, dst_key_t **keys,
+ unsigned int *nkeys) {
+ dns_rdataset_t rdataset;
+ dns_rdata_t rdata = DNS_RDATA_INIT;
+ isc_result_t result;
+ dst_key_t *pubkey = NULL;
+ unsigned int count = 0;
+
+ REQUIRE(nkeys != NULL);
+ REQUIRE(keys != NULL);
+
+ *nkeys = 0;
+ memset(keys, 0, sizeof(*keys) * maxkeys);
+ dns_rdataset_init(&rdataset);
+ RETERR(dns_db_findrdataset(db, node, ver, dns_rdatatype_dnskey, 0, 0,
+ &rdataset, NULL));
+ RETERR(dns_rdataset_first(&rdataset));
+ while (result == ISC_R_SUCCESS && count < maxkeys) {
+ pubkey = NULL;
+ dns_rdataset_current(&rdataset, &rdata);
+ RETERR(dns_dnssec_keyfromrdata(name, &rdata, mctx, &pubkey));
+ dst_key_setttl(pubkey, rdataset.ttl);
+
+ if (!is_zone_key(pubkey) ||
+ (dst_key_flags(pubkey) & DNS_KEYTYPE_NOAUTH) != 0)
+ {
+ goto next;
+ }
+ /* Corrupted .key file? */
+ if (!dns_name_equal(name, dst_key_name(pubkey))) {
+ goto next;
+ }
+ keys[count] = NULL;
+ result = dst_key_fromfile(
+ dst_key_name(pubkey), dst_key_id(pubkey),
+ dst_key_alg(pubkey),
+ DST_TYPE_PUBLIC | DST_TYPE_PRIVATE | DST_TYPE_STATE,
+ directory, mctx, &keys[count]);
+
+ /*
+ * If the key was revoked and the private file
+ * doesn't exist, maybe it was revoked internally
+ * by named. Try loading the unrevoked version.
+ */
+ if (result == ISC_R_FILENOTFOUND) {
+ uint32_t flags;
+ flags = dst_key_flags(pubkey);
+ if ((flags & DNS_KEYFLAG_REVOKE) != 0) {
+ dst_key_setflags(pubkey,
+ flags & ~DNS_KEYFLAG_REVOKE);
+ result = dst_key_fromfile(
+ dst_key_name(pubkey),
+ dst_key_id(pubkey), dst_key_alg(pubkey),
+ DST_TYPE_PUBLIC | DST_TYPE_PRIVATE |
+ DST_TYPE_STATE,
+ directory, mctx, &keys[count]);
+ if (result == ISC_R_SUCCESS &&
+ dst_key_pubcompare(pubkey, keys[count],
+ false))
+ {
+ dst_key_setflags(keys[count], flags);
+ }
+ dst_key_setflags(pubkey, flags);
+ }
+ }
+
+ if (result != ISC_R_SUCCESS) {
+ char filename[DNS_NAME_FORMATSIZE +
+ DNS_SECALG_FORMATSIZE +
+ sizeof("key file for //65535")];
+ isc_result_t result2;
+ isc_buffer_t buf;
+
+ isc_buffer_init(&buf, filename, NAME_MAX);
+ result2 = dst_key_getfilename(
+ dst_key_name(pubkey), dst_key_id(pubkey),
+ dst_key_alg(pubkey),
+ (DST_TYPE_PUBLIC | DST_TYPE_PRIVATE |
+ DST_TYPE_STATE),
+ directory, mctx, &buf);
+ if (result2 != ISC_R_SUCCESS) {
+ char namebuf[DNS_NAME_FORMATSIZE];
+ char algbuf[DNS_SECALG_FORMATSIZE];
+
+ dns_name_format(dst_key_name(pubkey), namebuf,
+ sizeof(namebuf));
+ dns_secalg_format(dst_key_alg(pubkey), algbuf,
+ sizeof(algbuf));
+ snprintf(filename, sizeof(filename) - 1,
+ "key file for %s/%s/%d", namebuf,
+ algbuf, dst_key_id(pubkey));
+ }
+
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL,
+ DNS_LOGMODULE_DNSSEC, ISC_LOG_WARNING,
+ "dns_dnssec_findzonekeys2: error "
+ "reading %s: %s",
+ filename, isc_result_totext(result));
+ }
+
+ if (result == ISC_R_FILENOTFOUND || result == ISC_R_NOPERM) {
+ keys[count] = pubkey;
+ pubkey = NULL;
+ count++;
+ goto next;
+ }
+
+ if (result != ISC_R_SUCCESS) {
+ goto failure;
+ }
+
+ /*
+ * If a key is marked inactive, skip it
+ */
+ if (!dns_dnssec_keyactive(keys[count], now)) {
+ dst_key_setinactive(pubkey, true);
+ dst_key_free(&keys[count]);
+ keys[count] = pubkey;
+ pubkey = NULL;
+ count++;
+ goto next;
+ }
+
+ /*
+ * Whatever the key's default TTL may have
+ * been, the rdataset TTL takes priority.
+ */
+ dst_key_setttl(keys[count], rdataset.ttl);
+
+ if ((dst_key_flags(keys[count]) & DNS_KEYTYPE_NOAUTH) != 0) {
+ /* We should never get here. */
+ dst_key_free(&keys[count]);
+ goto next;
+ }
+ count++;
+ next:
+ if (pubkey != NULL) {
+ dst_key_free(&pubkey);
+ }
+ dns_rdata_reset(&rdata);
+ result = dns_rdataset_next(&rdataset);
+ }
+ if (result != ISC_R_NOMORE) {
+ goto failure;
+ }
+ if (count == 0) {
+ result = ISC_R_NOTFOUND;
+ } else {
+ result = ISC_R_SUCCESS;
+ }
+
+failure:
+ if (dns_rdataset_isassociated(&rdataset)) {
+ dns_rdataset_disassociate(&rdataset);
+ }
+ if (pubkey != NULL) {
+ dst_key_free(&pubkey);
+ }
+ if (result != ISC_R_SUCCESS) {
+ while (count > 0) {
+ dst_key_free(&keys[--count]);
+ }
+ }
+ *nkeys = count;
+ return (result);
+}
+
+isc_result_t
+dns_dnssec_signmessage(dns_message_t *msg, dst_key_t *key) {
+ dns_rdata_sig_t sig; /* SIG(0) */
+ unsigned char data[512];
+ unsigned char header[DNS_MESSAGE_HEADERLEN];
+ isc_buffer_t headerbuf, databuf, sigbuf;
+ unsigned int sigsize;
+ isc_buffer_t *dynbuf = NULL;
+ dns_rdata_t *rdata;
+ dns_rdatalist_t *datalist;
+ dns_rdataset_t *dataset;
+ isc_region_t r;
+ isc_stdtime_t now;
+ dst_context_t *ctx = NULL;
+ isc_mem_t *mctx;
+ isc_result_t result;
+
+ REQUIRE(msg != NULL);
+ REQUIRE(key != NULL);
+
+ if (is_response(msg)) {
+ REQUIRE(msg->query.base != NULL);
+ }
+
+ mctx = msg->mctx;
+
+ memset(&sig, 0, sizeof(sig));
+
+ sig.mctx = mctx;
+ sig.common.rdclass = dns_rdataclass_any;
+ sig.common.rdtype = dns_rdatatype_sig; /* SIG(0) */
+ ISC_LINK_INIT(&sig.common, link);
+
+ sig.covered = 0;
+ sig.algorithm = dst_key_alg(key);
+ sig.labels = 0; /* the root name */
+ sig.originalttl = 0;
+
+ if (msg->fuzzing) {
+ now = msg->fuzztime;
+ } else {
+ isc_stdtime_get(&now);
+ }
+ sig.timesigned = now - DNS_TSIG_FUDGE;
+ sig.timeexpire = now + DNS_TSIG_FUDGE;
+
+ sig.keyid = dst_key_id(key);
+
+ dns_name_init(&sig.signer, NULL);
+ dns_name_clone(dst_key_name(key), &sig.signer);
+
+ sig.siglen = 0;
+ sig.signature = NULL;
+
+ isc_buffer_init(&databuf, data, sizeof(data));
+
+ RETERR(dst_context_create(key, mctx, DNS_LOGCATEGORY_DNSSEC, true, 0,
+ &ctx));
+
+ /*
+ * Digest the fields of the SIG - we can cheat and use
+ * dns_rdata_fromstruct. Since siglen is 0, the digested data
+ * is identical to dns format.
+ */
+ RETERR(dns_rdata_fromstruct(NULL, dns_rdataclass_any,
+ dns_rdatatype_sig /* SIG(0) */, &sig,
+ &databuf));
+ isc_buffer_usedregion(&databuf, &r);
+ RETERR(dst_context_adddata(ctx, &r));
+
+ /*
+ * If this is a response, digest the query.
+ */
+ if (is_response(msg)) {
+ RETERR(dst_context_adddata(ctx, &msg->query));
+ }
+
+ /*
+ * Digest the header.
+ */
+ isc_buffer_init(&headerbuf, header, sizeof(header));
+ dns_message_renderheader(msg, &headerbuf);
+ isc_buffer_usedregion(&headerbuf, &r);
+ RETERR(dst_context_adddata(ctx, &r));
+
+ /*
+ * Digest the remainder of the message.
+ */
+ isc_buffer_usedregion(msg->buffer, &r);
+ isc_region_consume(&r, DNS_MESSAGE_HEADERLEN);
+ RETERR(dst_context_adddata(ctx, &r));
+
+ RETERR(dst_key_sigsize(key, &sigsize));
+ sig.siglen = sigsize;
+ sig.signature = isc_mem_get(mctx, sig.siglen);
+
+ isc_buffer_init(&sigbuf, sig.signature, sig.siglen);
+ RETERR(dst_context_sign(ctx, &sigbuf));
+ dst_context_destroy(&ctx);
+
+ rdata = NULL;
+ RETERR(dns_message_gettemprdata(msg, &rdata));
+ isc_buffer_allocate(msg->mctx, &dynbuf, 1024);
+ RETERR(dns_rdata_fromstruct(rdata, dns_rdataclass_any,
+ dns_rdatatype_sig /* SIG(0) */, &sig,
+ dynbuf));
+
+ isc_mem_put(mctx, sig.signature, sig.siglen);
+
+ dns_message_takebuffer(msg, &dynbuf);
+
+ datalist = NULL;
+ RETERR(dns_message_gettemprdatalist(msg, &datalist));
+ datalist->rdclass = dns_rdataclass_any;
+ datalist->type = dns_rdatatype_sig; /* SIG(0) */
+ ISC_LIST_APPEND(datalist->rdata, rdata, link);
+ dataset = NULL;
+ RETERR(dns_message_gettemprdataset(msg, &dataset));
+ RUNTIME_CHECK(dns_rdatalist_tordataset(datalist, dataset) ==
+ ISC_R_SUCCESS);
+ msg->sig0 = dataset;
+
+ return (ISC_R_SUCCESS);
+
+failure:
+ if (dynbuf != NULL) {
+ isc_buffer_free(&dynbuf);
+ }
+ if (sig.signature != NULL) {
+ isc_mem_put(mctx, sig.signature, sig.siglen);
+ }
+ if (ctx != NULL) {
+ dst_context_destroy(&ctx);
+ }
+
+ return (result);
+}
+
+isc_result_t
+dns_dnssec_verifymessage(isc_buffer_t *source, dns_message_t *msg,
+ dst_key_t *key) {
+ dns_rdata_sig_t sig; /* SIG(0) */
+ unsigned char header[DNS_MESSAGE_HEADERLEN];
+ dns_rdata_t rdata = DNS_RDATA_INIT;
+ isc_region_t r, source_r, sig_r, header_r;
+ isc_stdtime_t now;
+ dst_context_t *ctx = NULL;
+ isc_mem_t *mctx;
+ isc_result_t result;
+ uint16_t addcount, addcount_n;
+ bool signeedsfree = false;
+
+ REQUIRE(source != NULL);
+ REQUIRE(msg != NULL);
+ REQUIRE(key != NULL);
+
+ mctx = msg->mctx;
+
+ msg->verify_attempted = 1;
+ msg->verified_sig = 0;
+ msg->sig0status = dns_tsigerror_badsig;
+
+ if (is_response(msg)) {
+ if (msg->query.base == NULL) {
+ return (DNS_R_UNEXPECTEDTSIG);
+ }
+ }
+
+ isc_buffer_usedregion(source, &source_r);
+
+ RETERR(dns_rdataset_first(msg->sig0));
+ dns_rdataset_current(msg->sig0, &rdata);
+
+ RETERR(dns_rdata_tostruct(&rdata, &sig, NULL));
+ signeedsfree = true;
+
+ if (sig.labels != 0) {
+ result = DNS_R_SIGINVALID;
+ goto failure;
+ }
+
+ if (isc_serial_lt(sig.timeexpire, sig.timesigned)) {
+ result = DNS_R_SIGINVALID;
+ msg->sig0status = dns_tsigerror_badtime;
+ goto failure;
+ }
+
+ if (msg->fuzzing) {
+ now = msg->fuzztime;
+ } else {
+ isc_stdtime_get(&now);
+ }
+
+ if (isc_serial_lt((uint32_t)now, sig.timesigned)) {
+ result = DNS_R_SIGFUTURE;
+ msg->sig0status = dns_tsigerror_badtime;
+ goto failure;
+ } else if (isc_serial_lt(sig.timeexpire, (uint32_t)now)) {
+ result = DNS_R_SIGEXPIRED;
+ msg->sig0status = dns_tsigerror_badtime;
+ goto failure;
+ }
+
+ if (!dns_name_equal(dst_key_name(key), &sig.signer)) {
+ result = DNS_R_SIGINVALID;
+ msg->sig0status = dns_tsigerror_badkey;
+ goto failure;
+ }
+
+ RETERR(dst_context_create(key, mctx, DNS_LOGCATEGORY_DNSSEC, false, 0,
+ &ctx));
+
+ /*
+ * Digest the SIG(0) record, except for the signature.
+ */
+ dns_rdata_toregion(&rdata, &r);
+ r.length -= sig.siglen;
+ RETERR(dst_context_adddata(ctx, &r));
+
+ /*
+ * If this is a response, digest the query.
+ */
+ if (is_response(msg)) {
+ RETERR(dst_context_adddata(ctx, &msg->query));
+ }
+
+ /*
+ * Extract the header.
+ */
+ memmove(header, source_r.base, DNS_MESSAGE_HEADERLEN);
+
+ /*
+ * Decrement the additional field counter.
+ */
+ memmove(&addcount, &header[DNS_MESSAGE_HEADERLEN - 2], 2);
+ addcount_n = ntohs(addcount);
+ addcount = htons((uint16_t)(addcount_n - 1));
+ memmove(&header[DNS_MESSAGE_HEADERLEN - 2], &addcount, 2);
+
+ /*
+ * Digest the modified header.
+ */
+ header_r.base = (unsigned char *)header;
+ header_r.length = DNS_MESSAGE_HEADERLEN;
+ RETERR(dst_context_adddata(ctx, &header_r));
+
+ /*
+ * Digest all non-SIG(0) records.
+ */
+ r.base = source_r.base + DNS_MESSAGE_HEADERLEN;
+ r.length = msg->sigstart - DNS_MESSAGE_HEADERLEN;
+ RETERR(dst_context_adddata(ctx, &r));
+
+ sig_r.base = sig.signature;
+ sig_r.length = sig.siglen;
+ result = dst_context_verify(ctx, &sig_r);
+ if (result != ISC_R_SUCCESS) {
+ msg->sig0status = dns_tsigerror_badsig;
+ goto failure;
+ }
+
+ msg->verified_sig = 1;
+ msg->sig0status = dns_rcode_noerror;
+
+ dst_context_destroy(&ctx);
+ dns_rdata_freestruct(&sig);
+
+ return (ISC_R_SUCCESS);
+
+failure:
+ if (signeedsfree) {
+ dns_rdata_freestruct(&sig);
+ }
+ if (ctx != NULL) {
+ dst_context_destroy(&ctx);
+ }
+
+ return (result);
+}
+
+/*%
+ * Does this key ('rdata') self sign the rrset ('rdataset')?
+ */
+bool
+dns_dnssec_selfsigns(dns_rdata_t *rdata, const dns_name_t *name,
+ dns_rdataset_t *rdataset, dns_rdataset_t *sigrdataset,
+ bool ignoretime, isc_mem_t *mctx) {
+ INSIST(rdataset->type == dns_rdatatype_key ||
+ rdataset->type == dns_rdatatype_dnskey);
+ if (rdataset->type == dns_rdatatype_key) {
+ INSIST(sigrdataset->type == dns_rdatatype_sig);
+ INSIST(sigrdataset->covers == dns_rdatatype_key);
+ } else {
+ INSIST(sigrdataset->type == dns_rdatatype_rrsig);
+ INSIST(sigrdataset->covers == dns_rdatatype_dnskey);
+ }
+
+ return (dns_dnssec_signs(rdata, name, rdataset, sigrdataset, ignoretime,
+ mctx));
+}
+
+bool
+dns_dnssec_signs(dns_rdata_t *rdata, const dns_name_t *name,
+ dns_rdataset_t *rdataset, dns_rdataset_t *sigrdataset,
+ bool ignoretime, isc_mem_t *mctx) {
+ dst_key_t *dstkey = NULL;
+ dns_keytag_t keytag;
+ dns_rdata_dnskey_t key;
+ dns_rdata_rrsig_t sig;
+ dns_rdata_t sigrdata = DNS_RDATA_INIT;
+ isc_result_t result;
+
+ INSIST(sigrdataset->type == dns_rdatatype_rrsig);
+ if (sigrdataset->covers != rdataset->type) {
+ return (false);
+ }
+
+ result = dns_dnssec_keyfromrdata(name, rdata, mctx, &dstkey);
+ if (result != ISC_R_SUCCESS) {
+ return (false);
+ }
+ result = dns_rdata_tostruct(rdata, &key, NULL);
+ RUNTIME_CHECK(result == ISC_R_SUCCESS);
+
+ keytag = dst_key_id(dstkey);
+ for (result = dns_rdataset_first(sigrdataset); result == ISC_R_SUCCESS;
+ result = dns_rdataset_next(sigrdataset))
+ {
+ dns_rdata_reset(&sigrdata);
+ dns_rdataset_current(sigrdataset, &sigrdata);
+ result = dns_rdata_tostruct(&sigrdata, &sig, NULL);
+ RUNTIME_CHECK(result == ISC_R_SUCCESS);
+
+ if (sig.algorithm == key.algorithm && sig.keyid == keytag) {
+ result = dns_dnssec_verify(name, rdataset, dstkey,
+ ignoretime, 0, mctx,
+ &sigrdata, NULL);
+ if (result == ISC_R_SUCCESS) {
+ dst_key_free(&dstkey);
+ return (true);
+ }
+ }
+ }
+ dst_key_free(&dstkey);
+ return (false);
+}
+
+isc_result_t
+dns_dnsseckey_create(isc_mem_t *mctx, dst_key_t **dstkey,
+ dns_dnsseckey_t **dkp) {
+ isc_result_t result;
+ dns_dnsseckey_t *dk;
+ int major, minor;
+
+ REQUIRE(dkp != NULL && *dkp == NULL);
+ dk = isc_mem_get(mctx, sizeof(dns_dnsseckey_t));
+
+ dk->key = *dstkey;
+ *dstkey = NULL;
+ dk->force_publish = false;
+ dk->force_sign = false;
+ dk->hint_publish = false;
+ dk->hint_sign = false;
+ dk->hint_revoke = false;
+ dk->hint_remove = false;
+ dk->first_sign = false;
+ dk->is_active = false;
+ dk->purge = false;
+ dk->prepublish = 0;
+ dk->source = dns_keysource_unknown;
+ dk->index = 0;
+
+ /* KSK or ZSK? */
+ result = dst_key_getbool(dk->key, DST_BOOL_KSK, &dk->ksk);
+ if (result != ISC_R_SUCCESS) {
+ dk->ksk = ((dst_key_flags(dk->key) & DNS_KEYFLAG_KSK) != 0);
+ }
+ result = dst_key_getbool(dk->key, DST_BOOL_ZSK, &dk->zsk);
+ if (result != ISC_R_SUCCESS) {
+ dk->zsk = ((dst_key_flags(dk->key) & DNS_KEYFLAG_KSK) == 0);
+ }
+
+ /* Is this an old-style key? */
+ result = dst_key_getprivateformat(dk->key, &major, &minor);
+ INSIST(result == ISC_R_SUCCESS);
+
+ /* Smart signing started with key format 1.3 */
+ dk->legacy = (major == 1 && minor <= 2);
+
+ ISC_LINK_INIT(dk, link);
+ *dkp = dk;
+ return (ISC_R_SUCCESS);
+}
+
+void
+dns_dnsseckey_destroy(isc_mem_t *mctx, dns_dnsseckey_t **dkp) {
+ dns_dnsseckey_t *dk;
+
+ REQUIRE(dkp != NULL && *dkp != NULL);
+ dk = *dkp;
+ *dkp = NULL;
+ if (dk->key != NULL) {
+ dst_key_free(&dk->key);
+ }
+ isc_mem_put(mctx, dk, sizeof(dns_dnsseckey_t));
+}
+
+void
+dns_dnssec_get_hints(dns_dnsseckey_t *key, isc_stdtime_t now) {
+ isc_stdtime_t publish = 0, active = 0, revoke = 0, remove = 0;
+
+ REQUIRE(key != NULL && key->key != NULL);
+
+ key->hint_publish = dst_key_is_published(key->key, now, &publish);
+ key->hint_sign = dst_key_is_signing(key->key, DST_BOOL_ZSK, now,
+ &active);
+ key->hint_revoke = dst_key_is_revoked(key->key, now, &revoke);
+ key->hint_remove = dst_key_is_removed(key->key, now, &remove);
+
+ /*
+ * Activation date is set (maybe in the future), but publication date
+ * isn't. Most likely the user wants to publish now and activate later.
+ * Most likely because this is true for most rollovers, except for:
+ * 1. The unpopular ZSK Double-RRSIG method.
+ * 2. When introducing a new algorithm.
+ * These two cases are rare enough that we will set hint_publish
+ * anyway when hint_sign is set, because BIND 9 natively does not
+ * support the ZSK Double-RRSIG method, and when introducing a new
+ * algorithm, we strive to publish its signatures and DNSKEY records
+ * at the same time.
+ */
+ if (key->hint_sign && publish == 0) {
+ key->hint_publish = true;
+ }
+
+ /*
+ * If activation date is in the future, make note of how far off.
+ */
+ if (key->hint_publish && active > now) {
+ key->prepublish = active - now;
+ }
+
+ /*
+ * Metadata says revoke. If the key is published, we *have to* sign
+ * with it per RFC5011 -- even if it was not active before.
+ *
+ * If it hasn't already been done, we should also revoke it now.
+ */
+ if (key->hint_publish && key->hint_revoke) {
+ uint32_t flags;
+ key->hint_sign = true;
+ flags = dst_key_flags(key->key);
+ if ((flags & DNS_KEYFLAG_REVOKE) == 0) {
+ flags |= DNS_KEYFLAG_REVOKE;
+ dst_key_setflags(key->key, flags);
+ }
+ }
+
+ /*
+ * Metadata says delete, so don't publish this key or sign with it
+ * (note that signatures of a removed key may still be reused).
+ */
+ if (key->hint_remove) {
+ key->hint_publish = false;
+ key->hint_sign = false;
+ }
+}
+
+/*%
+ * Get a list of DNSSEC keys from the key repository.
+ */
+isc_result_t
+dns_dnssec_findmatchingkeys(const dns_name_t *origin, const char *directory,
+ isc_stdtime_t now, isc_mem_t *mctx,
+ dns_dnsseckeylist_t *keylist) {
+ isc_result_t result = ISC_R_SUCCESS;
+ bool dir_open = false;
+ dns_dnsseckeylist_t list;
+ isc_dir_t dir;
+ dns_dnsseckey_t *key = NULL;
+ dst_key_t *dstkey = NULL;
+ char namebuf[DNS_NAME_FORMATSIZE];
+ isc_buffer_t b;
+ unsigned int len, i, alg;
+
+ REQUIRE(keylist != NULL);
+ ISC_LIST_INIT(list);
+ isc_dir_init(&dir);
+
+ isc_buffer_init(&b, namebuf, sizeof(namebuf) - 1);
+ RETERR(dns_name_tofilenametext(origin, false, &b));
+ len = isc_buffer_usedlength(&b);
+ namebuf[len] = '\0';
+
+ if (directory == NULL) {
+ directory = ".";
+ }
+ RETERR(isc_dir_open(&dir, directory));
+ dir_open = true;
+
+ while (isc_dir_read(&dir) == ISC_R_SUCCESS) {
+ if (dir.entry.name[0] != 'K' || dir.entry.length < len + 1 ||
+ dir.entry.name[len + 1] != '+' ||
+ strncasecmp(dir.entry.name + 1, namebuf, len) != 0)
+ {
+ continue;
+ }
+
+ alg = 0;
+ for (i = len + 1 + 1; i < dir.entry.length; i++) {
+ if (!isdigit((unsigned char)dir.entry.name[i])) {
+ break;
+ }
+ alg *= 10;
+ alg += dir.entry.name[i] - '0';
+ }
+
+ /*
+ * Did we not read exactly 3 digits?
+ * Did we overflow?
+ * Did we correctly terminate?
+ */
+ if (i != len + 1 + 1 + 3 || i >= dir.entry.length ||
+ dir.entry.name[i] != '+')
+ {
+ continue;
+ }
+
+ for (i++; i < dir.entry.length; i++) {
+ if (!isdigit((unsigned char)dir.entry.name[i])) {
+ break;
+ }
+ }
+
+ /*
+ * Did we not read exactly 5 more digits?
+ * Did we overflow?
+ * Did we correctly terminate?
+ */
+ if (i != len + 1 + 1 + 3 + 1 + 5 || i >= dir.entry.length ||
+ strcmp(dir.entry.name + i, ".private") != 0)
+ {
+ continue;
+ }
+
+ dstkey = NULL;
+ result = dst_key_fromnamedfile(
+ dir.entry.name, directory,
+ DST_TYPE_PUBLIC | DST_TYPE_PRIVATE | DST_TYPE_STATE,
+ mctx, &dstkey);
+
+ switch (alg) {
+ case DST_ALG_HMACMD5:
+ case DST_ALG_HMACSHA1:
+ case DST_ALG_HMACSHA224:
+ case DST_ALG_HMACSHA256:
+ case DST_ALG_HMACSHA384:
+ case DST_ALG_HMACSHA512:
+ case DST_ALG_DH:
+ if (result == DST_R_BADKEYTYPE) {
+ continue;
+ }
+ }
+
+ if (result != ISC_R_SUCCESS) {
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL,
+ DNS_LOGMODULE_DNSSEC, ISC_LOG_WARNING,
+ "dns_dnssec_findmatchingkeys: "
+ "error reading key file %s: %s",
+ dir.entry.name,
+ isc_result_totext(result));
+ continue;
+ }
+
+ RETERR(dns_dnsseckey_create(mctx, &dstkey, &key));
+ key->source = dns_keysource_repository;
+ dns_dnssec_get_hints(key, now);
+
+ if (key->legacy) {
+ dns_dnsseckey_destroy(mctx, &key);
+ } else {
+ ISC_LIST_APPEND(list, key, link);
+ key = NULL;
+ }
+ }
+
+ if (!ISC_LIST_EMPTY(list)) {
+ result = ISC_R_SUCCESS;
+ ISC_LIST_APPENDLIST(*keylist, list, link);
+ } else {
+ result = ISC_R_NOTFOUND;
+ }
+
+failure:
+ if (dir_open) {
+ isc_dir_close(&dir);
+ }
+ INSIST(key == NULL);
+ while ((key = ISC_LIST_HEAD(list)) != NULL) {
+ ISC_LIST_UNLINK(list, key, link);
+ INSIST(key->key != NULL);
+ dst_key_free(&key->key);
+ dns_dnsseckey_destroy(mctx, &key);
+ }
+ if (dstkey != NULL) {
+ dst_key_free(&dstkey);
+ }
+ return (result);
+}
+
+/*%
+ * Add 'newkey' to 'keylist' if it's not already there.
+ *
+ * If 'savekeys' is true, then we need to preserve all
+ * the keys in the keyset, regardless of whether they have
+ * metadata indicating they should be deactivated or removed.
+ */
+static isc_result_t
+addkey(dns_dnsseckeylist_t *keylist, dst_key_t **newkey, bool savekeys,
+ isc_mem_t *mctx) {
+ dns_dnsseckey_t *key;
+ isc_result_t result;
+
+ /* Skip duplicates */
+ for (key = ISC_LIST_HEAD(*keylist); key != NULL;
+ key = ISC_LIST_NEXT(key, link))
+ {
+ if (dst_key_id(key->key) == dst_key_id(*newkey) &&
+ dst_key_alg(key->key) == dst_key_alg(*newkey) &&
+ dns_name_equal(dst_key_name(key->key),
+ dst_key_name(*newkey)))
+ {
+ break;
+ }
+ }
+
+ if (key != NULL) {
+ /*
+ * Found a match. If the old key was only public and the
+ * new key is private, replace the old one; otherwise
+ * leave it. But either way, mark the key as having
+ * been found in the zone.
+ */
+ if (dst_key_isprivate(key->key)) {
+ dst_key_free(newkey);
+ } else if (dst_key_isprivate(*newkey)) {
+ dst_key_free(&key->key);
+ key->key = *newkey;
+ }
+
+ key->source = dns_keysource_zoneapex;
+ return (ISC_R_SUCCESS);
+ }
+
+ result = dns_dnsseckey_create(mctx, newkey, &key);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+ if (key->legacy || savekeys) {
+ key->force_publish = true;
+ key->force_sign = dst_key_isprivate(key->key);
+ }
+ key->source = dns_keysource_zoneapex;
+ ISC_LIST_APPEND(*keylist, key, link);
+ *newkey = NULL;
+ return (ISC_R_SUCCESS);
+}
+
+/*%
+ * Mark all keys which signed the DNSKEY/SOA RRsets as "active",
+ * for future reference.
+ */
+static isc_result_t
+mark_active_keys(dns_dnsseckeylist_t *keylist, dns_rdataset_t *rrsigs) {
+ isc_result_t result = ISC_R_SUCCESS;
+ dns_rdata_t rdata = DNS_RDATA_INIT;
+ dns_rdataset_t sigs;
+ dns_dnsseckey_t *key;
+
+ REQUIRE(rrsigs != NULL && dns_rdataset_isassociated(rrsigs));
+
+ dns_rdataset_init(&sigs);
+ dns_rdataset_clone(rrsigs, &sigs);
+ for (key = ISC_LIST_HEAD(*keylist); key != NULL;
+ key = ISC_LIST_NEXT(key, link))
+ {
+ uint16_t keyid, sigid;
+ dns_secalg_t keyalg, sigalg;
+ keyid = dst_key_id(key->key);
+ keyalg = dst_key_alg(key->key);
+
+ for (result = dns_rdataset_first(&sigs);
+ result == ISC_R_SUCCESS; result = dns_rdataset_next(&sigs))
+ {
+ dns_rdata_rrsig_t sig;
+
+ dns_rdata_reset(&rdata);
+ dns_rdataset_current(&sigs, &rdata);
+ result = dns_rdata_tostruct(&rdata, &sig, NULL);
+ RUNTIME_CHECK(result == ISC_R_SUCCESS);
+ sigalg = sig.algorithm;
+ sigid = sig.keyid;
+ if (keyid == sigid && keyalg == sigalg) {
+ key->is_active = true;
+ break;
+ }
+ }
+ }
+
+ if (result == ISC_R_NOMORE) {
+ result = ISC_R_SUCCESS;
+ }
+
+ if (dns_rdataset_isassociated(&sigs)) {
+ dns_rdataset_disassociate(&sigs);
+ }
+ return (result);
+}
+
+/*%
+ * Add the contents of a DNSKEY rdataset 'keyset' to 'keylist'.
+ */
+isc_result_t
+dns_dnssec_keylistfromrdataset(const dns_name_t *origin, const char *directory,
+ isc_mem_t *mctx, dns_rdataset_t *keyset,
+ dns_rdataset_t *keysigs, dns_rdataset_t *soasigs,
+ bool savekeys, bool publickey,
+ dns_dnsseckeylist_t *keylist) {
+ dns_rdataset_t keys;
+ dns_rdata_t rdata = DNS_RDATA_INIT;
+ dst_key_t *dnskey = NULL, *pubkey = NULL, *privkey = NULL;
+ isc_result_t result;
+
+ REQUIRE(keyset != NULL && dns_rdataset_isassociated(keyset));
+
+ dns_rdataset_init(&keys);
+
+ dns_rdataset_clone(keyset, &keys);
+ for (result = dns_rdataset_first(&keys); result == ISC_R_SUCCESS;
+ result = dns_rdataset_next(&keys))
+ {
+ dns_rdata_reset(&rdata);
+ dns_rdataset_current(&keys, &rdata);
+
+ REQUIRE(rdata.type == dns_rdatatype_key ||
+ rdata.type == dns_rdatatype_dnskey);
+ REQUIRE(rdata.length > 3);
+
+ /* Skip unsupported algorithms */
+ if (!dst_algorithm_supported(rdata.data[3])) {
+ goto skip;
+ }
+
+ RETERR(dns_dnssec_keyfromrdata(origin, &rdata, mctx, &dnskey));
+ dst_key_setttl(dnskey, keys.ttl);
+
+ if (!is_zone_key(dnskey) ||
+ (dst_key_flags(dnskey) & DNS_KEYTYPE_NOAUTH) != 0)
+ {
+ goto skip;
+ }
+
+ /* Corrupted .key file? */
+ if (!dns_name_equal(origin, dst_key_name(dnskey))) {
+ goto skip;
+ }
+
+ if (publickey) {
+ RETERR(addkey(keylist, &dnskey, savekeys, mctx));
+ goto skip;
+ }
+
+ /* Try to read the public key. */
+ result = dst_key_fromfile(
+ dst_key_name(dnskey), dst_key_id(dnskey),
+ dst_key_alg(dnskey), (DST_TYPE_PUBLIC | DST_TYPE_STATE),
+ directory, mctx, &pubkey);
+ if (result == ISC_R_FILENOTFOUND || result == ISC_R_NOPERM) {
+ result = ISC_R_SUCCESS;
+ }
+ RETERR(result);
+
+ /* Now read the private key. */
+ result = dst_key_fromfile(
+ dst_key_name(dnskey), dst_key_id(dnskey),
+ dst_key_alg(dnskey),
+ (DST_TYPE_PUBLIC | DST_TYPE_PRIVATE | DST_TYPE_STATE),
+ directory, mctx, &privkey);
+
+ /*
+ * If the key was revoked and the private file
+ * doesn't exist, maybe it was revoked internally
+ * by named. Try loading the unrevoked version.
+ */
+ if (result == ISC_R_FILENOTFOUND) {
+ uint32_t flags;
+ flags = dst_key_flags(dnskey);
+ if ((flags & DNS_KEYFLAG_REVOKE) != 0) {
+ dst_key_setflags(dnskey,
+ flags & ~DNS_KEYFLAG_REVOKE);
+ result = dst_key_fromfile(
+ dst_key_name(dnskey),
+ dst_key_id(dnskey), dst_key_alg(dnskey),
+ (DST_TYPE_PUBLIC | DST_TYPE_PRIVATE |
+ DST_TYPE_STATE),
+ directory, mctx, &privkey);
+ if (result == ISC_R_SUCCESS &&
+ dst_key_pubcompare(dnskey, privkey, false))
+ {
+ dst_key_setflags(privkey, flags);
+ }
+ dst_key_setflags(dnskey, flags);
+ }
+ }
+
+ if (result != ISC_R_SUCCESS) {
+ char filename[DNS_NAME_FORMATSIZE +
+ DNS_SECALG_FORMATSIZE +
+ sizeof("key file for //65535")];
+ isc_result_t result2;
+ isc_buffer_t buf;
+
+ isc_buffer_init(&buf, filename, NAME_MAX);
+ result2 = dst_key_getfilename(
+ dst_key_name(dnskey), dst_key_id(dnskey),
+ dst_key_alg(dnskey),
+ (DST_TYPE_PUBLIC | DST_TYPE_PRIVATE |
+ DST_TYPE_STATE),
+ directory, mctx, &buf);
+ if (result2 != ISC_R_SUCCESS) {
+ char namebuf[DNS_NAME_FORMATSIZE];
+ char algbuf[DNS_SECALG_FORMATSIZE];
+
+ dns_name_format(dst_key_name(dnskey), namebuf,
+ sizeof(namebuf));
+ dns_secalg_format(dst_key_alg(dnskey), algbuf,
+ sizeof(algbuf));
+ snprintf(filename, sizeof(filename) - 1,
+ "key file for %s/%s/%d", namebuf,
+ algbuf, dst_key_id(dnskey));
+ }
+
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL,
+ DNS_LOGMODULE_DNSSEC, ISC_LOG_WARNING,
+ "dns_dnssec_keylistfromrdataset: error "
+ "reading %s: %s",
+ filename, isc_result_totext(result));
+ }
+
+ if (result == ISC_R_FILENOTFOUND || result == ISC_R_NOPERM) {
+ if (pubkey != NULL) {
+ RETERR(addkey(keylist, &pubkey, savekeys,
+ mctx));
+ } else {
+ RETERR(addkey(keylist, &dnskey, savekeys,
+ mctx));
+ }
+ goto skip;
+ }
+ RETERR(result);
+
+ /* This should never happen. */
+ if ((dst_key_flags(privkey) & DNS_KEYTYPE_NOAUTH) != 0) {
+ goto skip;
+ }
+
+ /*
+ * Whatever the key's default TTL may have
+ * been, the rdataset TTL takes priority.
+ */
+ dst_key_setttl(privkey, dst_key_getttl(dnskey));
+
+ RETERR(addkey(keylist, &privkey, savekeys, mctx));
+ skip:
+ if (dnskey != NULL) {
+ dst_key_free(&dnskey);
+ }
+ if (pubkey != NULL) {
+ dst_key_free(&pubkey);
+ }
+ if (privkey != NULL) {
+ dst_key_free(&privkey);
+ }
+ }
+
+ if (result != ISC_R_NOMORE) {
+ RETERR(result);
+ }
+
+ if (keysigs != NULL && dns_rdataset_isassociated(keysigs)) {
+ RETERR(mark_active_keys(keylist, keysigs));
+ }
+
+ if (soasigs != NULL && dns_rdataset_isassociated(soasigs)) {
+ RETERR(mark_active_keys(keylist, soasigs));
+ }
+
+ result = ISC_R_SUCCESS;
+
+failure:
+ if (dns_rdataset_isassociated(&keys)) {
+ dns_rdataset_disassociate(&keys);
+ }
+ if (dnskey != NULL) {
+ dst_key_free(&dnskey);
+ }
+ if (pubkey != NULL) {
+ dst_key_free(&pubkey);
+ }
+ if (privkey != NULL) {
+ dst_key_free(&privkey);
+ }
+ return (result);
+}
+
+static isc_result_t
+make_dnskey(dst_key_t *key, unsigned char *buf, int bufsize,
+ dns_rdata_t *target) {
+ isc_result_t result;
+ isc_buffer_t b;
+ isc_region_t r;
+
+ isc_buffer_init(&b, buf, bufsize);
+ result = dst_key_todns(key, &b);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+
+ dns_rdata_reset(target);
+ isc_buffer_usedregion(&b, &r);
+ dns_rdata_fromregion(target, dst_key_class(key), dns_rdatatype_dnskey,
+ &r);
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+addrdata(dns_rdata_t *rdata, dns_diff_t *diff, const dns_name_t *origin,
+ dns_ttl_t ttl, isc_mem_t *mctx) {
+ isc_result_t result;
+ dns_difftuple_t *tuple = NULL;
+
+ RETERR(dns_difftuple_create(mctx, DNS_DIFFOP_ADD, origin, ttl, rdata,
+ &tuple));
+ dns_diff_appendminimal(diff, &tuple);
+
+failure:
+ return (result);
+}
+
+static isc_result_t
+delrdata(dns_rdata_t *rdata, dns_diff_t *diff, const dns_name_t *origin,
+ dns_ttl_t ttl, isc_mem_t *mctx) {
+ isc_result_t result;
+ dns_difftuple_t *tuple = NULL;
+
+ RETERR(dns_difftuple_create(mctx, DNS_DIFFOP_DEL, origin, ttl, rdata,
+ &tuple));
+ dns_diff_appendminimal(diff, &tuple);
+
+failure:
+ return (result);
+}
+
+static isc_result_t
+publish_key(dns_diff_t *diff, dns_dnsseckey_t *key, const dns_name_t *origin,
+ dns_ttl_t ttl, isc_mem_t *mctx,
+ void (*report)(const char *, ...) ISC_FORMAT_PRINTF(1, 2)) {
+ isc_result_t result;
+ unsigned char buf[DST_KEY_MAXSIZE];
+ char keystr[DST_KEY_FORMATSIZE];
+ dns_rdata_t dnskey = DNS_RDATA_INIT;
+
+ dns_rdata_reset(&dnskey);
+ RETERR(make_dnskey(key->key, buf, sizeof(buf), &dnskey));
+ dst_key_format(key->key, keystr, sizeof(keystr));
+
+ report("Fetching %s (%s) from key %s.", keystr,
+ key->ksk ? (key->zsk ? "CSK" : "KSK") : "ZSK",
+ key->source == dns_keysource_user ? "file" : "repository");
+
+ if (key->prepublish && ttl > key->prepublish) {
+ isc_stdtime_t now;
+
+ report("Key %s: Delaying activation to match the DNSKEY TTL "
+ "(%u).",
+ keystr, ttl);
+
+ isc_stdtime_get(&now);
+ dst_key_settime(key->key, DST_TIME_ACTIVATE, now + ttl);
+ }
+
+ /* publish key */
+ result = addrdata(&dnskey, diff, origin, ttl, mctx);
+
+failure:
+ return (result);
+}
+
+static isc_result_t
+remove_key(dns_diff_t *diff, dns_dnsseckey_t *key, const dns_name_t *origin,
+ dns_ttl_t ttl, isc_mem_t *mctx, const char *reason,
+ void (*report)(const char *, ...) ISC_FORMAT_PRINTF(1, 2)) {
+ isc_result_t result;
+ unsigned char buf[DST_KEY_MAXSIZE];
+ dns_rdata_t dnskey = DNS_RDATA_INIT;
+ char alg[80];
+ char namebuf[DNS_NAME_FORMATSIZE];
+
+ dns_secalg_format(dst_key_alg(key->key), alg, sizeof(alg));
+ dns_name_format(dst_key_name(key->key), namebuf, sizeof(namebuf));
+ report("Removing %s key %s/%d/%s from DNSKEY RRset.", reason, namebuf,
+ dst_key_id(key->key), alg);
+
+ RETERR(make_dnskey(key->key, buf, sizeof(buf), &dnskey));
+ result = delrdata(&dnskey, diff, origin, ttl, mctx);
+
+failure:
+ return (result);
+}
+
+static bool
+exists(dns_rdataset_t *rdataset, dns_rdata_t *rdata) {
+ isc_result_t result;
+ dns_rdataset_t trdataset;
+
+ dns_rdataset_init(&trdataset);
+ dns_rdataset_clone(rdataset, &trdataset);
+ for (result = dns_rdataset_first(&trdataset); result == ISC_R_SUCCESS;
+ result = dns_rdataset_next(&trdataset))
+ {
+ dns_rdata_t current = DNS_RDATA_INIT;
+
+ dns_rdataset_current(&trdataset, &current);
+ if (dns_rdata_compare(rdata, &current) == 0) {
+ dns_rdataset_disassociate(&trdataset);
+ return (true);
+ }
+ }
+ dns_rdataset_disassociate(&trdataset);
+ return (false);
+}
+
+isc_result_t
+dns_dnssec_syncupdate(dns_dnsseckeylist_t *keys, dns_dnsseckeylist_t *rmkeys,
+ dns_rdataset_t *cds, dns_rdataset_t *cdnskey,
+ isc_stdtime_t now, dns_ttl_t ttl, dns_diff_t *diff,
+ isc_mem_t *mctx) {
+ unsigned char dsbuf1[DNS_DS_BUFFERSIZE];
+ unsigned char dsbuf2[DNS_DS_BUFFERSIZE];
+ unsigned char keybuf[DST_KEY_MAXSIZE];
+ isc_result_t result;
+ dns_dnsseckey_t *key;
+
+ for (key = ISC_LIST_HEAD(*keys); key != NULL;
+ key = ISC_LIST_NEXT(key, link))
+ {
+ dns_rdata_t cds_sha1 = DNS_RDATA_INIT;
+ dns_rdata_t cds_sha256 = DNS_RDATA_INIT;
+ dns_rdata_t cdnskeyrdata = DNS_RDATA_INIT;
+ dns_name_t *origin = dst_key_name(key->key);
+
+ RETERR(make_dnskey(key->key, keybuf, sizeof(keybuf),
+ &cdnskeyrdata));
+
+ /*
+ * We construct the SHA-1 version of the record so we can
+ * delete any old records generated by previous versions of
+ * BIND. We only add SHA-256 records.
+ *
+ * XXXMPA we need to be able to specify the DS algorithms
+ * to be used here and below with rmkeys.
+ */
+ RETERR(dns_ds_buildrdata(origin, &cdnskeyrdata,
+ DNS_DSDIGEST_SHA1, dsbuf1, &cds_sha1));
+ RETERR(dns_ds_buildrdata(origin, &cdnskeyrdata,
+ DNS_DSDIGEST_SHA256, dsbuf2,
+ &cds_sha256));
+
+ /*
+ * Now that the we have created the DS records convert
+ * the rdata to CDNSKEY and CDS for comparison.
+ */
+ cdnskeyrdata.type = dns_rdatatype_cdnskey;
+ cds_sha1.type = dns_rdatatype_cds;
+ cds_sha256.type = dns_rdatatype_cds;
+
+ if (syncpublish(key->key, now)) {
+ char keystr[DST_KEY_FORMATSIZE];
+ dst_key_format(key->key, keystr, sizeof(keystr));
+
+ if (!dns_rdataset_isassociated(cdnskey) ||
+ !exists(cdnskey, &cdnskeyrdata))
+ {
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL,
+ DNS_LOGMODULE_DNSSEC,
+ ISC_LOG_INFO,
+ "CDS for key %s is now published",
+ keystr);
+ RETERR(addrdata(&cdnskeyrdata, diff, origin,
+ ttl, mctx));
+ }
+ /* Only publish SHA-256 (SHA-1 is deprecated) */
+ if (!dns_rdataset_isassociated(cds) ||
+ !exists(cds, &cds_sha256))
+ {
+ isc_log_write(
+ dns_lctx, DNS_LOGCATEGORY_GENERAL,
+ DNS_LOGMODULE_DNSSEC, ISC_LOG_INFO,
+ "CDNSKEY for key %s is now published",
+ keystr);
+ RETERR(addrdata(&cds_sha256, diff, origin, ttl,
+ mctx));
+ }
+ }
+
+ if (syncdelete(key->key, now)) {
+ char keystr[DST_KEY_FORMATSIZE];
+ dst_key_format(key->key, keystr, sizeof(keystr));
+
+ if (dns_rdataset_isassociated(cds)) {
+ /* Delete both SHA-1 and SHA-256 */
+ if (exists(cds, &cds_sha1)) {
+ isc_log_write(dns_lctx,
+ DNS_LOGCATEGORY_GENERAL,
+ DNS_LOGMODULE_DNSSEC,
+ ISC_LOG_INFO,
+ "CDS (SHA-1) for key %s "
+ "is now deleted",
+ keystr);
+ RETERR(delrdata(&cds_sha1, diff, origin,
+ cds->ttl, mctx));
+ }
+ if (exists(cds, &cds_sha256)) {
+ isc_log_write(dns_lctx,
+ DNS_LOGCATEGORY_GENERAL,
+ DNS_LOGMODULE_DNSSEC,
+ ISC_LOG_INFO,
+ "CDS (SHA-256) for key "
+ "%s is now deleted",
+ keystr);
+ RETERR(delrdata(&cds_sha256, diff,
+ origin, cds->ttl,
+ mctx));
+ }
+ }
+
+ if (dns_rdataset_isassociated(cdnskey)) {
+ if (exists(cdnskey, &cdnskeyrdata)) {
+ isc_log_write(dns_lctx,
+ DNS_LOGCATEGORY_GENERAL,
+ DNS_LOGMODULE_DNSSEC,
+ ISC_LOG_INFO,
+ "CDNSKEY for key %s is "
+ "now deleted",
+ keystr);
+ RETERR(delrdata(&cdnskeyrdata, diff,
+ origin, cdnskey->ttl,
+ mctx));
+ }
+ }
+ }
+ }
+
+ if (!dns_rdataset_isassociated(cds) &&
+ !dns_rdataset_isassociated(cdnskey))
+ {
+ return (ISC_R_SUCCESS);
+ }
+
+ /*
+ * Unconditionally remove CDS/DNSKEY records for removed keys.
+ */
+ for (key = ISC_LIST_HEAD(*rmkeys); key != NULL;
+ key = ISC_LIST_NEXT(key, link))
+ {
+ dns_rdata_t cds_sha1 = DNS_RDATA_INIT;
+ dns_rdata_t cds_sha256 = DNS_RDATA_INIT;
+ dns_rdata_t cdnskeyrdata = DNS_RDATA_INIT;
+ dns_name_t *origin = dst_key_name(key->key);
+
+ char keystr[DST_KEY_FORMATSIZE];
+ dst_key_format(key->key, keystr, sizeof(keystr));
+
+ RETERR(make_dnskey(key->key, keybuf, sizeof(keybuf),
+ &cdnskeyrdata));
+
+ if (dns_rdataset_isassociated(cds)) {
+ RETERR(dns_ds_buildrdata(origin, &cdnskeyrdata,
+ DNS_DSDIGEST_SHA1, dsbuf1,
+ &cds_sha1));
+ RETERR(dns_ds_buildrdata(origin, &cdnskeyrdata,
+ DNS_DSDIGEST_SHA256, dsbuf2,
+ &cds_sha256));
+ if (exists(cds, &cds_sha1)) {
+ isc_log_write(
+ dns_lctx, DNS_LOGCATEGORY_GENERAL,
+ DNS_LOGMODULE_DNSSEC, ISC_LOG_INFO,
+ "CDS (SHA-1) for key %s is now deleted",
+ keystr);
+ RETERR(delrdata(&cds_sha1, diff, origin,
+ cds->ttl, mctx));
+ }
+ if (exists(cds, &cds_sha256)) {
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL,
+ DNS_LOGMODULE_DNSSEC,
+ ISC_LOG_INFO,
+ "CDS (SHA-256) for key %s is now "
+ "deleted",
+ keystr);
+ RETERR(delrdata(&cds_sha256, diff, origin,
+ cds->ttl, mctx));
+ }
+ }
+
+ if (dns_rdataset_isassociated(cdnskey)) {
+ if (exists(cdnskey, &cdnskeyrdata)) {
+ isc_log_write(
+ dns_lctx, DNS_LOGCATEGORY_GENERAL,
+ DNS_LOGMODULE_DNSSEC, ISC_LOG_INFO,
+ "CDNSKEY for key %s is now deleted",
+ keystr);
+ RETERR(delrdata(&cdnskeyrdata, diff, origin,
+ cdnskey->ttl, mctx));
+ }
+ }
+ }
+
+ result = ISC_R_SUCCESS;
+
+failure:
+ return (result);
+}
+
+isc_result_t
+dns_dnssec_syncdelete(dns_rdataset_t *cds, dns_rdataset_t *cdnskey,
+ dns_name_t *origin, dns_rdataclass_t zclass,
+ dns_ttl_t ttl, dns_diff_t *diff, isc_mem_t *mctx,
+ bool expect_cds_delete, bool expect_cdnskey_delete) {
+ unsigned char dsbuf[5] = { 0, 0, 0, 0, 0 }; /* CDS DELETE rdata */
+ unsigned char keybuf[5] = { 0, 0, 3, 0, 0 }; /* CDNSKEY DELETE rdata */
+ char namebuf[DNS_NAME_FORMATSIZE];
+ dns_rdata_t cds_delete = DNS_RDATA_INIT;
+ dns_rdata_t cdnskey_delete = DNS_RDATA_INIT;
+ isc_region_t r;
+ isc_result_t result;
+
+ r.base = keybuf;
+ r.length = sizeof(keybuf);
+ dns_rdata_fromregion(&cdnskey_delete, zclass, dns_rdatatype_cdnskey,
+ &r);
+
+ r.base = dsbuf;
+ r.length = sizeof(dsbuf);
+ dns_rdata_fromregion(&cds_delete, zclass, dns_rdatatype_cds, &r);
+
+ dns_name_format(origin, namebuf, sizeof(namebuf));
+
+ if (expect_cds_delete) {
+ if (!dns_rdataset_isassociated(cds) ||
+ !exists(cds, &cds_delete))
+ {
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL,
+ DNS_LOGMODULE_DNSSEC, ISC_LOG_INFO,
+ "CDS (DELETE) for zone %s is now "
+ "published",
+ namebuf);
+ RETERR(addrdata(&cds_delete, diff, origin, ttl, mctx));
+ }
+ } else {
+ if (dns_rdataset_isassociated(cds) && exists(cds, &cds_delete))
+ {
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL,
+ DNS_LOGMODULE_DNSSEC, ISC_LOG_INFO,
+ "CDS (DELETE) for zone %s is now "
+ "deleted",
+ namebuf);
+ RETERR(delrdata(&cds_delete, diff, origin, cds->ttl,
+ mctx));
+ }
+ }
+
+ if (expect_cdnskey_delete) {
+ if (!dns_rdataset_isassociated(cdnskey) ||
+ !exists(cdnskey, &cdnskey_delete))
+ {
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL,
+ DNS_LOGMODULE_DNSSEC, ISC_LOG_INFO,
+ "CDNSKEY (DELETE) for zone %s is now "
+ "published",
+ namebuf);
+ RETERR(addrdata(&cdnskey_delete, diff, origin, ttl,
+ mctx));
+ }
+ } else {
+ if (dns_rdataset_isassociated(cdnskey) &&
+ exists(cdnskey, &cdnskey_delete))
+ {
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL,
+ DNS_LOGMODULE_DNSSEC, ISC_LOG_INFO,
+ "CDNSKEY (DELETE) for zone %s is now "
+ "deleted",
+ namebuf);
+ RETERR(delrdata(&cdnskey_delete, diff, origin,
+ cdnskey->ttl, mctx));
+ }
+ }
+
+ result = ISC_R_SUCCESS;
+
+failure:
+ return (result);
+}
+
+/*
+ * Update 'keys' with information from 'newkeys'.
+ *
+ * If 'removed' is not NULL, any keys that are being removed from
+ * the zone will be added to the list for post-removal processing.
+ */
+isc_result_t
+dns_dnssec_updatekeys(dns_dnsseckeylist_t *keys, dns_dnsseckeylist_t *newkeys,
+ dns_dnsseckeylist_t *removed, const dns_name_t *origin,
+ dns_ttl_t hint_ttl, dns_diff_t *diff, isc_mem_t *mctx,
+ void (*report)(const char *, ...)
+ ISC_FORMAT_PRINTF(1, 2)) {
+ isc_result_t result;
+ dns_dnsseckey_t *key, *key1, *key2, *next;
+ bool found_ttl = false;
+ dns_ttl_t ttl = hint_ttl;
+
+ /*
+ * First, look through the existing key list to find keys
+ * supplied from the command line which are not in the zone.
+ * Update the zone to include them.
+ *
+ * Also, if there are keys published in the zone already,
+ * use their TTL for all subsequent published keys.
+ */
+ for (key = ISC_LIST_HEAD(*keys); key != NULL;
+ key = ISC_LIST_NEXT(key, link))
+ {
+ if (key->source == dns_keysource_user &&
+ (key->hint_publish || key->force_publish))
+ {
+ RETERR(publish_key(diff, key, origin, ttl, mctx,
+ report));
+ }
+ if (key->source == dns_keysource_zoneapex) {
+ ttl = dst_key_getttl(key->key);
+ found_ttl = true;
+ }
+ }
+
+ /*
+ * If there were no existing keys, use the smallest nonzero
+ * TTL of the keys found in the repository.
+ */
+ if (!found_ttl && !ISC_LIST_EMPTY(*newkeys)) {
+ dns_ttl_t shortest = 0;
+
+ for (key = ISC_LIST_HEAD(*newkeys); key != NULL;
+ key = ISC_LIST_NEXT(key, link))
+ {
+ dns_ttl_t thisttl = dst_key_getttl(key->key);
+ if (thisttl != 0 &&
+ (shortest == 0 || thisttl < shortest))
+ {
+ shortest = thisttl;
+ }
+ }
+
+ if (shortest != 0) {
+ ttl = shortest;
+ }
+ }
+
+ /*
+ * Second, scan the list of newly found keys looking for matches
+ * with known keys, and update accordingly.
+ */
+ for (key1 = ISC_LIST_HEAD(*newkeys); key1 != NULL; key1 = next) {
+ bool key_revoked = false;
+ char keystr1[DST_KEY_FORMATSIZE];
+ char keystr2[DST_KEY_FORMATSIZE];
+
+ next = ISC_LIST_NEXT(key1, link);
+
+ for (key2 = ISC_LIST_HEAD(*keys); key2 != NULL;
+ key2 = ISC_LIST_NEXT(key2, link))
+ {
+ int f1 = dst_key_flags(key1->key);
+ int f2 = dst_key_flags(key2->key);
+ int nr1 = f1 & ~DNS_KEYFLAG_REVOKE;
+ int nr2 = f2 & ~DNS_KEYFLAG_REVOKE;
+ if (nr1 == nr2 &&
+ dst_key_alg(key1->key) == dst_key_alg(key2->key) &&
+ dst_key_pubcompare(key1->key, key2->key, true))
+ {
+ int r1, r2;
+ r1 = dst_key_flags(key1->key) &
+ DNS_KEYFLAG_REVOKE;
+ r2 = dst_key_flags(key2->key) &
+ DNS_KEYFLAG_REVOKE;
+ key_revoked = (r1 != r2);
+ break;
+ }
+ }
+
+ /* Printable version of key1 (the newly acquired key) */
+ dst_key_format(key1->key, keystr1, sizeof(keystr1));
+
+ /* No match found in keys; add the new key. */
+ if (key2 == NULL) {
+ ISC_LIST_UNLINK(*newkeys, key1, link);
+ ISC_LIST_APPEND(*keys, key1, link);
+
+ if (key1->source != dns_keysource_zoneapex &&
+ (key1->hint_publish || key1->force_publish))
+ {
+ RETERR(publish_key(diff, key1, origin, ttl,
+ mctx, report));
+ isc_log_write(
+ dns_lctx, DNS_LOGCATEGORY_DNSSEC,
+ DNS_LOGMODULE_DNSSEC, ISC_LOG_INFO,
+ "DNSKEY %s (%s) is now published",
+ keystr1,
+ key1->ksk ? (key1->zsk ? "CSK" : "KSK")
+ : "ZSK");
+ if (key1->hint_sign || key1->force_sign) {
+ key1->first_sign = true;
+ isc_log_write(
+ dns_lctx,
+ DNS_LOGCATEGORY_DNSSEC,
+ DNS_LOGMODULE_DNSSEC,
+ ISC_LOG_INFO,
+ "DNSKEY %s (%s) is now "
+ "active",
+ keystr1,
+ key1->ksk ? (key1->zsk ? "CSK"
+ : "KSK")
+ : "ZSK");
+ }
+ }
+
+ continue;
+ }
+
+ /* Printable version of key2 (the old key, if any) */
+ dst_key_format(key2->key, keystr2, sizeof(keystr2));
+
+ /* Copy key metadata. */
+ dst_key_copy_metadata(key2->key, key1->key);
+
+ /* Match found: remove or update it as needed */
+ if (key1->hint_remove) {
+ RETERR(remove_key(diff, key2, origin, ttl, mctx,
+ "expired", report));
+ ISC_LIST_UNLINK(*keys, key2, link);
+
+ if (removed != NULL) {
+ ISC_LIST_APPEND(*removed, key2, link);
+ isc_log_write(
+ dns_lctx, DNS_LOGCATEGORY_DNSSEC,
+ DNS_LOGMODULE_DNSSEC, ISC_LOG_INFO,
+ "DNSKEY %s (%s) is now deleted",
+ keystr2,
+ key2->ksk ? (key2->zsk ? "CSK" : "KSK")
+ : "ZSK");
+ } else {
+ dns_dnsseckey_destroy(mctx, &key2);
+ }
+ } else if (key_revoked &&
+ (dst_key_flags(key1->key) & DNS_KEYFLAG_REVOKE) != 0)
+ {
+ /*
+ * A previously valid key has been revoked.
+ * We need to remove the old version and pull
+ * in the new one.
+ */
+ RETERR(remove_key(diff, key2, origin, ttl, mctx,
+ "revoked", report));
+ ISC_LIST_UNLINK(*keys, key2, link);
+ if (removed != NULL) {
+ ISC_LIST_APPEND(*removed, key2, link);
+ isc_log_write(
+ dns_lctx, DNS_LOGCATEGORY_DNSSEC,
+ DNS_LOGMODULE_DNSSEC, ISC_LOG_INFO,
+ "DNSKEY %s (%s) is now revoked; "
+ "new ID is %05d",
+ keystr2,
+ key2->ksk ? (key2->zsk ? "CSK" : "KSK")
+ : "ZSK",
+ dst_key_id(key1->key));
+ } else {
+ dns_dnsseckey_destroy(mctx, &key2);
+ }
+
+ RETERR(publish_key(diff, key1, origin, ttl, mctx,
+ report));
+ ISC_LIST_UNLINK(*newkeys, key1, link);
+ ISC_LIST_APPEND(*keys, key1, link);
+
+ /*
+ * XXX: The revoke flag is only defined for trust
+ * anchors. Setting the flag on a non-KSK is legal,
+ * but not defined in any RFC. It seems reasonable
+ * to treat it the same as a KSK: keep it in the
+ * zone, sign the DNSKEY set with it, but not
+ * sign other records with it.
+ */
+ key1->ksk = true;
+ continue;
+ } else {
+ if (!key2->is_active &&
+ (key1->hint_sign || key1->force_sign))
+ {
+ key2->first_sign = true;
+ isc_log_write(
+ dns_lctx, DNS_LOGCATEGORY_DNSSEC,
+ DNS_LOGMODULE_DNSSEC, ISC_LOG_INFO,
+ "DNSKEY %s (%s) is now active", keystr1,
+ key1->ksk ? (key1->zsk ? "CSK" : "KSK")
+ : "ZSK");
+ } else if (key2->is_active && !key1->hint_sign &&
+ !key1->force_sign)
+ {
+ isc_log_write(
+ dns_lctx, DNS_LOGCATEGORY_DNSSEC,
+ DNS_LOGMODULE_DNSSEC, ISC_LOG_INFO,
+ "DNSKEY %s (%s) is now inactive",
+ keystr1,
+ key1->ksk ? (key1->zsk ? "CSK" : "KSK")
+ : "ZSK");
+ }
+
+ key2->hint_sign = key1->hint_sign;
+ key2->hint_publish = key1->hint_publish;
+ }
+ }
+
+ /* Free any leftover keys in newkeys */
+ while (!ISC_LIST_EMPTY(*newkeys)) {
+ key1 = ISC_LIST_HEAD(*newkeys);
+ ISC_LIST_UNLINK(*newkeys, key1, link);
+ dns_dnsseckey_destroy(mctx, &key1);
+ }
+
+ result = ISC_R_SUCCESS;
+
+failure:
+ return (result);
+}
+
+isc_result_t
+dns_dnssec_matchdskey(dns_name_t *name, dns_rdata_t *dsrdata,
+ dns_rdataset_t *keyset, dns_rdata_t *keyrdata) {
+ isc_result_t result;
+ unsigned char buf[DNS_DS_BUFFERSIZE];
+ dns_keytag_t keytag;
+ dns_rdata_dnskey_t key;
+ dns_rdata_ds_t ds;
+ isc_region_t r;
+
+ result = dns_rdata_tostruct(dsrdata, &ds, NULL);
+ RUNTIME_CHECK(result == ISC_R_SUCCESS);
+
+ for (result = dns_rdataset_first(keyset); result == ISC_R_SUCCESS;
+ result = dns_rdataset_next(keyset))
+ {
+ dns_rdata_t newdsrdata = DNS_RDATA_INIT;
+
+ dns_rdata_reset(keyrdata);
+ dns_rdataset_current(keyset, keyrdata);
+
+ result = dns_rdata_tostruct(keyrdata, &key, NULL);
+ RUNTIME_CHECK(result == ISC_R_SUCCESS);
+
+ dns_rdata_toregion(keyrdata, &r);
+ keytag = dst_region_computeid(&r);
+
+ if (ds.key_tag != keytag || ds.algorithm != key.algorithm) {
+ continue;
+ }
+
+ result = dns_ds_buildrdata(name, keyrdata, ds.digest_type, buf,
+ &newdsrdata);
+ if (result != ISC_R_SUCCESS) {
+ continue;
+ }
+
+ if (dns_rdata_compare(dsrdata, &newdsrdata) == 0) {
+ break;
+ }
+ }
+ if (result == ISC_R_NOMORE) {
+ result = ISC_R_NOTFOUND;
+ }
+
+ return (result);
+}
diff --git a/lib/dns/dnstap.c b/lib/dns/dnstap.c
new file mode 100644
index 0000000..28b88b7
--- /dev/null
+++ b/lib/dns/dnstap.c
@@ -0,0 +1,1386 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/*
+ * Copyright (c) 2013-2014, Farsight Security, Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * 3. Neither the name of the copyright holder nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/*! \file */
+
+#ifndef HAVE_DNSTAP
+#error DNSTAP not configured.
+#endif /* HAVE_DNSTAP */
+
+#include <fstrm.h>
+#include <inttypes.h>
+#include <stdbool.h>
+#include <stdlib.h>
+
+#include <isc/buffer.h>
+#include <isc/file.h>
+#include <isc/log.h>
+#include <isc/mem.h>
+#include <isc/mutex.h>
+#include <isc/once.h>
+#include <isc/print.h>
+#include <isc/result.h>
+#include <isc/sockaddr.h>
+#include <isc/task.h>
+#include <isc/thread.h>
+#include <isc/time.h>
+#include <isc/types.h>
+#include <isc/util.h>
+
+#include <dns/dnstap.h>
+#include <dns/events.h>
+#include <dns/log.h>
+#include <dns/message.h>
+#include <dns/name.h>
+#include <dns/rdataset.h>
+#include <dns/stats.h>
+#include <dns/types.h>
+#include <dns/view.h>
+
+#include "dnstap.pb-c.h"
+
+#define DTENV_MAGIC ISC_MAGIC('D', 't', 'n', 'v')
+#define VALID_DTENV(env) ISC_MAGIC_VALID(env, DTENV_MAGIC)
+
+#define DNSTAP_CONTENT_TYPE "protobuf:dnstap.Dnstap"
+#define DNSTAP_INITIAL_BUF_SIZE 256
+
+struct dns_dtmsg {
+ void *buf;
+ size_t len;
+ Dnstap__Dnstap d;
+ Dnstap__Message m;
+};
+
+struct dns_dthandle {
+ dns_dtmode_t mode;
+ struct fstrm_reader *reader;
+ isc_mem_t *mctx;
+};
+
+struct dns_dtenv {
+ unsigned int magic;
+ isc_refcount_t refcount;
+
+ isc_mem_t *mctx;
+
+ struct fstrm_iothr *iothr;
+ struct fstrm_iothr_options *fopt;
+
+ isc_task_t *reopen_task;
+ isc_mutex_t reopen_lock; /* locks 'reopen_queued'
+ * */
+ bool reopen_queued;
+
+ isc_region_t identity;
+ isc_region_t version;
+ char *path;
+ dns_dtmode_t mode;
+ isc_offset_t max_size;
+ int rolls;
+ isc_log_rollsuffix_t suffix;
+ isc_stats_t *stats;
+};
+
+#define CHECK(x) \
+ do { \
+ result = (x); \
+ if (result != ISC_R_SUCCESS) \
+ goto cleanup; \
+ } while (0)
+
+typedef struct ioq {
+ unsigned int generation;
+ struct fstrm_iothr_queue *ioq;
+} dt__ioq_t;
+
+static thread_local dt__ioq_t dt_ioq = { 0 };
+
+static atomic_uint_fast32_t global_generation;
+
+isc_result_t
+dns_dt_create(isc_mem_t *mctx, dns_dtmode_t mode, const char *path,
+ struct fstrm_iothr_options **foptp, isc_task_t *reopen_task,
+ dns_dtenv_t **envp) {
+ isc_result_t result = ISC_R_SUCCESS;
+ fstrm_res res;
+ struct fstrm_unix_writer_options *fuwopt = NULL;
+ struct fstrm_file_options *ffwopt = NULL;
+ struct fstrm_writer_options *fwopt = NULL;
+ struct fstrm_writer *fw = NULL;
+ dns_dtenv_t *env = NULL;
+
+ REQUIRE(path != NULL);
+ REQUIRE(envp != NULL && *envp == NULL);
+ REQUIRE(foptp != NULL && *foptp != NULL);
+
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_DNSTAP, DNS_LOGMODULE_DNSTAP,
+ ISC_LOG_INFO, "opening dnstap destination '%s'", path);
+
+ atomic_fetch_add_release(&global_generation, 1);
+
+ env = isc_mem_get(mctx, sizeof(dns_dtenv_t));
+
+ memset(env, 0, sizeof(dns_dtenv_t));
+ isc_mem_attach(mctx, &env->mctx);
+ env->reopen_task = reopen_task;
+ isc_mutex_init(&env->reopen_lock);
+ env->reopen_queued = false;
+ env->path = isc_mem_strdup(env->mctx, path);
+ isc_refcount_init(&env->refcount, 1);
+ CHECK(isc_stats_create(env->mctx, &env->stats, dns_dnstapcounter_max));
+
+ fwopt = fstrm_writer_options_init();
+ if (fwopt == NULL) {
+ CHECK(ISC_R_NOMEMORY);
+ }
+
+ res = fstrm_writer_options_add_content_type(
+ fwopt, DNSTAP_CONTENT_TYPE, sizeof(DNSTAP_CONTENT_TYPE) - 1);
+ if (res != fstrm_res_success) {
+ CHECK(ISC_R_FAILURE);
+ }
+
+ if (mode == dns_dtmode_file) {
+ ffwopt = fstrm_file_options_init();
+ if (ffwopt != NULL) {
+ fstrm_file_options_set_file_path(ffwopt, env->path);
+ fw = fstrm_file_writer_init(ffwopt, fwopt);
+ }
+ } else if (mode == dns_dtmode_unix) {
+ fuwopt = fstrm_unix_writer_options_init();
+ if (fuwopt != NULL) {
+ fstrm_unix_writer_options_set_socket_path(fuwopt,
+ env->path);
+ fw = fstrm_unix_writer_init(fuwopt, fwopt);
+ }
+ } else {
+ CHECK(ISC_R_FAILURE);
+ }
+
+ if (fw == NULL) {
+ CHECK(ISC_R_FAILURE);
+ }
+
+ env->iothr = fstrm_iothr_init(*foptp, &fw);
+ if (env->iothr == NULL) {
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_DNSTAP,
+ DNS_LOGMODULE_DNSTAP, ISC_LOG_WARNING,
+ "unable to initialize dnstap I/O thread");
+ fstrm_writer_destroy(&fw);
+ CHECK(ISC_R_FAILURE);
+ }
+ env->mode = mode;
+ env->max_size = 0;
+ env->rolls = ISC_LOG_ROLLINFINITE;
+ env->fopt = *foptp;
+ *foptp = NULL;
+
+ env->magic = DTENV_MAGIC;
+ *envp = env;
+
+cleanup:
+ if (ffwopt != NULL) {
+ fstrm_file_options_destroy(&ffwopt);
+ }
+
+ if (fuwopt != NULL) {
+ fstrm_unix_writer_options_destroy(&fuwopt);
+ }
+
+ if (fwopt != NULL) {
+ fstrm_writer_options_destroy(&fwopt);
+ }
+
+ if (result != ISC_R_SUCCESS) {
+ isc_mutex_destroy(&env->reopen_lock);
+ isc_mem_free(env->mctx, env->path);
+ if (env->stats != NULL) {
+ isc_stats_detach(&env->stats);
+ }
+ isc_mem_putanddetach(&env->mctx, env, sizeof(dns_dtenv_t));
+ }
+
+ return (result);
+}
+
+isc_result_t
+dns_dt_setupfile(dns_dtenv_t *env, uint64_t max_size, int rolls,
+ isc_log_rollsuffix_t suffix) {
+ REQUIRE(VALID_DTENV(env));
+
+ /*
+ * If we're using unix domain socket mode, then any
+ * change from the default values is invalid.
+ */
+ if (env->mode == dns_dtmode_unix) {
+ if (max_size == 0 && rolls == ISC_LOG_ROLLINFINITE &&
+ suffix == isc_log_rollsuffix_increment)
+ {
+ return (ISC_R_SUCCESS);
+ } else {
+ return (ISC_R_INVALIDFILE);
+ }
+ }
+
+ env->max_size = max_size;
+ env->rolls = rolls;
+ env->suffix = suffix;
+
+ return (ISC_R_SUCCESS);
+}
+
+isc_result_t
+dns_dt_reopen(dns_dtenv_t *env, int roll) {
+ isc_result_t result = ISC_R_SUCCESS;
+ fstrm_res res;
+ isc_logfile_t file;
+ struct fstrm_unix_writer_options *fuwopt = NULL;
+ struct fstrm_file_options *ffwopt = NULL;
+ struct fstrm_writer_options *fwopt = NULL;
+ struct fstrm_writer *fw = NULL;
+
+ REQUIRE(VALID_DTENV(env));
+
+ /*
+ * Run in task-exclusive mode.
+ */
+ result = isc_task_beginexclusive(env->reopen_task);
+ RUNTIME_CHECK(result == ISC_R_SUCCESS);
+
+ /*
+ * Check that we can create a new fw object.
+ */
+ fwopt = fstrm_writer_options_init();
+ if (fwopt == NULL) {
+ CHECK(ISC_R_NOMEMORY);
+ }
+
+ res = fstrm_writer_options_add_content_type(
+ fwopt, DNSTAP_CONTENT_TYPE, sizeof(DNSTAP_CONTENT_TYPE) - 1);
+ if (res != fstrm_res_success) {
+ CHECK(ISC_R_FAILURE);
+ }
+
+ if (env->mode == dns_dtmode_file) {
+ ffwopt = fstrm_file_options_init();
+ if (ffwopt != NULL) {
+ fstrm_file_options_set_file_path(ffwopt, env->path);
+ fw = fstrm_file_writer_init(ffwopt, fwopt);
+ }
+ } else if (env->mode == dns_dtmode_unix) {
+ fuwopt = fstrm_unix_writer_options_init();
+ if (fuwopt != NULL) {
+ fstrm_unix_writer_options_set_socket_path(fuwopt,
+ env->path);
+ fw = fstrm_unix_writer_init(fuwopt, fwopt);
+ }
+ } else {
+ CHECK(ISC_R_NOTIMPLEMENTED);
+ }
+
+ if (fw == NULL) {
+ CHECK(ISC_R_FAILURE);
+ }
+
+ /*
+ * We are committed here.
+ */
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_DNSTAP, DNS_LOGMODULE_DNSTAP,
+ ISC_LOG_INFO, "%s dnstap destination '%s'",
+ (roll < 0) ? "reopening" : "rolling", env->path);
+
+ atomic_fetch_add_release(&global_generation, 1);
+
+ if (env->iothr != NULL) {
+ fstrm_iothr_destroy(&env->iothr);
+ }
+
+ if (roll == 0) {
+ roll = env->rolls;
+ }
+
+ if (env->mode == dns_dtmode_file && roll != 0) {
+ /*
+ * Create a temporary isc_logfile_t structure so we can
+ * take advantage of the logfile rolling facility.
+ */
+ char *filename = isc_mem_strdup(env->mctx, env->path);
+ file.name = filename;
+ file.stream = NULL;
+ file.versions = roll;
+ file.maximum_size = 0;
+ file.maximum_reached = false;
+ file.suffix = env->suffix;
+ result = isc_logfile_roll(&file);
+ isc_mem_free(env->mctx, filename);
+ CHECK(result);
+ }
+
+ env->iothr = fstrm_iothr_init(env->fopt, &fw);
+ if (env->iothr == NULL) {
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_DNSTAP,
+ DNS_LOGMODULE_DNSTAP, ISC_LOG_WARNING,
+ "unable to initialize dnstap I/O thread");
+ CHECK(ISC_R_FAILURE);
+ }
+
+cleanup:
+ if (fw != NULL) {
+ fstrm_writer_destroy(&fw);
+ }
+
+ if (fuwopt != NULL) {
+ fstrm_unix_writer_options_destroy(&fuwopt);
+ }
+
+ if (ffwopt != NULL) {
+ fstrm_file_options_destroy(&ffwopt);
+ }
+
+ if (fwopt != NULL) {
+ fstrm_writer_options_destroy(&fwopt);
+ }
+
+ isc_task_endexclusive(env->reopen_task);
+
+ return (result);
+}
+
+static isc_result_t
+toregion(dns_dtenv_t *env, isc_region_t *r, const char *str) {
+ unsigned char *p = NULL;
+
+ REQUIRE(r != NULL);
+
+ if (str != NULL) {
+ p = (unsigned char *)isc_mem_strdup(env->mctx, str);
+ }
+
+ if (r->base != NULL) {
+ isc_mem_free(env->mctx, r->base);
+ r->length = 0;
+ }
+
+ if (p != NULL) {
+ r->base = p;
+ r->length = strlen((char *)p);
+ }
+
+ return (ISC_R_SUCCESS);
+}
+
+isc_result_t
+dns_dt_setidentity(dns_dtenv_t *env, const char *identity) {
+ REQUIRE(VALID_DTENV(env));
+
+ return (toregion(env, &env->identity, identity));
+}
+
+isc_result_t
+dns_dt_setversion(dns_dtenv_t *env, const char *version) {
+ REQUIRE(VALID_DTENV(env));
+
+ return (toregion(env, &env->version, version));
+}
+
+static void
+set_dt_ioq(unsigned int generation, struct fstrm_iothr_queue *ioq) {
+ dt_ioq.generation = generation;
+ dt_ioq.ioq = ioq;
+}
+
+static struct fstrm_iothr_queue *
+dt_queue(dns_dtenv_t *env) {
+ REQUIRE(VALID_DTENV(env));
+
+ unsigned int generation;
+
+ if (env->iothr == NULL) {
+ return (NULL);
+ }
+
+ generation = atomic_load_acquire(&global_generation);
+ if (dt_ioq.ioq != NULL && dt_ioq.generation != generation) {
+ set_dt_ioq(0, NULL);
+ }
+ if (dt_ioq.ioq == NULL) {
+ struct fstrm_iothr_queue *ioq =
+ fstrm_iothr_get_input_queue(env->iothr);
+ set_dt_ioq(generation, ioq);
+ }
+
+ return (dt_ioq.ioq);
+}
+
+void
+dns_dt_attach(dns_dtenv_t *source, dns_dtenv_t **destp) {
+ REQUIRE(VALID_DTENV(source));
+ REQUIRE(destp != NULL && *destp == NULL);
+
+ isc_refcount_increment(&source->refcount);
+ *destp = source;
+}
+
+isc_result_t
+dns_dt_getstats(dns_dtenv_t *env, isc_stats_t **statsp) {
+ REQUIRE(VALID_DTENV(env));
+ REQUIRE(statsp != NULL && *statsp == NULL);
+
+ if (env->stats == NULL) {
+ return (ISC_R_NOTFOUND);
+ }
+ isc_stats_attach(env->stats, statsp);
+ return (ISC_R_SUCCESS);
+}
+
+static void
+destroy(dns_dtenv_t *env) {
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_DNSTAP, DNS_LOGMODULE_DNSTAP,
+ ISC_LOG_INFO, "closing dnstap");
+ env->magic = 0;
+
+ atomic_fetch_add(&global_generation, 1);
+
+ if (env->iothr != NULL) {
+ fstrm_iothr_destroy(&env->iothr);
+ }
+ if (env->fopt != NULL) {
+ fstrm_iothr_options_destroy(&env->fopt);
+ }
+
+ if (env->identity.base != NULL) {
+ isc_mem_free(env->mctx, env->identity.base);
+ env->identity.length = 0;
+ }
+ if (env->version.base != NULL) {
+ isc_mem_free(env->mctx, env->version.base);
+ env->version.length = 0;
+ }
+ if (env->path != NULL) {
+ isc_mem_free(env->mctx, env->path);
+ }
+ if (env->stats != NULL) {
+ isc_stats_detach(&env->stats);
+ }
+
+ isc_mem_putanddetach(&env->mctx, env, sizeof(*env));
+}
+
+void
+dns_dt_detach(dns_dtenv_t **envp) {
+ REQUIRE(envp != NULL && VALID_DTENV(*envp));
+ dns_dtenv_t *env = *envp;
+ *envp = NULL;
+
+ if (isc_refcount_decrement(&env->refcount) == 1) {
+ isc_refcount_destroy(&env->refcount);
+ destroy(env);
+ }
+}
+
+static isc_result_t
+pack_dt(const Dnstap__Dnstap *d, void **buf, size_t *sz) {
+ ProtobufCBufferSimple sbuf;
+
+ REQUIRE(d != NULL);
+ REQUIRE(sz != NULL);
+
+ memset(&sbuf, 0, sizeof(sbuf));
+ sbuf.base.append = protobuf_c_buffer_simple_append;
+ sbuf.len = 0;
+ sbuf.alloced = DNSTAP_INITIAL_BUF_SIZE;
+
+ /* Need to use malloc() here because protobuf uses free() */
+ sbuf.data = malloc(sbuf.alloced);
+ if (sbuf.data == NULL) {
+ return (ISC_R_NOMEMORY);
+ }
+ sbuf.must_free_data = 1;
+
+ *sz = dnstap__dnstap__pack_to_buffer(d, (ProtobufCBuffer *)&sbuf);
+ if (sbuf.data == NULL) {
+ return (ISC_R_FAILURE);
+ }
+ *buf = sbuf.data;
+
+ return (ISC_R_SUCCESS);
+}
+
+static void
+send_dt(dns_dtenv_t *env, void *buf, size_t len) {
+ struct fstrm_iothr_queue *ioq;
+ fstrm_res res;
+
+ REQUIRE(env != NULL);
+
+ if (buf == NULL) {
+ return;
+ }
+
+ ioq = dt_queue(env);
+ if (ioq == NULL) {
+ free(buf);
+ return;
+ }
+
+ res = fstrm_iothr_submit(env->iothr, ioq, buf, len, fstrm_free_wrapper,
+ NULL);
+ if (res != fstrm_res_success) {
+ if (env->stats != NULL) {
+ isc_stats_increment(env->stats, dns_dnstapcounter_drop);
+ }
+ free(buf);
+ } else {
+ if (env->stats != NULL) {
+ isc_stats_increment(env->stats,
+ dns_dnstapcounter_success);
+ }
+ }
+}
+
+static void
+init_msg(dns_dtenv_t *env, dns_dtmsg_t *dm, Dnstap__Message__Type mtype) {
+ memset(dm, 0, sizeof(*dm));
+ dm->d.base.descriptor = &dnstap__dnstap__descriptor;
+ dm->m.base.descriptor = &dnstap__message__descriptor;
+ dm->d.type = DNSTAP__DNSTAP__TYPE__MESSAGE;
+ dm->d.message = &dm->m;
+ dm->m.type = mtype;
+
+ if (env->identity.length != 0) {
+ dm->d.identity.data = env->identity.base;
+ dm->d.identity.len = env->identity.length;
+ dm->d.has_identity = true;
+ }
+
+ if (env->version.length != 0) {
+ dm->d.version.data = env->version.base;
+ dm->d.version.len = env->version.length;
+ dm->d.has_version = true;
+ }
+}
+
+static Dnstap__Message__Type
+dnstap_type(dns_dtmsgtype_t msgtype) {
+ switch (msgtype) {
+ case DNS_DTTYPE_SQ:
+ return (DNSTAP__MESSAGE__TYPE__STUB_QUERY);
+ case DNS_DTTYPE_SR:
+ return (DNSTAP__MESSAGE__TYPE__STUB_RESPONSE);
+ case DNS_DTTYPE_CQ:
+ return (DNSTAP__MESSAGE__TYPE__CLIENT_QUERY);
+ case DNS_DTTYPE_CR:
+ return (DNSTAP__MESSAGE__TYPE__CLIENT_RESPONSE);
+ case DNS_DTTYPE_AQ:
+ return (DNSTAP__MESSAGE__TYPE__AUTH_QUERY);
+ case DNS_DTTYPE_AR:
+ return (DNSTAP__MESSAGE__TYPE__AUTH_RESPONSE);
+ case DNS_DTTYPE_RQ:
+ return (DNSTAP__MESSAGE__TYPE__RESOLVER_QUERY);
+ case DNS_DTTYPE_RR:
+ return (DNSTAP__MESSAGE__TYPE__RESOLVER_RESPONSE);
+ case DNS_DTTYPE_FQ:
+ return (DNSTAP__MESSAGE__TYPE__FORWARDER_QUERY);
+ case DNS_DTTYPE_FR:
+ return (DNSTAP__MESSAGE__TYPE__FORWARDER_RESPONSE);
+ case DNS_DTTYPE_TQ:
+ return (DNSTAP__MESSAGE__TYPE__TOOL_QUERY);
+ case DNS_DTTYPE_TR:
+ return (DNSTAP__MESSAGE__TYPE__TOOL_RESPONSE);
+ case DNS_DTTYPE_UQ:
+ return (DNSTAP__MESSAGE__TYPE__UPDATE_QUERY);
+ case DNS_DTTYPE_UR:
+ return (DNSTAP__MESSAGE__TYPE__UPDATE_RESPONSE);
+ default:
+ UNREACHABLE();
+ }
+}
+
+static void
+cpbuf(isc_buffer_t *buf, ProtobufCBinaryData *p, protobuf_c_boolean *has) {
+ p->data = isc_buffer_base(buf);
+ p->len = isc_buffer_usedlength(buf);
+ *has = 1;
+}
+
+static void
+setaddr(dns_dtmsg_t *dm, isc_sockaddr_t *sa, bool tcp,
+ ProtobufCBinaryData *addr, protobuf_c_boolean *has_addr, uint32_t *port,
+ protobuf_c_boolean *has_port) {
+ int family = isc_sockaddr_pf(sa);
+
+ if (family != AF_INET6 && family != AF_INET) {
+ return;
+ }
+
+ if (family == AF_INET6) {
+ dm->m.socket_family = DNSTAP__SOCKET_FAMILY__INET6;
+ addr->data = sa->type.sin6.sin6_addr.s6_addr;
+ addr->len = 16;
+ *port = ntohs(sa->type.sin6.sin6_port);
+ } else {
+ dm->m.socket_family = DNSTAP__SOCKET_FAMILY__INET;
+ addr->data = (uint8_t *)&sa->type.sin.sin_addr.s_addr;
+ addr->len = 4;
+ *port = ntohs(sa->type.sin.sin_port);
+ }
+
+ if (tcp) {
+ dm->m.socket_protocol = DNSTAP__SOCKET_PROTOCOL__TCP;
+ } else {
+ dm->m.socket_protocol = DNSTAP__SOCKET_PROTOCOL__UDP;
+ }
+
+ dm->m.has_socket_protocol = 1;
+ dm->m.has_socket_family = 1;
+ *has_addr = 1;
+ *has_port = 1;
+}
+
+/*%
+ * Invoke dns_dt_reopen() and re-allow dnstap output file rolling. This
+ * function is run in the context of the task stored in the 'reopen_task' field
+ * of the dnstap environment structure.
+ */
+static void
+perform_reopen(isc_task_t *task, isc_event_t *event) {
+ dns_dtenv_t *env;
+
+ REQUIRE(event != NULL);
+ REQUIRE(event->ev_type == DNS_EVENT_FREESTORAGE);
+
+ env = (dns_dtenv_t *)event->ev_arg;
+
+ REQUIRE(VALID_DTENV(env));
+ REQUIRE(task == env->reopen_task);
+
+ /*
+ * Roll output file in the context of env->reopen_task.
+ */
+ dns_dt_reopen(env, env->rolls);
+
+ /*
+ * Clean up.
+ */
+ isc_event_free(&event);
+ isc_task_detach(&task);
+
+ /*
+ * Re-allow output file rolling.
+ */
+ LOCK(&env->reopen_lock);
+ env->reopen_queued = false;
+ UNLOCK(&env->reopen_lock);
+}
+
+/*%
+ * Check whether a dnstap output file roll is due and if so, initiate it (the
+ * actual roll happens asynchronously).
+ */
+static void
+check_file_size_and_maybe_reopen(dns_dtenv_t *env) {
+ isc_task_t *reopen_task = NULL;
+ isc_event_t *event;
+ struct stat statbuf;
+
+ /*
+ * If the task from which the output file should be reopened was not
+ * specified, abort.
+ */
+ if (env->reopen_task == NULL) {
+ return;
+ }
+
+ /*
+ * If an output file roll is not currently queued, check the current
+ * size of the output file to see whether a roll is needed. Return if
+ * it is not.
+ */
+ LOCK(&env->reopen_lock);
+ if (env->reopen_queued || stat(env->path, &statbuf) < 0 ||
+ statbuf.st_size <= env->max_size)
+ {
+ goto unlock_and_return;
+ }
+
+ /*
+ * We need to roll the output file, but it needs to be done in the
+ * context of env->reopen_task. Allocate and send an event to achieve
+ * that, then disallow output file rolling until the roll we queue is
+ * completed.
+ */
+ event = isc_event_allocate(env->mctx, NULL, DNS_EVENT_FREESTORAGE,
+ perform_reopen, env, sizeof(*event));
+ isc_task_attach(env->reopen_task, &reopen_task);
+ isc_task_send(reopen_task, &event);
+ env->reopen_queued = true;
+
+unlock_and_return:
+ UNLOCK(&env->reopen_lock);
+}
+
+void
+dns_dt_send(dns_view_t *view, dns_dtmsgtype_t msgtype, isc_sockaddr_t *qaddr,
+ isc_sockaddr_t *raddr, bool tcp, isc_region_t *zone,
+ isc_time_t *qtime, isc_time_t *rtime, isc_buffer_t *buf) {
+ isc_time_t now, *t;
+ dns_dtmsg_t dm;
+
+ REQUIRE(DNS_VIEW_VALID(view));
+
+ if ((msgtype & view->dttypes) == 0) {
+ return;
+ }
+
+ if (view->dtenv == NULL) {
+ return;
+ }
+
+ REQUIRE(VALID_DTENV(view->dtenv));
+
+ if (view->dtenv->max_size != 0) {
+ check_file_size_and_maybe_reopen(view->dtenv);
+ }
+
+ TIME_NOW(&now);
+ t = &now;
+
+ init_msg(view->dtenv, &dm, dnstap_type(msgtype));
+
+ /* Query/response times */
+ switch (msgtype) {
+ case DNS_DTTYPE_AR:
+ case DNS_DTTYPE_CR:
+ case DNS_DTTYPE_RR:
+ case DNS_DTTYPE_FR:
+ case DNS_DTTYPE_SR:
+ case DNS_DTTYPE_TR:
+ case DNS_DTTYPE_UR:
+ if (rtime != NULL) {
+ t = rtime;
+ }
+
+ dm.m.response_time_sec = isc_time_seconds(t);
+ dm.m.has_response_time_sec = 1;
+ dm.m.response_time_nsec = isc_time_nanoseconds(t);
+ dm.m.has_response_time_nsec = 1;
+
+ /*
+ * Types RR and FR can fall through and get the query
+ * time set as well. Any other response type, break.
+ */
+ if (msgtype != DNS_DTTYPE_RR && msgtype != DNS_DTTYPE_FR) {
+ break;
+ }
+
+ FALLTHROUGH;
+ case DNS_DTTYPE_AQ:
+ case DNS_DTTYPE_CQ:
+ case DNS_DTTYPE_FQ:
+ case DNS_DTTYPE_RQ:
+ case DNS_DTTYPE_SQ:
+ case DNS_DTTYPE_TQ:
+ case DNS_DTTYPE_UQ:
+ if (qtime != NULL) {
+ t = qtime;
+ }
+
+ dm.m.query_time_sec = isc_time_seconds(t);
+ dm.m.has_query_time_sec = 1;
+ dm.m.query_time_nsec = isc_time_nanoseconds(t);
+ dm.m.has_query_time_nsec = 1;
+ break;
+ default:
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_DNSTAP,
+ DNS_LOGMODULE_DNSTAP, ISC_LOG_ERROR,
+ "invalid dnstap message type %d", msgtype);
+ return;
+ }
+
+ /* Query and response messages */
+ if ((msgtype & DNS_DTTYPE_QUERY) != 0) {
+ cpbuf(buf, &dm.m.query_message, &dm.m.has_query_message);
+ } else if ((msgtype & DNS_DTTYPE_RESPONSE) != 0) {
+ cpbuf(buf, &dm.m.response_message, &dm.m.has_response_message);
+ }
+
+ /* Zone/bailiwick */
+ switch (msgtype) {
+ case DNS_DTTYPE_AR:
+ case DNS_DTTYPE_RQ:
+ case DNS_DTTYPE_RR:
+ case DNS_DTTYPE_FQ:
+ case DNS_DTTYPE_FR:
+ if (zone != NULL && zone->base != NULL && zone->length != 0) {
+ dm.m.query_zone.data = zone->base;
+ dm.m.query_zone.len = zone->length;
+ dm.m.has_query_zone = 1;
+ }
+ break;
+ default:
+ break;
+ }
+
+ if (qaddr != NULL) {
+ setaddr(&dm, qaddr, tcp, &dm.m.query_address,
+ &dm.m.has_query_address, &dm.m.query_port,
+ &dm.m.has_query_port);
+ }
+ if (raddr != NULL) {
+ setaddr(&dm, raddr, tcp, &dm.m.response_address,
+ &dm.m.has_response_address, &dm.m.response_port,
+ &dm.m.has_response_port);
+ }
+
+ if (pack_dt(&dm.d, &dm.buf, &dm.len) == ISC_R_SUCCESS) {
+ send_dt(view->dtenv, dm.buf, dm.len);
+ }
+}
+
+static isc_result_t
+putstr(isc_buffer_t **b, const char *str) {
+ isc_result_t result;
+
+ result = isc_buffer_reserve(b, strlen(str));
+ if (result != ISC_R_SUCCESS) {
+ return (ISC_R_NOSPACE);
+ }
+
+ isc_buffer_putstr(*b, str);
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+putaddr(isc_buffer_t **b, isc_region_t *ip) {
+ char buf[64];
+
+ if (ip->length == 4) {
+ if (!inet_ntop(AF_INET, ip->base, buf, sizeof(buf))) {
+ return (ISC_R_FAILURE);
+ }
+ } else if (ip->length == 16) {
+ if (!inet_ntop(AF_INET6, ip->base, buf, sizeof(buf))) {
+ return (ISC_R_FAILURE);
+ }
+ } else {
+ return (ISC_R_BADADDRESSFORM);
+ }
+
+ return (putstr(b, buf));
+}
+
+static bool
+dnstap_file(struct fstrm_reader *r) {
+ fstrm_res res;
+ const struct fstrm_control *control = NULL;
+ const uint8_t *rtype = NULL;
+ size_t dlen = strlen(DNSTAP_CONTENT_TYPE), rlen = 0;
+ size_t n = 0;
+
+ res = fstrm_reader_get_control(r, FSTRM_CONTROL_START, &control);
+ if (res != fstrm_res_success) {
+ return (false);
+ }
+
+ res = fstrm_control_get_num_field_content_type(control, &n);
+ if (res != fstrm_res_success) {
+ return (false);
+ }
+ if (n > 0) {
+ res = fstrm_control_get_field_content_type(control, 0, &rtype,
+ &rlen);
+ if (res != fstrm_res_success) {
+ return (false);
+ }
+
+ if (rlen != dlen) {
+ return (false);
+ }
+
+ if (memcmp(DNSTAP_CONTENT_TYPE, rtype, dlen) == 0) {
+ return (true);
+ }
+ }
+
+ return (false);
+}
+
+isc_result_t
+dns_dt_open(const char *filename, dns_dtmode_t mode, isc_mem_t *mctx,
+ dns_dthandle_t **handlep) {
+ isc_result_t result;
+ struct fstrm_file_options *fopt = NULL;
+ fstrm_res res;
+ dns_dthandle_t *handle;
+
+ REQUIRE(handlep != NULL && *handlep == NULL);
+
+ handle = isc_mem_get(mctx, sizeof(*handle));
+
+ handle->mode = mode;
+ handle->mctx = NULL;
+
+ switch (mode) {
+ case dns_dtmode_file:
+ fopt = fstrm_file_options_init();
+ if (fopt == NULL) {
+ CHECK(ISC_R_NOMEMORY);
+ }
+
+ fstrm_file_options_set_file_path(fopt, filename);
+
+ handle->reader = fstrm_file_reader_init(fopt, NULL);
+ if (handle->reader == NULL) {
+ CHECK(ISC_R_NOMEMORY);
+ }
+
+ res = fstrm_reader_open(handle->reader);
+ if (res != fstrm_res_success) {
+ CHECK(ISC_R_FAILURE);
+ }
+
+ if (!dnstap_file(handle->reader)) {
+ CHECK(DNS_R_BADDNSTAP);
+ }
+ break;
+ case dns_dtmode_unix:
+ result = ISC_R_NOTIMPLEMENTED;
+ goto cleanup;
+ default:
+ UNREACHABLE();
+ }
+
+ isc_mem_attach(mctx, &handle->mctx);
+ result = ISC_R_SUCCESS;
+ *handlep = handle;
+ handle = NULL;
+
+cleanup:
+ if (result != ISC_R_SUCCESS && handle->reader != NULL) {
+ fstrm_reader_destroy(&handle->reader);
+ handle->reader = NULL;
+ }
+ if (fopt != NULL) {
+ fstrm_file_options_destroy(&fopt);
+ }
+ if (handle != NULL) {
+ isc_mem_put(mctx, handle, sizeof(*handle));
+ }
+ return (result);
+}
+
+isc_result_t
+dns_dt_getframe(dns_dthandle_t *handle, uint8_t **bufp, size_t *sizep) {
+ const uint8_t *data;
+ fstrm_res res;
+
+ REQUIRE(handle != NULL);
+ REQUIRE(bufp != NULL);
+ REQUIRE(sizep != NULL);
+
+ data = (const uint8_t *)*bufp;
+
+ res = fstrm_reader_read(handle->reader, &data, sizep);
+ switch (res) {
+ case fstrm_res_success:
+ if (data == NULL) {
+ return (ISC_R_FAILURE);
+ }
+ DE_CONST(data, *bufp);
+ return (ISC_R_SUCCESS);
+ case fstrm_res_stop:
+ return (ISC_R_NOMORE);
+ default:
+ return (ISC_R_FAILURE);
+ }
+}
+
+void
+dns_dt_close(dns_dthandle_t **handlep) {
+ dns_dthandle_t *handle;
+
+ REQUIRE(handlep != NULL && *handlep != NULL);
+
+ handle = *handlep;
+ *handlep = NULL;
+
+ if (handle->reader != NULL) {
+ fstrm_reader_destroy(&handle->reader);
+ handle->reader = NULL;
+ }
+ isc_mem_putanddetach(&handle->mctx, handle, sizeof(*handle));
+}
+
+isc_result_t
+dns_dt_parse(isc_mem_t *mctx, isc_region_t *src, dns_dtdata_t **destp) {
+ isc_result_t result;
+ Dnstap__Dnstap *frame;
+ Dnstap__Message *m;
+ dns_dtdata_t *d = NULL;
+ isc_buffer_t b;
+
+ REQUIRE(src != NULL);
+ REQUIRE(destp != NULL && *destp == NULL);
+
+ d = isc_mem_get(mctx, sizeof(*d));
+
+ memset(d, 0, sizeof(*d));
+ isc_mem_attach(mctx, &d->mctx);
+
+ d->frame = dnstap__dnstap__unpack(NULL, src->length, src->base);
+ if (d->frame == NULL) {
+ CHECK(ISC_R_NOMEMORY);
+ }
+
+ frame = (Dnstap__Dnstap *)d->frame;
+
+ if (frame->type != DNSTAP__DNSTAP__TYPE__MESSAGE) {
+ CHECK(DNS_R_BADDNSTAP);
+ }
+
+ m = frame->message;
+
+ /* Message type */
+ switch (m->type) {
+ case DNSTAP__MESSAGE__TYPE__AUTH_QUERY:
+ d->type = DNS_DTTYPE_AQ;
+ break;
+ case DNSTAP__MESSAGE__TYPE__AUTH_RESPONSE:
+ d->type = DNS_DTTYPE_AR;
+ break;
+ case DNSTAP__MESSAGE__TYPE__CLIENT_QUERY:
+ d->type = DNS_DTTYPE_CQ;
+ break;
+ case DNSTAP__MESSAGE__TYPE__CLIENT_RESPONSE:
+ d->type = DNS_DTTYPE_CR;
+ break;
+ case DNSTAP__MESSAGE__TYPE__FORWARDER_QUERY:
+ d->type = DNS_DTTYPE_FQ;
+ break;
+ case DNSTAP__MESSAGE__TYPE__FORWARDER_RESPONSE:
+ d->type = DNS_DTTYPE_FR;
+ break;
+ case DNSTAP__MESSAGE__TYPE__RESOLVER_QUERY:
+ d->type = DNS_DTTYPE_RQ;
+ break;
+ case DNSTAP__MESSAGE__TYPE__RESOLVER_RESPONSE:
+ d->type = DNS_DTTYPE_RR;
+ break;
+ case DNSTAP__MESSAGE__TYPE__STUB_QUERY:
+ d->type = DNS_DTTYPE_SQ;
+ break;
+ case DNSTAP__MESSAGE__TYPE__STUB_RESPONSE:
+ d->type = DNS_DTTYPE_SR;
+ break;
+ case DNSTAP__MESSAGE__TYPE__TOOL_QUERY:
+ d->type = DNS_DTTYPE_TQ;
+ break;
+ case DNSTAP__MESSAGE__TYPE__TOOL_RESPONSE:
+ d->type = DNS_DTTYPE_TR;
+ break;
+ case DNSTAP__MESSAGE__TYPE__UPDATE_QUERY:
+ d->type = DNS_DTTYPE_UQ;
+ break;
+ case DNSTAP__MESSAGE__TYPE__UPDATE_RESPONSE:
+ d->type = DNS_DTTYPE_UR;
+ break;
+ default:
+ CHECK(DNS_R_BADDNSTAP);
+ }
+
+ /* Query? */
+ if ((d->type & DNS_DTTYPE_QUERY) != 0) {
+ d->query = true;
+ } else {
+ d->query = false;
+ }
+
+ /* Parse DNS message */
+ if (d->query && m->has_query_message) {
+ d->msgdata.base = m->query_message.data;
+ d->msgdata.length = m->query_message.len;
+ } else if (!d->query && m->has_response_message) {
+ d->msgdata.base = m->response_message.data;
+ d->msgdata.length = m->response_message.len;
+ }
+
+ isc_buffer_init(&b, d->msgdata.base, d->msgdata.length);
+ isc_buffer_add(&b, d->msgdata.length);
+ dns_message_create(mctx, DNS_MESSAGE_INTENTPARSE, &d->msg);
+ result = dns_message_parse(d->msg, &b, 0);
+ if (result != ISC_R_SUCCESS) {
+ if (result != DNS_R_RECOVERABLE) {
+ dns_message_detach(&d->msg);
+ }
+ result = ISC_R_SUCCESS;
+ }
+
+ /* Timestamp */
+ if (d->query) {
+ if (m->has_query_time_sec && m->has_query_time_nsec) {
+ isc_time_set(&d->qtime, m->query_time_sec,
+ m->query_time_nsec);
+ }
+ } else {
+ if (m->has_response_time_sec && m->has_response_time_nsec) {
+ isc_time_set(&d->rtime, m->response_time_sec,
+ m->response_time_nsec);
+ }
+ }
+
+ /* Peer address */
+ if (m->has_query_address) {
+ d->qaddr.base = m->query_address.data;
+ d->qaddr.length = m->query_address.len;
+ }
+ if (m->has_query_port) {
+ d->qport = m->query_port;
+ }
+
+ if (m->has_response_address) {
+ d->raddr.base = m->response_address.data;
+ d->raddr.length = m->response_address.len;
+ }
+ if (m->has_response_port) {
+ d->rport = m->response_port;
+ }
+
+ /* Socket protocol */
+ if (m->has_socket_protocol) {
+ const ProtobufCEnumValue *type =
+ protobuf_c_enum_descriptor_get_value(
+ &dnstap__socket_protocol__descriptor,
+ m->socket_protocol);
+ if (type != NULL && type->value == DNSTAP__SOCKET_PROTOCOL__TCP)
+ {
+ d->tcp = true;
+ } else {
+ d->tcp = false;
+ }
+ }
+
+ /* Query tuple */
+ if (d->msg != NULL) {
+ dns_name_t *name = NULL;
+ dns_rdataset_t *rdataset;
+
+ CHECK(dns_message_firstname(d->msg, DNS_SECTION_QUESTION));
+ dns_message_currentname(d->msg, DNS_SECTION_QUESTION, &name);
+ rdataset = ISC_LIST_HEAD(name->list);
+
+ dns_name_format(name, d->namebuf, sizeof(d->namebuf));
+ dns_rdatatype_format(rdataset->type, d->typebuf,
+ sizeof(d->typebuf));
+ dns_rdataclass_format(rdataset->rdclass, d->classbuf,
+ sizeof(d->classbuf));
+ }
+
+ *destp = d;
+
+cleanup:
+ if (result != ISC_R_SUCCESS) {
+ dns_dtdata_free(&d);
+ }
+
+ return (result);
+}
+
+isc_result_t
+dns_dt_datatotext(dns_dtdata_t *d, isc_buffer_t **dest) {
+ isc_result_t result;
+ char buf[100];
+
+ REQUIRE(d != NULL);
+ REQUIRE(dest != NULL && *dest != NULL);
+
+ memset(buf, 0, sizeof(buf));
+
+ /* Timestamp */
+ if (d->query && !isc_time_isepoch(&d->qtime)) {
+ isc_time_formattimestamp(&d->qtime, buf, sizeof(buf));
+ } else if (!d->query && !isc_time_isepoch(&d->rtime)) {
+ isc_time_formattimestamp(&d->rtime, buf, sizeof(buf));
+ }
+
+ if (buf[0] == '\0') {
+ CHECK(putstr(dest, "???\?-?\?-?? ??:??:??.??? "));
+ } else {
+ CHECK(putstr(dest, buf));
+ CHECK(putstr(dest, " "));
+ }
+
+ /* Type mnemonic */
+ switch (d->type) {
+ case DNS_DTTYPE_AQ:
+ CHECK(putstr(dest, "AQ "));
+ break;
+ case DNS_DTTYPE_AR:
+ CHECK(putstr(dest, "AR "));
+ break;
+ case DNS_DTTYPE_CQ:
+ CHECK(putstr(dest, "CQ "));
+ break;
+ case DNS_DTTYPE_CR:
+ CHECK(putstr(dest, "CR "));
+ break;
+ case DNS_DTTYPE_FQ:
+ CHECK(putstr(dest, "FQ "));
+ break;
+ case DNS_DTTYPE_FR:
+ CHECK(putstr(dest, "FR "));
+ break;
+ case DNS_DTTYPE_RQ:
+ CHECK(putstr(dest, "RQ "));
+ break;
+ case DNS_DTTYPE_RR:
+ CHECK(putstr(dest, "RR "));
+ break;
+ case DNS_DTTYPE_SQ:
+ CHECK(putstr(dest, "SQ "));
+ break;
+ case DNS_DTTYPE_SR:
+ CHECK(putstr(dest, "SR "));
+ break;
+ case DNS_DTTYPE_TQ:
+ CHECK(putstr(dest, "TQ "));
+ break;
+ case DNS_DTTYPE_TR:
+ CHECK(putstr(dest, "TR "));
+ break;
+ case DNS_DTTYPE_UQ:
+ CHECK(putstr(dest, "UQ "));
+ break;
+ case DNS_DTTYPE_UR:
+ CHECK(putstr(dest, "UR "));
+ break;
+ default:
+ return (DNS_R_BADDNSTAP);
+ }
+
+ /* Query and response addresses */
+ if (d->qaddr.length != 0) {
+ CHECK(putaddr(dest, &d->qaddr));
+ snprintf(buf, sizeof(buf), ":%u", d->qport);
+ CHECK(putstr(dest, buf));
+ } else {
+ CHECK(putstr(dest, "?"));
+ }
+ if ((d->type & DNS_DTTYPE_QUERY) != 0) {
+ CHECK(putstr(dest, " -> "));
+ } else {
+ CHECK(putstr(dest, " <- "));
+ }
+ if (d->raddr.length != 0) {
+ CHECK(putaddr(dest, &d->raddr));
+ snprintf(buf, sizeof(buf), ":%u", d->rport);
+ CHECK(putstr(dest, buf));
+ } else {
+ CHECK(putstr(dest, "?"));
+ }
+
+ CHECK(putstr(dest, " "));
+
+ /* Protocol */
+ if (d->tcp) {
+ CHECK(putstr(dest, "TCP "));
+ } else {
+ CHECK(putstr(dest, "UDP "));
+ }
+
+ /* Message size */
+ if (d->msgdata.base != NULL) {
+ snprintf(buf, sizeof(buf), "%zub ", (size_t)d->msgdata.length);
+ CHECK(putstr(dest, buf));
+ } else {
+ CHECK(putstr(dest, "0b "));
+ }
+
+ /* Query tuple */
+ if (d->namebuf[0] == '\0') {
+ CHECK(putstr(dest, "?/"));
+ } else {
+ CHECK(putstr(dest, d->namebuf));
+ CHECK(putstr(dest, "/"));
+ }
+
+ if (d->classbuf[0] == '\0') {
+ CHECK(putstr(dest, "?/"));
+ } else {
+ CHECK(putstr(dest, d->classbuf));
+ CHECK(putstr(dest, "/"));
+ }
+
+ if (d->typebuf[0] == '\0') {
+ CHECK(putstr(dest, "?"));
+ } else {
+ CHECK(putstr(dest, d->typebuf));
+ }
+
+ CHECK(isc_buffer_reserve(dest, 1));
+ isc_buffer_putuint8(*dest, 0);
+
+cleanup:
+ return (result);
+}
+
+void
+dns_dtdata_free(dns_dtdata_t **dp) {
+ dns_dtdata_t *d;
+
+ REQUIRE(dp != NULL && *dp != NULL);
+
+ d = *dp;
+ *dp = NULL;
+
+ if (d->msg != NULL) {
+ dns_message_detach(&d->msg);
+ }
+ if (d->frame != NULL) {
+ dnstap__dnstap__free_unpacked(d->frame, NULL);
+ }
+
+ isc_mem_putanddetach(&d->mctx, d, sizeof(*d));
+}
diff --git a/lib/dns/dnstap.proto b/lib/dns/dnstap.proto
new file mode 100644
index 0000000..9d0ac41
--- /dev/null
+++ b/lib/dns/dnstap.proto
@@ -0,0 +1,289 @@
+// Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+//
+// SPDX-License-Identifier: MPL-2.0
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, you can obtain one at https://mozilla.org/MPL/2.0/.
+//
+// See the COPYRIGHT file distributed with this work for additional
+// information regarding copyright ownership.
+
+// dnstap: flexible, structured event replication format for DNS software
+//
+// This file contains the protobuf schemas for the "dnstap" structured event
+// replication format for DNS software.
+
+// Written in 2013-2014 by Farsight Security, Inc.
+//
+// To the extent possible under law, the author(s) have dedicated all
+// copyright and related and neighboring rights to this file to the public
+// domain worldwide. This file is distributed without any warranty.
+//
+// You should have received a copy of the CC0 Public Domain Dedication along
+// with this file. If not, see:
+//
+// <http://creativecommons.org/publicdomain/zero/1.0/>.
+
+package dnstap;
+
+// "Dnstap": this is the top-level dnstap type, which is a "union" type that
+// contains other kinds of dnstap payloads, although currently only one type
+// of dnstap payload is defined.
+// See: https://developers.google.com/protocol-buffers/docs/techniques#union
+message Dnstap {
+ // DNS server identity.
+ // If enabled, this is the identity string of the DNS server which generated
+ // this message. Typically this would be the same string as returned by an
+ // "NSID" (RFC 5001) query.
+ optional bytes identity = 1;
+
+ // DNS server version.
+ // If enabled, this is the version string of the DNS server which generated
+ // this message. Typically this would be the same string as returned by a
+ // "version.bind" query.
+ optional bytes version = 2;
+
+ // Extra data for this payload.
+ // This field can be used for adding an arbitrary byte-string annotation to
+ // the payload. No encoding or interpretation is applied or enforced.
+ optional bytes extra = 3;
+
+ // Identifies which field below is filled in.
+ enum Type {
+ MESSAGE = 1;
+ }
+ required Type type = 15;
+
+ // One of the following will be filled in.
+ optional Message message = 14;
+}
+
+// SocketFamily: the network protocol family of a socket. This specifies how
+// to interpret "network address" fields.
+enum SocketFamily {
+ INET = 1; // IPv4 (RFC 791)
+ INET6 = 2; // IPv6 (RFC 2460)
+}
+
+// SocketProtocol: the transport protocol of a socket. This specifies how to
+// interpret "transport port" fields.
+enum SocketProtocol {
+ UDP = 1; // User Datagram Protocol (RFC 768)
+ TCP = 2; // Transmission Control Protocol (RFC 793)
+}
+
+// Message: a wire-format (RFC 1035 section 4) DNS message and associated
+// metadata. Applications generating "Message" payloads should follow
+// certain requirements based on the MessageType, see below.
+message Message {
+
+ // There are eight types of "Message" defined that correspond to the
+ // four arrows in the following diagram, slightly modified from RFC 1035
+ // section 2:
+
+ // +---------+ +----------+ +--------+
+ // | | query | | query | |
+ // | Stub |-SQ--------CQ->| Recursive|-RQ----AQ->| Auth. |
+ // | Resolver| | Server | | Name |
+ // | |<-SR--------CR-| |<-RR----AR-| Server |
+ // +---------+ response | | response | |
+ // +----------+ +--------+
+
+ // Each arrow has two Type values each, one for each "end" of each arrow,
+ // because these are considered to be distinct events. Each end of each
+ // arrow on the diagram above has been marked with a two-letter Type
+ // mnemonic. Clockwise from upper left, these mnemonic values are:
+ //
+ // SQ: STUB_QUERY
+ // CQ: CLIENT_QUERY
+ // RQ: RESOLVER_QUERY
+ // AQ: AUTH_QUERY
+ // AR: AUTH_RESPONSE
+ // RR: RESOLVER_RESPONSE
+ // CR: CLIENT_RESPONSE
+ // SR: STUB_RESPONSE
+
+ // Two additional types of "Message" have been defined for the
+ // "forwarding" case where an upstream DNS server is responsible for
+ // further recursion. These are not shown on the diagram above, but have
+ // the following mnemonic values:
+
+ // FQ: FORWARDER_QUERY
+ // FR: FORWARDER_RESPONSE
+
+ // The "Message" Type values are defined below.
+
+ enum Type {
+ // AUTH_QUERY is a DNS query message received from a resolver by an
+ // authoritative name server, from the perspective of the authoritative
+ // name server.
+ AUTH_QUERY = 1;
+
+ // AUTH_RESPONSE is a DNS response message sent from an authoritative
+ // name server to a resolver, from the perspective of the authoritative
+ // name server.
+ AUTH_RESPONSE = 2;
+
+ // RESOLVER_QUERY is a DNS query message sent from a resolver to an
+ // authoritative name server, from the perspective of the resolver.
+ // Resolvers typically clear the RD (recursion desired) bit when
+ // sending queries.
+ RESOLVER_QUERY = 3;
+
+ // RESOLVER_RESPONSE is a DNS response message received from an
+ // authoritative name server by a resolver, from the perspective of
+ // the resolver.
+ RESOLVER_RESPONSE = 4;
+
+ // CLIENT_QUERY is a DNS query message sent from a client to a DNS
+ // server which is expected to perform further recursion, from the
+ // perspective of the DNS server. The client may be a stub resolver or
+ // forwarder or some other type of software which typically sets the RD
+ // (recursion desired) bit when querying the DNS server. The DNS server
+ // may be a simple forwarding proxy or it may be a full recursive
+ // resolver.
+ CLIENT_QUERY = 5;
+
+ // CLIENT_RESPONSE is a DNS response message sent from a DNS server to
+ // a client, from the perspective of the DNS server. The DNS server
+ // typically sets the RA (recursion available) bit when responding.
+ CLIENT_RESPONSE = 6;
+
+ // FORWARDER_QUERY is a DNS query message sent from a downstream DNS
+ // server to an upstream DNS server which is expected to perform
+ // further recursion, from the perspective of the downstream DNS
+ // server.
+ FORWARDER_QUERY = 7;
+
+ // FORWARDER_RESPONSE is a DNS response message sent from an upstream
+ // DNS server performing recursion to a downstream DNS server, from the
+ // perspective of the downstream DNS server.
+ FORWARDER_RESPONSE = 8;
+
+ // STUB_QUERY is a DNS query message sent from a stub resolver to a DNS
+ // server, from the perspective of the stub resolver.
+ STUB_QUERY = 9;
+
+ // STUB_RESPONSE is a DNS response message sent from a DNS server to a
+ // stub resolver, from the perspective of the stub resolver.
+ STUB_RESPONSE = 10;
+
+ // TOOL_QUERY is a DNS query message sent from a DNS software tool to a
+ // DNS server, from the perspective of the tool.
+ TOOL_QUERY = 11;
+
+ // TOOL_RESPONSE is a DNS response message received by a DNS software
+ // tool from a DNS server, from the perspective of the tool.
+ TOOL_RESPONSE = 12;
+
+ // UPDATE_QUERY is a DNS update query message received from a resolver
+ // by an authoritative name server, from the perspective of the
+ // authoritative name server.
+ UPDATE_QUERY = 13;
+
+ // UPDATE_RESPONSE is a DNS update response message sent from an
+ // authoritative name server to a resolver, from the perspective of the
+ // authoritative name server.
+ UPDATE_RESPONSE = 14;
+ }
+
+ // One of the Type values described above.
+ required Type type = 1;
+
+ // One of the SocketFamily values described above.
+ optional SocketFamily socket_family = 2;
+
+ // One of the SocketProtocol values described above.
+ optional SocketProtocol socket_protocol = 3;
+
+ // The network address of the message initiator.
+ // For SocketFamily INET, this field is 4 octets (IPv4 address).
+ // For SocketFamily INET6, this field is 16 octets (IPv6 address).
+ optional bytes query_address = 4;
+
+ // The network address of the message responder.
+ // For SocketFamily INET, this field is 4 octets (IPv4 address).
+ // For SocketFamily INET6, this field is 16 octets (IPv6 address).
+ optional bytes response_address = 5;
+
+ // The transport port of the message initiator.
+ // This is a 16-bit UDP or TCP port number, depending on SocketProtocol.
+ optional uint32 query_port = 6;
+
+ // The transport port of the message responder.
+ // This is a 16-bit UDP or TCP port number, depending on SocketProtocol.
+ optional uint32 response_port = 7;
+
+ // The time at which the DNS query message was sent or received, depending
+ // on whether this is an AUTH_QUERY, RESOLVER_QUERY, or CLIENT_QUERY.
+ // This is the number of seconds since the UNIX epoch.
+ optional uint64 query_time_sec = 8;
+
+ // The time at which the DNS query message was sent or received.
+ // This is the seconds fraction, expressed as a count of nanoseconds.
+ optional fixed32 query_time_nsec = 9;
+
+ // The initiator's original wire-format DNS query message, verbatim.
+ optional bytes query_message = 10;
+
+ // The "zone" or "bailiwick" pertaining to the DNS query message.
+ // This is a wire-format DNS domain name.
+ optional bytes query_zone = 11;
+
+ // The time at which the DNS response message was sent or received,
+ // depending on whether this is an AUTH_RESPONSE, RESOLVER_RESPONSE, or
+ // CLIENT_RESPONSE.
+ // This is the number of seconds since the UNIX epoch.
+ optional uint64 response_time_sec = 12;
+
+ // The time at which the DNS response message was sent or received.
+ // This is the seconds fraction, expressed as a count of nanoseconds.
+ optional fixed32 response_time_nsec = 13;
+
+ // The responder's original wire-format DNS response message, verbatim.
+ optional bytes response_message = 14;
+}
+
+// All fields except for 'type' in the Message schema are optional.
+// It is recommended that at least the following fields be filled in for
+// particular types of Messages.
+
+// AUTH_QUERY:
+// socket_family, socket_protocol
+// query_address, query_port
+// query_message
+// query_time_sec, query_time_nsec
+
+// AUTH_RESPONSE:
+// socket_family, socket_protocol
+// query_address, query_port
+// query_time_sec, query_time_nsec
+// response_message
+// response_time_sec, response_time_nsec
+
+// RESOLVER_QUERY:
+// socket_family, socket_protocol
+// query_message
+// query_time_sec, query_time_nsec
+// query_zone
+// response_address, response_port
+
+// RESOLVER_RESPONSE:
+// socket_family, socket_protocol
+// query_time_sec, query_time_nsec
+// query_zone
+// response_address, response_port
+// response_message
+// response_time_sec, response_time_nsec
+
+// CLIENT_QUERY:
+// socket_family, socket_protocol
+// query_message
+// query_time_sec, query_time_nsec
+
+// CLIENT_RESPONSE:
+// socket_family, socket_protocol
+// query_time_sec, query_time_nsec
+// response_message
+// response_time_sec, response_time_nsec
diff --git a/lib/dns/ds.c b/lib/dns/ds.c
new file mode 100644
index 0000000..22f158b
--- /dev/null
+++ b/lib/dns/ds.c
@@ -0,0 +1,135 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/*! \file */
+
+#include <string.h>
+
+#include <isc/buffer.h>
+#include <isc/md.h>
+#include <isc/region.h>
+#include <isc/result.h>
+#include <isc/util.h>
+
+#include <dns/ds.h>
+#include <dns/fixedname.h>
+#include <dns/name.h>
+#include <dns/rdata.h>
+#include <dns/rdatastruct.h>
+
+#include <dst/dst.h>
+
+isc_result_t
+dns_ds_fromkeyrdata(const dns_name_t *owner, dns_rdata_t *key,
+ dns_dsdigest_t digest_type, unsigned char *digest,
+ dns_rdata_ds_t *dsrdata) {
+ isc_result_t result;
+ dns_fixedname_t fname;
+ dns_name_t *name;
+ unsigned int digestlen;
+ isc_region_t r;
+ isc_md_t *md;
+ const isc_md_type_t *md_type = NULL;
+
+ REQUIRE(key != NULL);
+ REQUIRE(key->type == dns_rdatatype_dnskey ||
+ key->type == dns_rdatatype_cdnskey);
+
+ if (!dst_ds_digest_supported(digest_type)) {
+ return (ISC_R_NOTIMPLEMENTED);
+ }
+
+ switch (digest_type) {
+ case DNS_DSDIGEST_SHA1:
+ md_type = ISC_MD_SHA1;
+ break;
+
+ case DNS_DSDIGEST_SHA384:
+ md_type = ISC_MD_SHA384;
+ break;
+
+ case DNS_DSDIGEST_SHA256:
+ md_type = ISC_MD_SHA256;
+ break;
+
+ default:
+ UNREACHABLE();
+ }
+
+ name = dns_fixedname_initname(&fname);
+ (void)dns_name_downcase(owner, name, NULL);
+
+ md = isc_md_new();
+ if (md == NULL) {
+ return (ISC_R_NOMEMORY);
+ }
+
+ result = isc_md_init(md, md_type);
+ if (result != ISC_R_SUCCESS) {
+ goto end;
+ }
+
+ dns_name_toregion(name, &r);
+
+ result = isc_md_update(md, r.base, r.length);
+ if (result != ISC_R_SUCCESS) {
+ goto end;
+ }
+
+ dns_rdata_toregion(key, &r);
+ INSIST(r.length >= 4);
+
+ result = isc_md_update(md, r.base, r.length);
+ if (result != ISC_R_SUCCESS) {
+ goto end;
+ }
+
+ result = isc_md_final(md, digest, &digestlen);
+ if (result != ISC_R_SUCCESS) {
+ goto end;
+ }
+
+ dsrdata->mctx = NULL;
+ dsrdata->common.rdclass = key->rdclass;
+ dsrdata->common.rdtype = dns_rdatatype_ds;
+ dsrdata->algorithm = r.base[3];
+ dsrdata->key_tag = dst_region_computeid(&r);
+ dsrdata->digest_type = digest_type;
+ dsrdata->digest = digest;
+ dsrdata->length = digestlen;
+
+end:
+ isc_md_free(md);
+ return (result);
+}
+
+isc_result_t
+dns_ds_buildrdata(dns_name_t *owner, dns_rdata_t *key,
+ dns_dsdigest_t digest_type, unsigned char *buffer,
+ dns_rdata_t *rdata) {
+ isc_result_t result;
+ unsigned char digest[ISC_MAX_MD_SIZE];
+ dns_rdata_ds_t ds;
+ isc_buffer_t b;
+
+ result = dns_ds_fromkeyrdata(owner, key, digest_type, digest, &ds);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+
+ memset(buffer, 0, DNS_DS_BUFFERSIZE);
+ isc_buffer_init(&b, buffer, DNS_DS_BUFFERSIZE);
+ result = dns_rdata_fromstruct(rdata, key->rdclass, dns_rdatatype_ds,
+ &ds, &b);
+ return (result);
+}
diff --git a/lib/dns/dst_api.c b/lib/dns/dst_api.c
new file mode 100644
index 0000000..f04ae2f
--- /dev/null
+++ b/lib/dns/dst_api.c
@@ -0,0 +1,2831 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0 AND ISC
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/*
+ * Copyright (C) Network Associates, Inc.
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND ISC AND NETWORK ASSOCIATES DISCLAIMS
+ * ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE
+ * FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR
+ * IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+/*! \file */
+
+#include <errno.h>
+#include <fcntl.h>
+#include <inttypes.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <time.h>
+#include <unistd.h>
+
+#include <isc/buffer.h>
+#include <isc/dir.h>
+#include <isc/file.h>
+#include <isc/lex.h>
+#include <isc/mem.h>
+#include <isc/once.h>
+#include <isc/os.h>
+#include <isc/print.h>
+#include <isc/random.h>
+#include <isc/refcount.h>
+#include <isc/safe.h>
+#include <isc/string.h>
+#include <isc/time.h>
+#include <isc/util.h>
+
+#define DST_KEY_INTERNAL
+
+#include <isc/result.h>
+
+#include <dns/fixedname.h>
+#include <dns/keyvalues.h>
+#include <dns/name.h>
+#include <dns/rdata.h>
+#include <dns/rdataclass.h>
+#include <dns/ttl.h>
+#include <dns/types.h>
+
+#include "dst_internal.h"
+
+#define DST_AS_STR(t) ((t).value.as_textregion.base)
+
+#define NEXTTOKEN(lex, opt, token) \
+ { \
+ ret = isc_lex_gettoken(lex, opt, token); \
+ if (ret != ISC_R_SUCCESS) \
+ goto cleanup; \
+ }
+
+#define NEXTTOKEN_OR_EOF(lex, opt, token) \
+ do { \
+ ret = isc_lex_gettoken(lex, opt, token); \
+ if (ret == ISC_R_EOF) \
+ break; \
+ if (ret != ISC_R_SUCCESS) \
+ goto cleanup; \
+ } while ((*token).type == isc_tokentype_eol);
+
+#define READLINE(lex, opt, token) \
+ do { \
+ ret = isc_lex_gettoken(lex, opt, token); \
+ if (ret == ISC_R_EOF) \
+ break; \
+ if (ret != ISC_R_SUCCESS) \
+ goto cleanup; \
+ } while ((*token).type != isc_tokentype_eol)
+
+#define BADTOKEN() \
+ { \
+ ret = ISC_R_UNEXPECTEDTOKEN; \
+ goto cleanup; \
+ }
+
+#define NUMERIC_NTAGS (DST_MAX_NUMERIC + 1)
+static const char *numerictags[NUMERIC_NTAGS] = {
+ "Predecessor:", "Successor:", "MaxTTL:", "RollPeriod:",
+ "Lifetime:", "DSPubCount:", "DSRemCount:"
+};
+
+#define BOOLEAN_NTAGS (DST_MAX_BOOLEAN + 1)
+static const char *booleantags[BOOLEAN_NTAGS] = { "KSK:", "ZSK:" };
+
+#define TIMING_NTAGS (DST_MAX_TIMES + 1)
+static const char *timingtags[TIMING_NTAGS] = {
+ "Generated:", "Published:", "Active:", "Revoked:",
+ "Retired:", "Removed:",
+
+ "DSPublish:", "SyncPublish:", "SyncDelete:",
+
+ "DNSKEYChange:", "ZRRSIGChange:", "KRRSIGChange:", "DSChange:",
+
+ "DSRemoved:"
+};
+
+#define KEYSTATES_NTAGS (DST_MAX_KEYSTATES + 1)
+static const char *keystatestags[KEYSTATES_NTAGS] = {
+ "DNSKEYState:", "ZRRSIGState:", "KRRSIGState:", "DSState:", "GoalState:"
+};
+
+#define KEYSTATES_NVALUES 4
+static const char *keystates[KEYSTATES_NVALUES] = {
+ "hidden",
+ "rumoured",
+ "omnipresent",
+ "unretentive",
+};
+
+#define STATE_ALGORITHM_STR "Algorithm:"
+#define STATE_LENGTH_STR "Length:"
+#define MAX_NTAGS \
+ (DST_MAX_NUMERIC + DST_MAX_BOOLEAN + DST_MAX_TIMES + DST_MAX_KEYSTATES)
+
+static dst_func_t *dst_t_func[DST_MAX_ALGS];
+
+static bool dst_initialized = false;
+
+void
+gss_log(int level, const char *fmt, ...) ISC_FORMAT_PRINTF(2, 3);
+
+/*
+ * Static functions.
+ */
+static dst_key_t *
+get_key_struct(const dns_name_t *name, unsigned int alg, unsigned int flags,
+ unsigned int protocol, unsigned int bits,
+ dns_rdataclass_t rdclass, dns_ttl_t ttl, isc_mem_t *mctx);
+static isc_result_t
+write_public_key(const dst_key_t *key, int type, const char *directory);
+static isc_result_t
+write_key_state(const dst_key_t *key, int type, const char *directory);
+static isc_result_t
+buildfilename(dns_name_t *name, dns_keytag_t id, unsigned int alg,
+ unsigned int type, const char *directory, isc_buffer_t *out);
+static isc_result_t
+computeid(dst_key_t *key);
+static isc_result_t
+frombuffer(const dns_name_t *name, unsigned int alg, unsigned int flags,
+ unsigned int protocol, dns_rdataclass_t rdclass,
+ isc_buffer_t *source, isc_mem_t *mctx, dst_key_t **keyp);
+
+static isc_result_t
+algorithm_status(unsigned int alg);
+
+static isc_result_t
+addsuffix(char *filename, int len, const char *dirname, const char *ofilename,
+ const char *suffix);
+
+#define RETERR(x) \
+ do { \
+ result = (x); \
+ if (result != ISC_R_SUCCESS) \
+ goto out; \
+ } while (0)
+
+#define CHECKALG(alg) \
+ do { \
+ isc_result_t _r; \
+ _r = algorithm_status(alg); \
+ if (_r != ISC_R_SUCCESS) \
+ return ((_r)); \
+ } while (0);
+
+isc_result_t
+dst_lib_init(isc_mem_t *mctx, const char *engine) {
+ isc_result_t result;
+
+ REQUIRE(mctx != NULL);
+ REQUIRE(!dst_initialized);
+
+ UNUSED(engine);
+
+ memset(dst_t_func, 0, sizeof(dst_t_func));
+ RETERR(dst__hmacmd5_init(&dst_t_func[DST_ALG_HMACMD5]));
+ RETERR(dst__hmacsha1_init(&dst_t_func[DST_ALG_HMACSHA1]));
+ RETERR(dst__hmacsha224_init(&dst_t_func[DST_ALG_HMACSHA224]));
+ RETERR(dst__hmacsha256_init(&dst_t_func[DST_ALG_HMACSHA256]));
+ RETERR(dst__hmacsha384_init(&dst_t_func[DST_ALG_HMACSHA384]));
+ RETERR(dst__hmacsha512_init(&dst_t_func[DST_ALG_HMACSHA512]));
+ RETERR(dst__openssl_init(engine));
+ RETERR(dst__openssldh_init(&dst_t_func[DST_ALG_DH]));
+ RETERR(dst__opensslrsa_init(&dst_t_func[DST_ALG_RSASHA1],
+ DST_ALG_RSASHA1));
+ RETERR(dst__opensslrsa_init(&dst_t_func[DST_ALG_NSEC3RSASHA1],
+ DST_ALG_NSEC3RSASHA1));
+ RETERR(dst__opensslrsa_init(&dst_t_func[DST_ALG_RSASHA256],
+ DST_ALG_RSASHA256));
+ RETERR(dst__opensslrsa_init(&dst_t_func[DST_ALG_RSASHA512],
+ DST_ALG_RSASHA512));
+ RETERR(dst__opensslecdsa_init(&dst_t_func[DST_ALG_ECDSA256]));
+ RETERR(dst__opensslecdsa_init(&dst_t_func[DST_ALG_ECDSA384]));
+#ifdef HAVE_OPENSSL_ED25519
+ RETERR(dst__openssleddsa_init(&dst_t_func[DST_ALG_ED25519]));
+#endif /* ifdef HAVE_OPENSSL_ED25519 */
+#ifdef HAVE_OPENSSL_ED448
+ RETERR(dst__openssleddsa_init(&dst_t_func[DST_ALG_ED448]));
+#endif /* ifdef HAVE_OPENSSL_ED448 */
+
+#if HAVE_GSSAPI
+ RETERR(dst__gssapi_init(&dst_t_func[DST_ALG_GSSAPI]));
+#endif /* HAVE_GSSAPI */
+
+ dst_initialized = true;
+ return (ISC_R_SUCCESS);
+
+out:
+ /* avoid immediate crash! */
+ dst_initialized = true;
+ dst_lib_destroy();
+ return (result);
+}
+
+void
+dst_lib_destroy(void) {
+ int i;
+ RUNTIME_CHECK(dst_initialized);
+ dst_initialized = false;
+
+ for (i = 0; i < DST_MAX_ALGS; i++) {
+ if (dst_t_func[i] != NULL && dst_t_func[i]->cleanup != NULL) {
+ dst_t_func[i]->cleanup();
+ }
+ }
+ dst__openssl_destroy();
+}
+
+bool
+dst_algorithm_supported(unsigned int alg) {
+ REQUIRE(dst_initialized);
+
+ if (alg >= DST_MAX_ALGS || dst_t_func[alg] == NULL) {
+ return (false);
+ }
+ return (true);
+}
+
+bool
+dst_ds_digest_supported(unsigned int digest_type) {
+ return (digest_type == DNS_DSDIGEST_SHA1 ||
+ digest_type == DNS_DSDIGEST_SHA256 ||
+ digest_type == DNS_DSDIGEST_SHA384);
+}
+
+isc_result_t
+dst_context_create(dst_key_t *key, isc_mem_t *mctx, isc_logcategory_t *category,
+ bool useforsigning, int maxbits, dst_context_t **dctxp) {
+ dst_context_t *dctx;
+ isc_result_t result;
+
+ REQUIRE(dst_initialized);
+ REQUIRE(VALID_KEY(key));
+ REQUIRE(mctx != NULL);
+ REQUIRE(dctxp != NULL && *dctxp == NULL);
+
+ if (key->func->createctx == NULL && key->func->createctx2 == NULL) {
+ return (DST_R_UNSUPPORTEDALG);
+ }
+ if (key->keydata.generic == NULL) {
+ return (DST_R_NULLKEY);
+ }
+
+ dctx = isc_mem_get(mctx, sizeof(dst_context_t));
+ memset(dctx, 0, sizeof(*dctx));
+ dst_key_attach(key, &dctx->key);
+ isc_mem_attach(mctx, &dctx->mctx);
+ dctx->category = category;
+ if (useforsigning) {
+ dctx->use = DO_SIGN;
+ } else {
+ dctx->use = DO_VERIFY;
+ }
+ if (key->func->createctx2 != NULL) {
+ result = key->func->createctx2(key, maxbits, dctx);
+ } else {
+ result = key->func->createctx(key, dctx);
+ }
+ if (result != ISC_R_SUCCESS) {
+ if (dctx->key != NULL) {
+ dst_key_free(&dctx->key);
+ }
+ isc_mem_putanddetach(&dctx->mctx, dctx, sizeof(dst_context_t));
+ return (result);
+ }
+ dctx->magic = CTX_MAGIC;
+ *dctxp = dctx;
+ return (ISC_R_SUCCESS);
+}
+
+void
+dst_context_destroy(dst_context_t **dctxp) {
+ dst_context_t *dctx;
+
+ REQUIRE(dctxp != NULL && VALID_CTX(*dctxp));
+
+ dctx = *dctxp;
+ *dctxp = NULL;
+ INSIST(dctx->key->func->destroyctx != NULL);
+ dctx->key->func->destroyctx(dctx);
+ if (dctx->key != NULL) {
+ dst_key_free(&dctx->key);
+ }
+ dctx->magic = 0;
+ isc_mem_putanddetach(&dctx->mctx, dctx, sizeof(dst_context_t));
+}
+
+isc_result_t
+dst_context_adddata(dst_context_t *dctx, const isc_region_t *data) {
+ REQUIRE(VALID_CTX(dctx));
+ REQUIRE(data != NULL);
+ INSIST(dctx->key->func->adddata != NULL);
+
+ return (dctx->key->func->adddata(dctx, data));
+}
+
+isc_result_t
+dst_context_sign(dst_context_t *dctx, isc_buffer_t *sig) {
+ dst_key_t *key;
+
+ REQUIRE(VALID_CTX(dctx));
+ REQUIRE(sig != NULL);
+
+ key = dctx->key;
+ CHECKALG(key->key_alg);
+ if (key->keydata.generic == NULL) {
+ return (DST_R_NULLKEY);
+ }
+
+ if (key->func->sign == NULL) {
+ return (DST_R_NOTPRIVATEKEY);
+ }
+ if (key->func->isprivate == NULL || !key->func->isprivate(key)) {
+ return (DST_R_NOTPRIVATEKEY);
+ }
+
+ return (key->func->sign(dctx, sig));
+}
+
+isc_result_t
+dst_context_verify(dst_context_t *dctx, isc_region_t *sig) {
+ REQUIRE(VALID_CTX(dctx));
+ REQUIRE(sig != NULL);
+
+ CHECKALG(dctx->key->key_alg);
+ if (dctx->key->keydata.generic == NULL) {
+ return (DST_R_NULLKEY);
+ }
+ if (dctx->key->func->verify == NULL) {
+ return (DST_R_NOTPUBLICKEY);
+ }
+
+ return (dctx->key->func->verify(dctx, sig));
+}
+
+isc_result_t
+dst_context_verify2(dst_context_t *dctx, unsigned int maxbits,
+ isc_region_t *sig) {
+ REQUIRE(VALID_CTX(dctx));
+ REQUIRE(sig != NULL);
+
+ CHECKALG(dctx->key->key_alg);
+ if (dctx->key->keydata.generic == NULL) {
+ return (DST_R_NULLKEY);
+ }
+ if (dctx->key->func->verify == NULL && dctx->key->func->verify2 == NULL)
+ {
+ return (DST_R_NOTPUBLICKEY);
+ }
+
+ return (dctx->key->func->verify2 != NULL
+ ? dctx->key->func->verify2(dctx, maxbits, sig)
+ : dctx->key->func->verify(dctx, sig));
+}
+
+isc_result_t
+dst_key_computesecret(const dst_key_t *pub, const dst_key_t *priv,
+ isc_buffer_t *secret) {
+ REQUIRE(dst_initialized);
+ REQUIRE(VALID_KEY(pub) && VALID_KEY(priv));
+ REQUIRE(secret != NULL);
+
+ CHECKALG(pub->key_alg);
+ CHECKALG(priv->key_alg);
+
+ if (pub->keydata.generic == NULL || priv->keydata.generic == NULL) {
+ return (DST_R_NULLKEY);
+ }
+
+ if (pub->key_alg != priv->key_alg || pub->func->computesecret == NULL ||
+ priv->func->computesecret == NULL)
+ {
+ return (DST_R_KEYCANNOTCOMPUTESECRET);
+ }
+
+ if (!dst_key_isprivate(priv)) {
+ return (DST_R_NOTPRIVATEKEY);
+ }
+
+ return (pub->func->computesecret(pub, priv, secret));
+}
+
+isc_result_t
+dst_key_tofile(const dst_key_t *key, int type, const char *directory) {
+ isc_result_t ret = ISC_R_SUCCESS;
+
+ REQUIRE(dst_initialized);
+ REQUIRE(VALID_KEY(key));
+ REQUIRE((type &
+ (DST_TYPE_PRIVATE | DST_TYPE_PUBLIC | DST_TYPE_STATE)) != 0);
+
+ CHECKALG(key->key_alg);
+
+ if (key->func->tofile == NULL) {
+ return (DST_R_UNSUPPORTEDALG);
+ }
+
+ if ((type & DST_TYPE_PUBLIC) != 0) {
+ ret = write_public_key(key, type, directory);
+ if (ret != ISC_R_SUCCESS) {
+ return (ret);
+ }
+ }
+
+ if ((type & DST_TYPE_STATE) != 0) {
+ ret = write_key_state(key, type, directory);
+ if (ret != ISC_R_SUCCESS) {
+ return (ret);
+ }
+ }
+
+ if (((type & DST_TYPE_PRIVATE) != 0) &&
+ (key->key_flags & DNS_KEYFLAG_TYPEMASK) != DNS_KEYTYPE_NOKEY)
+ {
+ return (key->func->tofile(key, directory));
+ }
+ return (ISC_R_SUCCESS);
+}
+
+void
+dst_key_setexternal(dst_key_t *key, bool value) {
+ REQUIRE(VALID_KEY(key));
+
+ key->external = value;
+}
+
+bool
+dst_key_isexternal(dst_key_t *key) {
+ REQUIRE(VALID_KEY(key));
+
+ return (key->external);
+}
+
+void
+dst_key_setmodified(dst_key_t *key, bool value) {
+ REQUIRE(VALID_KEY(key));
+
+ isc_mutex_lock(&key->mdlock);
+ key->modified = value;
+ isc_mutex_unlock(&key->mdlock);
+}
+
+bool
+dst_key_ismodified(const dst_key_t *key) {
+ bool modified;
+
+ REQUIRE(VALID_KEY(key));
+
+ isc_mutex_lock(&(((dst_key_t *)key)->mdlock));
+ modified = key->modified;
+ isc_mutex_unlock(&(((dst_key_t *)key)->mdlock));
+
+ return (modified);
+}
+
+isc_result_t
+dst_key_getfilename(dns_name_t *name, dns_keytag_t id, unsigned int alg,
+ int type, const char *directory, isc_mem_t *mctx,
+ isc_buffer_t *buf) {
+ isc_result_t result;
+
+ REQUIRE(dst_initialized);
+ REQUIRE(dns_name_isabsolute(name));
+ REQUIRE((type &
+ (DST_TYPE_PRIVATE | DST_TYPE_PUBLIC | DST_TYPE_STATE)) != 0);
+ REQUIRE(mctx != NULL);
+ REQUIRE(buf != NULL);
+
+ CHECKALG(alg);
+
+ result = buildfilename(name, id, alg, type, directory, buf);
+ if (result == ISC_R_SUCCESS) {
+ if (isc_buffer_availablelength(buf) > 0) {
+ isc_buffer_putuint8(buf, 0);
+ } else {
+ result = ISC_R_NOSPACE;
+ }
+ }
+
+ return (result);
+}
+
+isc_result_t
+dst_key_fromfile(dns_name_t *name, dns_keytag_t id, unsigned int alg, int type,
+ const char *directory, isc_mem_t *mctx, dst_key_t **keyp) {
+ isc_result_t result;
+ char filename[NAME_MAX];
+ isc_buffer_t buf;
+ dst_key_t *key;
+
+ REQUIRE(dst_initialized);
+ REQUIRE(dns_name_isabsolute(name));
+ REQUIRE((type & (DST_TYPE_PRIVATE | DST_TYPE_PUBLIC)) != 0);
+ REQUIRE(mctx != NULL);
+ REQUIRE(keyp != NULL && *keyp == NULL);
+
+ CHECKALG(alg);
+
+ key = NULL;
+
+ isc_buffer_init(&buf, filename, NAME_MAX);
+ result = dst_key_getfilename(name, id, alg, type, NULL, mctx, &buf);
+ if (result != ISC_R_SUCCESS) {
+ goto out;
+ }
+
+ result = dst_key_fromnamedfile(filename, directory, type, mctx, &key);
+ if (result != ISC_R_SUCCESS) {
+ goto out;
+ }
+
+ result = computeid(key);
+ if (result != ISC_R_SUCCESS) {
+ goto out;
+ }
+
+ if (!dns_name_equal(name, key->key_name) || id != key->key_id ||
+ alg != key->key_alg)
+ {
+ result = DST_R_INVALIDPRIVATEKEY;
+ goto out;
+ }
+
+ *keyp = key;
+ result = ISC_R_SUCCESS;
+
+out:
+ if ((key != NULL) && (result != ISC_R_SUCCESS)) {
+ dst_key_free(&key);
+ }
+
+ return (result);
+}
+
+isc_result_t
+dst_key_fromnamedfile(const char *filename, const char *dirname, int type,
+ isc_mem_t *mctx, dst_key_t **keyp) {
+ isc_result_t result;
+ dst_key_t *pubkey = NULL, *key = NULL;
+ char *newfilename = NULL, *statefilename = NULL;
+ int newfilenamelen = 0, statefilenamelen = 0;
+ isc_lex_t *lex = NULL;
+
+ REQUIRE(dst_initialized);
+ REQUIRE(filename != NULL);
+ REQUIRE((type & (DST_TYPE_PRIVATE | DST_TYPE_PUBLIC)) != 0);
+ REQUIRE(mctx != NULL);
+ REQUIRE(keyp != NULL && *keyp == NULL);
+
+ /* If an absolute path is specified, don't use the key directory */
+ if (filename[0] == '/') {
+ dirname = NULL;
+ }
+
+ newfilenamelen = strlen(filename) + 5;
+ if (dirname != NULL) {
+ newfilenamelen += strlen(dirname) + 1;
+ }
+ newfilename = isc_mem_get(mctx, newfilenamelen);
+ result = addsuffix(newfilename, newfilenamelen, dirname, filename,
+ ".key");
+ INSIST(result == ISC_R_SUCCESS);
+
+ RETERR(dst_key_read_public(newfilename, type, mctx, &pubkey));
+ isc_mem_put(mctx, newfilename, newfilenamelen);
+
+ /*
+ * Read the state file, if requested by type.
+ */
+ if ((type & DST_TYPE_STATE) != 0) {
+ statefilenamelen = strlen(filename) + 7;
+ if (dirname != NULL) {
+ statefilenamelen += strlen(dirname) + 1;
+ }
+ statefilename = isc_mem_get(mctx, statefilenamelen);
+ result = addsuffix(statefilename, statefilenamelen, dirname,
+ filename, ".state");
+ INSIST(result == ISC_R_SUCCESS);
+ }
+
+ pubkey->kasp = false;
+ if ((type & DST_TYPE_STATE) != 0) {
+ result = dst_key_read_state(statefilename, mctx, &pubkey);
+ if (result == ISC_R_SUCCESS) {
+ pubkey->kasp = true;
+ } else if (result == ISC_R_FILENOTFOUND) {
+ /* Having no state is valid. */
+ result = ISC_R_SUCCESS;
+ }
+ RETERR(result);
+ }
+
+ if ((type & (DST_TYPE_PRIVATE | DST_TYPE_PUBLIC)) == DST_TYPE_PUBLIC ||
+ (pubkey->key_flags & DNS_KEYFLAG_TYPEMASK) == DNS_KEYTYPE_NOKEY)
+ {
+ RETERR(computeid(pubkey));
+ pubkey->modified = false;
+ *keyp = pubkey;
+ pubkey = NULL;
+ goto out;
+ }
+
+ RETERR(algorithm_status(pubkey->key_alg));
+
+ key = get_key_struct(pubkey->key_name, pubkey->key_alg,
+ pubkey->key_flags, pubkey->key_proto,
+ pubkey->key_size, pubkey->key_class,
+ pubkey->key_ttl, mctx);
+ if (key == NULL) {
+ RETERR(ISC_R_NOMEMORY);
+ }
+
+ if (key->func->parse == NULL) {
+ RETERR(DST_R_UNSUPPORTEDALG);
+ }
+
+ newfilenamelen = strlen(filename) + 9;
+ if (dirname != NULL) {
+ newfilenamelen += strlen(dirname) + 1;
+ }
+ newfilename = isc_mem_get(mctx, newfilenamelen);
+ result = addsuffix(newfilename, newfilenamelen, dirname, filename,
+ ".private");
+ INSIST(result == ISC_R_SUCCESS);
+
+ RETERR(isc_lex_create(mctx, 1500, &lex));
+ RETERR(isc_lex_openfile(lex, newfilename));
+ isc_mem_put(mctx, newfilename, newfilenamelen);
+
+ RETERR(key->func->parse(key, lex, pubkey));
+ isc_lex_destroy(&lex);
+
+ key->kasp = false;
+ if ((type & DST_TYPE_STATE) != 0) {
+ result = dst_key_read_state(statefilename, mctx, &key);
+ if (result == ISC_R_SUCCESS) {
+ key->kasp = true;
+ } else if (result == ISC_R_FILENOTFOUND) {
+ /* Having no state is valid. */
+ result = ISC_R_SUCCESS;
+ }
+ RETERR(result);
+ }
+
+ RETERR(computeid(key));
+
+ if (pubkey->key_id != key->key_id) {
+ RETERR(DST_R_INVALIDPRIVATEKEY);
+ }
+
+ key->modified = false;
+ *keyp = key;
+ key = NULL;
+
+out:
+ if (pubkey != NULL) {
+ dst_key_free(&pubkey);
+ }
+ if (newfilename != NULL) {
+ isc_mem_put(mctx, newfilename, newfilenamelen);
+ }
+ if (statefilename != NULL) {
+ isc_mem_put(mctx, statefilename, statefilenamelen);
+ }
+ if (lex != NULL) {
+ isc_lex_destroy(&lex);
+ }
+ if (key != NULL) {
+ dst_key_free(&key);
+ }
+ return (result);
+}
+
+isc_result_t
+dst_key_todns(const dst_key_t *key, isc_buffer_t *target) {
+ REQUIRE(dst_initialized);
+ REQUIRE(VALID_KEY(key));
+ REQUIRE(target != NULL);
+
+ CHECKALG(key->key_alg);
+
+ if (key->func->todns == NULL) {
+ return (DST_R_UNSUPPORTEDALG);
+ }
+
+ if (isc_buffer_availablelength(target) < 4) {
+ return (ISC_R_NOSPACE);
+ }
+ isc_buffer_putuint16(target, (uint16_t)(key->key_flags & 0xffff));
+ isc_buffer_putuint8(target, (uint8_t)key->key_proto);
+ isc_buffer_putuint8(target, (uint8_t)key->key_alg);
+
+ if ((key->key_flags & DNS_KEYFLAG_EXTENDED) != 0) {
+ if (isc_buffer_availablelength(target) < 2) {
+ return (ISC_R_NOSPACE);
+ }
+ isc_buffer_putuint16(
+ target, (uint16_t)((key->key_flags >> 16) & 0xffff));
+ }
+
+ if (key->keydata.generic == NULL) { /*%< NULL KEY */
+ return (ISC_R_SUCCESS);
+ }
+
+ return (key->func->todns(key, target));
+}
+
+isc_result_t
+dst_key_fromdns(const dns_name_t *name, dns_rdataclass_t rdclass,
+ isc_buffer_t *source, isc_mem_t *mctx, dst_key_t **keyp) {
+ uint8_t alg, proto;
+ uint32_t flags, extflags;
+ dst_key_t *key = NULL;
+ dns_keytag_t id, rid;
+ isc_region_t r;
+ isc_result_t result;
+
+ REQUIRE(dst_initialized);
+
+ isc_buffer_remainingregion(source, &r);
+
+ if (isc_buffer_remaininglength(source) < 4) {
+ return (DST_R_INVALIDPUBLICKEY);
+ }
+ flags = isc_buffer_getuint16(source);
+ proto = isc_buffer_getuint8(source);
+ alg = isc_buffer_getuint8(source);
+
+ id = dst_region_computeid(&r);
+ rid = dst_region_computerid(&r);
+
+ if ((flags & DNS_KEYFLAG_EXTENDED) != 0) {
+ if (isc_buffer_remaininglength(source) < 2) {
+ return (DST_R_INVALIDPUBLICKEY);
+ }
+ extflags = isc_buffer_getuint16(source);
+ flags |= (extflags << 16);
+ }
+
+ result = frombuffer(name, alg, flags, proto, rdclass, source, mctx,
+ &key);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+ key->key_id = id;
+ key->key_rid = rid;
+
+ *keyp = key;
+ return (ISC_R_SUCCESS);
+}
+
+isc_result_t
+dst_key_frombuffer(const dns_name_t *name, unsigned int alg, unsigned int flags,
+ unsigned int protocol, dns_rdataclass_t rdclass,
+ isc_buffer_t *source, isc_mem_t *mctx, dst_key_t **keyp) {
+ dst_key_t *key = NULL;
+ isc_result_t result;
+
+ REQUIRE(dst_initialized);
+
+ result = frombuffer(name, alg, flags, protocol, rdclass, source, mctx,
+ &key);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+
+ result = computeid(key);
+ if (result != ISC_R_SUCCESS) {
+ dst_key_free(&key);
+ return (result);
+ }
+
+ *keyp = key;
+ return (ISC_R_SUCCESS);
+}
+
+isc_result_t
+dst_key_tobuffer(const dst_key_t *key, isc_buffer_t *target) {
+ REQUIRE(dst_initialized);
+ REQUIRE(VALID_KEY(key));
+ REQUIRE(target != NULL);
+
+ CHECKALG(key->key_alg);
+
+ if (key->func->todns == NULL) {
+ return (DST_R_UNSUPPORTEDALG);
+ }
+
+ return (key->func->todns(key, target));
+}
+
+isc_result_t
+dst_key_privatefrombuffer(dst_key_t *key, isc_buffer_t *buffer) {
+ isc_lex_t *lex = NULL;
+ isc_result_t result = ISC_R_SUCCESS;
+
+ REQUIRE(dst_initialized);
+ REQUIRE(VALID_KEY(key));
+ REQUIRE(!dst_key_isprivate(key));
+ REQUIRE(buffer != NULL);
+
+ if (key->func->parse == NULL) {
+ RETERR(DST_R_UNSUPPORTEDALG);
+ }
+
+ RETERR(isc_lex_create(key->mctx, 1500, &lex));
+ RETERR(isc_lex_openbuffer(lex, buffer));
+ RETERR(key->func->parse(key, lex, NULL));
+out:
+ if (lex != NULL) {
+ isc_lex_destroy(&lex);
+ }
+ return (result);
+}
+
+dns_gss_ctx_id_t
+dst_key_getgssctx(const dst_key_t *key) {
+ REQUIRE(key != NULL);
+
+ return (key->keydata.gssctx);
+}
+
+isc_result_t
+dst_key_fromgssapi(const dns_name_t *name, dns_gss_ctx_id_t gssctx,
+ isc_mem_t *mctx, dst_key_t **keyp, isc_region_t *intoken) {
+ dst_key_t *key;
+ isc_result_t result;
+
+ REQUIRE(gssctx != NULL);
+ REQUIRE(keyp != NULL && *keyp == NULL);
+
+ key = get_key_struct(name, DST_ALG_GSSAPI, 0, DNS_KEYPROTO_DNSSEC, 0,
+ dns_rdataclass_in, 0, mctx);
+ if (key == NULL) {
+ return (ISC_R_NOMEMORY);
+ }
+
+ if (intoken != NULL) {
+ /*
+ * Keep the token for use by external ssu rules. They may need
+ * to examine the PAC in the kerberos ticket.
+ */
+ isc_buffer_allocate(key->mctx, &key->key_tkeytoken,
+ intoken->length);
+ RETERR(isc_buffer_copyregion(key->key_tkeytoken, intoken));
+ }
+
+ key->keydata.gssctx = gssctx;
+ *keyp = key;
+ result = ISC_R_SUCCESS;
+out:
+ if (result != ISC_R_SUCCESS) {
+ dst_key_free(&key);
+ }
+ return (result);
+}
+
+FILE *
+dst_key_open(char *tmpname, mode_t mode) {
+ /* Create public key file. */
+ int fd = mkstemp(tmpname);
+ if (fd == -1) {
+ return (NULL);
+ }
+
+ if (fchmod(fd, mode & ~isc_os_umask()) != 0) {
+ goto error;
+ }
+
+ FILE *fp = fdopen(fd, "w");
+ if (fp == NULL) {
+ goto error;
+ }
+
+ return (fp);
+error:
+ (void)close(fd);
+ (void)unlink(tmpname);
+ return (NULL);
+}
+
+isc_result_t
+dst_key_close(char *tmpname, FILE *fp, char *filename) {
+ if ((fflush(fp) != 0) || (ferror(fp) != 0)) {
+ return (dst_key_cleanup(tmpname, fp));
+ }
+
+ if (rename(tmpname, filename) != 0) {
+ return (dst_key_cleanup(tmpname, fp));
+ }
+
+ if (fclose(fp) != 0) {
+ /*
+ * This is in fact error, but we don't care at this point,
+ * as the file has been already flushed to disk.
+ */
+ }
+
+ return (ISC_R_SUCCESS);
+}
+
+isc_result_t
+dst_key_cleanup(char *tmpname, FILE *fp) {
+ if (ftruncate(fileno(fp), 0) != 0) {
+ /*
+ * ftruncate() result can't be ignored, but we don't care, as
+ * any sensitive data are protected by the permissions, and
+ * unlinked in the next step, this is just a good practice.
+ */
+ }
+
+ (void)unlink(tmpname);
+ (void)fclose(fp);
+
+ return (DST_R_WRITEERROR);
+}
+
+isc_result_t
+dst_key_buildinternal(const dns_name_t *name, unsigned int alg,
+ unsigned int bits, unsigned int flags,
+ unsigned int protocol, dns_rdataclass_t rdclass,
+ void *data, isc_mem_t *mctx, dst_key_t **keyp) {
+ dst_key_t *key;
+ isc_result_t result;
+
+ REQUIRE(dst_initialized);
+ REQUIRE(dns_name_isabsolute(name));
+ REQUIRE(mctx != NULL);
+ REQUIRE(keyp != NULL && *keyp == NULL);
+ REQUIRE(data != NULL);
+
+ CHECKALG(alg);
+
+ key = get_key_struct(name, alg, flags, protocol, bits, rdclass, 0,
+ mctx);
+ if (key == NULL) {
+ return (ISC_R_NOMEMORY);
+ }
+
+ key->keydata.generic = data;
+
+ result = computeid(key);
+ if (result != ISC_R_SUCCESS) {
+ dst_key_free(&key);
+ return (result);
+ }
+
+ *keyp = key;
+ return (ISC_R_SUCCESS);
+}
+
+isc_result_t
+dst_key_fromlabel(const dns_name_t *name, int alg, unsigned int flags,
+ unsigned int protocol, dns_rdataclass_t rdclass,
+ const char *engine, const char *label, const char *pin,
+ isc_mem_t *mctx, dst_key_t **keyp) {
+ dst_key_t *key;
+ isc_result_t result;
+
+ REQUIRE(dst_initialized);
+ REQUIRE(dns_name_isabsolute(name));
+ REQUIRE(mctx != NULL);
+ REQUIRE(keyp != NULL && *keyp == NULL);
+ REQUIRE(label != NULL);
+
+ CHECKALG(alg);
+
+ key = get_key_struct(name, alg, flags, protocol, 0, rdclass, 0, mctx);
+ if (key == NULL) {
+ return (ISC_R_NOMEMORY);
+ }
+
+ if (key->func->fromlabel == NULL) {
+ dst_key_free(&key);
+ return (DST_R_UNSUPPORTEDALG);
+ }
+
+ result = key->func->fromlabel(key, engine, label, pin);
+ if (result != ISC_R_SUCCESS) {
+ dst_key_free(&key);
+ return (result);
+ }
+
+ result = computeid(key);
+ if (result != ISC_R_SUCCESS) {
+ dst_key_free(&key);
+ return (result);
+ }
+
+ *keyp = key;
+ return (ISC_R_SUCCESS);
+}
+
+isc_result_t
+dst_key_generate(const dns_name_t *name, unsigned int alg, unsigned int bits,
+ unsigned int param, unsigned int flags, unsigned int protocol,
+ dns_rdataclass_t rdclass, isc_mem_t *mctx, dst_key_t **keyp,
+ void (*callback)(int)) {
+ dst_key_t *key;
+ isc_result_t ret;
+
+ REQUIRE(dst_initialized);
+ REQUIRE(dns_name_isabsolute(name));
+ REQUIRE(mctx != NULL);
+ REQUIRE(keyp != NULL && *keyp == NULL);
+
+ CHECKALG(alg);
+
+ key = get_key_struct(name, alg, flags, protocol, bits, rdclass, 0,
+ mctx);
+ if (key == NULL) {
+ return (ISC_R_NOMEMORY);
+ }
+
+ if (bits == 0) { /*%< NULL KEY */
+ key->key_flags |= DNS_KEYTYPE_NOKEY;
+ *keyp = key;
+ return (ISC_R_SUCCESS);
+ }
+
+ if (key->func->generate == NULL) {
+ dst_key_free(&key);
+ return (DST_R_UNSUPPORTEDALG);
+ }
+
+ ret = key->func->generate(key, param, callback);
+ if (ret != ISC_R_SUCCESS) {
+ dst_key_free(&key);
+ return (ret);
+ }
+
+ ret = computeid(key);
+ if (ret != ISC_R_SUCCESS) {
+ dst_key_free(&key);
+ return (ret);
+ }
+
+ *keyp = key;
+ return (ISC_R_SUCCESS);
+}
+
+isc_result_t
+dst_key_getbool(const dst_key_t *key, int type, bool *valuep) {
+ REQUIRE(VALID_KEY(key));
+ REQUIRE(valuep != NULL);
+ REQUIRE(type <= DST_MAX_BOOLEAN);
+
+ isc_mutex_lock(&(((dst_key_t *)key)->mdlock));
+ if (!key->boolset[type]) {
+ isc_mutex_unlock(&(((dst_key_t *)key)->mdlock));
+ return (ISC_R_NOTFOUND);
+ }
+ *valuep = key->bools[type];
+ isc_mutex_unlock(&(((dst_key_t *)key)->mdlock));
+
+ return (ISC_R_SUCCESS);
+}
+
+void
+dst_key_setbool(dst_key_t *key, int type, bool value) {
+ REQUIRE(VALID_KEY(key));
+ REQUIRE(type <= DST_MAX_BOOLEAN);
+
+ isc_mutex_lock(&key->mdlock);
+ key->modified = key->modified || !key->boolset[type] ||
+ key->bools[type] != value;
+ key->bools[type] = value;
+ key->boolset[type] = true;
+ isc_mutex_unlock(&key->mdlock);
+}
+
+void
+dst_key_unsetbool(dst_key_t *key, int type) {
+ REQUIRE(VALID_KEY(key));
+ REQUIRE(type <= DST_MAX_BOOLEAN);
+
+ isc_mutex_lock(&key->mdlock);
+ key->modified = key->modified || key->boolset[type];
+ key->boolset[type] = false;
+ isc_mutex_unlock(&key->mdlock);
+}
+
+isc_result_t
+dst_key_getnum(const dst_key_t *key, int type, uint32_t *valuep) {
+ REQUIRE(VALID_KEY(key));
+ REQUIRE(valuep != NULL);
+ REQUIRE(type <= DST_MAX_NUMERIC);
+
+ isc_mutex_lock(&(((dst_key_t *)key)->mdlock));
+ if (!key->numset[type]) {
+ isc_mutex_unlock(&(((dst_key_t *)key)->mdlock));
+ return (ISC_R_NOTFOUND);
+ }
+ *valuep = key->nums[type];
+ isc_mutex_unlock(&(((dst_key_t *)key)->mdlock));
+
+ return (ISC_R_SUCCESS);
+}
+
+void
+dst_key_setnum(dst_key_t *key, int type, uint32_t value) {
+ REQUIRE(VALID_KEY(key));
+ REQUIRE(type <= DST_MAX_NUMERIC);
+
+ isc_mutex_lock(&key->mdlock);
+ key->modified = key->modified || !key->numset[type] ||
+ key->nums[type] != value;
+ key->nums[type] = value;
+ key->numset[type] = true;
+ isc_mutex_unlock(&key->mdlock);
+}
+
+void
+dst_key_unsetnum(dst_key_t *key, int type) {
+ REQUIRE(VALID_KEY(key));
+ REQUIRE(type <= DST_MAX_NUMERIC);
+
+ isc_mutex_lock(&key->mdlock);
+ key->modified = key->modified || key->numset[type];
+ key->numset[type] = false;
+ isc_mutex_unlock(&key->mdlock);
+}
+
+isc_result_t
+dst_key_gettime(const dst_key_t *key, int type, isc_stdtime_t *timep) {
+ REQUIRE(VALID_KEY(key));
+ REQUIRE(timep != NULL);
+ REQUIRE(type <= DST_MAX_TIMES);
+
+ isc_mutex_lock(&(((dst_key_t *)key)->mdlock));
+ if (!key->timeset[type]) {
+ isc_mutex_unlock(&(((dst_key_t *)key)->mdlock));
+ return (ISC_R_NOTFOUND);
+ }
+ *timep = key->times[type];
+ isc_mutex_unlock(&(((dst_key_t *)key)->mdlock));
+ return (ISC_R_SUCCESS);
+}
+
+void
+dst_key_settime(dst_key_t *key, int type, isc_stdtime_t when) {
+ REQUIRE(VALID_KEY(key));
+ REQUIRE(type <= DST_MAX_TIMES);
+
+ isc_mutex_lock(&key->mdlock);
+ key->modified = key->modified || !key->timeset[type] ||
+ key->times[type] != when;
+ key->times[type] = when;
+ key->timeset[type] = true;
+ isc_mutex_unlock(&key->mdlock);
+}
+
+void
+dst_key_unsettime(dst_key_t *key, int type) {
+ REQUIRE(VALID_KEY(key));
+ REQUIRE(type <= DST_MAX_TIMES);
+
+ isc_mutex_lock(&key->mdlock);
+ key->modified = key->modified || key->timeset[type];
+ key->timeset[type] = false;
+ isc_mutex_unlock(&key->mdlock);
+}
+
+isc_result_t
+dst_key_getstate(const dst_key_t *key, int type, dst_key_state_t *statep) {
+ REQUIRE(VALID_KEY(key));
+ REQUIRE(statep != NULL);
+ REQUIRE(type <= DST_MAX_KEYSTATES);
+
+ isc_mutex_lock(&(((dst_key_t *)key)->mdlock));
+ if (!key->keystateset[type]) {
+ isc_mutex_unlock(&(((dst_key_t *)key)->mdlock));
+ return (ISC_R_NOTFOUND);
+ }
+ *statep = key->keystates[type];
+ isc_mutex_unlock(&(((dst_key_t *)key)->mdlock));
+
+ return (ISC_R_SUCCESS);
+}
+
+void
+dst_key_setstate(dst_key_t *key, int type, dst_key_state_t state) {
+ REQUIRE(VALID_KEY(key));
+ REQUIRE(type <= DST_MAX_KEYSTATES);
+
+ isc_mutex_lock(&key->mdlock);
+ key->modified = key->modified || !key->keystateset[type] ||
+ key->keystates[type] != state;
+ key->keystates[type] = state;
+ key->keystateset[type] = true;
+ isc_mutex_unlock(&key->mdlock);
+}
+
+void
+dst_key_unsetstate(dst_key_t *key, int type) {
+ REQUIRE(VALID_KEY(key));
+ REQUIRE(type <= DST_MAX_KEYSTATES);
+
+ isc_mutex_lock(&key->mdlock);
+ key->modified = key->modified || key->keystateset[type];
+ key->keystateset[type] = false;
+ isc_mutex_unlock(&key->mdlock);
+}
+
+isc_result_t
+dst_key_getprivateformat(const dst_key_t *key, int *majorp, int *minorp) {
+ REQUIRE(VALID_KEY(key));
+ REQUIRE(majorp != NULL);
+ REQUIRE(minorp != NULL);
+ *majorp = key->fmt_major;
+ *minorp = key->fmt_minor;
+ return (ISC_R_SUCCESS);
+}
+
+void
+dst_key_setprivateformat(dst_key_t *key, int major, int minor) {
+ REQUIRE(VALID_KEY(key));
+ key->fmt_major = major;
+ key->fmt_minor = minor;
+}
+
+static bool
+comparekeys(const dst_key_t *key1, const dst_key_t *key2,
+ bool match_revoked_key,
+ bool (*compare)(const dst_key_t *key1, const dst_key_t *key2)) {
+ REQUIRE(dst_initialized);
+ REQUIRE(VALID_KEY(key1));
+ REQUIRE(VALID_KEY(key2));
+
+ if (key1 == key2) {
+ return (true);
+ }
+
+ if (key1->key_alg != key2->key_alg) {
+ return (false);
+ }
+
+ if (key1->key_id != key2->key_id) {
+ if (!match_revoked_key) {
+ return (false);
+ }
+ if ((key1->key_flags & DNS_KEYFLAG_REVOKE) ==
+ (key2->key_flags & DNS_KEYFLAG_REVOKE))
+ {
+ return (false);
+ }
+ if (key1->key_id != key2->key_rid &&
+ key1->key_rid != key2->key_id)
+ {
+ return (false);
+ }
+ }
+
+ if (compare != NULL) {
+ return (compare(key1, key2));
+ } else {
+ return (false);
+ }
+}
+
+/*
+ * Compares only the public portion of two keys, by converting them
+ * both to wire format and comparing the results.
+ */
+static bool
+pub_compare(const dst_key_t *key1, const dst_key_t *key2) {
+ isc_result_t result;
+ unsigned char buf1[DST_KEY_MAXSIZE], buf2[DST_KEY_MAXSIZE];
+ isc_buffer_t b1, b2;
+ isc_region_t r1, r2;
+
+ isc_buffer_init(&b1, buf1, sizeof(buf1));
+ result = dst_key_todns(key1, &b1);
+ if (result != ISC_R_SUCCESS) {
+ return (false);
+ }
+ /* Zero out flags. */
+ buf1[0] = buf1[1] = 0;
+ if ((key1->key_flags & DNS_KEYFLAG_EXTENDED) != 0) {
+ isc_buffer_subtract(&b1, 2);
+ }
+
+ isc_buffer_init(&b2, buf2, sizeof(buf2));
+ result = dst_key_todns(key2, &b2);
+ if (result != ISC_R_SUCCESS) {
+ return (false);
+ }
+ /* Zero out flags. */
+ buf2[0] = buf2[1] = 0;
+ if ((key2->key_flags & DNS_KEYFLAG_EXTENDED) != 0) {
+ isc_buffer_subtract(&b2, 2);
+ }
+
+ isc_buffer_usedregion(&b1, &r1);
+ /* Remove extended flags. */
+ if ((key1->key_flags & DNS_KEYFLAG_EXTENDED) != 0) {
+ memmove(&buf1[4], &buf1[6], r1.length - 6);
+ r1.length -= 2;
+ }
+
+ isc_buffer_usedregion(&b2, &r2);
+ /* Remove extended flags. */
+ if ((key2->key_flags & DNS_KEYFLAG_EXTENDED) != 0) {
+ memmove(&buf2[4], &buf2[6], r2.length - 6);
+ r2.length -= 2;
+ }
+ return (isc_region_compare(&r1, &r2) == 0);
+}
+
+bool
+dst_key_compare(const dst_key_t *key1, const dst_key_t *key2) {
+ return (comparekeys(key1, key2, false, key1->func->compare));
+}
+
+bool
+dst_key_pubcompare(const dst_key_t *key1, const dst_key_t *key2,
+ bool match_revoked_key) {
+ return (comparekeys(key1, key2, match_revoked_key, pub_compare));
+}
+
+bool
+dst_key_paramcompare(const dst_key_t *key1, const dst_key_t *key2) {
+ REQUIRE(dst_initialized);
+ REQUIRE(VALID_KEY(key1));
+ REQUIRE(VALID_KEY(key2));
+
+ if (key1 == key2) {
+ return (true);
+ }
+ if (key1->key_alg == key2->key_alg &&
+ key1->func->paramcompare != NULL &&
+ key1->func->paramcompare(key1, key2))
+ {
+ return (true);
+ } else {
+ return (false);
+ }
+}
+
+void
+dst_key_attach(dst_key_t *source, dst_key_t **target) {
+ REQUIRE(dst_initialized);
+ REQUIRE(target != NULL && *target == NULL);
+ REQUIRE(VALID_KEY(source));
+
+ isc_refcount_increment(&source->refs);
+ *target = source;
+}
+
+void
+dst_key_free(dst_key_t **keyp) {
+ REQUIRE(dst_initialized);
+ REQUIRE(keyp != NULL && VALID_KEY(*keyp));
+ dst_key_t *key = *keyp;
+ *keyp = NULL;
+
+ if (isc_refcount_decrement(&key->refs) == 1) {
+ isc_refcount_destroy(&key->refs);
+ isc_mem_t *mctx = key->mctx;
+ if (key->keydata.generic != NULL) {
+ INSIST(key->func->destroy != NULL);
+ key->func->destroy(key);
+ }
+ if (key->engine != NULL) {
+ isc_mem_free(mctx, key->engine);
+ }
+ if (key->label != NULL) {
+ isc_mem_free(mctx, key->label);
+ }
+ dns_name_free(key->key_name, mctx);
+ isc_mem_put(mctx, key->key_name, sizeof(dns_name_t));
+ if (key->key_tkeytoken) {
+ isc_buffer_free(&key->key_tkeytoken);
+ }
+ isc_mutex_destroy(&key->mdlock);
+ isc_safe_memwipe(key, sizeof(*key));
+ isc_mem_putanddetach(&mctx, key, sizeof(*key));
+ }
+}
+
+bool
+dst_key_isprivate(const dst_key_t *key) {
+ REQUIRE(VALID_KEY(key));
+ INSIST(key->func->isprivate != NULL);
+ return (key->func->isprivate(key));
+}
+
+isc_result_t
+dst_key_buildfilename(const dst_key_t *key, int type, const char *directory,
+ isc_buffer_t *out) {
+ REQUIRE(VALID_KEY(key));
+ REQUIRE(type == DST_TYPE_PRIVATE || type == DST_TYPE_PUBLIC ||
+ type == DST_TYPE_STATE || type == DST_TYPE_TEMPLATE ||
+ type == 0);
+
+ return (buildfilename(key->key_name, key->key_id, key->key_alg, type,
+ directory, out));
+}
+
+isc_result_t
+dst_key_sigsize(const dst_key_t *key, unsigned int *n) {
+ REQUIRE(dst_initialized);
+ REQUIRE(VALID_KEY(key));
+ REQUIRE(n != NULL);
+
+ /* XXXVIX this switch statement is too sparse to gen a jump table. */
+ switch (key->key_alg) {
+ case DST_ALG_RSASHA1:
+ case DST_ALG_NSEC3RSASHA1:
+ case DST_ALG_RSASHA256:
+ case DST_ALG_RSASHA512:
+ *n = (key->key_size + 7) / 8;
+ break;
+ case DST_ALG_ECDSA256:
+ *n = DNS_SIG_ECDSA256SIZE;
+ break;
+ case DST_ALG_ECDSA384:
+ *n = DNS_SIG_ECDSA384SIZE;
+ break;
+ case DST_ALG_ED25519:
+ *n = DNS_SIG_ED25519SIZE;
+ break;
+ case DST_ALG_ED448:
+ *n = DNS_SIG_ED448SIZE;
+ break;
+ case DST_ALG_HMACMD5:
+ *n = isc_md_type_get_size(ISC_MD_MD5);
+ break;
+ case DST_ALG_HMACSHA1:
+ *n = isc_md_type_get_size(ISC_MD_SHA1);
+ break;
+ case DST_ALG_HMACSHA224:
+ *n = isc_md_type_get_size(ISC_MD_SHA224);
+ break;
+ case DST_ALG_HMACSHA256:
+ *n = isc_md_type_get_size(ISC_MD_SHA256);
+ break;
+ case DST_ALG_HMACSHA384:
+ *n = isc_md_type_get_size(ISC_MD_SHA384);
+ break;
+ case DST_ALG_HMACSHA512:
+ *n = isc_md_type_get_size(ISC_MD_SHA512);
+ break;
+ case DST_ALG_GSSAPI:
+ *n = 128; /*%< XXX */
+ break;
+ case DST_ALG_DH:
+ default:
+ return (DST_R_UNSUPPORTEDALG);
+ }
+ return (ISC_R_SUCCESS);
+}
+
+isc_result_t
+dst_key_secretsize(const dst_key_t *key, unsigned int *n) {
+ REQUIRE(dst_initialized);
+ REQUIRE(VALID_KEY(key));
+ REQUIRE(n != NULL);
+
+ if (key->key_alg == DST_ALG_DH) {
+ *n = (key->key_size + 7) / 8;
+ return (ISC_R_SUCCESS);
+ }
+ return (DST_R_UNSUPPORTEDALG);
+}
+
+/*%
+ * Set the flags on a key, then recompute the key ID
+ */
+isc_result_t
+dst_key_setflags(dst_key_t *key, uint32_t flags) {
+ REQUIRE(VALID_KEY(key));
+ key->key_flags = flags;
+ return (computeid(key));
+}
+
+void
+dst_key_format(const dst_key_t *key, char *cp, unsigned int size) {
+ char namestr[DNS_NAME_FORMATSIZE];
+ char algstr[DNS_NAME_FORMATSIZE];
+
+ dns_name_format(dst_key_name(key), namestr, sizeof(namestr));
+ dns_secalg_format((dns_secalg_t)dst_key_alg(key), algstr,
+ sizeof(algstr));
+ snprintf(cp, size, "%s/%s/%d", namestr, algstr, dst_key_id(key));
+}
+
+isc_result_t
+dst_key_dump(dst_key_t *key, isc_mem_t *mctx, char **buffer, int *length) {
+ REQUIRE(buffer != NULL && *buffer == NULL);
+ REQUIRE(length != NULL && *length == 0);
+ REQUIRE(VALID_KEY(key));
+
+ if (key->func->dump == NULL) {
+ return (ISC_R_NOTIMPLEMENTED);
+ }
+ return (key->func->dump(key, mctx, buffer, length));
+}
+
+isc_result_t
+dst_key_restore(dns_name_t *name, unsigned int alg, unsigned int flags,
+ unsigned int protocol, dns_rdataclass_t rdclass,
+ isc_mem_t *mctx, const char *keystr, dst_key_t **keyp) {
+ isc_result_t result;
+ dst_key_t *key;
+
+ REQUIRE(dst_initialized);
+ REQUIRE(keyp != NULL && *keyp == NULL);
+
+ if (alg >= DST_MAX_ALGS || dst_t_func[alg] == NULL) {
+ return (DST_R_UNSUPPORTEDALG);
+ }
+
+ if (dst_t_func[alg]->restore == NULL) {
+ return (ISC_R_NOTIMPLEMENTED);
+ }
+
+ key = get_key_struct(name, alg, flags, protocol, 0, rdclass, 0, mctx);
+ if (key == NULL) {
+ return (ISC_R_NOMEMORY);
+ }
+
+ result = (dst_t_func[alg]->restore)(key, keystr);
+ if (result == ISC_R_SUCCESS) {
+ *keyp = key;
+ } else {
+ dst_key_free(&key);
+ }
+
+ return (result);
+}
+
+/***
+ *** Static methods
+ ***/
+
+/*%
+ * Allocates a key structure and fills in some of the fields.
+ */
+static dst_key_t *
+get_key_struct(const dns_name_t *name, unsigned int alg, unsigned int flags,
+ unsigned int protocol, unsigned int bits,
+ dns_rdataclass_t rdclass, dns_ttl_t ttl, isc_mem_t *mctx) {
+ dst_key_t *key;
+ int i;
+
+ key = isc_mem_get(mctx, sizeof(dst_key_t));
+
+ memset(key, 0, sizeof(dst_key_t));
+
+ key->key_name = isc_mem_get(mctx, sizeof(dns_name_t));
+
+ dns_name_init(key->key_name, NULL);
+ dns_name_dup(name, mctx, key->key_name);
+
+ isc_refcount_init(&key->refs, 1);
+ isc_mem_attach(mctx, &key->mctx);
+ key->key_alg = alg;
+ key->key_flags = flags;
+ key->key_proto = protocol;
+ key->keydata.generic = NULL;
+ key->key_size = bits;
+ key->key_class = rdclass;
+ key->key_ttl = ttl;
+ key->func = dst_t_func[alg];
+ key->fmt_major = 0;
+ key->fmt_minor = 0;
+ for (i = 0; i < (DST_MAX_TIMES + 1); i++) {
+ key->times[i] = 0;
+ key->timeset[i] = false;
+ }
+ isc_mutex_init(&key->mdlock);
+ key->inactive = false;
+ key->magic = KEY_MAGIC;
+ return (key);
+}
+
+bool
+dst_key_inactive(const dst_key_t *key) {
+ REQUIRE(VALID_KEY(key));
+
+ return (key->inactive);
+}
+
+void
+dst_key_setinactive(dst_key_t *key, bool inactive) {
+ REQUIRE(VALID_KEY(key));
+
+ key->inactive = inactive;
+}
+
+/*%
+ * Reads a public key from disk.
+ */
+isc_result_t
+dst_key_read_public(const char *filename, int type, isc_mem_t *mctx,
+ dst_key_t **keyp) {
+ u_char rdatabuf[DST_KEY_MAXSIZE];
+ isc_buffer_t b;
+ dns_fixedname_t name;
+ isc_lex_t *lex = NULL;
+ isc_token_t token;
+ isc_result_t ret;
+ dns_rdata_t rdata = DNS_RDATA_INIT;
+ unsigned int opt = ISC_LEXOPT_DNSMULTILINE;
+ dns_rdataclass_t rdclass = dns_rdataclass_in;
+ isc_lexspecials_t specials;
+ uint32_t ttl = 0;
+ isc_result_t result;
+ dns_rdatatype_t keytype;
+
+ /*
+ * Open the file and read its formatted contents
+ * File format:
+ * domain.name [ttl] [class] [KEY|DNSKEY] <flags> <protocol>
+ * <algorithm> <key>
+ */
+
+ /* 1500 should be large enough for any key */
+ ret = isc_lex_create(mctx, 1500, &lex);
+ if (ret != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+
+ memset(specials, 0, sizeof(specials));
+ specials['('] = 1;
+ specials[')'] = 1;
+ specials['"'] = 1;
+ isc_lex_setspecials(lex, specials);
+ isc_lex_setcomments(lex, ISC_LEXCOMMENT_DNSMASTERFILE);
+
+ ret = isc_lex_openfile(lex, filename);
+ if (ret != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+
+ /* Read the domain name */
+ NEXTTOKEN(lex, opt, &token);
+ if (token.type != isc_tokentype_string) {
+ BADTOKEN();
+ }
+
+ /*
+ * We don't support "@" in .key files.
+ */
+ if (!strcmp(DST_AS_STR(token), "@")) {
+ BADTOKEN();
+ }
+
+ dns_fixedname_init(&name);
+ isc_buffer_init(&b, DST_AS_STR(token), strlen(DST_AS_STR(token)));
+ isc_buffer_add(&b, strlen(DST_AS_STR(token)));
+ ret = dns_name_fromtext(dns_fixedname_name(&name), &b, dns_rootname, 0,
+ NULL);
+ if (ret != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+
+ /* Read the next word: either TTL, class, or 'KEY' */
+ NEXTTOKEN(lex, opt, &token);
+
+ if (token.type != isc_tokentype_string) {
+ BADTOKEN();
+ }
+
+ /* If it's a TTL, read the next one */
+ result = dns_ttl_fromtext(&token.value.as_textregion, &ttl);
+ if (result == ISC_R_SUCCESS) {
+ NEXTTOKEN(lex, opt, &token);
+ }
+
+ if (token.type != isc_tokentype_string) {
+ BADTOKEN();
+ }
+
+ ret = dns_rdataclass_fromtext(&rdclass, &token.value.as_textregion);
+ if (ret == ISC_R_SUCCESS) {
+ NEXTTOKEN(lex, opt, &token);
+ }
+
+ if (token.type != isc_tokentype_string) {
+ BADTOKEN();
+ }
+
+ if (strcasecmp(DST_AS_STR(token), "DNSKEY") == 0) {
+ keytype = dns_rdatatype_dnskey;
+ } else if (strcasecmp(DST_AS_STR(token), "KEY") == 0) {
+ keytype = dns_rdatatype_key; /*%< SIG(0), TKEY */
+ } else {
+ BADTOKEN();
+ }
+
+ if (((type & DST_TYPE_KEY) != 0 && keytype != dns_rdatatype_key) ||
+ ((type & DST_TYPE_KEY) == 0 && keytype != dns_rdatatype_dnskey))
+ {
+ ret = DST_R_BADKEYTYPE;
+ goto cleanup;
+ }
+
+ isc_buffer_init(&b, rdatabuf, sizeof(rdatabuf));
+ ret = dns_rdata_fromtext(&rdata, rdclass, keytype, lex, NULL, false,
+ mctx, &b, NULL);
+ if (ret != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+
+ ret = dst_key_fromdns(dns_fixedname_name(&name), rdclass, &b, mctx,
+ keyp);
+ if (ret != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+
+ dst_key_setttl(*keyp, ttl);
+
+cleanup:
+ if (lex != NULL) {
+ isc_lex_destroy(&lex);
+ }
+ return (ret);
+}
+
+static int
+find_metadata(const char *s, const char *tags[], int ntags) {
+ for (int i = 0; i < ntags; i++) {
+ if (tags[i] != NULL && strcasecmp(s, tags[i]) == 0) {
+ return (i);
+ }
+ }
+ return (-1);
+}
+
+static int
+find_numericdata(const char *s) {
+ return (find_metadata(s, numerictags, NUMERIC_NTAGS));
+}
+
+static int
+find_booleandata(const char *s) {
+ return (find_metadata(s, booleantags, BOOLEAN_NTAGS));
+}
+
+static int
+find_timingdata(const char *s) {
+ return (find_metadata(s, timingtags, TIMING_NTAGS));
+}
+
+static int
+find_keystatedata(const char *s) {
+ return (find_metadata(s, keystatestags, KEYSTATES_NTAGS));
+}
+
+static isc_result_t
+keystate_fromtext(const char *s, dst_key_state_t *state) {
+ for (int i = 0; i < KEYSTATES_NVALUES; i++) {
+ if (keystates[i] != NULL && strcasecmp(s, keystates[i]) == 0) {
+ *state = (dst_key_state_t)i;
+ return (ISC_R_SUCCESS);
+ }
+ }
+ return (ISC_R_NOTFOUND);
+}
+
+/*%
+ * Reads a key state from disk.
+ */
+isc_result_t
+dst_key_read_state(const char *filename, isc_mem_t *mctx, dst_key_t **keyp) {
+ isc_lex_t *lex = NULL;
+ isc_token_t token;
+ isc_result_t ret;
+ unsigned int opt = ISC_LEXOPT_EOL;
+
+ ret = isc_lex_create(mctx, 1500, &lex);
+ if (ret != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+ isc_lex_setcomments(lex, ISC_LEXCOMMENT_DNSMASTERFILE);
+
+ ret = isc_lex_openfile(lex, filename);
+ if (ret != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+
+ /*
+ * Read the comment line.
+ */
+ READLINE(lex, opt, &token);
+
+ /*
+ * Read the algorithm line.
+ */
+ NEXTTOKEN(lex, opt, &token);
+ if (token.type != isc_tokentype_string ||
+ strcmp(DST_AS_STR(token), STATE_ALGORITHM_STR) != 0)
+ {
+ BADTOKEN();
+ }
+
+ NEXTTOKEN(lex, opt | ISC_LEXOPT_NUMBER, &token);
+ if (token.type != isc_tokentype_number ||
+ token.value.as_ulong != (unsigned long)dst_key_alg(*keyp))
+ {
+ BADTOKEN();
+ }
+
+ READLINE(lex, opt, &token);
+
+ /*
+ * Read the length line.
+ */
+ NEXTTOKEN(lex, opt, &token);
+ if (token.type != isc_tokentype_string ||
+ strcmp(DST_AS_STR(token), STATE_LENGTH_STR) != 0)
+ {
+ BADTOKEN();
+ }
+
+ NEXTTOKEN(lex, opt | ISC_LEXOPT_NUMBER, &token);
+ if (token.type != isc_tokentype_number ||
+ token.value.as_ulong != (unsigned long)dst_key_size(*keyp))
+ {
+ BADTOKEN();
+ }
+
+ READLINE(lex, opt, &token);
+
+ /*
+ * Read the metadata.
+ */
+ for (int n = 0; n < MAX_NTAGS; n++) {
+ int tag;
+
+ NEXTTOKEN_OR_EOF(lex, opt, &token);
+ if (ret == ISC_R_EOF) {
+ break;
+ }
+ if (token.type != isc_tokentype_string) {
+ BADTOKEN();
+ }
+
+ /* Numeric metadata */
+ tag = find_numericdata(DST_AS_STR(token));
+ if (tag >= 0) {
+ INSIST(tag < NUMERIC_NTAGS);
+
+ NEXTTOKEN(lex, opt | ISC_LEXOPT_NUMBER, &token);
+ if (token.type != isc_tokentype_number) {
+ BADTOKEN();
+ }
+
+ dst_key_setnum(*keyp, tag, token.value.as_ulong);
+ goto next;
+ }
+
+ /* Boolean metadata */
+ tag = find_booleandata(DST_AS_STR(token));
+ if (tag >= 0) {
+ INSIST(tag < BOOLEAN_NTAGS);
+
+ NEXTTOKEN(lex, opt, &token);
+ if (token.type != isc_tokentype_string) {
+ BADTOKEN();
+ }
+
+ if (strcmp(DST_AS_STR(token), "yes") == 0) {
+ dst_key_setbool(*keyp, tag, true);
+ } else if (strcmp(DST_AS_STR(token), "no") == 0) {
+ dst_key_setbool(*keyp, tag, false);
+ } else {
+ BADTOKEN();
+ }
+ goto next;
+ }
+
+ /* Timing metadata */
+ tag = find_timingdata(DST_AS_STR(token));
+ if (tag >= 0) {
+ uint32_t when;
+
+ INSIST(tag < TIMING_NTAGS);
+
+ NEXTTOKEN(lex, opt, &token);
+ if (token.type != isc_tokentype_string) {
+ BADTOKEN();
+ }
+
+ ret = dns_time32_fromtext(DST_AS_STR(token), &when);
+ if (ret != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+
+ dst_key_settime(*keyp, tag, when);
+ goto next;
+ }
+
+ /* Keystate metadata */
+ tag = find_keystatedata(DST_AS_STR(token));
+ if (tag >= 0) {
+ dst_key_state_t state;
+
+ INSIST(tag < KEYSTATES_NTAGS);
+
+ NEXTTOKEN(lex, opt, &token);
+ if (token.type != isc_tokentype_string) {
+ BADTOKEN();
+ }
+
+ ret = keystate_fromtext(DST_AS_STR(token), &state);
+ if (ret != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+
+ dst_key_setstate(*keyp, tag, state);
+ goto next;
+ }
+
+ next:
+ READLINE(lex, opt, &token);
+ }
+
+ /* Done, successfully parsed the whole file. */
+ ret = ISC_R_SUCCESS;
+
+cleanup:
+ if (lex != NULL) {
+ isc_lex_destroy(&lex);
+ }
+ return (ret);
+}
+
+static bool
+issymmetric(const dst_key_t *key) {
+ REQUIRE(dst_initialized);
+ REQUIRE(VALID_KEY(key));
+
+ /* XXXVIX this switch statement is too sparse to gen a jump table. */
+ switch (key->key_alg) {
+ case DST_ALG_RSASHA1:
+ case DST_ALG_NSEC3RSASHA1:
+ case DST_ALG_RSASHA256:
+ case DST_ALG_RSASHA512:
+ case DST_ALG_DH:
+ case DST_ALG_ECDSA256:
+ case DST_ALG_ECDSA384:
+ case DST_ALG_ED25519:
+ case DST_ALG_ED448:
+ return (false);
+ case DST_ALG_HMACMD5:
+ case DST_ALG_HMACSHA1:
+ case DST_ALG_HMACSHA224:
+ case DST_ALG_HMACSHA256:
+ case DST_ALG_HMACSHA384:
+ case DST_ALG_HMACSHA512:
+ case DST_ALG_GSSAPI:
+ return (true);
+ default:
+ return (false);
+ }
+}
+
+/*%
+ * Write key boolean metadata to a file pointer, preceded by 'tag'
+ */
+static void
+printbool(const dst_key_t *key, int type, const char *tag, FILE *stream) {
+ isc_result_t result;
+ bool value = 0;
+
+ result = dst_key_getbool(key, type, &value);
+ if (result != ISC_R_SUCCESS) {
+ return;
+ }
+ fprintf(stream, "%s: %s\n", tag, value ? "yes" : "no");
+}
+
+/*%
+ * Write key numeric metadata to a file pointer, preceded by 'tag'
+ */
+static void
+printnum(const dst_key_t *key, int type, const char *tag, FILE *stream) {
+ isc_result_t result;
+ uint32_t value = 0;
+
+ result = dst_key_getnum(key, type, &value);
+ if (result != ISC_R_SUCCESS) {
+ return;
+ }
+ fprintf(stream, "%s: %u\n", tag, value);
+}
+
+/*%
+ * Write key timing metadata to a file pointer, preceded by 'tag'
+ */
+static void
+printtime(const dst_key_t *key, int type, const char *tag, FILE *stream) {
+ isc_result_t result;
+ char output[26]; /* Minimum buffer as per ctime_r() specification. */
+ isc_stdtime_t when;
+ char utc[sizeof("YYYYMMDDHHSSMM")];
+ isc_buffer_t b;
+ isc_region_t r;
+
+ result = dst_key_gettime(key, type, &when);
+ if (result == ISC_R_NOTFOUND) {
+ return;
+ }
+
+ isc_stdtime_tostring(when, output, sizeof(output));
+ isc_buffer_init(&b, utc, sizeof(utc));
+ result = dns_time32_totext(when, &b);
+ if (result != ISC_R_SUCCESS) {
+ goto error;
+ }
+
+ isc_buffer_usedregion(&b, &r);
+ fprintf(stream, "%s: %.*s (%s)\n", tag, (int)r.length, r.base, output);
+ return;
+
+error:
+ fprintf(stream, "%s: (set, unable to display)\n", tag);
+}
+
+/*%
+ * Write key state metadata to a file pointer, preceded by 'tag'
+ */
+static void
+printstate(const dst_key_t *key, int type, const char *tag, FILE *stream) {
+ isc_result_t result;
+ dst_key_state_t value = 0;
+
+ result = dst_key_getstate(key, type, &value);
+ if (result != ISC_R_SUCCESS) {
+ return;
+ }
+ fprintf(stream, "%s: %s\n", tag, keystates[value]);
+}
+
+/*%
+ * Writes a key state to disk.
+ */
+static isc_result_t
+write_key_state(const dst_key_t *key, int type, const char *directory) {
+ FILE *fp;
+ isc_buffer_t fileb;
+ isc_buffer_t tmpb;
+ char filename[NAME_MAX];
+ char tmpname[NAME_MAX];
+ isc_result_t result;
+
+ REQUIRE(VALID_KEY(key));
+
+ /*
+ * Make the filename.
+ */
+ isc_buffer_init(&fileb, filename, sizeof(filename));
+ result = dst_key_buildfilename(key, DST_TYPE_STATE, directory, &fileb);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+
+ isc_buffer_init(&tmpb, tmpname, sizeof(tmpname));
+ result = dst_key_buildfilename(key, DST_TYPE_TEMPLATE, directory,
+ &tmpb);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+
+ mode_t mode = issymmetric(key) ? S_IRUSR | S_IWUSR
+ : S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH;
+
+ /* Create temporary public key file. */
+ fp = dst_key_open(tmpname, mode);
+ if (fp == NULL) {
+ return (DST_R_WRITEERROR);
+ }
+
+ /* Write key state */
+ if ((type & DST_TYPE_KEY) == 0) {
+ fprintf(fp, "; This is the state of key %d, for ", key->key_id);
+ result = dns_name_print(key->key_name, fp);
+ if (result != ISC_R_SUCCESS) {
+ return (dst_key_cleanup(tmpname, fp));
+ }
+ fputc('\n', fp);
+
+ fprintf(fp, "Algorithm: %u\n", key->key_alg);
+ fprintf(fp, "Length: %u\n", key->key_size);
+
+ printnum(key, DST_NUM_LIFETIME, "Lifetime", fp);
+ printnum(key, DST_NUM_PREDECESSOR, "Predecessor", fp);
+ printnum(key, DST_NUM_SUCCESSOR, "Successor", fp);
+
+ printbool(key, DST_BOOL_KSK, "KSK", fp);
+ printbool(key, DST_BOOL_ZSK, "ZSK", fp);
+
+ printtime(key, DST_TIME_CREATED, "Generated", fp);
+ printtime(key, DST_TIME_PUBLISH, "Published", fp);
+ printtime(key, DST_TIME_ACTIVATE, "Active", fp);
+ printtime(key, DST_TIME_INACTIVE, "Retired", fp);
+ printtime(key, DST_TIME_REVOKE, "Revoked", fp);
+ printtime(key, DST_TIME_DELETE, "Removed", fp);
+ printtime(key, DST_TIME_DSPUBLISH, "DSPublish", fp);
+ printtime(key, DST_TIME_DSDELETE, "DSRemoved", fp);
+ printtime(key, DST_TIME_SYNCPUBLISH, "PublishCDS", fp);
+ printtime(key, DST_TIME_SYNCDELETE, "DeleteCDS", fp);
+
+ printnum(key, DST_NUM_DSPUBCOUNT, "DSPubCount", fp);
+ printnum(key, DST_NUM_DSDELCOUNT, "DSDelCount", fp);
+
+ printtime(key, DST_TIME_DNSKEY, "DNSKEYChange", fp);
+ printtime(key, DST_TIME_ZRRSIG, "ZRRSIGChange", fp);
+ printtime(key, DST_TIME_KRRSIG, "KRRSIGChange", fp);
+ printtime(key, DST_TIME_DS, "DSChange", fp);
+
+ printstate(key, DST_KEY_DNSKEY, "DNSKEYState", fp);
+ printstate(key, DST_KEY_ZRRSIG, "ZRRSIGState", fp);
+ printstate(key, DST_KEY_KRRSIG, "KRRSIGState", fp);
+ printstate(key, DST_KEY_DS, "DSState", fp);
+ printstate(key, DST_KEY_GOAL, "GoalState", fp);
+ }
+
+ return (dst_key_close(tmpname, fp, filename));
+}
+
+/*%
+ * Writes a public key to disk in DNS format.
+ */
+static isc_result_t
+write_public_key(const dst_key_t *key, int type, const char *directory) {
+ FILE *fp;
+ isc_buffer_t keyb, tmpb, textb, fileb, classb;
+ isc_region_t r;
+ char tmpname[NAME_MAX];
+ char filename[NAME_MAX];
+ unsigned char key_array[DST_KEY_MAXSIZE];
+ char text_array[DST_KEY_MAXTEXTSIZE];
+ char class_array[10];
+ isc_result_t result;
+ dns_rdata_t rdata = DNS_RDATA_INIT;
+
+ REQUIRE(VALID_KEY(key));
+
+ isc_buffer_init(&keyb, key_array, sizeof(key_array));
+ isc_buffer_init(&textb, text_array, sizeof(text_array));
+ isc_buffer_init(&classb, class_array, sizeof(class_array));
+
+ result = dst_key_todns(key, &keyb);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+
+ isc_buffer_usedregion(&keyb, &r);
+ dns_rdata_fromregion(&rdata, key->key_class, dns_rdatatype_dnskey, &r);
+
+ result = dns_rdata_totext(&rdata, (dns_name_t *)NULL, &textb);
+ if (result != ISC_R_SUCCESS) {
+ return (DST_R_INVALIDPUBLICKEY);
+ }
+
+ result = dns_rdataclass_totext(key->key_class, &classb);
+ if (result != ISC_R_SUCCESS) {
+ return (DST_R_INVALIDPUBLICKEY);
+ }
+
+ /*
+ * Make the filename.
+ */
+ isc_buffer_init(&fileb, filename, sizeof(filename));
+ result = dst_key_buildfilename(key, DST_TYPE_PUBLIC, directory, &fileb);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+
+ isc_buffer_init(&tmpb, tmpname, sizeof(tmpname));
+ result = dst_key_buildfilename(key, DST_TYPE_TEMPLATE, directory,
+ &tmpb);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+
+ /* Create temporary public key file. */
+ mode_t mode = issymmetric(key) ? S_IRUSR | S_IWUSR
+ : S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH;
+
+ fp = dst_key_open(tmpname, mode);
+ if (fp == NULL) {
+ return (DST_R_WRITEERROR);
+ }
+
+ /* Write key information in comments */
+ if ((type & DST_TYPE_KEY) == 0) {
+ fprintf(fp, "; This is a %s%s-signing key, keyid %d, for ",
+ (key->key_flags & DNS_KEYFLAG_REVOKE) != 0 ? "revoked "
+ : "",
+ (key->key_flags & DNS_KEYFLAG_KSK) != 0 ? "key"
+ : "zone",
+ key->key_id);
+ result = dns_name_print(key->key_name, fp);
+ if (result != ISC_R_SUCCESS) {
+ return (dst_key_cleanup(tmpname, fp));
+ }
+ fputc('\n', fp);
+
+ printtime(key, DST_TIME_CREATED, "; Created", fp);
+ printtime(key, DST_TIME_PUBLISH, "; Publish", fp);
+ printtime(key, DST_TIME_ACTIVATE, "; Activate", fp);
+ printtime(key, DST_TIME_REVOKE, "; Revoke", fp);
+ printtime(key, DST_TIME_INACTIVE, "; Inactive", fp);
+ printtime(key, DST_TIME_DELETE, "; Delete", fp);
+ printtime(key, DST_TIME_SYNCPUBLISH, "; SyncPublish", fp);
+ printtime(key, DST_TIME_SYNCDELETE, "; SyncDelete", fp);
+ }
+
+ /* Now print the actual key */
+ result = dns_name_print(key->key_name, fp);
+ if (result != ISC_R_SUCCESS) {
+ return (dst_key_cleanup(tmpname, fp));
+ }
+ fprintf(fp, " ");
+
+ if (key->key_ttl != 0) {
+ fprintf(fp, "%u ", key->key_ttl);
+ }
+
+ isc_buffer_usedregion(&classb, &r);
+ if ((unsigned)fwrite(r.base, 1, r.length, fp) != r.length) {
+ return (dst_key_cleanup(tmpname, fp));
+ }
+
+ if ((type & DST_TYPE_KEY) != 0) {
+ fprintf(fp, " KEY ");
+ } else {
+ fprintf(fp, " DNSKEY ");
+ }
+
+ isc_buffer_usedregion(&textb, &r);
+ if ((unsigned)fwrite(r.base, 1, r.length, fp) != r.length) {
+ return (dst_key_cleanup(tmpname, fp));
+ }
+
+ fputc('\n', fp);
+
+ return (dst_key_close(tmpname, fp, filename));
+}
+
+static isc_result_t
+buildfilename(dns_name_t *name, dns_keytag_t id, unsigned int alg,
+ unsigned int type, const char *directory, isc_buffer_t *out) {
+ const char *suffix = "";
+ isc_result_t result;
+
+ REQUIRE(out != NULL);
+
+ if ((type & DST_TYPE_PRIVATE) != 0) {
+ suffix = ".private";
+ } else if ((type & DST_TYPE_PUBLIC) != 0) {
+ suffix = ".key";
+ } else if ((type & DST_TYPE_STATE) != 0) {
+ suffix = ".state";
+ } else if ((type & DST_TYPE_TEMPLATE) != 0) {
+ suffix = ".XXXXXX";
+ }
+
+ if (directory != NULL) {
+ if (isc_buffer_availablelength(out) < strlen(directory)) {
+ return (ISC_R_NOSPACE);
+ }
+ isc_buffer_putstr(out, directory);
+ if (strlen(directory) > 0U &&
+ directory[strlen(directory) - 1] != '/')
+ {
+ isc_buffer_putstr(out, "/");
+ }
+ }
+ if (isc_buffer_availablelength(out) < 1) {
+ return (ISC_R_NOSPACE);
+ }
+ isc_buffer_putstr(out, "K");
+ result = dns_name_tofilenametext(name, false, out);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+
+ return (isc_buffer_printf(out, "+%03d+%05d%s", alg, id, suffix));
+}
+
+static isc_result_t
+computeid(dst_key_t *key) {
+ isc_buffer_t dnsbuf;
+ unsigned char dns_array[DST_KEY_MAXSIZE];
+ isc_region_t r;
+ isc_result_t ret;
+
+ isc_buffer_init(&dnsbuf, dns_array, sizeof(dns_array));
+ ret = dst_key_todns(key, &dnsbuf);
+ if (ret != ISC_R_SUCCESS) {
+ return (ret);
+ }
+
+ isc_buffer_usedregion(&dnsbuf, &r);
+ key->key_id = dst_region_computeid(&r);
+ key->key_rid = dst_region_computerid(&r);
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+frombuffer(const dns_name_t *name, unsigned int alg, unsigned int flags,
+ unsigned int protocol, dns_rdataclass_t rdclass,
+ isc_buffer_t *source, isc_mem_t *mctx, dst_key_t **keyp) {
+ dst_key_t *key;
+ isc_result_t ret;
+
+ REQUIRE(dns_name_isabsolute(name));
+ REQUIRE(source != NULL);
+ REQUIRE(mctx != NULL);
+ REQUIRE(keyp != NULL && *keyp == NULL);
+
+ key = get_key_struct(name, alg, flags, protocol, 0, rdclass, 0, mctx);
+ if (key == NULL) {
+ return (ISC_R_NOMEMORY);
+ }
+
+ if (isc_buffer_remaininglength(source) > 0) {
+ ret = algorithm_status(alg);
+ if (ret != ISC_R_SUCCESS) {
+ dst_key_free(&key);
+ return (ret);
+ }
+ if (key->func->fromdns == NULL) {
+ dst_key_free(&key);
+ return (DST_R_UNSUPPORTEDALG);
+ }
+
+ ret = key->func->fromdns(key, source);
+ if (ret != ISC_R_SUCCESS) {
+ dst_key_free(&key);
+ return (ret);
+ }
+ }
+
+ *keyp = key;
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+algorithm_status(unsigned int alg) {
+ REQUIRE(dst_initialized);
+
+ if (dst_algorithm_supported(alg)) {
+ return (ISC_R_SUCCESS);
+ }
+ return (DST_R_UNSUPPORTEDALG);
+}
+
+static isc_result_t
+addsuffix(char *filename, int len, const char *odirname, const char *ofilename,
+ const char *suffix) {
+ int olen = strlen(ofilename);
+ int n;
+
+ if (olen > 1 && ofilename[olen - 1] == '.') {
+ olen -= 1;
+ } else if (olen > 8 && strcmp(ofilename + olen - 8, ".private") == 0) {
+ olen -= 8;
+ } else if (olen > 4 && strcmp(ofilename + olen - 4, ".key") == 0) {
+ olen -= 4;
+ }
+
+ if (odirname == NULL) {
+ n = snprintf(filename, len, "%.*s%s", olen, ofilename, suffix);
+ } else {
+ n = snprintf(filename, len, "%s/%.*s%s", odirname, olen,
+ ofilename, suffix);
+ }
+ if (n < 0) {
+ return (ISC_R_FAILURE);
+ }
+ if (n >= len) {
+ return (ISC_R_NOSPACE);
+ }
+ return (ISC_R_SUCCESS);
+}
+
+isc_buffer_t *
+dst_key_tkeytoken(const dst_key_t *key) {
+ REQUIRE(VALID_KEY(key));
+ return (key->key_tkeytoken);
+}
+
+/*
+ * A key is considered unused if it does not have any timing metadata set
+ * other than "Created".
+ *
+ */
+bool
+dst_key_is_unused(dst_key_t *key) {
+ isc_stdtime_t val;
+ dst_key_state_t st;
+ int state_type;
+ bool state_type_set;
+
+ REQUIRE(VALID_KEY(key));
+
+ /*
+ * None of the key timing metadata, except Created, may be set. Key
+ * state times may be set only if their respective state is HIDDEN.
+ */
+ for (int i = 0; i < DST_MAX_TIMES + 1; i++) {
+ state_type_set = false;
+
+ switch (i) {
+ case DST_TIME_CREATED:
+ break;
+ case DST_TIME_DNSKEY:
+ state_type = DST_KEY_DNSKEY;
+ state_type_set = true;
+ break;
+ case DST_TIME_ZRRSIG:
+ state_type = DST_KEY_ZRRSIG;
+ state_type_set = true;
+ break;
+ case DST_TIME_KRRSIG:
+ state_type = DST_KEY_KRRSIG;
+ state_type_set = true;
+ break;
+ case DST_TIME_DS:
+ state_type = DST_KEY_DS;
+ state_type_set = true;
+ break;
+ default:
+ break;
+ }
+
+ /* Created is fine. */
+ if (i == DST_TIME_CREATED) {
+ continue;
+ }
+ /* No such timing metadata found, that is fine too. */
+ if (dst_key_gettime(key, i, &val) == ISC_R_NOTFOUND) {
+ continue;
+ }
+ /*
+ * Found timing metadata and it is not related to key states.
+ * This key is used.
+ */
+ if (!state_type_set) {
+ return (false);
+ }
+ /*
+ * If the state is not HIDDEN, the key is in use.
+ * If the state is not set, this is odd and we default to NA.
+ */
+ if (dst_key_getstate(key, state_type, &st) != ISC_R_SUCCESS) {
+ st = DST_KEY_STATE_NA;
+ }
+ if (st != DST_KEY_STATE_HIDDEN) {
+ return (false);
+ }
+ }
+ /* This key is unused. */
+ return (true);
+}
+
+isc_result_t
+dst_key_role(dst_key_t *key, bool *ksk, bool *zsk) {
+ bool k = false, z = false;
+ isc_result_t result, ret = ISC_R_SUCCESS;
+
+ if (ksk != NULL) {
+ result = dst_key_getbool(key, DST_BOOL_KSK, &k);
+ if (result == ISC_R_SUCCESS) {
+ *ksk = k;
+ } else {
+ *ksk = ((dst_key_flags(key) & DNS_KEYFLAG_KSK) != 0);
+ ret = result;
+ }
+ }
+
+ if (zsk != NULL) {
+ result = dst_key_getbool(key, DST_BOOL_ZSK, &z);
+ if (result == ISC_R_SUCCESS) {
+ *zsk = z;
+ } else {
+ *zsk = ((dst_key_flags(key) & DNS_KEYFLAG_KSK) == 0);
+ ret = result;
+ }
+ }
+ return (ret);
+}
+
+/* Hints on key whether it can be published and/or used for signing. */
+
+bool
+dst_key_is_published(dst_key_t *key, isc_stdtime_t now,
+ isc_stdtime_t *publish) {
+ dst_key_state_t state;
+ isc_result_t result;
+ isc_stdtime_t when;
+ bool state_ok = true, time_ok = false;
+
+ REQUIRE(VALID_KEY(key));
+
+ result = dst_key_gettime(key, DST_TIME_PUBLISH, &when);
+ if (result == ISC_R_SUCCESS) {
+ *publish = when;
+ time_ok = (when <= now);
+ }
+
+ /* Check key states:
+ * If the DNSKEY state is RUMOURED or OMNIPRESENT, it means it
+ * should be published.
+ */
+ result = dst_key_getstate(key, DST_KEY_DNSKEY, &state);
+ if (result == ISC_R_SUCCESS) {
+ state_ok = ((state == DST_KEY_STATE_RUMOURED) ||
+ (state == DST_KEY_STATE_OMNIPRESENT));
+ /*
+ * Key states trump timing metadata.
+ * Ignore inactive time.
+ */
+ time_ok = true;
+ }
+
+ return (state_ok && time_ok);
+}
+
+bool
+dst_key_is_active(dst_key_t *key, isc_stdtime_t now) {
+ dst_key_state_t state;
+ isc_result_t result;
+ isc_stdtime_t when = 0;
+ bool ksk = false, zsk = false, inactive = false;
+ bool ds_ok = true, zrrsig_ok = true, time_ok = false;
+
+ REQUIRE(VALID_KEY(key));
+
+ result = dst_key_gettime(key, DST_TIME_INACTIVE, &when);
+ if (result == ISC_R_SUCCESS) {
+ inactive = (when <= now);
+ }
+
+ result = dst_key_gettime(key, DST_TIME_ACTIVATE, &when);
+ if (result == ISC_R_SUCCESS) {
+ time_ok = (when <= now);
+ }
+
+ (void)dst_key_role(key, &ksk, &zsk);
+
+ /* Check key states:
+ * KSK: If the DS is RUMOURED or OMNIPRESENT the key is considered
+ * active.
+ */
+ if (ksk) {
+ result = dst_key_getstate(key, DST_KEY_DS, &state);
+ if (result == ISC_R_SUCCESS) {
+ ds_ok = ((state == DST_KEY_STATE_RUMOURED) ||
+ (state == DST_KEY_STATE_OMNIPRESENT));
+ /*
+ * Key states trump timing metadata.
+ * Ignore inactive time.
+ */
+ time_ok = true;
+ inactive = false;
+ }
+ }
+ /*
+ * ZSK: If the ZRRSIG state is RUMOURED or OMNIPRESENT, it means the
+ * key is active.
+ */
+ if (zsk) {
+ result = dst_key_getstate(key, DST_KEY_ZRRSIG, &state);
+ if (result == ISC_R_SUCCESS) {
+ zrrsig_ok = ((state == DST_KEY_STATE_RUMOURED) ||
+ (state == DST_KEY_STATE_OMNIPRESENT));
+ /*
+ * Key states trump timing metadata.
+ * Ignore inactive time.
+ */
+ time_ok = true;
+ inactive = false;
+ }
+ }
+ return (ds_ok && zrrsig_ok && time_ok && !inactive);
+}
+
+bool
+dst_key_is_signing(dst_key_t *key, int role, isc_stdtime_t now,
+ isc_stdtime_t *active) {
+ dst_key_state_t state;
+ isc_result_t result;
+ isc_stdtime_t when = 0;
+ bool ksk = false, zsk = false, inactive = false;
+ bool krrsig_ok = true, zrrsig_ok = true, time_ok = false;
+
+ REQUIRE(VALID_KEY(key));
+
+ result = dst_key_gettime(key, DST_TIME_INACTIVE, &when);
+ if (result == ISC_R_SUCCESS) {
+ inactive = (when <= now);
+ }
+
+ result = dst_key_gettime(key, DST_TIME_ACTIVATE, &when);
+ if (result == ISC_R_SUCCESS) {
+ *active = when;
+ time_ok = (when <= now);
+ }
+
+ (void)dst_key_role(key, &ksk, &zsk);
+
+ /* Check key states:
+ * If the RRSIG state is RUMOURED or OMNIPRESENT, it means the key
+ * is active.
+ */
+ if (ksk && role == DST_BOOL_KSK) {
+ result = dst_key_getstate(key, DST_KEY_KRRSIG, &state);
+ if (result == ISC_R_SUCCESS) {
+ krrsig_ok = ((state == DST_KEY_STATE_RUMOURED) ||
+ (state == DST_KEY_STATE_OMNIPRESENT));
+ /*
+ * Key states trump timing metadata.
+ * Ignore inactive time.
+ */
+ time_ok = true;
+ inactive = false;
+ }
+ } else if (zsk && role == DST_BOOL_ZSK) {
+ result = dst_key_getstate(key, DST_KEY_ZRRSIG, &state);
+ if (result == ISC_R_SUCCESS) {
+ zrrsig_ok = ((state == DST_KEY_STATE_RUMOURED) ||
+ (state == DST_KEY_STATE_OMNIPRESENT));
+ /*
+ * Key states trump timing metadata.
+ * Ignore inactive time.
+ */
+ time_ok = true;
+ inactive = false;
+ }
+ }
+ return (krrsig_ok && zrrsig_ok && time_ok && !inactive);
+}
+
+bool
+dst_key_is_revoked(dst_key_t *key, isc_stdtime_t now, isc_stdtime_t *revoke) {
+ isc_result_t result;
+ isc_stdtime_t when = 0;
+ bool time_ok = false;
+
+ REQUIRE(VALID_KEY(key));
+
+ result = dst_key_gettime(key, DST_TIME_REVOKE, &when);
+ if (result == ISC_R_SUCCESS) {
+ *revoke = when;
+ time_ok = (when <= now);
+ }
+
+ return (time_ok);
+}
+
+bool
+dst_key_is_removed(dst_key_t *key, isc_stdtime_t now, isc_stdtime_t *remove) {
+ dst_key_state_t state;
+ isc_result_t result;
+ isc_stdtime_t when = 0;
+ bool state_ok = true, time_ok = false;
+
+ REQUIRE(VALID_KEY(key));
+
+ if (dst_key_is_unused(key)) {
+ /* This key was never used. */
+ return (false);
+ }
+
+ result = dst_key_gettime(key, DST_TIME_DELETE, &when);
+ if (result == ISC_R_SUCCESS) {
+ *remove = when;
+ time_ok = (when <= now);
+ }
+
+ /* Check key states:
+ * If the DNSKEY state is UNRETENTIVE or HIDDEN, it means the key
+ * should not be published.
+ */
+ result = dst_key_getstate(key, DST_KEY_DNSKEY, &state);
+ if (result == ISC_R_SUCCESS) {
+ state_ok = ((state == DST_KEY_STATE_UNRETENTIVE) ||
+ (state == DST_KEY_STATE_HIDDEN));
+ /*
+ * Key states trump timing metadata.
+ * Ignore delete time.
+ */
+ time_ok = true;
+ }
+
+ return (state_ok && time_ok);
+}
+
+dst_key_state_t
+dst_key_goal(dst_key_t *key) {
+ dst_key_state_t state;
+ isc_result_t result;
+
+ REQUIRE(VALID_KEY(key));
+
+ result = dst_key_getstate(key, DST_KEY_GOAL, &state);
+ if (result == ISC_R_SUCCESS) {
+ return (state);
+ }
+ return (DST_KEY_STATE_HIDDEN);
+}
+
+bool
+dst_key_haskasp(dst_key_t *key) {
+ REQUIRE(VALID_KEY(key));
+
+ return (key->kasp);
+}
+
+void
+dst_key_copy_metadata(dst_key_t *to, dst_key_t *from) {
+ dst_key_state_t state;
+ isc_stdtime_t when;
+ uint32_t num;
+ bool yesno;
+ isc_result_t result;
+
+ REQUIRE(VALID_KEY(to));
+ REQUIRE(VALID_KEY(from));
+
+ for (int i = 0; i < DST_MAX_TIMES + 1; i++) {
+ result = dst_key_gettime(from, i, &when);
+ if (result == ISC_R_SUCCESS) {
+ dst_key_settime(to, i, when);
+ } else {
+ dst_key_unsettime(to, i);
+ }
+ }
+
+ for (int i = 0; i < DST_MAX_NUMERIC + 1; i++) {
+ result = dst_key_getnum(from, i, &num);
+ if (result == ISC_R_SUCCESS) {
+ dst_key_setnum(to, i, num);
+ } else {
+ dst_key_unsetnum(to, i);
+ }
+ }
+
+ for (int i = 0; i < DST_MAX_BOOLEAN + 1; i++) {
+ result = dst_key_getbool(from, i, &yesno);
+ if (result == ISC_R_SUCCESS) {
+ dst_key_setbool(to, i, yesno);
+ } else {
+ dst_key_unsetbool(to, i);
+ }
+ }
+
+ for (int i = 0; i < DST_MAX_KEYSTATES + 1; i++) {
+ result = dst_key_getstate(from, i, &state);
+ if (result == ISC_R_SUCCESS) {
+ dst_key_setstate(to, i, state);
+ } else {
+ dst_key_unsetstate(to, i);
+ }
+ }
+
+ dst_key_setmodified(to, dst_key_ismodified(from));
+}
+
+const char *
+dst_hmac_algorithm_totext(dst_algorithm_t alg) {
+ switch (alg) {
+ case DST_ALG_HMACMD5:
+ return ("hmac-md5");
+ case DST_ALG_HMACSHA1:
+ return ("hmac-sha1");
+ case DST_ALG_HMACSHA224:
+ return ("hmac-sha224");
+ case DST_ALG_HMACSHA256:
+ return ("hmac-sha256");
+ case DST_ALG_HMACSHA384:
+ return ("hmac-sha384");
+ case DST_ALG_HMACSHA512:
+ return ("hmac-sha512");
+ default:
+ return ("unknown");
+ }
+}
diff --git a/lib/dns/dst_internal.h b/lib/dns/dst_internal.h
new file mode 100644
index 0000000..e8eca94
--- /dev/null
+++ b/lib/dns/dst_internal.h
@@ -0,0 +1,254 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0 AND ISC
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/*
+ * Portions Copyright (C) Network Associates, Inc.
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND ISC AND NETWORK ASSOCIATES DISCLAIMS
+ * ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE
+ * FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR
+ * IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#pragma once
+
+#include <inttypes.h>
+#include <stdbool.h>
+
+#include <openssl/dh.h>
+#include <openssl/err.h>
+#include <openssl/evp.h>
+#include <openssl/objects.h>
+#include <openssl/rsa.h>
+
+#include <isc/buffer.h>
+#include <isc/hmac.h>
+#include <isc/lang.h>
+#include <isc/magic.h>
+#include <isc/md.h>
+#include <isc/refcount.h>
+#include <isc/region.h>
+#include <isc/stdtime.h>
+#include <isc/types.h>
+
+#include <dns/time.h>
+
+#include <dst/dst.h>
+
+ISC_LANG_BEGINDECLS
+
+#define KEY_MAGIC ISC_MAGIC('D', 'S', 'T', 'K')
+#define CTX_MAGIC ISC_MAGIC('D', 'S', 'T', 'C')
+
+#define VALID_KEY(x) ISC_MAGIC_VALID(x, KEY_MAGIC)
+#define VALID_CTX(x) ISC_MAGIC_VALID(x, CTX_MAGIC)
+
+/***
+ *** Types
+ ***/
+
+typedef struct dst_func dst_func_t;
+
+typedef struct dst_hmac_key dst_hmac_key_t;
+
+/*%
+ * Indicate whether a DST context will be used for signing
+ * or for verification
+ */
+typedef enum { DO_SIGN, DO_VERIFY } dst_use_t;
+
+/*% DST Key Structure */
+struct dst_key {
+ unsigned int magic;
+ isc_refcount_t refs;
+ isc_mutex_t mdlock; /*%< lock for read/write metadata */
+ dns_name_t *key_name; /*%< name of the key */
+ unsigned int key_size; /*%< size of the key in bits */
+ unsigned int key_proto; /*%< protocols this key is used for
+ * */
+ unsigned int key_alg; /*%< algorithm of the key */
+ uint32_t key_flags; /*%< flags of the public key */
+ uint16_t key_id; /*%< identifier of the key */
+ uint16_t key_rid; /*%< identifier of the key when
+ * revoked */
+ uint16_t key_bits; /*%< hmac digest bits */
+ dns_rdataclass_t key_class; /*%< class of the key record */
+ dns_ttl_t key_ttl; /*%< default/initial dnskey ttl */
+ isc_mem_t *mctx; /*%< memory context */
+ char *engine; /*%< engine name (HSM) */
+ char *label; /*%< engine label (HSM) */
+ union {
+ void *generic;
+ dns_gss_ctx_id_t gssctx;
+ DH *dh;
+ EVP_PKEY *pkey;
+ dst_hmac_key_t *hmac_key;
+ } keydata; /*%< pointer to key in crypto pkg fmt */
+
+ isc_stdtime_t times[DST_MAX_TIMES + 1]; /*%< timing metadata */
+ bool timeset[DST_MAX_TIMES + 1]; /*%< data set? */
+
+ uint32_t nums[DST_MAX_NUMERIC + 1]; /*%< numeric metadata
+ * */
+ bool numset[DST_MAX_NUMERIC + 1]; /*%< data set? */
+
+ bool bools[DST_MAX_BOOLEAN + 1]; /*%< boolean metadata
+ * */
+ bool boolset[DST_MAX_BOOLEAN + 1]; /*%< data set? */
+
+ dst_key_state_t keystates[DST_MAX_KEYSTATES + 1]; /*%< key states
+ * */
+ bool keystateset[DST_MAX_KEYSTATES + 1]; /*%< data
+ * set? */
+
+ bool kasp; /*%< key has kasp state */
+ bool inactive; /*%< private key not present as it is
+ * inactive */
+ bool external; /*%< external key */
+ bool modified; /*%< set to true if key file metadata has changed */
+
+ int fmt_major; /*%< private key format, major version
+ * */
+ int fmt_minor; /*%< private key format, minor version
+ * */
+
+ dst_func_t *func; /*%< crypto package specific functions */
+ isc_buffer_t *key_tkeytoken; /*%< TKEY token data */
+};
+
+struct dst_context {
+ unsigned int magic;
+ dst_use_t use;
+ dst_key_t *key;
+ isc_mem_t *mctx;
+ isc_logcategory_t *category;
+ union {
+ void *generic;
+ dst_gssapi_signverifyctx_t *gssctx;
+ isc_hmac_t *hmac_ctx;
+ EVP_MD_CTX *evp_md_ctx;
+ } ctxdata;
+};
+
+struct dst_func {
+ /*
+ * Context functions
+ */
+ isc_result_t (*createctx)(dst_key_t *key, dst_context_t *dctx);
+ isc_result_t (*createctx2)(dst_key_t *key, int maxbits,
+ dst_context_t *dctx);
+ void (*destroyctx)(dst_context_t *dctx);
+ isc_result_t (*adddata)(dst_context_t *dctx, const isc_region_t *data);
+
+ /*
+ * Key operations
+ */
+ isc_result_t (*sign)(dst_context_t *dctx, isc_buffer_t *sig);
+ isc_result_t (*verify)(dst_context_t *dctx, const isc_region_t *sig);
+ isc_result_t (*verify2)(dst_context_t *dctx, int maxbits,
+ const isc_region_t *sig);
+ isc_result_t (*computesecret)(const dst_key_t *pub,
+ const dst_key_t *priv,
+ isc_buffer_t *secret);
+ bool (*compare)(const dst_key_t *key1, const dst_key_t *key2);
+ bool (*paramcompare)(const dst_key_t *key1, const dst_key_t *key2);
+ isc_result_t (*generate)(dst_key_t *key, int parms,
+ void (*callback)(int));
+ bool (*isprivate)(const dst_key_t *key);
+ void (*destroy)(dst_key_t *key);
+
+ /* conversion functions */
+ isc_result_t (*todns)(const dst_key_t *key, isc_buffer_t *data);
+ isc_result_t (*fromdns)(dst_key_t *key, isc_buffer_t *data);
+ isc_result_t (*tofile)(const dst_key_t *key, const char *directory);
+ isc_result_t (*parse)(dst_key_t *key, isc_lex_t *lexer, dst_key_t *pub);
+
+ /* cleanup */
+ void (*cleanup)(void);
+
+ isc_result_t (*fromlabel)(dst_key_t *key, const char *engine,
+ const char *label, const char *pin);
+ isc_result_t (*dump)(dst_key_t *key, isc_mem_t *mctx, char **buffer,
+ int *length);
+ isc_result_t (*restore)(dst_key_t *key, const char *keystr);
+};
+
+/*%
+ * Initializers
+ */
+isc_result_t
+dst__openssl_init(const char *engine);
+
+isc_result_t
+dst__hmacmd5_init(struct dst_func **funcp);
+isc_result_t
+dst__hmacsha1_init(struct dst_func **funcp);
+isc_result_t
+dst__hmacsha224_init(struct dst_func **funcp);
+isc_result_t
+dst__hmacsha256_init(struct dst_func **funcp);
+isc_result_t
+dst__hmacsha384_init(struct dst_func **funcp);
+isc_result_t
+dst__hmacsha512_init(struct dst_func **funcp);
+isc_result_t
+dst__openssldh_init(struct dst_func **funcp);
+isc_result_t
+dst__opensslrsa_init(struct dst_func **funcp, unsigned char algorithm);
+isc_result_t
+dst__opensslecdsa_init(struct dst_func **funcp);
+#if HAVE_OPENSSL_ED25519 || HAVE_OPENSSL_ED448
+isc_result_t
+dst__openssleddsa_init(struct dst_func **funcp);
+#endif /* HAVE_OPENSSL_ED25519 || HAVE_OPENSSL_ED448 */
+#if HAVE_GSSAPI
+isc_result_t
+dst__gssapi_init(struct dst_func **funcp);
+#endif /* HAVE_GSSAPI*/
+
+/*%
+ * Destructors
+ */
+void
+dst__openssl_destroy(void);
+
+/*%
+ * Memory allocators using the DST memory pool.
+ */
+void *
+dst__mem_alloc(size_t size);
+void
+dst__mem_free(void *ptr);
+void *
+dst__mem_realloc(void *ptr, size_t size);
+
+/*%
+ * Secure private file handling
+ */
+FILE *
+dst_key_open(char *tmpname, mode_t mode);
+isc_result_t
+dst_key_close(char *tmpname, FILE *fp, char *filename);
+isc_result_t
+dst_key_cleanup(char *tmpname, FILE *fp);
+
+ISC_LANG_ENDDECLS
+
+/*! \file */
diff --git a/lib/dns/dst_openssl.h b/lib/dns/dst_openssl.h
new file mode 100644
index 0000000..819af0f
--- /dev/null
+++ b/lib/dns/dst_openssl.h
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#pragma once
+
+#include <openssl/bn.h>
+#include <openssl/conf.h>
+#include <openssl/crypto.h>
+#include <openssl/err.h>
+#include <openssl/evp.h>
+#include <openssl/rand.h>
+
+#include <isc/lang.h>
+#include <isc/log.h>
+#include <isc/result.h>
+
+#if !HAVE_BN_GENCB_NEW
+/*
+ * These are new in OpenSSL 1.1.0. BN_GENCB _cb needs to be declared in
+ * the function like this before the BN_GENCB_new call:
+ *
+ * #if !HAVE_BN_GENCB_NEW
+ * _cb;
+ * #endif
+ */
+#define BN_GENCB_free(x) ((void)0)
+#define BN_GENCB_new() (&_cb)
+#define BN_GENCB_get_arg(x) ((x)->arg)
+#endif /* !HAVE_BN_GENCB_NEW */
+
+#if OPENSSL_VERSION_NUMBER >= 0x10100000L
+/*
+ * EVP_dss1() is a version of EVP_sha1() that was needed prior to
+ * 1.1.0 because there was a link between digests and signing algorithms;
+ * the link has been eliminated and EVP_sha1() can be used now instead.
+ */
+#define EVP_dss1 EVP_sha1
+#endif /* if OPENSSL_VERSION_NUMBER >= 0x10100000L */
+
+ISC_LANG_BEGINDECLS
+
+isc_result_t
+dst__openssl_toresult(isc_result_t fallback);
+
+isc_result_t
+dst__openssl_toresult2(const char *funcname, isc_result_t fallback);
+
+isc_result_t
+dst__openssl_toresult3(isc_logcategory_t *category, const char *funcname,
+ isc_result_t fallback);
+
+#if !defined(OPENSSL_NO_ENGINE) && OPENSSL_API_LEVEL < 30000
+ENGINE *
+dst__openssl_getengine(const char *engine);
+#endif /* if !defined(OPENSSL_NO_ENGINE) && OPENSSL_API_LEVEL < 30000 */
+
+ISC_LANG_ENDDECLS
diff --git a/lib/dns/dst_parse.c b/lib/dns/dst_parse.c
new file mode 100644
index 0000000..a3ff011
--- /dev/null
+++ b/lib/dns/dst_parse.c
@@ -0,0 +1,798 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0 AND ISC
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/*
+ * Copyright (C) Network Associates, Inc.
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND ISC AND NETWORK ASSOCIATES DISCLAIMS
+ * ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE
+ * FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR
+ * IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include "dst_parse.h"
+#include <inttypes.h>
+#include <stdbool.h>
+#include <unistd.h>
+
+#include <isc/base64.h>
+#include <isc/dir.h>
+#include <isc/file.h>
+#include <isc/lex.h>
+#include <isc/mem.h>
+#include <isc/print.h>
+#include <isc/stdtime.h>
+#include <isc/string.h>
+#include <isc/util.h>
+
+#include <dns/log.h>
+#include <dns/time.h>
+
+#include "dst_internal.h"
+#include "isc/result.h"
+
+#define DST_AS_STR(t) ((t).value.as_textregion.base)
+
+#define PRIVATE_KEY_STR "Private-key-format:"
+#define ALGORITHM_STR "Algorithm:"
+
+#define TIMING_NTAGS (DST_MAX_TIMES + 1)
+static const char *timetags[TIMING_NTAGS] = {
+ "Created:", "Publish:", "Activate:", "Revoke:",
+ "Inactive:", "Delete:", "DSPublish:", "SyncPublish:",
+ "SyncDelete:", NULL, NULL, NULL,
+ NULL
+};
+
+#define NUMERIC_NTAGS (DST_MAX_NUMERIC + 1)
+static const char *numerictags[NUMERIC_NTAGS] = {
+ "Predecessor:", "Successor:", "MaxTTL:", "RollPeriod:", NULL, NULL, NULL
+};
+
+struct parse_map {
+ const int value;
+ const char *tag;
+};
+
+static struct parse_map map[] = { { TAG_RSA_MODULUS, "Modulus:" },
+ { TAG_RSA_PUBLICEXPONENT, "PublicExponent:" },
+ { TAG_RSA_PRIVATEEXPONENT, "PrivateExponent"
+ ":" },
+ { TAG_RSA_PRIME1, "Prime1:" },
+ { TAG_RSA_PRIME2, "Prime2:" },
+ { TAG_RSA_EXPONENT1, "Exponent1:" },
+ { TAG_RSA_EXPONENT2, "Exponent2:" },
+ { TAG_RSA_COEFFICIENT, "Coefficient:" },
+ { TAG_RSA_ENGINE, "Engine:" },
+ { TAG_RSA_LABEL, "Label:" },
+
+ { TAG_DH_PRIME, "Prime(p):" },
+ { TAG_DH_GENERATOR, "Generator(g):" },
+ { TAG_DH_PRIVATE, "Private_value(x):" },
+ { TAG_DH_PUBLIC, "Public_value(y):" },
+
+ { TAG_ECDSA_PRIVATEKEY, "PrivateKey:" },
+ { TAG_ECDSA_ENGINE, "Engine:" },
+ { TAG_ECDSA_LABEL, "Label:" },
+
+ { TAG_EDDSA_PRIVATEKEY, "PrivateKey:" },
+ { TAG_EDDSA_ENGINE, "Engine:" },
+ { TAG_EDDSA_LABEL, "Label:" },
+
+ { TAG_HMACMD5_KEY, "Key:" },
+ { TAG_HMACMD5_BITS, "Bits:" },
+
+ { TAG_HMACSHA1_KEY, "Key:" },
+ { TAG_HMACSHA1_BITS, "Bits:" },
+
+ { TAG_HMACSHA224_KEY, "Key:" },
+ { TAG_HMACSHA224_BITS, "Bits:" },
+
+ { TAG_HMACSHA256_KEY, "Key:" },
+ { TAG_HMACSHA256_BITS, "Bits:" },
+
+ { TAG_HMACSHA384_KEY, "Key:" },
+ { TAG_HMACSHA384_BITS, "Bits:" },
+
+ { TAG_HMACSHA512_KEY, "Key:" },
+ { TAG_HMACSHA512_BITS, "Bits:" },
+
+ { 0, NULL } };
+
+static int
+find_value(const char *s, const unsigned int alg) {
+ int i;
+
+ for (i = 0; map[i].tag != NULL; i++) {
+ if (strcasecmp(s, map[i].tag) == 0 &&
+ (TAG_ALG(map[i].value) == alg))
+ {
+ return (map[i].value);
+ }
+ }
+ return (-1);
+}
+
+static const char *
+find_tag(const int value) {
+ int i;
+
+ for (i = 0;; i++) {
+ if (map[i].tag == NULL) {
+ return (NULL);
+ } else if (value == map[i].value) {
+ return (map[i].tag);
+ }
+ }
+}
+
+static int
+find_metadata(const char *s, const char *tags[], int ntags) {
+ int i;
+
+ for (i = 0; i < ntags; i++) {
+ if (tags[i] != NULL && strcasecmp(s, tags[i]) == 0) {
+ return (i);
+ }
+ }
+
+ return (-1);
+}
+
+static int
+find_timedata(const char *s) {
+ return (find_metadata(s, timetags, TIMING_NTAGS));
+}
+
+static int
+find_numericdata(const char *s) {
+ return (find_metadata(s, numerictags, NUMERIC_NTAGS));
+}
+
+static int
+check_rsa(const dst_private_t *priv, bool external) {
+ int i, j;
+ bool have[RSA_NTAGS];
+ bool ok;
+ unsigned int mask;
+
+ if (external) {
+ return ((priv->nelements == 0) ? 0 : -1);
+ }
+
+ for (i = 0; i < RSA_NTAGS; i++) {
+ have[i] = false;
+ }
+
+ for (j = 0; j < priv->nelements; j++) {
+ for (i = 0; i < RSA_NTAGS; i++) {
+ if (priv->elements[j].tag == TAG(DST_ALG_RSA, i)) {
+ break;
+ }
+ }
+ if (i == RSA_NTAGS) {
+ return (-1);
+ }
+ have[i] = true;
+ }
+
+ mask = (1ULL << TAG_SHIFT) - 1;
+
+ if (have[TAG_RSA_ENGINE & mask]) {
+ ok = have[TAG_RSA_MODULUS & mask] &&
+ have[TAG_RSA_PUBLICEXPONENT & mask] &&
+ have[TAG_RSA_LABEL & mask];
+ } else {
+ ok = have[TAG_RSA_MODULUS & mask] &&
+ have[TAG_RSA_PUBLICEXPONENT & mask] &&
+ have[TAG_RSA_PRIVATEEXPONENT & mask] &&
+ have[TAG_RSA_PRIME1 & mask] &&
+ have[TAG_RSA_PRIME2 & mask] &&
+ have[TAG_RSA_EXPONENT1 & mask] &&
+ have[TAG_RSA_EXPONENT2 & mask] &&
+ have[TAG_RSA_COEFFICIENT & mask];
+ }
+ return (ok ? 0 : -1);
+}
+
+static int
+check_dh(const dst_private_t *priv) {
+ int i, j;
+ if (priv->nelements != DH_NTAGS) {
+ return (-1);
+ }
+ for (i = 0; i < DH_NTAGS; i++) {
+ for (j = 0; j < priv->nelements; j++) {
+ if (priv->elements[j].tag == TAG(DST_ALG_DH, i)) {
+ break;
+ }
+ }
+ if (j == priv->nelements) {
+ return (-1);
+ }
+ }
+ return (0);
+}
+
+static int
+check_ecdsa(const dst_private_t *priv, bool external) {
+ int i, j;
+ bool have[ECDSA_NTAGS];
+ bool ok;
+ unsigned int mask;
+
+ if (external) {
+ return ((priv->nelements == 0) ? 0 : -1);
+ }
+
+ for (i = 0; i < ECDSA_NTAGS; i++) {
+ have[i] = false;
+ }
+ for (j = 0; j < priv->nelements; j++) {
+ for (i = 0; i < ECDSA_NTAGS; i++) {
+ if (priv->elements[j].tag == TAG(DST_ALG_ECDSA256, i)) {
+ break;
+ }
+ }
+ if (i == ECDSA_NTAGS) {
+ return (-1);
+ }
+ have[i] = true;
+ }
+
+ mask = (1ULL << TAG_SHIFT) - 1;
+
+ if (have[TAG_ECDSA_ENGINE & mask]) {
+ ok = have[TAG_ECDSA_LABEL & mask];
+ } else {
+ ok = have[TAG_ECDSA_PRIVATEKEY & mask];
+ }
+ return (ok ? 0 : -1);
+}
+
+static int
+check_eddsa(const dst_private_t *priv, bool external) {
+ int i, j;
+ bool have[EDDSA_NTAGS];
+ bool ok;
+ unsigned int mask;
+
+ if (external) {
+ return ((priv->nelements == 0) ? 0 : -1);
+ }
+
+ for (i = 0; i < EDDSA_NTAGS; i++) {
+ have[i] = false;
+ }
+ for (j = 0; j < priv->nelements; j++) {
+ for (i = 0; i < EDDSA_NTAGS; i++) {
+ if (priv->elements[j].tag == TAG(DST_ALG_ED25519, i)) {
+ break;
+ }
+ }
+ if (i == EDDSA_NTAGS) {
+ return (-1);
+ }
+ have[i] = true;
+ }
+
+ mask = (1ULL << TAG_SHIFT) - 1;
+
+ if (have[TAG_EDDSA_ENGINE & mask]) {
+ ok = have[TAG_EDDSA_LABEL & mask];
+ } else {
+ ok = have[TAG_EDDSA_PRIVATEKEY & mask];
+ }
+ return (ok ? 0 : -1);
+}
+
+static int
+check_hmac_md5(const dst_private_t *priv, bool old) {
+ int i, j;
+
+ if (priv->nelements != HMACMD5_NTAGS) {
+ /*
+ * If this is a good old format and we are accepting
+ * the old format return success.
+ */
+ if (old && priv->nelements == OLD_HMACMD5_NTAGS &&
+ priv->elements[0].tag == TAG_HMACMD5_KEY)
+ {
+ return (0);
+ }
+ return (-1);
+ }
+ /*
+ * We must be new format at this point.
+ */
+ for (i = 0; i < HMACMD5_NTAGS; i++) {
+ for (j = 0; j < priv->nelements; j++) {
+ if (priv->elements[j].tag == TAG(DST_ALG_HMACMD5, i)) {
+ break;
+ }
+ }
+ if (j == priv->nelements) {
+ return (-1);
+ }
+ }
+ return (0);
+}
+
+static int
+check_hmac_sha(const dst_private_t *priv, unsigned int ntags,
+ unsigned int alg) {
+ unsigned int i, j;
+ if (priv->nelements != ntags) {
+ return (-1);
+ }
+ for (i = 0; i < ntags; i++) {
+ for (j = 0; j < priv->nelements; j++) {
+ if (priv->elements[j].tag == TAG(alg, i)) {
+ break;
+ }
+ }
+ if (j == priv->nelements) {
+ return (-1);
+ }
+ }
+ return (0);
+}
+
+static int
+check_data(const dst_private_t *priv, const unsigned int alg, bool old,
+ bool external) {
+ /* XXXVIX this switch statement is too sparse to gen a jump table. */
+ switch (alg) {
+ case DST_ALG_RSA:
+ case DST_ALG_RSASHA1:
+ case DST_ALG_NSEC3RSASHA1:
+ case DST_ALG_RSASHA256:
+ case DST_ALG_RSASHA512:
+ return (check_rsa(priv, external));
+ case DST_ALG_DH:
+ return (check_dh(priv));
+ case DST_ALG_ECDSA256:
+ case DST_ALG_ECDSA384:
+ return (check_ecdsa(priv, external));
+ case DST_ALG_ED25519:
+ case DST_ALG_ED448:
+ return (check_eddsa(priv, external));
+ case DST_ALG_HMACMD5:
+ return (check_hmac_md5(priv, old));
+ case DST_ALG_HMACSHA1:
+ return (check_hmac_sha(priv, HMACSHA1_NTAGS, alg));
+ case DST_ALG_HMACSHA224:
+ return (check_hmac_sha(priv, HMACSHA224_NTAGS, alg));
+ case DST_ALG_HMACSHA256:
+ return (check_hmac_sha(priv, HMACSHA256_NTAGS, alg));
+ case DST_ALG_HMACSHA384:
+ return (check_hmac_sha(priv, HMACSHA384_NTAGS, alg));
+ case DST_ALG_HMACSHA512:
+ return (check_hmac_sha(priv, HMACSHA512_NTAGS, alg));
+ default:
+ return (DST_R_UNSUPPORTEDALG);
+ }
+}
+
+void
+dst__privstruct_free(dst_private_t *priv, isc_mem_t *mctx) {
+ int i;
+
+ if (priv == NULL) {
+ return;
+ }
+ for (i = 0; i < priv->nelements; i++) {
+ if (priv->elements[i].data == NULL) {
+ continue;
+ }
+ memset(priv->elements[i].data, 0, MAXFIELDSIZE);
+ isc_mem_put(mctx, priv->elements[i].data, MAXFIELDSIZE);
+ }
+ priv->nelements = 0;
+}
+
+isc_result_t
+dst__privstruct_parse(dst_key_t *key, unsigned int alg, isc_lex_t *lex,
+ isc_mem_t *mctx, dst_private_t *priv) {
+ int n = 0, major, minor, check;
+ isc_buffer_t b;
+ isc_token_t token;
+ unsigned char *data = NULL;
+ unsigned int opt = ISC_LEXOPT_EOL;
+ isc_stdtime_t when;
+ isc_result_t ret;
+ bool external = false;
+
+ REQUIRE(priv != NULL);
+
+ priv->nelements = 0;
+ memset(priv->elements, 0, sizeof(priv->elements));
+
+#define NEXTTOKEN(lex, opt, token) \
+ do { \
+ ret = isc_lex_gettoken(lex, opt, token); \
+ if (ret != ISC_R_SUCCESS) \
+ goto fail; \
+ } while (0)
+
+#define READLINE(lex, opt, token) \
+ do { \
+ ret = isc_lex_gettoken(lex, opt, token); \
+ if (ret == ISC_R_EOF) \
+ break; \
+ else if (ret != ISC_R_SUCCESS) \
+ goto fail; \
+ } while ((*token).type != isc_tokentype_eol)
+
+ /*
+ * Read the description line.
+ */
+ NEXTTOKEN(lex, opt, &token);
+ if (token.type != isc_tokentype_string ||
+ strcmp(DST_AS_STR(token), PRIVATE_KEY_STR) != 0)
+ {
+ ret = DST_R_INVALIDPRIVATEKEY;
+ goto fail;
+ }
+
+ NEXTTOKEN(lex, opt, &token);
+ if (token.type != isc_tokentype_string || (DST_AS_STR(token))[0] != 'v')
+ {
+ ret = DST_R_INVALIDPRIVATEKEY;
+ goto fail;
+ }
+ if (sscanf(DST_AS_STR(token), "v%d.%d", &major, &minor) != 2) {
+ ret = DST_R_INVALIDPRIVATEKEY;
+ goto fail;
+ }
+
+ if (major > DST_MAJOR_VERSION) {
+ ret = DST_R_INVALIDPRIVATEKEY;
+ goto fail;
+ }
+
+ /*
+ * Store the private key format version number
+ */
+ dst_key_setprivateformat(key, major, minor);
+
+ READLINE(lex, opt, &token);
+
+ /*
+ * Read the algorithm line.
+ */
+ NEXTTOKEN(lex, opt, &token);
+ if (token.type != isc_tokentype_string ||
+ strcmp(DST_AS_STR(token), ALGORITHM_STR) != 0)
+ {
+ ret = DST_R_INVALIDPRIVATEKEY;
+ goto fail;
+ }
+
+ NEXTTOKEN(lex, opt | ISC_LEXOPT_NUMBER, &token);
+ if (token.type != isc_tokentype_number ||
+ token.value.as_ulong != (unsigned long)dst_key_alg(key))
+ {
+ ret = DST_R_INVALIDPRIVATEKEY;
+ goto fail;
+ }
+
+ READLINE(lex, opt, &token);
+
+ /*
+ * Read the key data.
+ */
+ for (n = 0; n < MAXFIELDS; n++) {
+ int tag;
+ isc_region_t r;
+ do {
+ ret = isc_lex_gettoken(lex, opt, &token);
+ if (ret == ISC_R_EOF) {
+ goto done;
+ }
+ if (ret != ISC_R_SUCCESS) {
+ goto fail;
+ }
+ } while (token.type == isc_tokentype_eol);
+
+ if (token.type != isc_tokentype_string) {
+ ret = DST_R_INVALIDPRIVATEKEY;
+ goto fail;
+ }
+
+ if (strcmp(DST_AS_STR(token), "External:") == 0) {
+ external = true;
+ goto next;
+ }
+
+ /* Numeric metadata */
+ tag = find_numericdata(DST_AS_STR(token));
+ if (tag >= 0) {
+ INSIST(tag < NUMERIC_NTAGS);
+
+ NEXTTOKEN(lex, opt | ISC_LEXOPT_NUMBER, &token);
+ if (token.type != isc_tokentype_number) {
+ ret = DST_R_INVALIDPRIVATEKEY;
+ goto fail;
+ }
+
+ dst_key_setnum(key, tag, token.value.as_ulong);
+ goto next;
+ }
+
+ /* Timing metadata */
+ tag = find_timedata(DST_AS_STR(token));
+ if (tag >= 0) {
+ INSIST(tag < TIMING_NTAGS);
+
+ NEXTTOKEN(lex, opt, &token);
+ if (token.type != isc_tokentype_string) {
+ ret = DST_R_INVALIDPRIVATEKEY;
+ goto fail;
+ }
+
+ ret = dns_time32_fromtext(DST_AS_STR(token), &when);
+ if (ret != ISC_R_SUCCESS) {
+ goto fail;
+ }
+
+ dst_key_settime(key, tag, when);
+
+ goto next;
+ }
+
+ /* Key data */
+ tag = find_value(DST_AS_STR(token), alg);
+ if (tag < 0 && minor > DST_MINOR_VERSION) {
+ goto next;
+ } else if (tag < 0) {
+ ret = DST_R_INVALIDPRIVATEKEY;
+ goto fail;
+ }
+
+ priv->elements[n].tag = tag;
+
+ data = isc_mem_get(mctx, MAXFIELDSIZE);
+
+ isc_buffer_init(&b, data, MAXFIELDSIZE);
+ ret = isc_base64_tobuffer(lex, &b, -1);
+ if (ret != ISC_R_SUCCESS) {
+ goto fail;
+ }
+
+ isc_buffer_usedregion(&b, &r);
+ priv->elements[n].length = r.length;
+ priv->elements[n].data = r.base;
+ priv->nelements++;
+
+ next:
+ READLINE(lex, opt, &token);
+ data = NULL;
+ }
+
+done:
+ if (external && priv->nelements != 0) {
+ ret = DST_R_INVALIDPRIVATEKEY;
+ goto fail;
+ }
+
+ check = check_data(priv, alg, true, external);
+ if (check < 0) {
+ ret = DST_R_INVALIDPRIVATEKEY;
+ goto fail;
+ } else if (check != ISC_R_SUCCESS) {
+ ret = check;
+ goto fail;
+ }
+
+ key->external = external;
+
+ return (ISC_R_SUCCESS);
+
+fail:
+ dst__privstruct_free(priv, mctx);
+ if (data != NULL) {
+ isc_mem_put(mctx, data, MAXFIELDSIZE);
+ }
+
+ return (ret);
+}
+
+isc_result_t
+dst__privstruct_writefile(const dst_key_t *key, const dst_private_t *priv,
+ const char *directory) {
+ FILE *fp;
+ isc_result_t result;
+ char filename[NAME_MAX];
+ char tmpname[NAME_MAX];
+ char buffer[MAXFIELDSIZE * 2];
+ isc_stdtime_t when;
+ uint32_t value;
+ isc_buffer_t b;
+ isc_buffer_t fileb;
+ isc_buffer_t tmpb;
+ isc_region_t r;
+ int major, minor;
+ mode_t mode;
+ int i, ret;
+
+ REQUIRE(priv != NULL);
+
+ ret = check_data(priv, dst_key_alg(key), false, key->external);
+ if (ret < 0) {
+ return (DST_R_INVALIDPRIVATEKEY);
+ } else if (ret != ISC_R_SUCCESS) {
+ return (ret);
+ }
+
+ isc_buffer_init(&fileb, filename, sizeof(filename));
+ result = dst_key_buildfilename(key, DST_TYPE_PRIVATE, directory,
+ &fileb);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+
+ result = isc_file_mode(filename, &mode);
+ if (result == ISC_R_SUCCESS && mode != (S_IRUSR | S_IWUSR)) {
+ /* File exists; warn that we are changing its permissions */
+ int level;
+
+ level = ISC_LOG_WARNING;
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL,
+ DNS_LOGMODULE_DNSSEC, level,
+ "Permissions on the file %s "
+ "have changed from 0%o to 0600 as "
+ "a result of this operation.",
+ filename, (unsigned int)mode);
+ }
+
+ isc_buffer_init(&tmpb, tmpname, sizeof(tmpname));
+ result = dst_key_buildfilename(key, DST_TYPE_TEMPLATE, directory,
+ &tmpb);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+
+ fp = dst_key_open(tmpname, S_IRUSR | S_IWUSR);
+ if (fp == NULL) {
+ return (DST_R_WRITEERROR);
+ }
+
+ dst_key_getprivateformat(key, &major, &minor);
+ if (major == 0 && minor == 0) {
+ major = DST_MAJOR_VERSION;
+ minor = DST_MINOR_VERSION;
+ }
+
+ /* XXXDCL return value should be checked for full filesystem */
+ fprintf(fp, "%s v%d.%d\n", PRIVATE_KEY_STR, major, minor);
+
+ fprintf(fp, "%s %u ", ALGORITHM_STR, dst_key_alg(key));
+
+ /* XXXVIX this switch statement is too sparse to gen a jump table. */
+ switch (dst_key_alg(key)) {
+ case DST_ALG_DH:
+ fprintf(fp, "(DH)\n");
+ break;
+ case DST_ALG_RSASHA1:
+ fprintf(fp, "(RSASHA1)\n");
+ break;
+ case DST_ALG_NSEC3RSASHA1:
+ fprintf(fp, "(NSEC3RSASHA1)\n");
+ break;
+ case DST_ALG_RSASHA256:
+ fprintf(fp, "(RSASHA256)\n");
+ break;
+ case DST_ALG_RSASHA512:
+ fprintf(fp, "(RSASHA512)\n");
+ break;
+ case DST_ALG_ECDSA256:
+ fprintf(fp, "(ECDSAP256SHA256)\n");
+ break;
+ case DST_ALG_ECDSA384:
+ fprintf(fp, "(ECDSAP384SHA384)\n");
+ break;
+ case DST_ALG_ED25519:
+ fprintf(fp, "(ED25519)\n");
+ break;
+ case DST_ALG_ED448:
+ fprintf(fp, "(ED448)\n");
+ break;
+ case DST_ALG_HMACMD5:
+ fprintf(fp, "(HMAC_MD5)\n");
+ break;
+ case DST_ALG_HMACSHA1:
+ fprintf(fp, "(HMAC_SHA1)\n");
+ break;
+ case DST_ALG_HMACSHA224:
+ fprintf(fp, "(HMAC_SHA224)\n");
+ break;
+ case DST_ALG_HMACSHA256:
+ fprintf(fp, "(HMAC_SHA256)\n");
+ break;
+ case DST_ALG_HMACSHA384:
+ fprintf(fp, "(HMAC_SHA384)\n");
+ break;
+ case DST_ALG_HMACSHA512:
+ fprintf(fp, "(HMAC_SHA512)\n");
+ break;
+ default:
+ fprintf(fp, "(?)\n");
+ break;
+ }
+
+ for (i = 0; i < priv->nelements; i++) {
+ const char *s;
+
+ s = find_tag(priv->elements[i].tag);
+
+ r.base = priv->elements[i].data;
+ r.length = priv->elements[i].length;
+ isc_buffer_init(&b, buffer, sizeof(buffer));
+ result = isc_base64_totext(&r, sizeof(buffer), "", &b);
+ if (result != ISC_R_SUCCESS) {
+ return (dst_key_cleanup(tmpname, fp));
+ }
+ isc_buffer_usedregion(&b, &r);
+
+ fprintf(fp, "%s %.*s\n", s, (int)r.length, r.base);
+ }
+
+ if (key->external) {
+ fprintf(fp, "External:\n");
+ }
+
+ /* Add the metadata tags */
+ if (major > 1 || (major == 1 && minor >= 3)) {
+ for (i = 0; i < NUMERIC_NTAGS; i++) {
+ result = dst_key_getnum(key, i, &value);
+ if (result != ISC_R_SUCCESS) {
+ continue;
+ }
+ if (numerictags[i] != NULL) {
+ fprintf(fp, "%s %u\n", numerictags[i], value);
+ }
+ }
+ for (i = 0; i < TIMING_NTAGS; i++) {
+ result = dst_key_gettime(key, i, &when);
+ if (result != ISC_R_SUCCESS) {
+ continue;
+ }
+
+ isc_buffer_init(&b, buffer, sizeof(buffer));
+ result = dns_time32_totext(when, &b);
+ if (result != ISC_R_SUCCESS) {
+ return (dst_key_cleanup(tmpname, fp));
+ }
+
+ isc_buffer_usedregion(&b, &r);
+
+ if (timetags[i] != NULL) {
+ fprintf(fp, "%s %.*s\n", timetags[i],
+ (int)r.length, r.base);
+ }
+ }
+ }
+
+ result = dst_key_close(tmpname, fp, filename);
+ return (result);
+}
+
+/*! \file */
diff --git a/lib/dns/dst_parse.h b/lib/dns/dst_parse.h
new file mode 100644
index 0000000..cc12e9b
--- /dev/null
+++ b/lib/dns/dst_parse.h
@@ -0,0 +1,131 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0 AND ISC
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/*
+ * Copyright (C) Network Associates, Inc.
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND ISC AND NETWORK ASSOCIATES DISCLAIMS
+ * ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE
+ * FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR
+ * IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+/*! \file */
+#pragma once
+
+#include <isc/lang.h>
+
+#include <dst/dst.h>
+
+#define MAXFIELDSIZE 512
+
+/*
+ * Maximum number of fields in a private file is 18 (12 algorithm-
+ * specific fields for RSA, plus 6 generic fields).
+ */
+#define MAXFIELDS 12 + 6
+
+#define TAG_SHIFT 4
+#define TAG_ALG(tag) ((unsigned int)(tag) >> TAG_SHIFT)
+#define TAG(alg, off) (((alg) << TAG_SHIFT) + (off))
+
+/* These are used by RSA-SHA1, RSASHA256 and RSASHA512 */
+#define RSA_NTAGS 11
+#define TAG_RSA_MODULUS ((DST_ALG_RSA << TAG_SHIFT) + 0)
+#define TAG_RSA_PUBLICEXPONENT ((DST_ALG_RSA << TAG_SHIFT) + 1)
+#define TAG_RSA_PRIVATEEXPONENT ((DST_ALG_RSA << TAG_SHIFT) + 2)
+#define TAG_RSA_PRIME1 ((DST_ALG_RSA << TAG_SHIFT) + 3)
+#define TAG_RSA_PRIME2 ((DST_ALG_RSA << TAG_SHIFT) + 4)
+#define TAG_RSA_EXPONENT1 ((DST_ALG_RSA << TAG_SHIFT) + 5)
+#define TAG_RSA_EXPONENT2 ((DST_ALG_RSA << TAG_SHIFT) + 6)
+#define TAG_RSA_COEFFICIENT ((DST_ALG_RSA << TAG_SHIFT) + 7)
+#define TAG_RSA_ENGINE ((DST_ALG_RSA << TAG_SHIFT) + 8)
+#define TAG_RSA_LABEL ((DST_ALG_RSA << TAG_SHIFT) + 9)
+
+#define DH_NTAGS 4
+#define TAG_DH_PRIME ((DST_ALG_DH << TAG_SHIFT) + 0)
+#define TAG_DH_GENERATOR ((DST_ALG_DH << TAG_SHIFT) + 1)
+#define TAG_DH_PRIVATE ((DST_ALG_DH << TAG_SHIFT) + 2)
+#define TAG_DH_PUBLIC ((DST_ALG_DH << TAG_SHIFT) + 3)
+
+#define ECDSA_NTAGS 4
+#define TAG_ECDSA_PRIVATEKEY ((DST_ALG_ECDSA256 << TAG_SHIFT) + 0)
+#define TAG_ECDSA_ENGINE ((DST_ALG_ECDSA256 << TAG_SHIFT) + 1)
+#define TAG_ECDSA_LABEL ((DST_ALG_ECDSA256 << TAG_SHIFT) + 2)
+
+#define EDDSA_NTAGS 4
+#define TAG_EDDSA_PRIVATEKEY ((DST_ALG_ED25519 << TAG_SHIFT) + 0)
+#define TAG_EDDSA_ENGINE ((DST_ALG_ED25519 << TAG_SHIFT) + 1)
+#define TAG_EDDSA_LABEL ((DST_ALG_ED25519 << TAG_SHIFT) + 2)
+
+#define OLD_HMACMD5_NTAGS 1
+#define HMACMD5_NTAGS 2
+#define TAG_HMACMD5_KEY ((DST_ALG_HMACMD5 << TAG_SHIFT) + 0)
+#define TAG_HMACMD5_BITS ((DST_ALG_HMACMD5 << TAG_SHIFT) + 1)
+
+#define HMACSHA1_NTAGS 2
+#define TAG_HMACSHA1_KEY ((DST_ALG_HMACSHA1 << TAG_SHIFT) + 0)
+#define TAG_HMACSHA1_BITS ((DST_ALG_HMACSHA1 << TAG_SHIFT) + 1)
+
+#define HMACSHA224_NTAGS 2
+#define TAG_HMACSHA224_KEY ((DST_ALG_HMACSHA224 << TAG_SHIFT) + 0)
+#define TAG_HMACSHA224_BITS ((DST_ALG_HMACSHA224 << TAG_SHIFT) + 1)
+
+#define HMACSHA256_NTAGS 2
+#define TAG_HMACSHA256_KEY ((DST_ALG_HMACSHA256 << TAG_SHIFT) + 0)
+#define TAG_HMACSHA256_BITS ((DST_ALG_HMACSHA256 << TAG_SHIFT) + 1)
+
+#define HMACSHA384_NTAGS 2
+#define TAG_HMACSHA384_KEY ((DST_ALG_HMACSHA384 << TAG_SHIFT) + 0)
+#define TAG_HMACSHA384_BITS ((DST_ALG_HMACSHA384 << TAG_SHIFT) + 1)
+
+#define HMACSHA512_NTAGS 2
+#define TAG_HMACSHA512_KEY ((DST_ALG_HMACSHA512 << TAG_SHIFT) + 0)
+#define TAG_HMACSHA512_BITS ((DST_ALG_HMACSHA512 << TAG_SHIFT) + 1)
+
+struct dst_private_element {
+ unsigned short tag;
+ unsigned short length;
+ unsigned char *data;
+};
+
+typedef struct dst_private_element dst_private_element_t;
+
+struct dst_private {
+ unsigned short nelements;
+ dst_private_element_t elements[MAXFIELDS];
+};
+
+typedef struct dst_private dst_private_t;
+
+ISC_LANG_BEGINDECLS
+
+void
+dst__privstruct_free(dst_private_t *priv, isc_mem_t *mctx);
+
+isc_result_t
+dst__privstruct_parse(dst_key_t *key, unsigned int alg, isc_lex_t *lex,
+ isc_mem_t *mctx, dst_private_t *priv);
+
+isc_result_t
+dst__privstruct_writefile(const dst_key_t *key, const dst_private_t *priv,
+ const char *directory);
+
+ISC_LANG_ENDDECLS
diff --git a/lib/dns/dyndb.c b/lib/dns/dyndb.c
new file mode 100644
index 0000000..6e3c7c3
--- /dev/null
+++ b/lib/dns/dyndb.c
@@ -0,0 +1,325 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#include <string.h>
+#include <uv.h>
+
+#include <isc/buffer.h>
+#include <isc/mem.h>
+#include <isc/mutex.h>
+#include <isc/once.h>
+#include <isc/region.h>
+#include <isc/result.h>
+#include <isc/task.h>
+#include <isc/types.h>
+#include <isc/util.h>
+
+#include <dns/dyndb.h>
+#include <dns/log.h>
+#include <dns/types.h>
+#include <dns/view.h>
+#include <dns/zone.h>
+
+#define CHECK(op) \
+ do { \
+ result = (op); \
+ if (result != ISC_R_SUCCESS) \
+ goto cleanup; \
+ } while (0)
+
+typedef struct dyndb_implementation dyndb_implementation_t;
+struct dyndb_implementation {
+ isc_mem_t *mctx;
+ uv_lib_t handle;
+ dns_dyndb_register_t *register_func;
+ dns_dyndb_destroy_t *destroy_func;
+ char *name;
+ void *inst;
+ LINK(dyndb_implementation_t) link;
+};
+
+/*
+ * List of dyndb implementations. Locked by dyndb_lock.
+ *
+ * These are stored here so they can be cleaned up on shutdown.
+ * (The order in which they are stored is not important.)
+ */
+static LIST(dyndb_implementation_t) dyndb_implementations;
+
+/* Locks dyndb_implementations. */
+static isc_mutex_t dyndb_lock;
+static isc_once_t once = ISC_ONCE_INIT;
+
+static void
+dyndb_initialize(void) {
+ isc_mutex_init(&dyndb_lock);
+ INIT_LIST(dyndb_implementations);
+}
+
+static dyndb_implementation_t *
+impfind(const char *name) {
+ dyndb_implementation_t *imp;
+
+ for (imp = ISC_LIST_HEAD(dyndb_implementations); imp != NULL;
+ imp = ISC_LIST_NEXT(imp, link))
+ {
+ if (strcasecmp(name, imp->name) == 0) {
+ return (imp);
+ }
+ }
+ return (NULL);
+}
+
+static isc_result_t
+load_symbol(uv_lib_t *handle, const char *filename, const char *symbol_name,
+ void **symbolp) {
+ void *symbol;
+ int r;
+
+ REQUIRE(handle != NULL);
+ REQUIRE(symbolp != NULL && *symbolp == NULL);
+
+ r = uv_dlsym(handle, symbol_name, &symbol);
+ if (r != 0) {
+ const char *errmsg = uv_dlerror(handle);
+ if (errmsg == NULL) {
+ errmsg = "returned function pointer is NULL";
+ }
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE,
+ DNS_LOGMODULE_DYNDB, ISC_LOG_ERROR,
+ "failed to lookup symbol %s in "
+ "DynDB module '%s': %s",
+ symbol_name, filename, errmsg);
+ return (ISC_R_FAILURE);
+ }
+
+ *symbolp = symbol;
+
+ return (ISC_R_SUCCESS);
+}
+
+static void
+unload_library(dyndb_implementation_t **impp);
+
+static isc_result_t
+load_library(isc_mem_t *mctx, const char *filename, const char *instname,
+ dyndb_implementation_t **impp) {
+ isc_result_t result;
+ dyndb_implementation_t *imp = NULL;
+ dns_dyndb_version_t *version_func = NULL;
+ int version;
+ int r;
+
+ REQUIRE(impp != NULL && *impp == NULL);
+
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, DNS_LOGMODULE_DYNDB,
+ ISC_LOG_INFO, "loading DynDB instance '%s' driver '%s'",
+ instname, filename);
+
+ imp = isc_mem_get(mctx, sizeof(*imp));
+ memset(imp, 0, sizeof(*imp));
+ isc_mem_attach(mctx, &imp->mctx);
+
+ imp->name = isc_mem_strdup(imp->mctx, instname);
+
+ INIT_LINK(imp, link);
+
+ r = uv_dlopen(filename, &imp->handle);
+ if (r != 0) {
+ const char *errmsg = uv_dlerror(&imp->handle);
+ if (errmsg == NULL) {
+ errmsg = "unknown error";
+ }
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE,
+ DNS_LOGMODULE_DYNDB, ISC_LOG_ERROR,
+ "failed to dlopen() DynDB instance '%s' driver "
+ "'%s': %s",
+ instname, filename, errmsg);
+ CHECK(ISC_R_FAILURE);
+ }
+
+ CHECK(load_symbol(&imp->handle, filename, "dyndb_version",
+ (void **)&version_func));
+
+ version = version_func(NULL);
+ if (version < (DNS_DYNDB_VERSION - DNS_DYNDB_AGE) ||
+ version > DNS_DYNDB_VERSION)
+ {
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE,
+ DNS_LOGMODULE_DYNDB, ISC_LOG_ERROR,
+ "driver API version mismatch: %d/%d", version,
+ DNS_DYNDB_VERSION);
+ CHECK(ISC_R_FAILURE);
+ }
+
+ CHECK(load_symbol(&imp->handle, filename, "dyndb_init",
+ (void **)&imp->register_func));
+ CHECK(load_symbol(&imp->handle, filename, "dyndb_destroy",
+ (void **)&imp->destroy_func));
+
+ *impp = imp;
+
+ return (ISC_R_SUCCESS);
+
+cleanup:
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, DNS_LOGMODULE_DYNDB,
+ ISC_LOG_ERROR,
+ "failed to dynamically load DynDB instance '%s' driver "
+ "'%s': %s",
+ instname, filename, isc_result_totext(result));
+
+ unload_library(&imp);
+
+ return (result);
+}
+
+static void
+unload_library(dyndb_implementation_t **impp) {
+ dyndb_implementation_t *imp;
+
+ REQUIRE(impp != NULL && *impp != NULL);
+
+ imp = *impp;
+ *impp = NULL;
+
+ /*
+ * This is a resource leak, but there is nothing we can currently do
+ * about it due to how configuration loading/reloading is designed.
+ */
+ /* uv_dlclose(&imp->handle); */
+ isc_mem_free(imp->mctx, imp->name);
+ isc_mem_putanddetach(&imp->mctx, imp, sizeof(*imp));
+}
+
+isc_result_t
+dns_dyndb_load(const char *libname, const char *name, const char *parameters,
+ const char *file, unsigned long line, isc_mem_t *mctx,
+ const dns_dyndbctx_t *dctx) {
+ isc_result_t result;
+ dyndb_implementation_t *implementation = NULL;
+
+ REQUIRE(DNS_DYNDBCTX_VALID(dctx));
+ REQUIRE(name != NULL);
+
+ RUNTIME_CHECK(isc_once_do(&once, dyndb_initialize) == ISC_R_SUCCESS);
+
+ LOCK(&dyndb_lock);
+
+ /* duplicate instance names are not allowed */
+ if (impfind(name) != NULL) {
+ CHECK(ISC_R_EXISTS);
+ }
+
+ CHECK(load_library(mctx, libname, name, &implementation));
+ CHECK(implementation->register_func(mctx, name, parameters, file, line,
+ dctx, &implementation->inst));
+
+ APPEND(dyndb_implementations, implementation, link);
+ result = ISC_R_SUCCESS;
+
+cleanup:
+ if (result != ISC_R_SUCCESS) {
+ if (implementation != NULL) {
+ unload_library(&implementation);
+ }
+ }
+
+ UNLOCK(&dyndb_lock);
+ return (result);
+}
+
+void
+dns_dyndb_cleanup(bool exiting) {
+ dyndb_implementation_t *elem;
+ dyndb_implementation_t *prev;
+
+ RUNTIME_CHECK(isc_once_do(&once, dyndb_initialize) == ISC_R_SUCCESS);
+
+ LOCK(&dyndb_lock);
+ elem = TAIL(dyndb_implementations);
+ while (elem != NULL) {
+ prev = PREV(elem, link);
+ UNLINK(dyndb_implementations, elem, link);
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE,
+ DNS_LOGMODULE_DYNDB, ISC_LOG_INFO,
+ "unloading DynDB instance '%s'", elem->name);
+ elem->destroy_func(&elem->inst);
+ ENSURE(elem->inst == NULL);
+ unload_library(&elem);
+ elem = prev;
+ }
+ UNLOCK(&dyndb_lock);
+
+ if (exiting) {
+ isc_mutex_destroy(&dyndb_lock);
+ }
+}
+
+isc_result_t
+dns_dyndb_createctx(isc_mem_t *mctx, const void *hashinit, isc_log_t *lctx,
+ dns_view_t *view, dns_zonemgr_t *zmgr, isc_task_t *task,
+ isc_timermgr_t *tmgr, dns_dyndbctx_t **dctxp) {
+ dns_dyndbctx_t *dctx;
+
+ REQUIRE(dctxp != NULL && *dctxp == NULL);
+
+ dctx = isc_mem_get(mctx, sizeof(*dctx));
+ *dctx = (dns_dyndbctx_t){
+ .timermgr = tmgr,
+ .hashinit = hashinit,
+ .lctx = lctx,
+ };
+
+ if (view != NULL) {
+ dns_view_attach(view, &dctx->view);
+ }
+ if (zmgr != NULL) {
+ dns_zonemgr_attach(zmgr, &dctx->zmgr);
+ }
+ if (task != NULL) {
+ isc_task_attach(task, &dctx->task);
+ }
+
+ isc_mem_attach(mctx, &dctx->mctx);
+ dctx->magic = DNS_DYNDBCTX_MAGIC;
+
+ *dctxp = dctx;
+
+ return (ISC_R_SUCCESS);
+}
+
+void
+dns_dyndb_destroyctx(dns_dyndbctx_t **dctxp) {
+ dns_dyndbctx_t *dctx;
+
+ REQUIRE(dctxp != NULL && DNS_DYNDBCTX_VALID(*dctxp));
+
+ dctx = *dctxp;
+ *dctxp = NULL;
+
+ dctx->magic = 0;
+
+ if (dctx->view != NULL) {
+ dns_view_detach(&dctx->view);
+ }
+ if (dctx->zmgr != NULL) {
+ dns_zonemgr_detach(&dctx->zmgr);
+ }
+ if (dctx->task != NULL) {
+ isc_task_detach(&dctx->task);
+ }
+ dctx->timermgr = NULL;
+ dctx->lctx = NULL;
+
+ isc_mem_putanddetach(&dctx->mctx, dctx, sizeof(*dctx));
+}
diff --git a/lib/dns/ecs.c b/lib/dns/ecs.c
new file mode 100644
index 0000000..676c740
--- /dev/null
+++ b/lib/dns/ecs.c
@@ -0,0 +1,113 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/*! \file */
+
+#include <string.h>
+
+#include <isc/buffer.h>
+#include <isc/mem.h>
+#include <isc/netaddr.h>
+#include <isc/print.h>
+#include <isc/util.h>
+
+#include <dns/ecs.h>
+#include <dns/nsec.h>
+#include <dns/rbt.h>
+#include <dns/rdata.h>
+#include <dns/rdatatype.h>
+#include <dns/result.h>
+#include <dns/types.h>
+
+void
+dns_ecs_init(dns_ecs_t *ecs) {
+ isc_netaddr_unspec(&ecs->addr);
+ ecs->source = 0;
+ ecs->scope = 0xff;
+}
+
+bool
+dns_ecs_equals(const dns_ecs_t *ecs1, const dns_ecs_t *ecs2) {
+ const unsigned char *addr1, *addr2;
+ uint8_t mask;
+ size_t alen;
+
+ REQUIRE(ecs1 != NULL && ecs2 != NULL);
+
+ if (ecs1->source != ecs2->source ||
+ ecs1->addr.family != ecs2->addr.family)
+ {
+ return (false);
+ }
+
+ alen = (ecs1->source + 7) / 8;
+ if (alen == 0) {
+ return (true);
+ }
+
+ switch (ecs1->addr.family) {
+ case AF_INET:
+ INSIST(alen <= 4);
+ addr1 = (const unsigned char *)&ecs1->addr.type.in;
+ addr2 = (const unsigned char *)&ecs2->addr.type.in;
+ break;
+ case AF_INET6:
+ INSIST(alen <= 16);
+ addr1 = (const unsigned char *)&ecs1->addr.type.in6;
+ addr2 = (const unsigned char *)&ecs2->addr.type.in6;
+ break;
+ default:
+ UNREACHABLE();
+ }
+
+ /*
+ * Compare all octets except the final octet of the address
+ * prefix.
+ */
+ if (alen > 1 && memcmp(addr1, addr2, alen - 1) != 0) {
+ return (false);
+ }
+
+ /*
+ * It should not be necessary to mask the final octet; all
+ * bits past the source prefix length are supposed to be 0.
+ * However, it seems prudent not to omit them from the
+ * comparison anyway.
+ */
+ mask = (~0U << (8 - (ecs1->source % 8))) & 0xff;
+ if (mask == 0) {
+ mask = 0xff;
+ }
+
+ if ((addr1[alen - 1] & mask) != (addr2[alen - 1] & mask)) {
+ return (false);
+ }
+
+ return (true);
+}
+
+void
+dns_ecs_format(const dns_ecs_t *ecs, char *buf, size_t size) {
+ size_t len;
+ char *p;
+
+ REQUIRE(ecs != NULL);
+ REQUIRE(buf != NULL);
+ REQUIRE(size >= DNS_ECS_FORMATSIZE);
+
+ isc_netaddr_format(&ecs->addr, buf, size);
+ len = strlen(buf);
+ p = buf + len;
+ snprintf(p, size - len, "/%d/%d", ecs->source,
+ ecs->scope == 0xff ? 0 : ecs->scope);
+}
diff --git a/lib/dns/fixedname.c b/lib/dns/fixedname.c
new file mode 100644
index 0000000..c6d899d
--- /dev/null
+++ b/lib/dns/fixedname.c
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/*! \file */
+
+#include <dns/fixedname.h>
+
+void
+dns_fixedname_init(dns_fixedname_t *fixed) {
+ dns_name_init(&fixed->name, fixed->offsets);
+ isc_buffer_init(&fixed->buffer, fixed->data, DNS_NAME_MAXWIRE);
+ dns_name_setbuffer(&fixed->name, &fixed->buffer);
+}
+
+void
+dns_fixedname_invalidate(dns_fixedname_t *fixed) {
+ dns_name_invalidate(&fixed->name);
+}
+
+dns_name_t *
+dns_fixedname_name(dns_fixedname_t *fixed) {
+ return (&fixed->name);
+}
+
+dns_name_t *
+dns_fixedname_initname(dns_fixedname_t *fixed) {
+ dns_fixedname_init(fixed);
+ return (dns_fixedname_name(fixed));
+}
diff --git a/lib/dns/forward.c b/lib/dns/forward.c
new file mode 100644
index 0000000..8750569
--- /dev/null
+++ b/lib/dns/forward.c
@@ -0,0 +1,217 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/*! \file */
+
+#include <isc/magic.h>
+#include <isc/mem.h>
+#include <isc/result.h>
+#include <isc/rwlock.h>
+#include <isc/util.h>
+
+#include <dns/forward.h>
+#include <dns/rbt.h>
+#include <dns/types.h>
+
+struct dns_fwdtable {
+ /* Unlocked. */
+ unsigned int magic;
+ isc_mem_t *mctx;
+ isc_rwlock_t rwlock;
+ /* Locked by lock. */
+ dns_rbt_t *table;
+};
+
+#define FWDTABLEMAGIC ISC_MAGIC('F', 'w', 'd', 'T')
+#define VALID_FWDTABLE(ft) ISC_MAGIC_VALID(ft, FWDTABLEMAGIC)
+
+static void
+auto_detach(void *, void *);
+
+isc_result_t
+dns_fwdtable_create(isc_mem_t *mctx, dns_fwdtable_t **fwdtablep) {
+ dns_fwdtable_t *fwdtable;
+ isc_result_t result;
+
+ REQUIRE(fwdtablep != NULL && *fwdtablep == NULL);
+
+ fwdtable = isc_mem_get(mctx, sizeof(*fwdtable));
+
+ fwdtable->table = NULL;
+ result = dns_rbt_create(mctx, auto_detach, fwdtable, &fwdtable->table);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup_fwdtable;
+ }
+
+ isc_rwlock_init(&fwdtable->rwlock, 0, 0);
+ fwdtable->mctx = NULL;
+ isc_mem_attach(mctx, &fwdtable->mctx);
+ fwdtable->magic = FWDTABLEMAGIC;
+ *fwdtablep = fwdtable;
+
+ return (ISC_R_SUCCESS);
+
+cleanup_fwdtable:
+ isc_mem_put(mctx, fwdtable, sizeof(*fwdtable));
+
+ return (result);
+}
+
+isc_result_t
+dns_fwdtable_addfwd(dns_fwdtable_t *fwdtable, const dns_name_t *name,
+ dns_forwarderlist_t *fwdrs, dns_fwdpolicy_t fwdpolicy) {
+ isc_result_t result;
+ dns_forwarders_t *forwarders;
+ dns_forwarder_t *fwd, *nfwd;
+
+ REQUIRE(VALID_FWDTABLE(fwdtable));
+
+ forwarders = isc_mem_get(fwdtable->mctx, sizeof(*forwarders));
+
+ ISC_LIST_INIT(forwarders->fwdrs);
+ for (fwd = ISC_LIST_HEAD(*fwdrs); fwd != NULL;
+ fwd = ISC_LIST_NEXT(fwd, link))
+ {
+ nfwd = isc_mem_get(fwdtable->mctx, sizeof(*nfwd));
+ *nfwd = *fwd;
+ ISC_LINK_INIT(nfwd, link);
+ ISC_LIST_APPEND(forwarders->fwdrs, nfwd, link);
+ }
+ forwarders->fwdpolicy = fwdpolicy;
+
+ RWLOCK(&fwdtable->rwlock, isc_rwlocktype_write);
+ result = dns_rbt_addname(fwdtable->table, name, forwarders);
+ RWUNLOCK(&fwdtable->rwlock, isc_rwlocktype_write);
+
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+
+ return (ISC_R_SUCCESS);
+
+cleanup:
+ while (!ISC_LIST_EMPTY(forwarders->fwdrs)) {
+ fwd = ISC_LIST_HEAD(forwarders->fwdrs);
+ ISC_LIST_UNLINK(forwarders->fwdrs, fwd, link);
+ isc_mem_put(fwdtable->mctx, fwd, sizeof(*fwd));
+ }
+ isc_mem_put(fwdtable->mctx, forwarders, sizeof(*forwarders));
+ return (result);
+}
+
+isc_result_t
+dns_fwdtable_add(dns_fwdtable_t *fwdtable, const dns_name_t *name,
+ isc_sockaddrlist_t *addrs, dns_fwdpolicy_t fwdpolicy) {
+ isc_result_t result;
+ dns_forwarders_t *forwarders;
+ dns_forwarder_t *fwd;
+ isc_sockaddr_t *sa;
+
+ REQUIRE(VALID_FWDTABLE(fwdtable));
+
+ forwarders = isc_mem_get(fwdtable->mctx, sizeof(*forwarders));
+
+ ISC_LIST_INIT(forwarders->fwdrs);
+ for (sa = ISC_LIST_HEAD(*addrs); sa != NULL;
+ sa = ISC_LIST_NEXT(sa, link))
+ {
+ fwd = isc_mem_get(fwdtable->mctx, sizeof(*fwd));
+ fwd->addr = *sa;
+ ISC_LINK_INIT(fwd, link);
+ ISC_LIST_APPEND(forwarders->fwdrs, fwd, link);
+ }
+ forwarders->fwdpolicy = fwdpolicy;
+
+ RWLOCK(&fwdtable->rwlock, isc_rwlocktype_write);
+ result = dns_rbt_addname(fwdtable->table, name, forwarders);
+ RWUNLOCK(&fwdtable->rwlock, isc_rwlocktype_write);
+
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+
+ return (ISC_R_SUCCESS);
+
+cleanup:
+ while (!ISC_LIST_EMPTY(forwarders->fwdrs)) {
+ fwd = ISC_LIST_HEAD(forwarders->fwdrs);
+ ISC_LIST_UNLINK(forwarders->fwdrs, fwd, link);
+ isc_mem_put(fwdtable->mctx, fwd, sizeof(*fwd));
+ }
+ isc_mem_put(fwdtable->mctx, forwarders, sizeof(*forwarders));
+ return (result);
+}
+
+isc_result_t
+dns_fwdtable_delete(dns_fwdtable_t *fwdtable, const dns_name_t *name) {
+ isc_result_t result;
+
+ REQUIRE(VALID_FWDTABLE(fwdtable));
+
+ RWLOCK(&fwdtable->rwlock, isc_rwlocktype_write);
+ result = dns_rbt_deletename(fwdtable->table, name, false);
+ RWUNLOCK(&fwdtable->rwlock, isc_rwlocktype_write);
+
+ return (result);
+}
+
+isc_result_t
+dns_fwdtable_find(dns_fwdtable_t *fwdtable, const dns_name_t *name,
+ dns_name_t *foundname, dns_forwarders_t **forwardersp) {
+ isc_result_t result;
+
+ REQUIRE(VALID_FWDTABLE(fwdtable));
+
+ RWLOCK(&fwdtable->rwlock, isc_rwlocktype_read);
+ result = dns_rbt_findname(fwdtable->table, name, 0, foundname,
+ (void **)forwardersp);
+ RWUNLOCK(&fwdtable->rwlock, isc_rwlocktype_read);
+
+ return (result);
+}
+
+void
+dns_fwdtable_destroy(dns_fwdtable_t **fwdtablep) {
+ dns_fwdtable_t *fwdtable;
+
+ REQUIRE(fwdtablep != NULL && VALID_FWDTABLE(*fwdtablep));
+
+ fwdtable = *fwdtablep;
+ *fwdtablep = NULL;
+
+ dns_rbt_destroy(&fwdtable->table);
+ isc_rwlock_destroy(&fwdtable->rwlock);
+ fwdtable->magic = 0;
+
+ isc_mem_putanddetach(&fwdtable->mctx, fwdtable, sizeof(*fwdtable));
+}
+
+/***
+ *** Private
+ ***/
+
+static void
+auto_detach(void *data, void *arg) {
+ dns_forwarders_t *forwarders = data;
+ dns_fwdtable_t *fwdtable = arg;
+ dns_forwarder_t *fwd;
+
+ UNUSED(arg);
+
+ while (!ISC_LIST_EMPTY(forwarders->fwdrs)) {
+ fwd = ISC_LIST_HEAD(forwarders->fwdrs);
+ ISC_LIST_UNLINK(forwarders->fwdrs, fwd, link);
+ isc_mem_put(fwdtable->mctx, fwd, sizeof(*fwd));
+ }
+ isc_mem_put(fwdtable->mctx, forwarders, sizeof(*forwarders));
+}
diff --git a/lib/dns/gen.c b/lib/dns/gen.c
new file mode 100644
index 0000000..7cbdf0a
--- /dev/null
+++ b/lib/dns/gen.c
@@ -0,0 +1,1062 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/*! \file */
+
+#include <ctype.h>
+#include <dirent.h>
+#include <errno.h>
+#include <limits.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/types.h>
+#include <time.h>
+#include <unistd.h>
+
+#ifndef PATH_MAX
+#define PATH_MAX 1024
+#endif /* ifndef PATH_MAX */
+
+#ifndef ULLONG_MAX
+#define ULLONG_MAX (~0ULL)
+#endif /* ifndef ULLONG_MAX */
+
+#define INSIST(cond) \
+ if (!(cond)) { \
+ fprintf(stderr, "%s:%d: INSIST(%s)\n", __FILE__, __LINE__, \
+ #cond); \
+ abort(); \
+ }
+
+#define FROMTEXTARGS "rdclass, type, lexer, origin, options, target, callbacks"
+#define FROMTEXTCLASS "rdclass"
+#define FROMTEXTTYPE "type"
+#define FROMTEXTDEF "result = DNS_R_UNKNOWN"
+
+#define TOTEXTARGS "rdata, tctx, target"
+#define TOTEXTCLASS "rdata->rdclass"
+#define TOTEXTTYPE "rdata->type"
+#define TOTEXTDEF "use_default = true"
+
+#define FROMWIREARGS "rdclass, type, source, dctx, options, target"
+#define FROMWIRECLASS "rdclass"
+#define FROMWIRETYPE "type"
+#define FROMWIREDEF "use_default = true"
+
+#define TOWIREARGS "rdata, cctx, target"
+#define TOWIRECLASS "rdata->rdclass"
+#define TOWIRETYPE "rdata->type"
+#define TOWIREDEF "use_default = true"
+
+#define FROMSTRUCTARGS "rdclass, type, source, target"
+#define FROMSTRUCTCLASS "rdclass"
+#define FROMSTRUCTTYPE "type"
+#define FROMSTRUCTDEF "use_default = true"
+
+#define TOSTRUCTARGS "rdata, target, mctx"
+#define TOSTRUCTCLASS "rdata->rdclass"
+#define TOSTRUCTTYPE "rdata->type"
+#define TOSTRUCTDEF "use_default = true"
+
+#define FREESTRUCTARGS "source"
+#define FREESTRUCTCLASS "common->rdclass"
+#define FREESTRUCTTYPE "common->rdtype"
+#define FREESTRUCTDEF NULL
+
+#define COMPAREARGS "rdata1, rdata2"
+#define COMPARECLASS "rdata1->rdclass"
+#define COMPARETYPE "rdata1->type"
+#define COMPAREDEF "use_default = true"
+
+#define ADDITIONALDATAARGS "rdata, owner, add, arg"
+#define ADDITIONALDATACLASS "rdata->rdclass"
+#define ADDITIONALDATATYPE "rdata->type"
+#define ADDITIONALDATADEF "use_default = true"
+
+#define DIGESTARGS "rdata, digest, arg"
+#define DIGESTCLASS "rdata->rdclass"
+#define DIGESTTYPE "rdata->type"
+#define DIGESTDEF "use_default = true"
+
+#define CHECKOWNERARGS "name, rdclass, type, wildcard"
+#define CHECKOWNERCLASS "rdclass"
+#define CHECKOWNERTYPE "type"
+#define CHECKOWNERDEF "result = true"
+
+#define CHECKNAMESARGS "rdata, owner, bad"
+#define CHECKNAMESCLASS "rdata->rdclass"
+#define CHECKNAMESTYPE "rdata->type"
+#define CHECKNAMESDEF "result = true"
+
+static const char copyright[] = "/*\n"
+ " * Copyright (C) 1998%s Internet Systems "
+ "Consortium, Inc. (\"ISC\")\n"
+ " *\n"
+ " * This Source Code Form is subject to the "
+ "terms of the Mozilla Public\n"
+ " * License, v. 2.0. If a copy of the MPL was "
+ "not distributed with this\n"
+ " * file, you can obtain one at "
+ "https://mozilla.org/MPL/2.0/.\n"
+ " */\n"
+ "\n"
+ "/***************\n"
+ " ***************\n"
+ " *************** THIS FILE IS AUTOMATICALLY "
+ "GENERATED BY gen.c.\n"
+ " *************** DO NOT EDIT!\n"
+ " ***************\n"
+ " ***************/\n"
+ "\n"
+ "/*! \\file */\n"
+ "\n";
+
+#define STR_EXPAND(tok) #tok
+#define STR(tok) STR_EXPAND(tok)
+
+#define TYPENAMES 256
+#define TYPECLASSLEN 20 /* DNS mnemonic size. Must be less than 100. */
+#define TYPECLASSBUF (TYPECLASSLEN + 1)
+#define TYPECLASSFMT "%" STR(TYPECLASSLEN) "[-0-9a-z]_%u"
+#define ATTRIBUTESIZE 256
+
+static struct cc {
+ struct cc *next;
+ int rdclass;
+ char classbuf[TYPECLASSBUF];
+} *classes;
+
+static struct tt {
+ struct tt *next;
+ uint16_t rdclass;
+ uint16_t type;
+ char classbuf[TYPECLASSBUF];
+ char typebuf[TYPECLASSBUF];
+ char dirbuf[PATH_MAX - 30];
+} *types;
+
+static struct ttnam {
+ char typebuf[TYPECLASSBUF];
+ char macroname[TYPECLASSBUF];
+ char attr[ATTRIBUTESIZE];
+ unsigned int sorted;
+ uint16_t type;
+} typenames[TYPENAMES];
+
+static int maxtype = -1;
+
+typedef struct {
+ DIR *handle;
+ char *filename;
+} isc_dir_t;
+
+static char *
+upper(char *);
+static char *
+funname(const char *, char *);
+static void
+doswitch(const char *, const char *, const char *, const char *, const char *,
+ const char *);
+static void
+add(unsigned int, const char *, int, const char *, const char *);
+static void
+sd(unsigned int, const char *, const char *, char);
+static void
+insert_into_typenames(int, const char *, const char *);
+
+static bool
+start_directory(const char *path, isc_dir_t *dir) {
+ dir->handle = opendir(path);
+
+ if (dir->handle != NULL) {
+ return (true);
+ } else {
+ return (false);
+ }
+}
+
+static bool
+next_file(isc_dir_t *dir) {
+ struct dirent *dirent;
+
+ dir->filename = NULL;
+
+ if (dir->handle != NULL) {
+ errno = 0;
+ dirent = readdir(dir->handle);
+ if (dirent != NULL) {
+ dir->filename = dirent->d_name;
+ } else {
+ if (errno != 0) {
+ fprintf(stderr,
+ "Error: reading directory: %s\n",
+ strerror(errno));
+ exit(1);
+ }
+ }
+ }
+
+ if (dir->filename != NULL) {
+ return (true);
+ } else {
+ return (false);
+ }
+}
+
+static void
+end_directory(isc_dir_t *dir) {
+ if (dir->handle != NULL) {
+ (void)closedir(dir->handle);
+ }
+
+ dir->handle = NULL;
+}
+
+/*%
+ * If you use more than 10 of these in, say, a printf(), you'll have problems.
+ */
+static char *
+upper(char *s) {
+ static int buf_to_use = 0;
+ static char buf[10][256];
+ char *b;
+ int c;
+
+ buf_to_use++;
+ if (buf_to_use > 9) {
+ buf_to_use = 0;
+ }
+
+ b = buf[buf_to_use];
+ memset(b, 0, 256);
+
+ while ((c = (*s++) & 0xff)) {
+ *b++ = islower(c) ? toupper(c) : c;
+ }
+ *b = '\0';
+ return (buf[buf_to_use]);
+}
+
+static char *
+funname(const char *s, char *buf) {
+ char *b = buf;
+ char c;
+
+ INSIST(strlen(s) < TYPECLASSBUF);
+ while ((c = *s++)) {
+ *b++ = (c == '-') ? '_' : c;
+ }
+ *b = '\0';
+ return (buf);
+}
+
+static void
+doswitch(const char *name, const char *function, const char *args,
+ const char *tsw, const char *csw, const char *res) {
+ struct tt *tt;
+ int first = 1;
+ int lasttype = 0;
+ int subswitch = 0;
+ char buf1[TYPECLASSBUF], buf2[TYPECLASSBUF];
+ const char *result = " result =";
+
+ if (res == NULL) {
+ result = "";
+ }
+
+ for (tt = types; tt != NULL; tt = tt->next) {
+ if (first) {
+ printf("\n#define %s \\\n", name);
+ printf("\tswitch (%s) { \\\n" /*}*/, tsw);
+ first = 0;
+ }
+ if (tt->type != lasttype && subswitch) {
+ if (res == NULL) {
+ printf("\t\tdefault: break; \\\n");
+ } else {
+ printf("\t\tdefault: %s; break; \\\n", res);
+ }
+ printf("\t\t} \\\n");
+ printf("\t\tbreak; \\\n");
+ subswitch = 0;
+ }
+ if (tt->rdclass && tt->type != lasttype) {
+ printf("\tcase %d: switch (%s) { \\\n" /*}*/, tt->type,
+ csw);
+ subswitch = 1;
+ }
+ if (tt->rdclass == 0) {
+ printf("\tcase %d:%s %s_%s(%s); break;", tt->type,
+ result, function, funname(tt->typebuf, buf1),
+ args);
+ } else {
+ printf("\t\tcase %d:%s %s_%s_%s(%s); break;",
+ tt->rdclass, result, function,
+ funname(tt->classbuf, buf1),
+ funname(tt->typebuf, buf2), args);
+ }
+ printf(" \\\n");
+ lasttype = tt->type;
+ }
+ if (subswitch) {
+ if (res == NULL) {
+ printf("\t\tdefault: break; \\\n");
+ } else {
+ printf("\t\tdefault: %s; break; \\\n", res);
+ }
+ printf("\t\t} \\\n");
+ printf("\t\tbreak; \\\n");
+ }
+ if (first) {
+ if (res == NULL) {
+ printf("\n#define %s\n", name);
+ } else {
+ printf("\n#define %s %s;\n", name, res);
+ }
+ } else {
+ if (res == NULL) {
+ printf("\tdefault: break; \\\n");
+ } else {
+ printf("\tdefault: %s; break; \\\n", res);
+ }
+ printf("\t}\n");
+ }
+}
+
+static struct ttnam *
+find_typename(int type) {
+ int i;
+
+ for (i = 0; i < TYPENAMES; i++) {
+ if (typenames[i].typebuf[0] != 0 && typenames[i].type == type) {
+ return (&typenames[i]);
+ }
+ }
+ return (NULL);
+}
+
+static void
+insert_into_typenames(int type, const char *typebuf, const char *attr) {
+ struct ttnam *ttn = NULL;
+ size_t c;
+ int i, n;
+ char tmp[256];
+
+ INSIST(strlen(typebuf) < TYPECLASSBUF);
+ for (i = 0; i < TYPENAMES; i++) {
+ if (typenames[i].typebuf[0] != 0 && typenames[i].type == type &&
+ strcmp(typebuf, typenames[i].typebuf) != 0)
+ {
+ fprintf(stderr,
+ "Error: type %d has two names: %s, %s\n", type,
+ typenames[i].typebuf, typebuf);
+ exit(1);
+ }
+ if (typenames[i].typebuf[0] == 0 && ttn == NULL) {
+ ttn = &typenames[i];
+ }
+ }
+ if (ttn == NULL) {
+ fprintf(stderr, "Error: typenames array too small\n");
+ exit(1);
+ }
+
+ /* XXXMUKS: This is redundant due to the INSIST above. */
+ if (strlen(typebuf) > sizeof(ttn->typebuf) - 1) {
+ fprintf(stderr, "Error: type name %s is too long\n", typebuf);
+ exit(1);
+ }
+
+ strncpy(ttn->typebuf, typebuf, sizeof(ttn->typebuf));
+ ttn->typebuf[sizeof(ttn->typebuf) - 1] = '\0';
+
+ strncpy(ttn->macroname, ttn->typebuf, sizeof(ttn->macroname));
+ ttn->macroname[sizeof(ttn->macroname) - 1] = '\0';
+
+ ttn->type = type;
+ c = strlen(ttn->macroname);
+ while (c > 0) {
+ if (ttn->macroname[c - 1] == '-') {
+ ttn->macroname[c - 1] = '_';
+ }
+ c--;
+ }
+
+ if (attr == NULL) {
+ n = snprintf(tmp, sizeof(tmp), "RRTYPE_%s_ATTRIBUTES",
+ upper(ttn->macroname));
+ INSIST(n > 0 && (unsigned)n < sizeof(tmp));
+ attr = tmp;
+ }
+
+ if (ttn->attr[0] != 0 && strcmp(attr, ttn->attr) != 0) {
+ fprintf(stderr,
+ "Error: type %d has different attributes: "
+ "%s, %s\n",
+ type, ttn->attr, attr);
+ exit(1);
+ }
+
+ if (strlen(attr) > sizeof(ttn->attr) - 1) {
+ fprintf(stderr, "Error: attr (%s) [name %s] is too long\n",
+ attr, typebuf);
+ exit(1);
+ }
+
+ strncpy(ttn->attr, attr, sizeof(ttn->attr));
+ ttn->attr[sizeof(ttn->attr) - 1] = '\0';
+
+ ttn->sorted = 0;
+ if (maxtype < type) {
+ maxtype = type;
+ }
+}
+
+static void
+add(unsigned int rdclass, const char *classbuf, int type, const char *typebuf,
+ const char *dirbuf) {
+ struct tt *newtt = (struct tt *)malloc(sizeof(*newtt));
+ struct tt *tt, *oldtt;
+ struct cc *newcc;
+ struct cc *cc, *oldcc;
+
+ INSIST(strlen(typebuf) < TYPECLASSBUF);
+ INSIST(strlen(classbuf) < TYPECLASSBUF);
+ INSIST(strlen(dirbuf) < PATH_MAX);
+
+ insert_into_typenames(type, typebuf, NULL);
+
+ if (newtt == NULL) {
+ fprintf(stderr, "malloc() failed\n");
+ exit(1);
+ }
+
+ newtt->next = NULL;
+ newtt->rdclass = rdclass;
+ newtt->type = type;
+
+ strncpy(newtt->classbuf, classbuf, sizeof(newtt->classbuf));
+ newtt->classbuf[sizeof(newtt->classbuf) - 1] = '\0';
+
+ strncpy(newtt->typebuf, typebuf, sizeof(newtt->typebuf));
+ newtt->typebuf[sizeof(newtt->typebuf) - 1] = '\0';
+
+ if (strncmp(dirbuf, "./", 2) == 0) {
+ dirbuf += 2;
+ }
+ strncpy(newtt->dirbuf, dirbuf, sizeof(newtt->dirbuf));
+ newtt->dirbuf[sizeof(newtt->dirbuf) - 1] = '\0';
+
+ tt = types;
+ oldtt = NULL;
+
+ while ((tt != NULL) && (tt->type < type)) {
+ oldtt = tt;
+ tt = tt->next;
+ }
+
+ while ((tt != NULL) && (tt->type == type) && (tt->rdclass < rdclass)) {
+ if (strcmp(tt->typebuf, typebuf) != 0) {
+ exit(1);
+ }
+ oldtt = tt;
+ tt = tt->next;
+ }
+
+ if ((tt != NULL) && (tt->type == type) && (tt->rdclass == rdclass)) {
+ exit(1);
+ }
+
+ newtt->next = tt;
+ if (oldtt != NULL) {
+ oldtt->next = newtt;
+ } else {
+ types = newtt;
+ }
+
+ /*
+ * Do a class switch for this type.
+ */
+ if (rdclass == 0) {
+ return;
+ }
+
+ newcc = (struct cc *)malloc(sizeof(*newcc));
+ if (newcc == NULL) {
+ fprintf(stderr, "malloc() failed\n");
+ exit(1);
+ }
+ newcc->rdclass = rdclass;
+ strncpy(newcc->classbuf, classbuf, sizeof(newcc->classbuf));
+ newcc->classbuf[sizeof(newcc->classbuf) - 1] = '\0';
+ cc = classes;
+ oldcc = NULL;
+
+ while ((cc != NULL) && (cc->rdclass < rdclass)) {
+ oldcc = cc;
+ cc = cc->next;
+ }
+
+ if ((cc != NULL) && cc->rdclass == rdclass) {
+ free((char *)newcc);
+ return;
+ }
+
+ newcc->next = cc;
+ if (oldcc != NULL) {
+ oldcc->next = newcc;
+ } else {
+ classes = newcc;
+ }
+}
+
+static void
+sd(unsigned int rdclass, const char *classbuf, const char *dirbuf,
+ char filetype) {
+ char buf[TYPECLASSLEN + sizeof("_4294967295.h")];
+ char typebuf[TYPECLASSBUF];
+ unsigned int type;
+ int n;
+ isc_dir_t dir;
+
+ if (!start_directory(dirbuf, &dir)) {
+ return;
+ }
+
+ while (next_file(&dir)) {
+ if (sscanf(dir.filename, TYPECLASSFMT, typebuf, &type) != 2) {
+ continue;
+ }
+
+ /*
+ * sscanf accepts leading sign and zeros before type so
+ * compare the scanned items against the filename. Filter
+ * out mismatches. Also filter out bad file extensions.
+ */
+ n = snprintf(buf, sizeof(buf), "%s_%u.%c", typebuf, type,
+ filetype);
+ INSIST(n > 0 && (unsigned)n < sizeof(buf));
+ if (strcmp(buf, dir.filename) != 0) {
+ continue;
+ }
+ if (type > 65535) {
+ fprintf(stderr, "Error: type value > 65535 (%s)\n",
+ dir.filename);
+ exit(1);
+ }
+ add(rdclass, classbuf, type, typebuf, dirbuf);
+ }
+
+ end_directory(&dir);
+}
+
+static unsigned int
+HASH(char *string) {
+ size_t n;
+ unsigned char a, b;
+
+ n = strlen(string);
+ if (n == 0) {
+ fprintf(stderr, "n == 0?\n");
+ exit(1);
+ }
+ a = tolower((unsigned char)string[0]);
+ b = tolower((unsigned char)string[n - 1]);
+
+ return (((a + n) * b) % 256);
+}
+
+int
+main(int argc, char **argv) {
+ char buf[PATH_MAX];
+ char srcdir[PATH_MAX];
+ unsigned int rdclass;
+ char classbuf[TYPECLASSBUF];
+ struct tt *tt;
+ struct cc *cc;
+ struct ttnam *ttn, *ttn2;
+ unsigned int hash;
+ time_t now;
+ char year[11];
+ int lasttype;
+ int code = 1;
+ int class_enum = 0;
+ int type_enum = 0;
+ int structs = 0;
+ int depend = 0;
+ int c, i, j, n;
+ char buf1[TYPECLASSBUF];
+ char filetype = 'c';
+ FILE *fd;
+ char *prefix = NULL;
+ char *suffix = NULL;
+ char *file = NULL;
+ char *source_date_epoch;
+ unsigned long long epoch;
+ char *endptr;
+ isc_dir_t dir;
+
+ for (i = 0; i < TYPENAMES; i++) {
+ memset(&typenames[i], 0, sizeof(typenames[i]));
+ }
+
+ srcdir[0] = '\0';
+ while ((c = getopt(argc, argv, "cdits:F:P:S:")) != -1) {
+ switch (c) {
+ case 'c':
+ code = 0;
+ depend = 0;
+ type_enum = 0;
+ class_enum = 1;
+ filetype = 'c';
+ structs = 0;
+ break;
+ case 'd':
+ code = 0;
+ depend = 1;
+ class_enum = 0;
+ type_enum = 0;
+ structs = 0;
+ filetype = 'h';
+ break;
+ case 't':
+ code = 0;
+ depend = 0;
+ class_enum = 0;
+ type_enum = 1;
+ filetype = 'c';
+ structs = 0;
+ break;
+ case 'i':
+ code = 0;
+ depend = 0;
+ class_enum = 0;
+ type_enum = 0;
+ structs = 1;
+ filetype = 'h';
+ break;
+ case 's':
+ if (strlen(optarg) >
+ PATH_MAX - 2 * TYPECLASSLEN -
+ sizeof("/rdata/_65535_65535"))
+ {
+ fprintf(stderr, "\"%s\" too long\n", optarg);
+ exit(1);
+ }
+ n = snprintf(srcdir, sizeof(srcdir), "%s/", optarg);
+ INSIST(n > 0 && (unsigned)n < sizeof(srcdir));
+ break;
+ case 'F':
+ file = optarg;
+ break;
+ case 'P':
+ prefix = optarg;
+ break;
+ case 'S':
+ suffix = optarg;
+ break;
+ case '?':
+ exit(1);
+ }
+ }
+
+ n = snprintf(buf, sizeof(buf), "%srdata", srcdir);
+ INSIST(n > 0 && (unsigned)n < sizeof(srcdir));
+
+ if (!start_directory(buf, &dir)) {
+ exit(1);
+ }
+
+ while (next_file(&dir)) {
+ if (sscanf(dir.filename, TYPECLASSFMT, classbuf, &rdclass) != 2)
+ {
+ continue;
+ }
+
+ /*
+ * sscanf accepts leading sign and zeros before type so
+ * compare the scanned items against the filename. Filter
+ * out mismatches.
+ */
+ n = snprintf(buf, sizeof(buf), "%srdata/%s_%u", srcdir,
+ classbuf, rdclass);
+ INSIST(n > 0 && (unsigned)n < sizeof(buf));
+ if (strcmp(buf + 6 + strlen(srcdir), dir.filename) != 0) {
+ continue;
+ }
+ if (rdclass > 65535) {
+ fprintf(stderr, "Error: class value > 65535 (%s)\n",
+ dir.filename);
+ exit(1);
+ }
+ sd(rdclass, classbuf, buf, filetype);
+ }
+ end_directory(&dir);
+ n = snprintf(buf, sizeof(buf), "%srdata/generic", srcdir);
+ INSIST(n > 0 && (unsigned)n < sizeof(srcdir));
+ sd(0, "", buf, filetype);
+
+ source_date_epoch = getenv("SOURCE_DATE_EPOCH");
+ if (source_date_epoch) {
+ errno = 0;
+ epoch = strtoull(source_date_epoch, &endptr, 10);
+ if ((errno == ERANGE && (epoch == ULLONG_MAX || epoch == 0)) ||
+ (errno != 0 && epoch == 0))
+ {
+ fprintf(stderr,
+ "Environment variable "
+ "$SOURCE_DATE_EPOCH: strtoull: %s\n",
+ strerror(errno));
+ exit(EXIT_FAILURE);
+ }
+ if (endptr == source_date_epoch) {
+ fprintf(stderr,
+ "Environment variable "
+ "$SOURCE_DATE_EPOCH: "
+ "No digits were found: %s\n",
+ endptr);
+ exit(EXIT_FAILURE);
+ }
+ if (*endptr != '\0') {
+ fprintf(stderr,
+ "Environment variable "
+ "$SOURCE_DATE_EPOCH: Trailing garbage: %s\n",
+ endptr);
+ exit(EXIT_FAILURE);
+ }
+ if (epoch > ULONG_MAX) {
+ fprintf(stderr,
+ "Environment variable "
+ "$SOURCE_DATE_EPOCH: value must be "
+ "smaller than or equal to: %lu but "
+ "was found to be: %llu \n",
+ ULONG_MAX, epoch);
+ exit(EXIT_FAILURE);
+ }
+ now = epoch;
+ } else {
+ time(&now);
+ }
+
+ if (now != -1) {
+ struct tm t, *tm = gmtime_r(&now, &t);
+
+ if (tm != NULL && tm->tm_year > 104) {
+ n = snprintf(year, sizeof(year), "-%d",
+ tm->tm_year + 1900);
+ INSIST(n > 0 && (unsigned)n < sizeof(year));
+ } else {
+ snprintf(year, sizeof(year), "-2016");
+ }
+ } else {
+ snprintf(year, sizeof(year), "-2016");
+ }
+
+ if (!depend) {
+ printf(copyright, year);
+ }
+
+ if (code) {
+ printf("#pragma once\n");
+
+ printf("#include <stdbool.h>\n");
+ printf("#include <isc/result.h>\n\n");
+ printf("#include <dns/name.h>\n\n");
+
+ for (tt = types; tt != NULL; tt = tt->next) {
+ printf("#include \"%s/%s_%d.c\"\n", tt->dirbuf,
+ tt->typebuf, tt->type);
+ }
+
+ printf("\n\n");
+
+ doswitch("FROMTEXTSWITCH", "fromtext", FROMTEXTARGS,
+ FROMTEXTTYPE, FROMTEXTCLASS, FROMTEXTDEF);
+ doswitch("TOTEXTSWITCH", "totext", TOTEXTARGS, TOTEXTTYPE,
+ TOTEXTCLASS, TOTEXTDEF);
+ doswitch("FROMWIRESWITCH", "fromwire", FROMWIREARGS,
+ FROMWIRETYPE, FROMWIRECLASS, FROMWIREDEF);
+ doswitch("TOWIRESWITCH", "towire", TOWIREARGS, TOWIRETYPE,
+ TOWIRECLASS, TOWIREDEF);
+ doswitch("COMPARESWITCH", "compare", COMPAREARGS, COMPARETYPE,
+ COMPARECLASS, COMPAREDEF);
+ doswitch("CASECOMPARESWITCH", "casecompare", COMPAREARGS,
+ COMPARETYPE, COMPARECLASS, COMPAREDEF);
+ doswitch("FROMSTRUCTSWITCH", "fromstruct", FROMSTRUCTARGS,
+ FROMSTRUCTTYPE, FROMSTRUCTCLASS, FROMSTRUCTDEF);
+ doswitch("TOSTRUCTSWITCH", "tostruct", TOSTRUCTARGS,
+ TOSTRUCTTYPE, TOSTRUCTCLASS, TOSTRUCTDEF);
+ doswitch("FREESTRUCTSWITCH", "freestruct", FREESTRUCTARGS,
+ FREESTRUCTTYPE, FREESTRUCTCLASS, FREESTRUCTDEF);
+ doswitch("ADDITIONALDATASWITCH", "additionaldata",
+ ADDITIONALDATAARGS, ADDITIONALDATATYPE,
+ ADDITIONALDATACLASS, ADDITIONALDATADEF);
+ doswitch("DIGESTSWITCH", "digest", DIGESTARGS, DIGESTTYPE,
+ DIGESTCLASS, DIGESTDEF);
+ doswitch("CHECKOWNERSWITCH", "checkowner", CHECKOWNERARGS,
+ CHECKOWNERTYPE, CHECKOWNERCLASS, CHECKOWNERDEF);
+ doswitch("CHECKNAMESSWITCH", "checknames", CHECKNAMESARGS,
+ CHECKNAMESTYPE, CHECKNAMESCLASS, CHECKNAMESDEF);
+
+ /*
+ * From here down, we are processing the rdata names and
+ * attributes.
+ */
+
+#define PRINT_COMMA(x) (x == maxtype ? "" : ",")
+
+#define METANOTQUESTION \
+ "DNS_RDATATYPEATTR_META | " \
+ "DNS_RDATATYPEATTR_NOTQUESTION"
+#define METAQUESTIONONLY \
+ "DNS_RDATATYPEATTR_META | " \
+ "DNS_RDATATYPEATTR_QUESTIONONLY"
+#define RESERVEDNAME "0"
+#define RESERVED "DNS_RDATATYPEATTR_RESERVED"
+
+ /*
+ * Add in reserved/special types. This will let us
+ * sort them without special cases.
+ */
+ insert_into_typenames(100, "uinfo", RESERVEDNAME);
+ insert_into_typenames(101, "uid", RESERVEDNAME);
+ insert_into_typenames(102, "gid", RESERVEDNAME);
+ insert_into_typenames(103, "unspec", RESERVEDNAME);
+ insert_into_typenames(251, "ixfr", METAQUESTIONONLY);
+ insert_into_typenames(252, "axfr", METAQUESTIONONLY);
+ insert_into_typenames(253, "mailb", METAQUESTIONONLY);
+ insert_into_typenames(254, "maila", METAQUESTIONONLY);
+ insert_into_typenames(255, "any", METAQUESTIONONLY);
+
+ /*
+ * Spit out a quick and dirty hash function. Here,
+ * we walk through the list of type names, and calculate
+ * a hash. This isn't perfect, but it will generate "pretty
+ * good" estimates. Lowercase the characters before
+ * computing in all cases.
+ *
+ * Here, walk the list from top to bottom, calculating
+ * the hash (mod 256) for each name.
+ */
+ printf("#define RDATATYPE_COMPARE(_s, _d, _tn, _n, _tp) \\\n");
+ printf("\tdo { \\\n");
+ printf("\t\tif (sizeof(_s) - 1 == _n && \\\n"
+ "\t\t strncasecmp(_s,(_tn),"
+ "(sizeof(_s) - 1)) == 0) { \\\n");
+ printf("\t\t\tif ((dns_rdatatype_attributes(_d) & "
+ "DNS_RDATATYPEATTR_RESERVED) != 0) \\\n");
+ printf("\t\t\t\treturn (ISC_R_NOTIMPLEMENTED); \\\n");
+ printf("\t\t\t*(_tp) = _d; \\\n");
+ printf("\t\t\treturn (ISC_R_SUCCESS); \\\n");
+ printf("\t\t} \\\n");
+ printf("\t} while (0)\n\n");
+
+ printf("#define RDATATYPE_FROMTEXT_SW(_hash,"
+ "_typename,_length,_typep) \\\n");
+ printf("\tswitch (_hash) { \\\n");
+ for (i = 0; i <= maxtype; i++) {
+ ttn = find_typename(i);
+ if (ttn == NULL) {
+ continue;
+ }
+
+ /*
+ * Skip entries we already processed.
+ */
+ if (ttn->sorted != 0) {
+ continue;
+ }
+
+ hash = HASH(ttn->typebuf);
+ printf("\t\tcase %u: \\\n", hash);
+
+ /*
+ * Find all other entries that happen to match
+ * this hash.
+ */
+ for (j = 0; j <= maxtype; j++) {
+ ttn2 = find_typename(j);
+ if (ttn2 == NULL) {
+ continue;
+ }
+ if (hash == HASH(ttn2->typebuf)) {
+ printf("\t\t\tRDATATYPE_COMPARE"
+ "(\"%s\", %d, _typename, "
+ " _length, _typep); \\\n",
+ ttn2->typebuf, ttn2->type);
+ ttn2->sorted = 1;
+ }
+ }
+ printf("\t\t\tbreak; \\\n");
+ }
+ printf("\t}\n");
+
+ printf("#define RDATATYPE_ATTRIBUTE_SW \\\n");
+ printf("\tswitch (type) { \\\n");
+ for (i = 0; i <= maxtype; i++) {
+ ttn = find_typename(i);
+ if (ttn == NULL) {
+ continue;
+ }
+ printf("\tcase %d: return (%s); \\\n", i,
+ upper(ttn->attr));
+ }
+ printf("\t}\n");
+
+ printf("#define RDATATYPE_TOTEXT_SW \\\n");
+ printf("\tswitch (type) { \\\n");
+ for (i = 0; i <= maxtype; i++) {
+ ttn = find_typename(i);
+ if (ttn == NULL) {
+ continue;
+ }
+ /*
+ * Remove KEYDATA (65533) from the type to memonic
+ * translation as it is internal use only. This
+ * stops the tools from displaying KEYDATA instead
+ * of TYPE65533.
+ */
+ if (i == 65533U) {
+ continue;
+ }
+ printf("\tcase %d: return "
+ "(str_totext(\"%s\", target)); \\\n",
+ i, upper(ttn->typebuf));
+ }
+ printf("\t}\n");
+ } else if (type_enum) {
+ char *s;
+
+ printf("#pragma once\n");
+
+ printf("enum {\n");
+ printf("\tdns_rdatatype_none = 0,\n");
+
+ lasttype = 0;
+ for (tt = types; tt != NULL; tt = tt->next) {
+ if (tt->type != lasttype) {
+ printf("\tdns_rdatatype_%s = %d,\n",
+ funname(tt->typebuf, buf1),
+ lasttype = tt->type);
+ }
+ }
+
+ printf("\tdns_rdatatype_ixfr = 251,\n");
+ printf("\tdns_rdatatype_axfr = 252,\n");
+ printf("\tdns_rdatatype_mailb = 253,\n");
+ printf("\tdns_rdatatype_maila = 254,\n");
+ printf("\tdns_rdatatype_any = 255\n");
+
+ printf("};\n\n");
+
+ printf("#define dns_rdatatype_none\t"
+ "((dns_rdatatype_t)dns_rdatatype_none)\n");
+
+ for (tt = types; tt != NULL; tt = tt->next) {
+ if (tt->type != lasttype) {
+ s = funname(tt->typebuf, buf1);
+ printf("#define dns_rdatatype_%s\t%s"
+ "((dns_rdatatype_t)dns_rdatatype_%s)\n",
+ s, strlen(s) < 2U ? "\t" : "", s);
+ lasttype = tt->type;
+ }
+ }
+
+ printf("#define dns_rdatatype_ixfr\t"
+ "((dns_rdatatype_t)dns_rdatatype_ixfr)\n");
+ printf("#define dns_rdatatype_axfr\t"
+ "((dns_rdatatype_t)dns_rdatatype_axfr)\n");
+ printf("#define dns_rdatatype_mailb\t"
+ "((dns_rdatatype_t)dns_rdatatype_mailb)\n");
+ printf("#define dns_rdatatype_maila\t"
+ "((dns_rdatatype_t)dns_rdatatype_maila)\n");
+ printf("#define dns_rdatatype_any\t"
+ "((dns_rdatatype_t)dns_rdatatype_any)\n");
+ } else if (class_enum) {
+ char *s;
+ int classnum;
+
+ printf("#pragma once\n");
+
+ printf("enum {\n");
+
+ printf("\tdns_rdataclass_reserved0 = 0,\n");
+ printf("#define dns_rdataclass_reserved0 \\\n\t\t\t\t"
+ "((dns_rdataclass_t)dns_rdataclass_reserved0)\n");
+
+#define PRINTCLASS(name, num) \
+ do { \
+ s = funname(name, buf1); \
+ classnum = num; \
+ printf("\tdns_rdataclass_%s = %d%s\n", s, classnum, \
+ classnum != 255 ? "," : ""); \
+ printf("#define dns_rdataclass_%s\t" \
+ "((dns_rdataclass_t)dns_rdataclass_%s)\n", \
+ s, s); \
+ } while (0)
+
+ for (cc = classes; cc != NULL; cc = cc->next) {
+ if (cc->rdclass == 3) {
+ PRINTCLASS("chaos", 3);
+ } else if (cc->rdclass == 255) {
+ PRINTCLASS("none", 254);
+ }
+ PRINTCLASS(cc->classbuf, cc->rdclass);
+ }
+
+#undef PRINTCLASS
+
+ printf("};\n\n");
+ } else if (structs) {
+ if (prefix != NULL) {
+ if ((fd = fopen(prefix, "r")) != NULL) {
+ while (fgets(buf, sizeof(buf), fd) != NULL) {
+ printf("%s", buf);
+ }
+ fclose(fd);
+ }
+ }
+ for (tt = types; tt != NULL; tt = tt->next) {
+ snprintf(buf, sizeof(buf), "%s/%s_%d.h", tt->dirbuf,
+ tt->typebuf, tt->type);
+ if ((fd = fopen(buf, "r")) != NULL) {
+ while (fgets(buf, sizeof(buf), fd) != NULL) {
+ printf("%s", buf);
+ }
+ fclose(fd);
+ }
+ }
+ if (suffix != NULL) {
+ if ((fd = fopen(suffix, "r")) != NULL) {
+ while (fgets(buf, sizeof(buf), fd) != NULL) {
+ printf("%s", buf);
+ }
+ fclose(fd);
+ }
+ }
+ } else if (depend) {
+ for (tt = types; tt != NULL; tt = tt->next) {
+ printf("%s:\t%s/%s_%d.h\n", file, tt->dirbuf,
+ tt->typebuf, tt->type);
+ }
+ }
+
+ if (ferror(stdout) != 0) {
+ exit(1);
+ }
+
+ return (0);
+}
diff --git a/lib/dns/geoip2.c b/lib/dns/geoip2.c
new file mode 100644
index 0000000..a3b2206
--- /dev/null
+++ b/lib/dns/geoip2.c
@@ -0,0 +1,382 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/*! \file */
+
+#include <inttypes.h>
+#include <stdbool.h>
+#include <stdlib.h>
+
+/*
+ * This file is only built and linked if GeoIP2 has been configured.
+ */
+#include <math.h>
+#include <maxminddb.h>
+#include <netinet/in.h>
+
+#include <isc/mem.h>
+#include <isc/once.h>
+#include <isc/sockaddr.h>
+#include <isc/string.h>
+#include <isc/thread.h>
+#include <isc/util.h>
+
+#include <dns/acl.h>
+#include <dns/geoip.h>
+#include <dns/log.h>
+
+/*
+ * This structure preserves state from the previous GeoIP lookup,
+ * so that successive lookups for the same data from the same IP
+ * address will not require repeated database lookups.
+ * This should improve performance somewhat.
+ *
+ * For all lookups we preserve pointers to the MMDB_lookup_result_s
+ * and MMDB_entry_s structures, a pointer to the database from which
+ * the lookup was answered, and a copy of the request address.
+ *
+ * If the next geoip ACL lookup is for the same database and from the
+ * same address, we can reuse the MMDB entry without repeating the lookup.
+ * This is for the case when a single query has to process multiple
+ * geoip ACLs: for example, when there are multiple views with
+ * match-clients statements that search for different countries.
+ *
+ * (XXX: Currently the persistent state is stored in thread specific
+ * memory, but it could more simply be stored in the client object.
+ * Also multiple entries could be stored in case the ACLs require
+ * searching in more than one GeoIP database.)
+ */
+
+typedef struct geoip_state {
+ uint16_t subtype;
+ const MMDB_s *db;
+ isc_netaddr_t addr;
+ MMDB_lookup_result_s mmresult;
+ MMDB_entry_s entry;
+} geoip_state_t;
+
+static thread_local geoip_state_t geoip_state = { 0 };
+
+static void
+set_state(const MMDB_s *db, const isc_netaddr_t *addr,
+ MMDB_lookup_result_s mmresult, MMDB_entry_s entry) {
+ geoip_state.db = db;
+ geoip_state.addr = *addr;
+ geoip_state.mmresult = mmresult;
+ geoip_state.entry = entry;
+}
+
+static geoip_state_t *
+get_entry_for(MMDB_s *const db, const isc_netaddr_t *addr) {
+ isc_sockaddr_t sa;
+ MMDB_lookup_result_s match;
+ int err;
+
+ if (db == geoip_state.db && isc_netaddr_equal(addr, &geoip_state.addr))
+ {
+ return (&geoip_state);
+ }
+
+ isc_sockaddr_fromnetaddr(&sa, addr, 0);
+ match = MMDB_lookup_sockaddr(db, &sa.type.sa, &err);
+ if (err != MMDB_SUCCESS || !match.found_entry) {
+ return (NULL);
+ }
+
+ set_state(db, addr, match, match.entry);
+
+ return (&geoip_state);
+}
+
+static dns_geoip_subtype_t
+fix_subtype(const dns_geoip_databases_t *geoip, dns_geoip_subtype_t subtype) {
+ dns_geoip_subtype_t ret = subtype;
+
+ switch (subtype) {
+ case dns_geoip_countrycode:
+ if (geoip->city != NULL) {
+ ret = dns_geoip_city_countrycode;
+ } else if (geoip->country != NULL) {
+ ret = dns_geoip_country_code;
+ }
+ break;
+ case dns_geoip_countryname:
+ if (geoip->city != NULL) {
+ ret = dns_geoip_city_countryname;
+ } else if (geoip->country != NULL) {
+ ret = dns_geoip_country_name;
+ }
+ break;
+ case dns_geoip_continentcode:
+ if (geoip->city != NULL) {
+ ret = dns_geoip_city_continentcode;
+ } else if (geoip->country != NULL) {
+ ret = dns_geoip_country_continentcode;
+ }
+ break;
+ case dns_geoip_continent:
+ if (geoip->city != NULL) {
+ ret = dns_geoip_city_continent;
+ } else if (geoip->country != NULL) {
+ ret = dns_geoip_country_continent;
+ }
+ break;
+ case dns_geoip_region:
+ if (geoip->city != NULL) {
+ ret = dns_geoip_city_region;
+ }
+ break;
+ case dns_geoip_regionname:
+ if (geoip->city != NULL) {
+ ret = dns_geoip_city_regionname;
+ }
+ default:
+ break;
+ }
+
+ return (ret);
+}
+
+static MMDB_s *
+geoip2_database(const dns_geoip_databases_t *geoip,
+ dns_geoip_subtype_t subtype) {
+ switch (subtype) {
+ case dns_geoip_country_code:
+ case dns_geoip_country_name:
+ case dns_geoip_country_continentcode:
+ case dns_geoip_country_continent:
+ return (geoip->country);
+
+ case dns_geoip_city_countrycode:
+ case dns_geoip_city_countryname:
+ case dns_geoip_city_continentcode:
+ case dns_geoip_city_continent:
+ case dns_geoip_city_region:
+ case dns_geoip_city_regionname:
+ case dns_geoip_city_name:
+ case dns_geoip_city_postalcode:
+ case dns_geoip_city_timezonecode:
+ case dns_geoip_city_metrocode:
+ case dns_geoip_city_areacode:
+ return (geoip->city);
+
+ case dns_geoip_isp_name:
+ return (geoip->isp);
+
+ case dns_geoip_as_asnum:
+ case dns_geoip_org_name:
+ return (geoip->as);
+
+ case dns_geoip_domain_name:
+ return (geoip->domain);
+
+ default:
+ /*
+ * All other subtypes are unavailable in GeoIP2.
+ */
+ return (NULL);
+ }
+}
+
+static bool
+match_string(MMDB_entry_data_s *value, const char *str) {
+ REQUIRE(str != NULL);
+
+ if (value == NULL || !value->has_data ||
+ value->type != MMDB_DATA_TYPE_UTF8_STRING ||
+ value->utf8_string == NULL)
+ {
+ return (false);
+ }
+
+ return (strncasecmp(value->utf8_string, str, value->data_size) == 0);
+}
+
+static bool
+match_int(MMDB_entry_data_s *value, const uint32_t ui32) {
+ if (value == NULL || !value->has_data ||
+ (value->type != MMDB_DATA_TYPE_UINT32 &&
+ value->type != MMDB_DATA_TYPE_UINT16))
+ {
+ return (false);
+ }
+
+ return (value->uint32 == ui32);
+}
+
+bool
+dns_geoip_match(const isc_netaddr_t *reqaddr,
+ const dns_geoip_databases_t *geoip,
+ const dns_geoip_elem_t *elt) {
+ MMDB_s *db = NULL;
+ MMDB_entry_data_s value;
+ geoip_state_t *state = NULL;
+ dns_geoip_subtype_t subtype;
+ const char *s = NULL;
+ int ret;
+
+ REQUIRE(reqaddr != NULL);
+ REQUIRE(elt != NULL);
+ REQUIRE(geoip != NULL);
+
+ subtype = fix_subtype(geoip, elt->subtype);
+ db = geoip2_database(geoip, subtype);
+ if (db == NULL) {
+ return (false);
+ }
+
+ state = get_entry_for(db, reqaddr);
+ if (state == NULL) {
+ return (false);
+ }
+
+ switch (subtype) {
+ case dns_geoip_country_code:
+ case dns_geoip_city_countrycode:
+ ret = MMDB_get_value(&state->entry, &value, "country",
+ "iso_code", (char *)0);
+ if (ret == MMDB_SUCCESS) {
+ return (match_string(&value, elt->as_string));
+ }
+ break;
+
+ case dns_geoip_country_name:
+ case dns_geoip_city_countryname:
+ ret = MMDB_get_value(&state->entry, &value, "country", "names",
+ "en", (char *)0);
+ if (ret == MMDB_SUCCESS) {
+ return (match_string(&value, elt->as_string));
+ }
+ break;
+
+ case dns_geoip_country_continentcode:
+ case dns_geoip_city_continentcode:
+ ret = MMDB_get_value(&state->entry, &value, "continent", "code",
+ (char *)0);
+ if (ret == MMDB_SUCCESS) {
+ return (match_string(&value, elt->as_string));
+ }
+ break;
+
+ case dns_geoip_country_continent:
+ case dns_geoip_city_continent:
+ ret = MMDB_get_value(&state->entry, &value, "continent",
+ "names", "en", (char *)0);
+ if (ret == MMDB_SUCCESS) {
+ return (match_string(&value, elt->as_string));
+ }
+ break;
+
+ case dns_geoip_region:
+ case dns_geoip_city_region:
+ ret = MMDB_get_value(&state->entry, &value, "subdivisions", "0",
+ "iso_code", (char *)0);
+ if (ret == MMDB_SUCCESS) {
+ return (match_string(&value, elt->as_string));
+ }
+ break;
+
+ case dns_geoip_regionname:
+ case dns_geoip_city_regionname:
+ ret = MMDB_get_value(&state->entry, &value, "subdivisions", "0",
+ "names", "en", (char *)0);
+ if (ret == MMDB_SUCCESS) {
+ return (match_string(&value, elt->as_string));
+ }
+ break;
+
+ case dns_geoip_city_name:
+ ret = MMDB_get_value(&state->entry, &value, "city", "names",
+ "en", (char *)0);
+ if (ret == MMDB_SUCCESS) {
+ return (match_string(&value, elt->as_string));
+ }
+ break;
+
+ case dns_geoip_city_postalcode:
+ ret = MMDB_get_value(&state->entry, &value, "postal", "code",
+ (char *)0);
+ if (ret == MMDB_SUCCESS) {
+ return (match_string(&value, elt->as_string));
+ }
+ break;
+
+ case dns_geoip_city_timezonecode:
+ ret = MMDB_get_value(&state->entry, &value, "location",
+ "time_zone", (char *)0);
+ if (ret == MMDB_SUCCESS) {
+ return (match_string(&value, elt->as_string));
+ }
+ break;
+
+ case dns_geoip_city_metrocode:
+ ret = MMDB_get_value(&state->entry, &value, "location",
+ "metro_code", (char *)0);
+ if (ret == MMDB_SUCCESS) {
+ return (match_string(&value, elt->as_string));
+ }
+ break;
+
+ case dns_geoip_isp_name:
+ ret = MMDB_get_value(&state->entry, &value, "isp", (char *)0);
+ if (ret == MMDB_SUCCESS) {
+ return (match_string(&value, elt->as_string));
+ }
+ break;
+
+ case dns_geoip_as_asnum:
+ INSIST(elt->as_string != NULL);
+
+ ret = MMDB_get_value(&state->entry, &value,
+ "autonomous_system_number", (char *)0);
+ if (ret == MMDB_SUCCESS) {
+ int i;
+ s = elt->as_string;
+ if (strncasecmp(s, "AS", 2) == 0) {
+ s += 2;
+ }
+ i = strtol(s, NULL, 10);
+ return (match_int(&value, i));
+ }
+ break;
+
+ case dns_geoip_org_name:
+ ret = MMDB_get_value(&state->entry, &value,
+ "autonomous_system_organization",
+ (char *)0);
+ if (ret == MMDB_SUCCESS) {
+ return (match_string(&value, elt->as_string));
+ }
+ break;
+
+ case dns_geoip_domain_name:
+ ret = MMDB_get_value(&state->entry, &value, "domain",
+ (char *)0);
+ if (ret == MMDB_SUCCESS) {
+ return (match_string(&value, elt->as_string));
+ }
+ break;
+
+ default:
+ /*
+ * For any other subtype, we assume the database was
+ * unavailable and return false.
+ */
+ return (false);
+ }
+
+ /*
+ * No database matched: return false.
+ */
+ return (false);
+}
diff --git a/lib/dns/gssapi_link.c b/lib/dns/gssapi_link.c
new file mode 100644
index 0000000..a3fbb6e
--- /dev/null
+++ b/lib/dns/gssapi_link.c
@@ -0,0 +1,364 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#include <inttypes.h> /* IWYU pragma: keep */
+#include <stdbool.h>
+#include <time.h> /* IWYU pragma: keep */
+
+#if HAVE_GSSAPI_GSSAPI_H
+#include <gssapi/gssapi.h>
+#elif HAVE_GSSAPI_H
+#include <gssapi.h>
+#endif
+
+#if HAVE_GSSAPI_GSSAPI_KRB5_H
+#include <gssapi/gssapi_krb5.h>
+#elif HAVE_GSSAPI_KRB5_H
+#include <gssapi_krb5.h>
+#endif
+
+#include <isc/base64.h>
+#include <isc/buffer.h>
+#include <isc/mem.h>
+#include <isc/print.h>
+#include <isc/result.h>
+#include <isc/string.h>
+#include <isc/util.h>
+
+#include <dst/gssapi.h>
+
+#include "dst_internal.h"
+#include "dst_parse.h"
+
+#define INITIAL_BUFFER_SIZE 1024
+#define BUFFER_EXTRA 1024
+
+#define REGION_TO_GBUFFER(r, gb) \
+ do { \
+ (gb).length = (r).length; \
+ (gb).value = (r).base; \
+ } while (0)
+
+#define GBUFFER_TO_REGION(gb, r) \
+ do { \
+ (r).length = (unsigned int)(gb).length; \
+ (r).base = (gb).value; \
+ } while (0)
+
+struct dst_gssapi_signverifyctx {
+ isc_buffer_t *buffer;
+};
+
+/*%
+ * Allocate a temporary "context" for use in gathering data for signing
+ * or verifying.
+ */
+static isc_result_t
+gssapi_create_signverify_ctx(dst_key_t *key, dst_context_t *dctx) {
+ dst_gssapi_signverifyctx_t *ctx;
+
+ UNUSED(key);
+
+ ctx = isc_mem_get(dctx->mctx, sizeof(dst_gssapi_signverifyctx_t));
+ ctx->buffer = NULL;
+ isc_buffer_allocate(dctx->mctx, &ctx->buffer, INITIAL_BUFFER_SIZE);
+
+ dctx->ctxdata.gssctx = ctx;
+
+ return (ISC_R_SUCCESS);
+}
+
+/*%
+ * Destroy the temporary sign/verify context.
+ */
+static void
+gssapi_destroy_signverify_ctx(dst_context_t *dctx) {
+ dst_gssapi_signverifyctx_t *ctx = dctx->ctxdata.gssctx;
+
+ if (ctx != NULL) {
+ if (ctx->buffer != NULL) {
+ isc_buffer_free(&ctx->buffer);
+ }
+ isc_mem_put(dctx->mctx, ctx,
+ sizeof(dst_gssapi_signverifyctx_t));
+ dctx->ctxdata.gssctx = NULL;
+ }
+}
+
+/*%
+ * Add data to our running buffer of data we will be signing or verifying.
+ * This code will see if the new data will fit in our existing buffer, and
+ * copy it in if it will. If not, it will attempt to allocate a larger
+ * buffer and copy old+new into it, and free the old buffer.
+ */
+static isc_result_t
+gssapi_adddata(dst_context_t *dctx, const isc_region_t *data) {
+ dst_gssapi_signverifyctx_t *ctx = dctx->ctxdata.gssctx;
+ isc_buffer_t *newbuffer = NULL;
+ isc_region_t r;
+ unsigned int length;
+ isc_result_t result;
+
+ result = isc_buffer_copyregion(ctx->buffer, data);
+ if (result == ISC_R_SUCCESS) {
+ return (ISC_R_SUCCESS);
+ }
+
+ length = isc_buffer_length(ctx->buffer) + data->length + BUFFER_EXTRA;
+
+ isc_buffer_allocate(dctx->mctx, &newbuffer, length);
+
+ isc_buffer_usedregion(ctx->buffer, &r);
+ (void)isc_buffer_copyregion(newbuffer, &r);
+ (void)isc_buffer_copyregion(newbuffer, data);
+
+ isc_buffer_free(&ctx->buffer);
+ ctx->buffer = newbuffer;
+
+ return (ISC_R_SUCCESS);
+}
+
+/*%
+ * Sign.
+ */
+static isc_result_t
+gssapi_sign(dst_context_t *dctx, isc_buffer_t *sig) {
+ dst_gssapi_signverifyctx_t *ctx = dctx->ctxdata.gssctx;
+ isc_region_t message;
+ gss_buffer_desc gmessage, gsig;
+ OM_uint32 minor, gret;
+ gss_ctx_id_t gssctx = dctx->key->keydata.gssctx;
+ char buf[1024];
+
+ /*
+ * Convert the data we wish to sign into a structure gssapi can
+ * understand.
+ */
+ isc_buffer_usedregion(ctx->buffer, &message);
+ REGION_TO_GBUFFER(message, gmessage);
+
+ /*
+ * Generate the signature.
+ */
+ gret = gss_get_mic(&minor, gssctx, GSS_C_QOP_DEFAULT, &gmessage, &gsig);
+
+ /*
+ * If it did not complete, we log the result and return a generic
+ * failure code.
+ */
+ if (gret != GSS_S_COMPLETE) {
+ gss_log(3, "GSS sign error: %s",
+ gss_error_tostring(gret, minor, buf, sizeof(buf)));
+ return (ISC_R_FAILURE);
+ }
+
+ /*
+ * If it will not fit in our allocated buffer, return that we need
+ * more space.
+ */
+ if (gsig.length > isc_buffer_availablelength(sig)) {
+ gss_release_buffer(&minor, &gsig);
+ return (ISC_R_NOSPACE);
+ }
+
+ /*
+ * Copy the output into our buffer space, and release the gssapi
+ * allocated space.
+ */
+ isc_buffer_putmem(sig, gsig.value, (unsigned int)gsig.length);
+ if (gsig.length != 0U) {
+ gss_release_buffer(&minor, &gsig);
+ }
+
+ return (ISC_R_SUCCESS);
+}
+
+/*%
+ * Verify.
+ */
+static isc_result_t
+gssapi_verify(dst_context_t *dctx, const isc_region_t *sig) {
+ dst_gssapi_signverifyctx_t *ctx = dctx->ctxdata.gssctx;
+ isc_region_t message;
+ gss_buffer_desc gmessage, gsig;
+ OM_uint32 minor, gret;
+ gss_ctx_id_t gssctx = dctx->key->keydata.gssctx;
+ char err[1024];
+
+ /*
+ * Convert the data we wish to sign into a structure gssapi can
+ * understand.
+ */
+ isc_buffer_usedregion(ctx->buffer, &message);
+ REGION_TO_GBUFFER(message, gmessage);
+ REGION_TO_GBUFFER(*sig, gsig);
+
+ /*
+ * Verify the data.
+ */
+ gret = gss_verify_mic(&minor, gssctx, &gmessage, &gsig, NULL);
+
+ /*
+ * Convert return codes into something useful to us.
+ */
+ if (gret != GSS_S_COMPLETE) {
+ gss_log(3, "GSS verify error: %s",
+ gss_error_tostring(gret, minor, err, sizeof(err)));
+ if (gret == GSS_S_DEFECTIVE_TOKEN || gret == GSS_S_BAD_SIG ||
+ gret == GSS_S_DUPLICATE_TOKEN || gret == GSS_S_OLD_TOKEN ||
+ gret == GSS_S_UNSEQ_TOKEN || gret == GSS_S_GAP_TOKEN ||
+ gret == GSS_S_CONTEXT_EXPIRED || gret == GSS_S_NO_CONTEXT ||
+ gret == GSS_S_FAILURE)
+ {
+ return (DST_R_VERIFYFAILURE);
+ } else {
+ return (ISC_R_FAILURE);
+ }
+ }
+
+ return (ISC_R_SUCCESS);
+}
+
+static bool
+gssapi_compare(const dst_key_t *key1, const dst_key_t *key2) {
+ gss_ctx_id_t gsskey1 = key1->keydata.gssctx;
+ gss_ctx_id_t gsskey2 = key2->keydata.gssctx;
+
+ /* No idea */
+ return (gsskey1 == gsskey2);
+}
+
+static isc_result_t
+gssapi_generate(dst_key_t *key, int unused, void (*callback)(int)) {
+ UNUSED(key);
+ UNUSED(unused);
+ UNUSED(callback);
+
+ /* No idea */
+ return (ISC_R_FAILURE);
+}
+
+static bool
+gssapi_isprivate(const dst_key_t *key) {
+ UNUSED(key);
+ return (true);
+}
+
+static void
+gssapi_destroy(dst_key_t *key) {
+ REQUIRE(key != NULL);
+ dst_gssapi_deletectx(key->mctx, &key->keydata.gssctx);
+ key->keydata.gssctx = NULL;
+}
+
+static isc_result_t
+gssapi_restore(dst_key_t *key, const char *keystr) {
+ OM_uint32 major, minor;
+ unsigned int len;
+ isc_buffer_t *b = NULL;
+ isc_region_t r;
+ gss_buffer_desc gssbuffer;
+ isc_result_t result;
+
+ len = strlen(keystr);
+ if ((len % 4) != 0U) {
+ return (ISC_R_BADBASE64);
+ }
+
+ len = (len / 4) * 3;
+
+ isc_buffer_allocate(key->mctx, &b, len);
+
+ result = isc_base64_decodestring(keystr, b);
+ if (result != ISC_R_SUCCESS) {
+ isc_buffer_free(&b);
+ return (result);
+ }
+
+ isc_buffer_remainingregion(b, &r);
+ REGION_TO_GBUFFER(r, gssbuffer);
+ major = gss_import_sec_context(&minor, &gssbuffer,
+ (gss_ctx_id_t *)&key->keydata.gssctx);
+ if (major != GSS_S_COMPLETE) {
+ isc_buffer_free(&b);
+ return (ISC_R_FAILURE);
+ }
+
+ isc_buffer_free(&b);
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+gssapi_dump(dst_key_t *key, isc_mem_t *mctx, char **buffer, int *length) {
+ OM_uint32 major, minor;
+ gss_buffer_desc gssbuffer;
+ size_t len;
+ char *buf;
+ isc_buffer_t b;
+ isc_region_t r;
+ isc_result_t result;
+
+ major = gss_export_sec_context(
+ &minor, (gss_ctx_id_t *)&key->keydata.gssctx, &gssbuffer);
+ if (major != GSS_S_COMPLETE) {
+ fprintf(stderr, "gss_export_sec_context -> %u, %u\n", major,
+ minor);
+ return (ISC_R_FAILURE);
+ }
+ if (gssbuffer.length == 0U) {
+ return (ISC_R_FAILURE);
+ }
+ len = ((gssbuffer.length + 2) / 3) * 4;
+ buf = isc_mem_get(mctx, len);
+ isc_buffer_init(&b, buf, (unsigned int)len);
+ GBUFFER_TO_REGION(gssbuffer, r);
+ result = isc_base64_totext(&r, 0, "", &b);
+ RUNTIME_CHECK(result == ISC_R_SUCCESS);
+ gss_release_buffer(&minor, &gssbuffer);
+ *buffer = buf;
+ *length = (int)len;
+ return (ISC_R_SUCCESS);
+}
+
+static dst_func_t gssapi_functions = {
+ gssapi_create_signverify_ctx,
+ NULL, /*%< createctx2 */
+ gssapi_destroy_signverify_ctx,
+ gssapi_adddata,
+ gssapi_sign,
+ gssapi_verify,
+ NULL, /*%< verify2 */
+ NULL, /*%< computesecret */
+ gssapi_compare,
+ NULL, /*%< paramcompare */
+ gssapi_generate,
+ gssapi_isprivate,
+ gssapi_destroy,
+ NULL, /*%< todns */
+ NULL, /*%< fromdns */
+ NULL, /*%< tofile */
+ NULL, /*%< parse */
+ NULL, /*%< cleanup */
+ NULL, /*%< fromlabel */
+ gssapi_dump,
+ gssapi_restore,
+};
+
+isc_result_t
+dst__gssapi_init(dst_func_t **funcp) {
+ REQUIRE(funcp != NULL);
+ if (*funcp == NULL) {
+ *funcp = &gssapi_functions;
+ }
+ return (ISC_R_SUCCESS);
+}
diff --git a/lib/dns/gssapictx.c b/lib/dns/gssapictx.c
new file mode 100644
index 0000000..6eed756
--- /dev/null
+++ b/lib/dns/gssapictx.c
@@ -0,0 +1,962 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#include <ctype.h>
+#include <inttypes.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+
+#if HAVE_GSSAPI_GSSAPI_H
+#include <gssapi/gssapi.h>
+#elif HAVE_GSSAPI_H
+#include <gssapi.h>
+#endif
+
+#if HAVE_GSSAPI_GSSAPI_KRB5_H
+#include <gssapi/gssapi_krb5.h>
+#elif HAVE_GSSAPI_KRB5_H
+#include <gssapi_krb5.h>
+#endif
+
+#if HAVE_KRB5_KRB5_H
+#include <krb5/krb5.h>
+#elif HAVE_KRB5_H
+#include <krb5.h>
+#endif
+
+#include <isc/buffer.h>
+#include <isc/dir.h>
+#include <isc/file.h>
+#include <isc/lex.h>
+#include <isc/mem.h>
+#include <isc/once.h>
+#include <isc/print.h>
+#include <isc/random.h>
+#include <isc/result.h>
+#include <isc/string.h>
+#include <isc/time.h>
+#include <isc/util.h>
+
+#include <dns/fixedname.h>
+#include <dns/keyvalues.h>
+#include <dns/log.h>
+#include <dns/name.h>
+#include <dns/rdata.h>
+#include <dns/rdataclass.h>
+#include <dns/types.h>
+
+#include <dst/gssapi.h>
+
+#include "dst_internal.h"
+
+#if HAVE_GSSAPI
+
+#ifndef GSS_KRB5_MECHANISM
+static unsigned char krb5_mech_oid_bytes[] = { 0x2a, 0x86, 0x48, 0x86, 0xf7,
+ 0x12, 0x01, 0x02, 0x02 };
+static gss_OID_desc __gss_krb5_mechanism_oid_desc = {
+ sizeof(krb5_mech_oid_bytes), krb5_mech_oid_bytes
+};
+#define GSS_KRB5_MECHANISM (&__gss_krb5_mechanism_oid_desc)
+#endif /* ifndef GSS_KRB5_MECHANISM */
+
+#ifndef GSS_SPNEGO_MECHANISM
+static unsigned char spnego_mech_oid_bytes[] = { 0x2b, 0x06, 0x01,
+ 0x05, 0x05, 0x02 };
+static gss_OID_desc __gss_spnego_mechanism_oid_desc = {
+ sizeof(spnego_mech_oid_bytes), spnego_mech_oid_bytes
+};
+#define GSS_SPNEGO_MECHANISM (&__gss_spnego_mechanism_oid_desc)
+#endif /* ifndef GSS_SPNEGO_MECHANISM */
+
+#define REGION_TO_GBUFFER(r, gb) \
+ do { \
+ (gb).length = (r).length; \
+ (gb).value = (r).base; \
+ } while (0)
+
+#define GBUFFER_TO_REGION(gb, r) \
+ do { \
+ (r).length = (unsigned int)(gb).length; \
+ (r).base = (gb).value; \
+ } while (0)
+
+#define RETERR(x) \
+ do { \
+ result = (x); \
+ if (result != ISC_R_SUCCESS) \
+ goto out; \
+ } while (0)
+
+static void
+name_to_gbuffer(const dns_name_t *name, isc_buffer_t *buffer,
+ gss_buffer_desc *gbuffer) {
+ dns_name_t tname;
+ const dns_name_t *namep;
+ isc_region_t r;
+ isc_result_t result;
+
+ if (!dns_name_isabsolute(name)) {
+ namep = name;
+ } else {
+ unsigned int labels;
+ dns_name_init(&tname, NULL);
+ labels = dns_name_countlabels(name);
+ dns_name_getlabelsequence(name, 0, labels - 1, &tname);
+ namep = &tname;
+ }
+
+ result = dns_name_toprincipal(namep, buffer);
+ RUNTIME_CHECK(result == ISC_R_SUCCESS);
+ isc_buffer_putuint8(buffer, 0);
+ isc_buffer_usedregion(buffer, &r);
+ REGION_TO_GBUFFER(r, *gbuffer);
+}
+
+static void
+log_cred(const gss_cred_id_t cred) {
+ OM_uint32 gret, minor, lifetime;
+ gss_name_t gname;
+ gss_buffer_desc gbuffer;
+ gss_cred_usage_t usage;
+ const char *usage_text;
+ char buf[1024];
+
+ gret = gss_inquire_cred(&minor, cred, &gname, &lifetime, &usage, NULL);
+ if (gret != GSS_S_COMPLETE) {
+ gss_log(3, "failed gss_inquire_cred: %s",
+ gss_error_tostring(gret, minor, buf, sizeof(buf)));
+ return;
+ }
+
+ gret = gss_display_name(&minor, gname, &gbuffer, NULL);
+ if (gret != GSS_S_COMPLETE) {
+ gss_log(3, "failed gss_display_name: %s",
+ gss_error_tostring(gret, minor, buf, sizeof(buf)));
+ } else {
+ switch (usage) {
+ case GSS_C_BOTH:
+ usage_text = "GSS_C_BOTH";
+ break;
+ case GSS_C_INITIATE:
+ usage_text = "GSS_C_INITIATE";
+ break;
+ case GSS_C_ACCEPT:
+ usage_text = "GSS_C_ACCEPT";
+ break;
+ default:
+ usage_text = "???";
+ }
+ gss_log(3, "gss cred: \"%s\", %s, %lu", (char *)gbuffer.value,
+ usage_text, (unsigned long)lifetime);
+ }
+
+ if (gret == GSS_S_COMPLETE) {
+ if (gbuffer.length != 0U) {
+ gret = gss_release_buffer(&minor, &gbuffer);
+ if (gret != GSS_S_COMPLETE) {
+ gss_log(3, "failed gss_release_buffer: %s",
+ gss_error_tostring(gret, minor, buf,
+ sizeof(buf)));
+ }
+ }
+ }
+
+ gret = gss_release_name(&minor, &gname);
+ if (gret != GSS_S_COMPLETE) {
+ gss_log(3, "failed gss_release_name: %s",
+ gss_error_tostring(gret, minor, buf, sizeof(buf)));
+ }
+}
+
+/*
+ * check for the most common configuration errors.
+ *
+ * The errors checked for are:
+ * - tkey-gssapi-credential doesn't start with DNS/
+ * - the default realm in /etc/krb5.conf and the
+ * tkey-gssapi-credential bind config option don't match
+ *
+ * Note that if tkey-gssapi-keytab is set then these configure checks
+ * are not performed, and runtime errors from gssapi are used instead
+ */
+static void
+check_config(const char *gss_name) {
+ const char *p;
+ krb5_context krb5_ctx;
+ char *krb5_realm_name = NULL;
+
+ if (strncasecmp(gss_name, "DNS/", 4) != 0) {
+ gss_log(ISC_LOG_ERROR,
+ "tkey-gssapi-credential (%s) "
+ "should start with 'DNS/'",
+ gss_name);
+ return;
+ }
+
+ if (krb5_init_context(&krb5_ctx) != 0) {
+ gss_log(ISC_LOG_ERROR, "Unable to initialise krb5 context");
+ return;
+ }
+ if (krb5_get_default_realm(krb5_ctx, &krb5_realm_name) != 0) {
+ gss_log(ISC_LOG_ERROR, "Unable to get krb5 default realm");
+ krb5_free_context(krb5_ctx);
+ return;
+ }
+ p = strchr(gss_name, '@');
+ if (p == NULL) {
+ gss_log(ISC_LOG_ERROR,
+ "badly formatted "
+ "tkey-gssapi-credentials (%s)",
+ gss_name);
+ krb5_free_context(krb5_ctx);
+ return;
+ }
+ if (strcasecmp(p + 1, krb5_realm_name) != 0) {
+ gss_log(ISC_LOG_ERROR,
+ "default realm from krb5.conf (%s) "
+ "does not match tkey-gssapi-credential (%s)",
+ krb5_realm_name, gss_name);
+ krb5_free_context(krb5_ctx);
+ return;
+ }
+ krb5_free_context(krb5_ctx);
+}
+
+static OM_uint32
+mech_oid_set_create(OM_uint32 *minor, gss_OID_set *mech_oid_set) {
+ OM_uint32 gret;
+
+ gret = gss_create_empty_oid_set(minor, mech_oid_set);
+ if (gret != GSS_S_COMPLETE) {
+ return (gret);
+ }
+
+ gret = gss_add_oid_set_member(minor, GSS_KRB5_MECHANISM, mech_oid_set);
+ if (gret != GSS_S_COMPLETE) {
+ goto release;
+ }
+
+ gret = gss_add_oid_set_member(minor, GSS_SPNEGO_MECHANISM,
+ mech_oid_set);
+ if (gret != GSS_S_COMPLETE) {
+ goto release;
+ }
+
+release:
+ REQUIRE(gss_release_oid_set(minor, mech_oid_set) == GSS_S_COMPLETE);
+
+ return (gret);
+}
+
+static void
+mech_oid_set_release(gss_OID_set *mech_oid_set) {
+ OM_uint32 minor;
+
+ REQUIRE(gss_release_oid_set(&minor, mech_oid_set) == GSS_S_COMPLETE);
+}
+
+isc_result_t
+dst_gssapi_acquirecred(const dns_name_t *name, bool initiate,
+ dns_gss_cred_id_t *cred) {
+ isc_result_t result;
+ isc_buffer_t namebuf;
+ gss_name_t gname;
+ gss_buffer_desc gnamebuf;
+ unsigned char array[DNS_NAME_MAXTEXT + 1];
+ OM_uint32 gret, minor;
+ OM_uint32 lifetime;
+ gss_cred_usage_t usage;
+ char buf[1024];
+ gss_OID_set mech_oid_set;
+
+ REQUIRE(cred != NULL && *cred == NULL);
+
+ /*
+ * XXXSRA In theory we could use GSS_C_NT_HOSTBASED_SERVICE
+ * here when we're in the acceptor role, which would let us
+ * default the hostname and use a compiled in default service
+ * name of "DNS", giving one less thing to configure in
+ * named.conf. Unfortunately, this creates a circular
+ * dependency due to DNS-based realm lookup in at least one
+ * GSSAPI implementation (Heimdal). Oh well.
+ */
+ if (name != NULL) {
+ isc_buffer_init(&namebuf, array, sizeof(array));
+ name_to_gbuffer(name, &namebuf, &gnamebuf);
+ gret = gss_import_name(&minor, &gnamebuf, GSS_C_NO_OID, &gname);
+ if (gret != GSS_S_COMPLETE) {
+ check_config((char *)array);
+
+ gss_log(3, "failed gss_import_name: %s",
+ gss_error_tostring(gret, minor, buf,
+ sizeof(buf)));
+ return (ISC_R_FAILURE);
+ }
+ } else {
+ gname = NULL;
+ }
+
+ /* Get the credentials. */
+ if (gname != NULL) {
+ gss_log(3, "acquiring credentials for %s",
+ (char *)gnamebuf.value);
+ } else {
+ /* XXXDCL does this even make any sense? */
+ gss_log(3, "acquiring credentials for ?");
+ }
+
+ if (initiate) {
+ usage = GSS_C_INITIATE;
+ } else {
+ usage = GSS_C_ACCEPT;
+ }
+
+ gret = mech_oid_set_create(&minor, &mech_oid_set);
+ if (gret != GSS_S_COMPLETE) {
+ gss_log(3, "failed to create OID_set: %s",
+ gss_error_tostring(gret, minor, buf, sizeof(buf)));
+ return (ISC_R_FAILURE);
+ }
+
+ gret = gss_acquire_cred(&minor, gname, GSS_C_INDEFINITE, mech_oid_set,
+ usage, (gss_cred_id_t *)cred, NULL, &lifetime);
+
+ if (gret != GSS_S_COMPLETE) {
+ gss_log(3, "failed to acquire %s credentials for %s: %s",
+ initiate ? "initiate" : "accept",
+ (gname != NULL) ? (char *)gnamebuf.value : "?",
+ gss_error_tostring(gret, minor, buf, sizeof(buf)));
+ if (gname != NULL) {
+ check_config((char *)array);
+ }
+ result = ISC_R_FAILURE;
+ goto cleanup;
+ }
+
+ gss_log(4, "acquired %s credentials for %s",
+ initiate ? "initiate" : "accept",
+ (gname != NULL) ? (char *)gnamebuf.value : "?");
+
+ log_cred(*cred);
+ result = ISC_R_SUCCESS;
+
+cleanup:
+ mech_oid_set_release(&mech_oid_set);
+
+ if (gname != NULL) {
+ gret = gss_release_name(&minor, &gname);
+ if (gret != GSS_S_COMPLETE) {
+ gss_log(3, "failed gss_release_name: %s",
+ gss_error_tostring(gret, minor, buf,
+ sizeof(buf)));
+ }
+ }
+
+ return (result);
+}
+
+bool
+dst_gssapi_identitymatchesrealmkrb5(const dns_name_t *signer,
+ const dns_name_t *name,
+ const dns_name_t *realm, bool subdomain) {
+ char sbuf[DNS_NAME_FORMATSIZE];
+ char rbuf[DNS_NAME_FORMATSIZE];
+ char *sname;
+ char *rname;
+ isc_buffer_t buffer;
+ isc_result_t result;
+
+ /*
+ * It is far, far easier to write the names we are looking at into
+ * a string, and do string operations on them.
+ */
+ isc_buffer_init(&buffer, sbuf, sizeof(sbuf));
+ result = dns_name_toprincipal(signer, &buffer);
+ RUNTIME_CHECK(result == ISC_R_SUCCESS);
+ isc_buffer_putuint8(&buffer, 0);
+ dns_name_format(realm, rbuf, sizeof(rbuf));
+
+ /*
+ * Find the realm portion. This is the part after the @. If it
+ * does not exist, we don't have something we like, so we fail our
+ * compare.
+ */
+ rname = strchr(sbuf, '@');
+ if (rname == NULL) {
+ return (false);
+ }
+ *rname = '\0';
+ rname++;
+
+ if (strcmp(rname, rbuf) != 0) {
+ return (false);
+ }
+
+ /*
+ * Find the host portion of the signer's name. We do this by
+ * searching for the first / character. We then check to make
+ * certain the instance name is "host"
+ *
+ * This will work for
+ * host/example.com@EXAMPLE.COM
+ */
+ sname = strchr(sbuf, '/');
+ if (sname == NULL) {
+ return (false);
+ }
+ *sname = '\0';
+ sname++;
+ if (strcmp(sbuf, "host") != 0) {
+ return (false);
+ }
+
+ /*
+ * If name is non NULL check that it matches against the
+ * machine name as expected.
+ */
+ if (name != NULL) {
+ dns_fixedname_t fixed;
+ dns_name_t *machine;
+
+ machine = dns_fixedname_initname(&fixed);
+ result = dns_name_fromstring(machine, sname, 0, NULL);
+ if (result != ISC_R_SUCCESS) {
+ return (false);
+ }
+ if (subdomain) {
+ return (dns_name_issubdomain(name, machine));
+ }
+ return (dns_name_equal(name, machine));
+ }
+
+ return (true);
+}
+
+bool
+dst_gssapi_identitymatchesrealmms(const dns_name_t *signer,
+ const dns_name_t *name,
+ const dns_name_t *realm, bool subdomain) {
+ char sbuf[DNS_NAME_FORMATSIZE];
+ char rbuf[DNS_NAME_FORMATSIZE];
+ char *sname;
+ char *rname;
+ isc_buffer_t buffer;
+ isc_result_t result;
+
+ /*
+ * It is far, far easier to write the names we are looking at into
+ * a string, and do string operations on them.
+ */
+ isc_buffer_init(&buffer, sbuf, sizeof(sbuf));
+ result = dns_name_toprincipal(signer, &buffer);
+ RUNTIME_CHECK(result == ISC_R_SUCCESS);
+ isc_buffer_putuint8(&buffer, 0);
+ dns_name_format(realm, rbuf, sizeof(rbuf));
+
+ /*
+ * Find the realm portion. This is the part after the @. If it
+ * does not exist, we don't have something we like, so we fail our
+ * compare.
+ */
+ rname = strchr(sbuf, '@');
+ if (rname == NULL) {
+ return (false);
+ }
+ sname = strchr(sbuf, '$');
+ if (sname == NULL) {
+ return (false);
+ }
+
+ /*
+ * Verify that the $ and @ follow one another.
+ */
+ if (rname - sname != 1) {
+ return (false);
+ }
+
+ /*
+ * Find the host portion of the signer's name. Zero out the $ so
+ * it terminates the signer's name, and skip past the @ for
+ * the realm.
+ *
+ * All service principals in Microsoft format seem to be in
+ * machinename$@EXAMPLE.COM
+ * format.
+ */
+ rname++;
+ *sname = '\0';
+
+ if (strcmp(rname, rbuf) != 0) {
+ return (false);
+ }
+
+ /*
+ * Now, we check that the realm matches (case sensitive) and that
+ * 'name' matches against 'machinename' qualified with 'realm'.
+ */
+ if (name != NULL) {
+ dns_fixedname_t fixed;
+ dns_name_t *machine;
+
+ machine = dns_fixedname_initname(&fixed);
+ result = dns_name_fromstring2(machine, sbuf, realm, 0, NULL);
+ if (result != ISC_R_SUCCESS) {
+ return (false);
+ }
+ if (subdomain) {
+ return (dns_name_issubdomain(name, machine));
+ }
+ return (dns_name_equal(name, machine));
+ }
+
+ return (true);
+}
+
+isc_result_t
+dst_gssapi_releasecred(dns_gss_cred_id_t *cred) {
+ OM_uint32 gret, minor;
+ char buf[1024];
+
+ REQUIRE(cred != NULL && *cred != NULL);
+
+ gret = gss_release_cred(&minor, (gss_cred_id_t *)cred);
+ if (gret != GSS_S_COMPLETE) {
+ /* Log the error, but still free the credential's memory */
+ gss_log(3, "failed releasing credential: %s",
+ gss_error_tostring(gret, minor, buf, sizeof(buf)));
+ }
+ *cred = NULL;
+
+ return (ISC_R_SUCCESS);
+}
+
+/*
+ * Format a gssapi error message info into a char ** on the given memory
+ * context. This is used to return gssapi error messages back up the
+ * call chain for reporting to the user.
+ */
+static void
+gss_err_message(isc_mem_t *mctx, uint32_t major, uint32_t minor,
+ char **err_message) {
+ char buf[1024];
+ char *estr;
+
+ if (err_message == NULL || mctx == NULL) {
+ /* the caller doesn't want any error messages */
+ return;
+ }
+
+ estr = gss_error_tostring(major, minor, buf, sizeof(buf));
+ if (estr != NULL) {
+ (*err_message) = isc_mem_strdup(mctx, estr);
+ }
+}
+
+isc_result_t
+dst_gssapi_initctx(const dns_name_t *name, isc_buffer_t *intoken,
+ isc_buffer_t *outtoken, dns_gss_ctx_id_t *gssctx,
+ isc_mem_t *mctx, char **err_message) {
+ isc_region_t r;
+ isc_buffer_t namebuf;
+ gss_name_t gname;
+ OM_uint32 gret, minor, ret_flags, flags;
+ gss_buffer_desc gintoken, *gintokenp, gouttoken = GSS_C_EMPTY_BUFFER;
+ isc_result_t result;
+ gss_buffer_desc gnamebuf;
+ unsigned char array[DNS_NAME_MAXTEXT + 1];
+
+ /* Client must pass us a valid gss_ctx_id_t here */
+ REQUIRE(gssctx != NULL);
+ REQUIRE(mctx != NULL);
+
+ isc_buffer_init(&namebuf, array, sizeof(array));
+ name_to_gbuffer(name, &namebuf, &gnamebuf);
+
+ /* Get the name as a GSS name */
+ gret = gss_import_name(&minor, &gnamebuf, GSS_C_NO_OID, &gname);
+ if (gret != GSS_S_COMPLETE) {
+ gss_err_message(mctx, gret, minor, err_message);
+ result = ISC_R_FAILURE;
+ goto out;
+ }
+
+ if (intoken != NULL) {
+ /* Don't call gss_release_buffer for gintoken! */
+ REGION_TO_GBUFFER(*intoken, gintoken);
+ gintokenp = &gintoken;
+ } else {
+ gintokenp = NULL;
+ }
+
+ /*
+ * Note that we don't set GSS_C_SEQUENCE_FLAG as Windows DNS
+ * servers don't like it.
+ */
+ flags = GSS_C_REPLAY_FLAG | GSS_C_MUTUAL_FLAG | GSS_C_INTEG_FLAG;
+
+ gret = gss_init_sec_context(
+ &minor, GSS_C_NO_CREDENTIAL, (gss_ctx_id_t *)gssctx, gname,
+ GSS_SPNEGO_MECHANISM, flags, 0, NULL, gintokenp, NULL,
+ &gouttoken, &ret_flags, NULL);
+
+ if (gret != GSS_S_COMPLETE && gret != GSS_S_CONTINUE_NEEDED) {
+ gss_err_message(mctx, gret, minor, err_message);
+ if (err_message != NULL && *err_message != NULL) {
+ gss_log(3, "Failure initiating security context: %s",
+ *err_message);
+ } else {
+ gss_log(3, "Failure initiating security context");
+ }
+
+ result = ISC_R_FAILURE;
+ goto out;
+ }
+
+ /*
+ * XXXSRA Not handled yet: RFC 3645 3.1.1: check ret_flags
+ * MUTUAL and INTEG flags, fail if either not set.
+ */
+
+ /*
+ * RFC 2744 states the a valid output token has a non-zero length.
+ */
+ if (gouttoken.length != 0U) {
+ GBUFFER_TO_REGION(gouttoken, r);
+ RETERR(isc_buffer_copyregion(outtoken, &r));
+ }
+
+ if (gret == GSS_S_COMPLETE) {
+ result = ISC_R_SUCCESS;
+ } else {
+ result = DNS_R_CONTINUE;
+ }
+
+out:
+ if (gouttoken.length != 0U) {
+ (void)gss_release_buffer(&minor, &gouttoken);
+ }
+ (void)gss_release_name(&minor, &gname);
+ return (result);
+}
+
+isc_result_t
+dst_gssapi_acceptctx(dns_gss_cred_id_t cred, const char *gssapi_keytab,
+ isc_region_t *intoken, isc_buffer_t **outtoken,
+ dns_gss_ctx_id_t *ctxout, dns_name_t *principal,
+ isc_mem_t *mctx) {
+ isc_region_t r;
+ isc_buffer_t namebuf;
+ gss_buffer_desc gnamebuf = GSS_C_EMPTY_BUFFER, gintoken,
+ gouttoken = GSS_C_EMPTY_BUFFER;
+ OM_uint32 gret, minor;
+ gss_ctx_id_t context = GSS_C_NO_CONTEXT;
+ gss_name_t gname = NULL;
+ isc_result_t result;
+ char buf[1024];
+
+ REQUIRE(outtoken != NULL && *outtoken == NULL);
+
+ REGION_TO_GBUFFER(*intoken, gintoken);
+
+ if (*ctxout == NULL) {
+ context = GSS_C_NO_CONTEXT;
+ } else {
+ context = *ctxout;
+ }
+
+ if (gssapi_keytab != NULL) {
+#if HAVE_GSSAPI_GSSAPI_KRB5_H || HAVE_GSSAPI_KRB5_H
+ gret = gsskrb5_register_acceptor_identity(gssapi_keytab);
+ if (gret != GSS_S_COMPLETE) {
+ gss_log(3,
+ "failed "
+ "gsskrb5_register_acceptor_identity(%s): %s",
+ gssapi_keytab,
+ gss_error_tostring(gret, 0, buf, sizeof(buf)));
+ return (DNS_R_INVALIDTKEY);
+ }
+#else
+ /*
+ * Minimize memory leakage by only setting KRB5_KTNAME
+ * if it needs to change.
+ */
+ const char *old = getenv("KRB5_KTNAME");
+ if (old == NULL || strcmp(old, gssapi_keytab) != 0) {
+ size_t size;
+ char *kt;
+
+ size = strlen(gssapi_keytab) + 13;
+ kt = malloc(size);
+ if (kt == NULL) {
+ return (ISC_R_NOMEMORY);
+ }
+ snprintf(kt, size, "KRB5_KTNAME=%s", gssapi_keytab);
+ if (putenv(kt) != 0) {
+ return (ISC_R_NOMEMORY);
+ }
+ }
+#endif
+ }
+
+ log_cred(cred);
+
+ gret = gss_accept_sec_context(&minor, &context, cred, &gintoken,
+ GSS_C_NO_CHANNEL_BINDINGS, &gname, NULL,
+ &gouttoken, NULL, NULL, NULL);
+
+ result = ISC_R_FAILURE;
+
+ switch (gret) {
+ case GSS_S_COMPLETE:
+ case GSS_S_CONTINUE_NEEDED:
+ break;
+ case GSS_S_DEFECTIVE_TOKEN:
+ case GSS_S_DEFECTIVE_CREDENTIAL:
+ case GSS_S_BAD_SIG:
+ case GSS_S_DUPLICATE_TOKEN:
+ case GSS_S_OLD_TOKEN:
+ case GSS_S_NO_CRED:
+ case GSS_S_CREDENTIALS_EXPIRED:
+ case GSS_S_BAD_BINDINGS:
+ case GSS_S_NO_CONTEXT:
+ case GSS_S_BAD_MECH:
+ case GSS_S_FAILURE:
+ result = DNS_R_INVALIDTKEY;
+ /* fall through */
+ default:
+ gss_log(3, "failed gss_accept_sec_context: %s",
+ gss_error_tostring(gret, minor, buf, sizeof(buf)));
+ if (gouttoken.length > 0U) {
+ (void)gss_release_buffer(&minor, &gouttoken);
+ }
+ return (result);
+ }
+
+ if (gouttoken.length > 0U) {
+ isc_buffer_allocate(mctx, outtoken,
+ (unsigned int)gouttoken.length);
+ GBUFFER_TO_REGION(gouttoken, r);
+ RETERR(isc_buffer_copyregion(*outtoken, &r));
+ (void)gss_release_buffer(&minor, &gouttoken);
+ }
+
+ if (gret == GSS_S_COMPLETE) {
+ gret = gss_display_name(&minor, gname, &gnamebuf, NULL);
+ if (gret != GSS_S_COMPLETE) {
+ gss_log(3, "failed gss_display_name: %s",
+ gss_error_tostring(gret, minor, buf,
+ sizeof(buf)));
+ RETERR(ISC_R_FAILURE);
+ }
+
+ /*
+ * Compensate for a bug in Solaris8's implementation
+ * of gss_display_name(). Should be harmless in any
+ * case, since principal names really should not
+ * contain null characters.
+ */
+ if (gnamebuf.length > 0U &&
+ ((char *)gnamebuf.value)[gnamebuf.length - 1] == '\0')
+ {
+ gnamebuf.length--;
+ }
+
+ gss_log(3, "gss-api source name (accept) is %.*s",
+ (int)gnamebuf.length, (char *)gnamebuf.value);
+
+ GBUFFER_TO_REGION(gnamebuf, r);
+ isc_buffer_init(&namebuf, r.base, r.length);
+ isc_buffer_add(&namebuf, r.length);
+
+ RETERR(dns_name_fromtext(principal, &namebuf, dns_rootname, 0,
+ NULL));
+
+ if (gnamebuf.length != 0U) {
+ gret = gss_release_buffer(&minor, &gnamebuf);
+ if (gret != GSS_S_COMPLETE) {
+ gss_log(3, "failed gss_release_buffer: %s",
+ gss_error_tostring(gret, minor, buf,
+ sizeof(buf)));
+ }
+ }
+ } else {
+ result = DNS_R_CONTINUE;
+ }
+
+ *ctxout = context;
+
+out:
+ if (gname != NULL) {
+ gret = gss_release_name(&minor, &gname);
+ if (gret != GSS_S_COMPLETE) {
+ gss_log(3, "failed gss_release_name: %s",
+ gss_error_tostring(gret, minor, buf,
+ sizeof(buf)));
+ }
+ }
+
+ return (result);
+}
+
+isc_result_t
+dst_gssapi_deletectx(isc_mem_t *mctx, dns_gss_ctx_id_t *gssctx) {
+ OM_uint32 gret, minor;
+ char buf[1024];
+
+ UNUSED(mctx);
+
+ REQUIRE(gssctx != NULL && *gssctx != NULL);
+
+ /* Delete the context from the GSS provider */
+ gret = gss_delete_sec_context(&minor, (gss_ctx_id_t *)gssctx,
+ GSS_C_NO_BUFFER);
+ if (gret != GSS_S_COMPLETE) {
+ /* Log the error, but still free the context's memory */
+ gss_log(3, "Failure deleting security context %s",
+ gss_error_tostring(gret, minor, buf, sizeof(buf)));
+ }
+ return (ISC_R_SUCCESS);
+}
+
+char *
+gss_error_tostring(uint32_t major, uint32_t minor, char *buf, size_t buflen) {
+ gss_buffer_desc msg_minor = GSS_C_EMPTY_BUFFER,
+ msg_major = GSS_C_EMPTY_BUFFER;
+ OM_uint32 msg_ctx, minor_stat;
+
+ /* Handle major status */
+ msg_ctx = 0;
+ (void)gss_display_status(&minor_stat, major, GSS_C_GSS_CODE,
+ GSS_C_NULL_OID, &msg_ctx, &msg_major);
+
+ /* Handle minor status */
+ msg_ctx = 0;
+ (void)gss_display_status(&minor_stat, minor, GSS_C_MECH_CODE,
+ GSS_C_NULL_OID, &msg_ctx, &msg_minor);
+
+ snprintf(buf, buflen, "GSSAPI error: Major = %s, Minor = %s.",
+ (char *)msg_major.value, (char *)msg_minor.value);
+
+ if (msg_major.length != 0U) {
+ (void)gss_release_buffer(&minor_stat, &msg_major);
+ }
+ if (msg_minor.length != 0U) {
+ (void)gss_release_buffer(&minor_stat, &msg_minor);
+ }
+ return (buf);
+}
+
+#else
+
+isc_result_t
+dst_gssapi_acquirecred(const dns_name_t *name, bool initiate,
+ dns_gss_cred_id_t *cred) {
+ REQUIRE(cred != NULL && *cred == NULL);
+
+ UNUSED(name);
+ UNUSED(initiate);
+ UNUSED(cred);
+
+ return (ISC_R_NOTIMPLEMENTED);
+}
+
+bool
+dst_gssapi_identitymatchesrealmkrb5(const dns_name_t *signer,
+ const dns_name_t *name,
+ const dns_name_t *realm, bool subdomain) {
+ UNUSED(signer);
+ UNUSED(name);
+ UNUSED(realm);
+ UNUSED(subdomain);
+
+ return (false);
+}
+
+bool
+dst_gssapi_identitymatchesrealmms(const dns_name_t *signer,
+ const dns_name_t *name,
+ const dns_name_t *realm, bool subdomain) {
+ UNUSED(signer);
+ UNUSED(name);
+ UNUSED(realm);
+ UNUSED(subdomain);
+
+ return (false);
+}
+
+isc_result_t
+dst_gssapi_releasecred(dns_gss_cred_id_t *cred) {
+ UNUSED(cred);
+
+ return (ISC_R_NOTIMPLEMENTED);
+}
+
+isc_result_t
+dst_gssapi_initctx(const dns_name_t *name, isc_buffer_t *intoken,
+ isc_buffer_t *outtoken, dns_gss_ctx_id_t *gssctx,
+ isc_mem_t *mctx, char **err_message) {
+ UNUSED(name);
+ UNUSED(intoken);
+ UNUSED(outtoken);
+ UNUSED(gssctx);
+ UNUSED(mctx);
+ UNUSED(err_message);
+
+ return (ISC_R_NOTIMPLEMENTED);
+}
+
+isc_result_t
+dst_gssapi_acceptctx(dns_gss_cred_id_t cred, const char *gssapi_keytab,
+ isc_region_t *intoken, isc_buffer_t **outtoken,
+ dns_gss_ctx_id_t *ctxout, dns_name_t *principal,
+ isc_mem_t *mctx) {
+ UNUSED(cred);
+ UNUSED(gssapi_keytab);
+ UNUSED(intoken);
+ UNUSED(outtoken);
+ UNUSED(ctxout);
+ UNUSED(principal);
+ UNUSED(mctx);
+
+ return (ISC_R_NOTIMPLEMENTED);
+}
+
+isc_result_t
+dst_gssapi_deletectx(isc_mem_t *mctx, dns_gss_ctx_id_t *gssctx) {
+ UNUSED(mctx);
+ UNUSED(gssctx);
+ return (ISC_R_NOTIMPLEMENTED);
+}
+
+char *
+gss_error_tostring(uint32_t major, uint32_t minor, char *buf, size_t buflen) {
+ snprintf(buf, buflen, "GSSAPI error: Major = %u, Minor = %u.", major,
+ minor);
+
+ return (buf);
+}
+
+#endif
+
+void
+gss_log(int level, const char *fmt, ...) {
+ va_list ap;
+
+ va_start(ap, fmt);
+ isc_log_vwrite(dns_lctx, DNS_LOGCATEGORY_GENERAL, DNS_LOGMODULE_TKEY,
+ ISC_LOG_DEBUG(level), fmt, ap);
+ va_end(ap);
+}
diff --git a/lib/dns/hmac_link.c b/lib/dns/hmac_link.c
new file mode 100644
index 0000000..9f8e94b
--- /dev/null
+++ b/lib/dns/hmac_link.c
@@ -0,0 +1,527 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0 AND ISC
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/*
+ * Copyright (C) Network Associates, Inc.
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND ISC AND NETWORK ASSOCIATES DISCLAIMS
+ * ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE
+ * FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR
+ * IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <arpa/inet.h>
+#include <stdbool.h>
+
+#include <isc/buffer.h>
+#include <isc/hmac.h>
+#include <isc/lex.h>
+#include <isc/md.h>
+#include <isc/mem.h>
+#include <isc/nonce.h>
+#include <isc/random.h>
+#include <isc/result.h>
+#include <isc/safe.h>
+#include <isc/string.h>
+#include <isc/util.h>
+
+#include "dst_internal.h"
+#ifdef HAVE_FIPS_MODE
+#include "dst_openssl.h" /* FIPS_mode() prototype */
+#endif /* ifdef HAVE_FIPS_MODE */
+#include "dst_parse.h"
+
+#define ISC_MD_md5 ISC_MD_MD5
+#define ISC_MD_sha1 ISC_MD_SHA1
+#define ISC_MD_sha224 ISC_MD_SHA224
+#define ISC_MD_sha256 ISC_MD_SHA256
+#define ISC_MD_sha384 ISC_MD_SHA384
+#define ISC_MD_sha512 ISC_MD_SHA512
+
+#define hmac_register_algorithm(alg) \
+ static isc_result_t hmac##alg##_createctx(dst_key_t *key, \
+ dst_context_t *dctx) { \
+ return (hmac_createctx(ISC_MD_##alg, key, dctx)); \
+ } \
+ static void hmac##alg##_destroyctx(dst_context_t *dctx) { \
+ hmac_destroyctx(dctx); \
+ } \
+ static isc_result_t hmac##alg##_adddata(dst_context_t *dctx, \
+ const isc_region_t *data) { \
+ return (hmac_adddata(dctx, data)); \
+ } \
+ static isc_result_t hmac##alg##_sign(dst_context_t *dctx, \
+ isc_buffer_t *sig) { \
+ return (hmac_sign(dctx, sig)); \
+ } \
+ static isc_result_t hmac##alg##_verify(dst_context_t *dctx, \
+ const isc_region_t *sig) { \
+ return (hmac_verify(dctx, sig)); \
+ } \
+ static bool hmac##alg##_compare(const dst_key_t *key1, \
+ const dst_key_t *key2) { \
+ return (hmac_compare(ISC_MD_##alg, key1, key2)); \
+ } \
+ static isc_result_t hmac##alg##_generate( \
+ dst_key_t *key, int pseudorandom_ok, void (*callback)(int)) { \
+ UNUSED(pseudorandom_ok); \
+ UNUSED(callback); \
+ return (hmac_generate(ISC_MD_##alg, key)); \
+ } \
+ static bool hmac##alg##_isprivate(const dst_key_t *key) { \
+ return (hmac_isprivate(key)); \
+ } \
+ static void hmac##alg##_destroy(dst_key_t *key) { hmac_destroy(key); } \
+ static isc_result_t hmac##alg##_todns(const dst_key_t *key, \
+ isc_buffer_t *data) { \
+ return (hmac_todns(key, data)); \
+ } \
+ static isc_result_t hmac##alg##_fromdns(dst_key_t *key, \
+ isc_buffer_t *data) { \
+ return (hmac_fromdns(ISC_MD_##alg, key, data)); \
+ } \
+ static isc_result_t hmac##alg##_tofile(const dst_key_t *key, \
+ const char *directory) { \
+ return (hmac_tofile(ISC_MD_##alg, key, directory)); \
+ } \
+ static isc_result_t hmac##alg##_parse( \
+ dst_key_t *key, isc_lex_t *lexer, dst_key_t *pub) { \
+ const char *file = isc_lex_getsourcename(lexer); \
+ isc_result_t result; \
+ result = hmac_parse(ISC_MD_##alg, key, lexer, pub); \
+ if (result == ISC_R_SUCCESS && file != NULL) { \
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL, \
+ DNS_LOGMODULE_CRYPTO, ISC_LOG_WARNING, \
+ "%s: Use of K* file pairs for HMAC is " \
+ "deprecated\n", \
+ file); \
+ } \
+ return (result); \
+ } \
+ static dst_func_t hmac##alg##_functions = { \
+ hmac##alg##_createctx, \
+ NULL, /*%< createctx2 */ \
+ hmac##alg##_destroyctx, \
+ hmac##alg##_adddata, \
+ hmac##alg##_sign, \
+ hmac##alg##_verify, \
+ NULL, /*%< verify2 */ \
+ NULL, /*%< computesecret */ \
+ hmac##alg##_compare, \
+ NULL, /*%< paramcompare */ \
+ hmac##alg##_generate, \
+ hmac##alg##_isprivate, \
+ hmac##alg##_destroy, \
+ hmac##alg##_todns, \
+ hmac##alg##_fromdns, \
+ hmac##alg##_tofile, \
+ hmac##alg##_parse, \
+ NULL, /*%< cleanup */ \
+ NULL, /*%< fromlabel */ \
+ NULL, /*%< dump */ \
+ NULL, /*%< restore */ \
+ }; \
+ isc_result_t dst__hmac##alg##_init(dst_func_t **funcp) { \
+ REQUIRE(funcp != NULL); \
+ if (*funcp == NULL) { \
+ *funcp = &hmac##alg##_functions; \
+ } \
+ return (ISC_R_SUCCESS); \
+ }
+
+static isc_result_t
+hmac_fromdns(const isc_md_type_t *type, dst_key_t *key, isc_buffer_t *data);
+
+struct dst_hmac_key {
+ uint8_t key[ISC_MAX_BLOCK_SIZE];
+};
+
+static isc_result_t
+getkeybits(dst_key_t *key, struct dst_private_element *element) {
+ uint16_t *bits = (uint16_t *)element->data;
+
+ if (element->length != 2) {
+ return (DST_R_INVALIDPRIVATEKEY);
+ }
+
+ key->key_bits = ntohs(*bits);
+
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+hmac_createctx(const isc_md_type_t *type, const dst_key_t *key,
+ dst_context_t *dctx) {
+ isc_result_t result;
+ const dst_hmac_key_t *hkey = key->keydata.hmac_key;
+ isc_hmac_t *ctx = isc_hmac_new(); /* Either returns or abort()s */
+
+ result = isc_hmac_init(ctx, hkey->key, isc_md_type_get_block_size(type),
+ type);
+ if (result != ISC_R_SUCCESS) {
+ isc_hmac_free(ctx);
+ return (DST_R_UNSUPPORTEDALG);
+ }
+
+ dctx->ctxdata.hmac_ctx = ctx;
+ return (ISC_R_SUCCESS);
+}
+
+static void
+hmac_destroyctx(dst_context_t *dctx) {
+ isc_hmac_t *ctx = dctx->ctxdata.hmac_ctx;
+ REQUIRE(ctx != NULL);
+
+ isc_hmac_free(ctx);
+ dctx->ctxdata.hmac_ctx = NULL;
+}
+
+static isc_result_t
+hmac_adddata(const dst_context_t *dctx, const isc_region_t *data) {
+ isc_result_t result;
+ isc_hmac_t *ctx = dctx->ctxdata.hmac_ctx;
+
+ REQUIRE(ctx != NULL);
+
+ result = isc_hmac_update(ctx, data->base, data->length);
+ if (result != ISC_R_SUCCESS) {
+ return (DST_R_OPENSSLFAILURE);
+ }
+
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+hmac_sign(const dst_context_t *dctx, isc_buffer_t *sig) {
+ isc_hmac_t *ctx = dctx->ctxdata.hmac_ctx;
+ REQUIRE(ctx != NULL);
+ unsigned char digest[ISC_MAX_MD_SIZE];
+ unsigned int digestlen = sizeof(digest);
+
+ if (isc_hmac_final(ctx, digest, &digestlen) != ISC_R_SUCCESS) {
+ return (DST_R_OPENSSLFAILURE);
+ }
+
+ if (isc_hmac_reset(ctx) != ISC_R_SUCCESS) {
+ return (DST_R_OPENSSLFAILURE);
+ }
+
+ if (isc_buffer_availablelength(sig) < digestlen) {
+ return (ISC_R_NOSPACE);
+ }
+
+ isc_buffer_putmem(sig, digest, digestlen);
+
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+hmac_verify(const dst_context_t *dctx, const isc_region_t *sig) {
+ isc_hmac_t *ctx = dctx->ctxdata.hmac_ctx;
+ unsigned char digest[ISC_MAX_MD_SIZE];
+ unsigned int digestlen = sizeof(digest);
+
+ REQUIRE(ctx != NULL);
+
+ if (isc_hmac_final(ctx, digest, &digestlen) != ISC_R_SUCCESS) {
+ return (DST_R_OPENSSLFAILURE);
+ }
+
+ if (isc_hmac_reset(ctx) != ISC_R_SUCCESS) {
+ return (DST_R_OPENSSLFAILURE);
+ }
+
+ if (sig->length > digestlen) {
+ return (DST_R_VERIFYFAILURE);
+ }
+
+ return (isc_safe_memequal(digest, sig->base, sig->length)
+ ? ISC_R_SUCCESS
+ : DST_R_VERIFYFAILURE);
+}
+
+static bool
+hmac_compare(const isc_md_type_t *type, const dst_key_t *key1,
+ const dst_key_t *key2) {
+ dst_hmac_key_t *hkey1, *hkey2;
+
+ hkey1 = key1->keydata.hmac_key;
+ hkey2 = key2->keydata.hmac_key;
+
+ if (hkey1 == NULL && hkey2 == NULL) {
+ return (true);
+ } else if (hkey1 == NULL || hkey2 == NULL) {
+ return (false);
+ }
+
+ return (isc_safe_memequal(hkey1->key, hkey2->key,
+ isc_md_type_get_block_size(type)));
+}
+
+static isc_result_t
+hmac_generate(const isc_md_type_t *type, dst_key_t *key) {
+ isc_buffer_t b;
+ isc_result_t ret;
+ unsigned int bytes, len;
+ unsigned char data[ISC_MAX_MD_SIZE] = { 0 };
+
+ len = isc_md_type_get_block_size(type);
+
+ bytes = (key->key_size + 7) / 8;
+
+ if (bytes > len) {
+ bytes = len;
+ key->key_size = len * 8;
+ }
+
+ isc_nonce_buf(data, bytes);
+
+ isc_buffer_init(&b, data, bytes);
+ isc_buffer_add(&b, bytes);
+
+ ret = hmac_fromdns(type, key, &b);
+
+ isc_safe_memwipe(data, sizeof(data));
+
+ return (ret);
+}
+
+static bool
+hmac_isprivate(const dst_key_t *key) {
+ UNUSED(key);
+ return (true);
+}
+
+static void
+hmac_destroy(dst_key_t *key) {
+ dst_hmac_key_t *hkey = key->keydata.hmac_key;
+ isc_safe_memwipe(hkey, sizeof(*hkey));
+ isc_mem_put(key->mctx, hkey, sizeof(*hkey));
+ key->keydata.hmac_key = NULL;
+}
+
+static isc_result_t
+hmac_todns(const dst_key_t *key, isc_buffer_t *data) {
+ REQUIRE(key != NULL && key->keydata.hmac_key != NULL);
+ dst_hmac_key_t *hkey = key->keydata.hmac_key;
+ unsigned int bytes;
+
+ bytes = (key->key_size + 7) / 8;
+ if (isc_buffer_availablelength(data) < bytes) {
+ return (ISC_R_NOSPACE);
+ }
+ isc_buffer_putmem(data, hkey->key, bytes);
+
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+hmac_fromdns(const isc_md_type_t *type, dst_key_t *key, isc_buffer_t *data) {
+ dst_hmac_key_t *hkey;
+ unsigned int keylen;
+ isc_region_t r;
+
+ isc_buffer_remainingregion(data, &r);
+ if (r.length == 0) {
+ return (ISC_R_SUCCESS);
+ }
+
+ hkey = isc_mem_get(key->mctx, sizeof(dst_hmac_key_t));
+
+ memset(hkey->key, 0, sizeof(hkey->key));
+
+ /* Hash the key if the key is longer then chosen MD block size */
+ if (r.length > (unsigned int)isc_md_type_get_block_size(type)) {
+ if (isc_md(type, r.base, r.length, hkey->key, &keylen) !=
+ ISC_R_SUCCESS)
+ {
+ isc_mem_put(key->mctx, hkey, sizeof(dst_hmac_key_t));
+ return (DST_R_OPENSSLFAILURE);
+ }
+ } else {
+ memmove(hkey->key, r.base, r.length);
+ keylen = r.length;
+ }
+
+ key->key_size = keylen * 8;
+ key->keydata.hmac_key = hkey;
+
+ isc_buffer_forward(data, r.length);
+
+ return (ISC_R_SUCCESS);
+}
+
+static int
+hmac__get_tag_key(const isc_md_type_t *type) {
+ if (type == ISC_MD_MD5) {
+ return (TAG_HMACMD5_KEY);
+ } else if (type == ISC_MD_SHA1) {
+ return (TAG_HMACSHA1_KEY);
+ } else if (type == ISC_MD_SHA224) {
+ return (TAG_HMACSHA224_KEY);
+ } else if (type == ISC_MD_SHA256) {
+ return (TAG_HMACSHA256_KEY);
+ } else if (type == ISC_MD_SHA384) {
+ return (TAG_HMACSHA384_KEY);
+ } else if (type == ISC_MD_SHA512) {
+ return (TAG_HMACSHA512_KEY);
+ } else {
+ UNREACHABLE();
+ }
+}
+
+static int
+hmac__get_tag_bits(const isc_md_type_t *type) {
+ if (type == ISC_MD_MD5) {
+ return (TAG_HMACMD5_BITS);
+ } else if (type == ISC_MD_SHA1) {
+ return (TAG_HMACSHA1_BITS);
+ } else if (type == ISC_MD_SHA224) {
+ return (TAG_HMACSHA224_BITS);
+ } else if (type == ISC_MD_SHA256) {
+ return (TAG_HMACSHA256_BITS);
+ } else if (type == ISC_MD_SHA384) {
+ return (TAG_HMACSHA384_BITS);
+ } else if (type == ISC_MD_SHA512) {
+ return (TAG_HMACSHA512_BITS);
+ } else {
+ UNREACHABLE();
+ }
+}
+
+static isc_result_t
+hmac_tofile(const isc_md_type_t *type, const dst_key_t *key,
+ const char *directory) {
+ dst_hmac_key_t *hkey;
+ dst_private_t priv;
+ int bytes = (key->key_size + 7) / 8;
+ uint16_t bits;
+
+ if (key->keydata.hmac_key == NULL) {
+ return (DST_R_NULLKEY);
+ }
+
+ if (key->external) {
+ return (DST_R_EXTERNALKEY);
+ }
+
+ hkey = key->keydata.hmac_key;
+
+ priv.elements[0].tag = hmac__get_tag_key(type);
+ priv.elements[0].length = bytes;
+ priv.elements[0].data = hkey->key;
+
+ bits = htons(key->key_bits);
+
+ priv.elements[1].tag = hmac__get_tag_bits(type);
+ priv.elements[1].length = sizeof(bits);
+ priv.elements[1].data = (uint8_t *)&bits;
+
+ priv.nelements = 2;
+
+ return (dst__privstruct_writefile(key, &priv, directory));
+}
+
+static int
+hmac__to_dst_alg(const isc_md_type_t *type) {
+ if (type == ISC_MD_MD5) {
+ return (DST_ALG_HMACMD5);
+ } else if (type == ISC_MD_SHA1) {
+ return (DST_ALG_HMACSHA1);
+ } else if (type == ISC_MD_SHA224) {
+ return (DST_ALG_HMACSHA224);
+ } else if (type == ISC_MD_SHA256) {
+ return (DST_ALG_HMACSHA256);
+ } else if (type == ISC_MD_SHA384) {
+ return (DST_ALG_HMACSHA384);
+ } else if (type == ISC_MD_SHA512) {
+ return (DST_ALG_HMACSHA512);
+ } else {
+ UNREACHABLE();
+ }
+}
+
+static isc_result_t
+hmac_parse(const isc_md_type_t *type, dst_key_t *key, isc_lex_t *lexer,
+ dst_key_t *pub) {
+ dst_private_t priv;
+ isc_result_t result, tresult;
+ isc_buffer_t b;
+ isc_mem_t *mctx = key->mctx;
+ unsigned int i;
+
+ UNUSED(pub);
+ /* read private key file */
+ result = dst__privstruct_parse(key, hmac__to_dst_alg(type), lexer, mctx,
+ &priv);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+
+ if (key->external) {
+ result = DST_R_EXTERNALKEY;
+ }
+
+ key->key_bits = 0;
+ for (i = 0; i < priv.nelements && result == ISC_R_SUCCESS; i++) {
+ switch (priv.elements[i].tag) {
+ case TAG_HMACMD5_KEY:
+ case TAG_HMACSHA1_KEY:
+ case TAG_HMACSHA224_KEY:
+ case TAG_HMACSHA256_KEY:
+ case TAG_HMACSHA384_KEY:
+ case TAG_HMACSHA512_KEY:
+ isc_buffer_init(&b, priv.elements[i].data,
+ priv.elements[i].length);
+ isc_buffer_add(&b, priv.elements[i].length);
+ tresult = hmac_fromdns(type, key, &b);
+ if (tresult != ISC_R_SUCCESS) {
+ result = tresult;
+ }
+ break;
+ case TAG_HMACMD5_BITS:
+ case TAG_HMACSHA1_BITS:
+ case TAG_HMACSHA224_BITS:
+ case TAG_HMACSHA256_BITS:
+ case TAG_HMACSHA384_BITS:
+ case TAG_HMACSHA512_BITS:
+ tresult = getkeybits(key, &priv.elements[i]);
+ if (tresult != ISC_R_SUCCESS) {
+ result = tresult;
+ }
+ break;
+ default:
+ result = DST_R_INVALIDPRIVATEKEY;
+ break;
+ }
+ }
+ dst__privstruct_free(&priv, mctx);
+ isc_safe_memwipe(&priv, sizeof(priv));
+ return (result);
+}
+
+hmac_register_algorithm(md5);
+hmac_register_algorithm(sha1);
+hmac_register_algorithm(sha224);
+hmac_register_algorithm(sha256);
+hmac_register_algorithm(sha384);
+hmac_register_algorithm(sha512);
+
+/*! \file */
diff --git a/lib/dns/include/dns/acl.h b/lib/dns/include/dns/acl.h
new file mode 100644
index 0000000..a2f303b
--- /dev/null
+++ b/lib/dns/include/dns/acl.h
@@ -0,0 +1,337 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#pragma once
+
+/*****
+***** Module Info
+*****/
+
+/*! \file dns/acl.h
+ * \brief
+ * Address match list handling.
+ */
+
+/***
+ *** Imports
+ ***/
+
+#include <stdbool.h>
+
+#include <isc/lang.h>
+#include <isc/magic.h>
+#include <isc/netaddr.h>
+#include <isc/refcount.h>
+#include <isc/rwlock.h>
+
+#include <dns/geoip.h>
+#include <dns/iptable.h>
+#include <dns/name.h>
+#include <dns/types.h>
+
+/***
+ *** Types
+ ***/
+
+typedef enum {
+ dns_aclelementtype_ipprefix,
+ dns_aclelementtype_keyname,
+ dns_aclelementtype_nestedacl,
+ dns_aclelementtype_localhost,
+ dns_aclelementtype_localnets,
+#if defined(HAVE_GEOIP2)
+ dns_aclelementtype_geoip,
+#endif /* HAVE_GEOIP2 */
+ dns_aclelementtype_any
+} dns_aclelementtype_t;
+
+typedef struct dns_acl_port_transports {
+ in_port_t port;
+ uint32_t transports;
+ bool encrypted; /* for protocols with optional encryption (e.g. HTTP) */
+ bool negative;
+ ISC_LINK(struct dns_acl_port_transports) link;
+} dns_acl_port_transports_t;
+
+typedef struct dns_aclipprefix dns_aclipprefix_t;
+
+struct dns_aclipprefix {
+ isc_netaddr_t address; /* IP4/IP6 */
+ unsigned int prefixlen;
+};
+
+struct dns_aclelement {
+ dns_aclelementtype_t type;
+ bool negative;
+ dns_name_t keyname;
+#if defined(HAVE_GEOIP2)
+ dns_geoip_elem_t geoip_elem;
+#endif /* HAVE_GEOIP2 */
+ dns_acl_t *nestedacl;
+ int node_num;
+};
+
+#define dns_acl_node_count(acl) acl->iptable->radix->num_added_node
+
+struct dns_acl {
+ unsigned int magic;
+ isc_mem_t *mctx;
+ isc_refcount_t refcount;
+ dns_iptable_t *iptable;
+ dns_aclelement_t *elements;
+ bool has_negatives;
+ unsigned int alloc; /*%< Elements allocated */
+ unsigned int length; /*%< Elements initialized */
+ char *name; /*%< Temporary use only */
+ ISC_LINK(dns_acl_t) nextincache; /*%< Ditto */
+ ISC_LIST(dns_acl_port_transports_t) ports_and_transports;
+ size_t port_proto_entries;
+};
+
+struct dns_aclenv {
+ unsigned int magic;
+ isc_mem_t *mctx;
+ isc_refcount_t references;
+
+ isc_rwlock_t rwlock; /*%< Locks localhost and localnets */
+ dns_acl_t *localhost;
+ dns_acl_t *localnets;
+
+ bool match_mapped;
+#if defined(HAVE_GEOIP2)
+ dns_geoip_databases_t *geoip;
+#endif /* HAVE_GEOIP2 */
+};
+
+#define DNS_ACL_MAGIC ISC_MAGIC('D', 'a', 'c', 'l')
+#define DNS_ACL_VALID(a) ISC_MAGIC_VALID(a, DNS_ACL_MAGIC)
+
+/***
+ *** Functions
+ ***/
+
+ISC_LANG_BEGINDECLS
+
+isc_result_t
+dns_acl_create(isc_mem_t *mctx, int n, dns_acl_t **target);
+/*%<
+ * Create a new ACL, including an IP table and an array with room
+ * for 'n' ACL elements. The elements are uninitialized and the
+ * length is 0.
+ */
+
+isc_result_t
+dns_acl_any(isc_mem_t *mctx, dns_acl_t **target);
+/*%<
+ * Create a new ACL that matches everything.
+ */
+
+isc_result_t
+dns_acl_none(isc_mem_t *mctx, dns_acl_t **target);
+/*%<
+ * Create a new ACL that matches nothing.
+ */
+
+bool
+dns_acl_isany(dns_acl_t *acl);
+/*%<
+ * Test whether ACL is set to "{ any; }"
+ */
+
+bool
+dns_acl_isnone(dns_acl_t *acl);
+/*%<
+ * Test whether ACL is set to "{ none; }"
+ */
+
+isc_result_t
+dns_acl_merge(dns_acl_t *dest, dns_acl_t *source, bool pos);
+/*%<
+ * Merge the contents of one ACL into another. Call dns_iptable_merge()
+ * for the IP tables, then concatenate the element arrays.
+ *
+ * If pos is set to false, then the nested ACL is to be negated. This
+ * means reverse the sense of each *positive* element or IP table node,
+ * but leave negatives alone, so as to prevent a double-negative causing
+ * an unexpected positive match in the parent ACL.
+ */
+
+void
+dns_acl_attach(dns_acl_t *source, dns_acl_t **target);
+/*%<
+ * Attach to acl 'source'.
+ *
+ * Requires:
+ *\li 'source' to be a valid acl.
+ *\li 'target' to be non NULL and '*target' to be NULL.
+ */
+
+void
+dns_acl_detach(dns_acl_t **aclp);
+/*%<
+ * Detach the acl. On final detach the acl must not be linked on any
+ * list.
+ *
+ * Requires:
+ *\li '*aclp' to be a valid acl.
+ *
+ * Insists:
+ *\li '*aclp' is not linked on final detach.
+ */
+
+bool
+dns_acl_isinsecure(const dns_acl_t *a);
+/*%<
+ * Return #true iff the acl 'a' is considered insecure, that is,
+ * if it contains IP addresses other than those of the local host.
+ * This is intended for applications such as printing warning
+ * messages for suspect ACLs; it is not intended for making access
+ * control decisions. We make no guarantee that an ACL for which
+ * this function returns #false is safe.
+ */
+
+bool
+dns_acl_allowed(isc_netaddr_t *addr, const dns_name_t *signer, dns_acl_t *acl,
+ dns_aclenv_t *aclenv);
+/*%<
+ * Return #true iff the 'addr', 'signer', or ECS values are
+ * permitted by 'acl' in environment 'aclenv'.
+ */
+
+isc_result_t
+dns_aclenv_create(isc_mem_t *mctx, dns_aclenv_t **envp);
+/*%<
+ * Create ACL environment, setting up localhost and localnets ACLs
+ */
+
+void
+dns_aclenv_copy(dns_aclenv_t *t, dns_aclenv_t *s);
+/*%<
+ * Copy the ACLs from one ACL environment object to another.
+ *
+ * Requires:
+ *\li both 's' and 't' are valid ACL environments.
+ */
+
+void
+dns_aclenv_set(dns_aclenv_t *env, dns_acl_t *localhost, dns_acl_t *localnets);
+/*%<
+ * Attach the 'localhost' and 'localnets' arguments to 'env' ACL environment
+ */
+
+void
+dns_aclenv_attach(dns_aclenv_t *source, dns_aclenv_t **targetp);
+/*%<
+ * Attach '*targetp' to ACL environment 'source'.
+ *
+ * Requires:
+ *\li 'source' is a valid ACL environment.
+ *\li 'targetp' is not NULL and '*targetp' is NULL.
+ */
+
+void
+dns_aclenv_detach(dns_aclenv_t **aclenvp);
+/*%<
+ * Detach an ACL environment; on final detach, destroy it.
+ *
+ * Requires:
+ *\li '*aclenvp' to be a valid ACL environment
+ */
+
+isc_result_t
+dns_acl_match(const isc_netaddr_t *reqaddr, const dns_name_t *reqsigner,
+ const dns_acl_t *acl, dns_aclenv_t *env, int *match,
+ const dns_aclelement_t **matchelt);
+/*%<
+ * General, low-level ACL matching. This is expected to
+ * be useful even for weird stuff like the topology and sortlist statements.
+ *
+ * Match the address 'reqaddr', and optionally the key name 'reqsigner',
+ * against 'acl'. 'reqsigner' may be NULL.
+ *
+ * If there is a match, '*match' will be set to an integer whose absolute
+ * value corresponds to the order in which the matching value was inserted
+ * into the ACL. For a positive match, this value will be positive; for a
+ * negative match, it will be negative.
+ *
+ * If there is no match, *match will be set to zero.
+ *
+ * If there is a match in the element list (either positive or negative)
+ * and 'matchelt' is non-NULL, *matchelt will be pointed to the matching
+ * element.
+ *
+ * 'env' points to the current ACL environment, including the
+ * current values of localhost and localnets and (if applicable)
+ * the GeoIP context.
+ *
+ * Returns:
+ *\li #ISC_R_SUCCESS Always succeeds.
+ */
+
+bool
+dns_aclelement_match(const isc_netaddr_t *reqaddr, const dns_name_t *reqsigner,
+ const dns_aclelement_t *e, dns_aclenv_t *env,
+ const dns_aclelement_t **matchelt);
+/*%<
+ * Like dns_acl_match, but matches against the single ACL element 'e'
+ * rather than a complete ACL, and returns true iff it matched.
+ *
+ * To determine whether the match was positive or negative, the
+ * caller should examine e->negative. Since the element 'e' may be
+ * a reference to a named ACL or a nested ACL, a matching element
+ * returned through 'matchelt' is not necessarily 'e' itself.
+ */
+
+isc_result_t
+dns_acl_match_port_transport(const isc_netaddr_t *reqaddr,
+ const in_port_t local_port,
+ const isc_nmsocket_type_t transport,
+ const bool encrypted, const dns_name_t *reqsigner,
+ const dns_acl_t *acl, dns_aclenv_t *env,
+ int *match, const dns_aclelement_t **matchelt);
+/*%<
+ * Like dns_acl_match, but able to match the server port and
+ * transport, as well as encryption status.
+ *
+ * Requires:
+ *\li 'reqaddr' is not 'NULL';
+ *\li 'acl' is a valid ACL object.
+ */
+
+void
+dns_acl_add_port_transports(dns_acl_t *acl, const in_port_t port,
+ const uint32_t transports, const bool encrypted,
+ const bool negative);
+/*%<
+ * Adds a "port-transports" entry to the specified ACL. Transports
+ * are specified as a bit-set 'transports' consisting of entries
+ * defined in the isc_nmsocket_type enumeration.
+ *
+ * Requires:
+ *\li 'acl' is a valid ACL object;
+ *\li either 'port' or 'transports' is not equal to 0.
+ */
+
+void
+dns_acl_merge_ports_transports(dns_acl_t *dest, dns_acl_t *source, bool pos);
+/*%<
+ * Merges "port-transports" entries from the 'dest' ACL into
+ * the 'source' ACL. The 'pos' parameter works in a way similar to
+ * 'dns_acl_merge()'.
+ *
+ * Requires:
+ *\li 'dest' is a valid ACL object;
+ *\li 'source' is a valid ACL object.
+ */
+
+ISC_LANG_ENDDECLS
diff --git a/lib/dns/include/dns/adb.h b/lib/dns/include/dns/adb.h
new file mode 100644
index 0000000..6310a8c
--- /dev/null
+++ b/lib/dns/include/dns/adb.h
@@ -0,0 +1,805 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#pragma once
+
+/*****
+***** Module Info
+*****/
+
+/*! \file dns/adb.h
+ *\brief
+ * DNS Address Database
+ *
+ * This module implements an address database (ADB) for mapping a name
+ * to an isc_sockaddr_t. It also provides statistical information on
+ * how good that address might be.
+ *
+ * A client will pass in a dns_name_t, and the ADB will walk through
+ * the rdataset looking up addresses associated with the name. If it
+ * is found on the internal lists, a structure is filled in with the
+ * address information and stats for found addresses.
+ *
+ * If the name cannot be found on the internal lists, a new entry will
+ * be created for a name if all the information needed can be found
+ * in the zone table or cache. This new address will then be returned.
+ *
+ * If a request must be made to remote servers to satisfy a name lookup,
+ * this module will start fetches to try to complete these addresses. When
+ * at least one more completes, an event is sent to the caller. If none of
+ * them resolve before the fetch times out, an event indicating this is
+ * sent instead.
+ *
+ * Records are stored internally until a timer expires. The timer is the
+ * smaller of the TTL or signature validity period.
+ *
+ * Lameness is stored per <qname,qtype> 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 <inttypes.h>
+#include <stdbool.h>
+
+#include <isc/lang.h>
+#include <isc/magic.h>
+#include <isc/mem.h>
+#include <isc/sockaddr.h>
+
+#include <dns/types.h>
+#include <dns/view.h>
+
+ISC_LANG_BEGINDECLS
+
+/***
+ *** Magic number checks
+ ***/
+
+#define DNS_ADBFIND_MAGIC ISC_MAGIC('a', 'd', 'b', 'H')
+#define DNS_ADBFIND_VALID(x) ISC_MAGIC_VALID(x, DNS_ADBFIND_MAGIC)
+#define DNS_ADBADDRINFO_MAGIC ISC_MAGIC('a', 'd', 'A', 'I')
+#define DNS_ADBADDRINFO_VALID(x) ISC_MAGIC_VALID(x, DNS_ADBADDRINFO_MAGIC)
+
+/***
+ *** TYPES
+ ***/
+
+typedef struct dns_adbname dns_adbname_t;
+
+/*!
+ *\brief
+ * Represents a lookup for a single name.
+ *
+ * On return, the client can safely use "list", and can reorder the list.
+ * Items may not be _deleted_ from this list, however, or added to it
+ * other than by using the dns_adb_*() API.
+ */
+struct dns_adbfind {
+ /* Public */
+ unsigned int magic; /*%< RO: magic */
+ dns_adbaddrinfolist_t list; /*%< RO: list of addrs */
+ unsigned int query_pending; /*%< RO: partial list */
+ unsigned int partial_result; /*%< RO: addrs missing */
+ unsigned int options; /*%< RO: options */
+ isc_result_t result_v4; /*%< RO: v4 result */
+ isc_result_t result_v6; /*%< RO: v6 result */
+ ISC_LINK(dns_adbfind_t) publink; /*%< RW: client use */
+
+ /* Private */
+ isc_mutex_t lock; /* locks all below */
+ in_port_t port;
+ int name_bucket;
+ unsigned int flags;
+ dns_adbname_t *adbname;
+ dns_adb_t *adb;
+ isc_event_t event;
+ ISC_LINK(dns_adbfind_t) plink;
+};
+
+/*
+ * _INET:
+ * _INET6:
+ * return addresses of that type.
+ *
+ * _EMPTYEVENT:
+ * Only schedule an event if no addresses are known.
+ * Must set _WANTEVENT for this to be meaningful.
+ *
+ * _WANTEVENT:
+ * An event is desired. Check this bit in the returned find to see
+ * if one will actually be generated.
+ *
+ * _AVOIDFETCHES:
+ * If set, fetches will not be generated unless no addresses are
+ * available in any of the address families requested.
+ *
+ * _STARTATZONE:
+ * Fetches will start using the closest zone data or use the root servers.
+ * This is useful for reestablishing glue that has expired.
+ *
+ * _GLUEOK:
+ * _HINTOK:
+ * Glue or hints are ok. These are used when matching names already
+ * in the adb, and when dns databases are searched.
+ *
+ * _RETURNLAME:
+ * Return lame servers in a find, so that all addresses are returned.
+ *
+ * _LAMEPRUNED:
+ * At least one address was omitted from the list because it was lame.
+ * This bit will NEVER be set if _RETURNLAME is set in the createfind().
+ */
+/*% Return addresses of type INET. */
+#define DNS_ADBFIND_INET 0x00000001
+/*% Return addresses of type INET6. */
+#define DNS_ADBFIND_INET6 0x00000002
+#define DNS_ADBFIND_ADDRESSMASK 0x00000003
+/*%
+ * Only schedule an event if no addresses are known.
+ * Must set _WANTEVENT for this to be meaningful.
+ */
+#define DNS_ADBFIND_EMPTYEVENT 0x00000004
+/*%
+ * An event is desired. Check this bit in the returned find to see
+ * if one will actually be generated.
+ */
+#define DNS_ADBFIND_WANTEVENT 0x00000008
+/*%
+ * If set, fetches will not be generated unless no addresses are
+ * available in any of the address families requested.
+ */
+#define DNS_ADBFIND_AVOIDFETCHES 0x00000010
+/*%
+ * Fetches will start using the closest zone data or use the root servers.
+ * This is useful for reestablishing glue that has expired.
+ */
+#define DNS_ADBFIND_STARTATZONE 0x00000020
+/*%
+ * Glue or hints are ok. These are used when matching names already
+ * in the adb, and when dns databases are searched.
+ */
+#define DNS_ADBFIND_GLUEOK 0x00000040
+/*%
+ * Glue or hints are ok. These are used when matching names already
+ * in the adb, and when dns databases are searched.
+ */
+#define DNS_ADBFIND_HINTOK 0x00000080
+/*%
+ * Return lame servers in a find, so that all addresses are returned.
+ */
+#define DNS_ADBFIND_RETURNLAME 0x00000100
+/*%
+ * Only schedule an event if no addresses are known.
+ * Must set _WANTEVENT for this to be meaningful.
+ */
+#define DNS_ADBFIND_LAMEPRUNED 0x00000200
+/*%
+ * The server's fetch quota is exceeded; it will be treated as
+ * lame for this query.
+ */
+#define DNS_ADBFIND_OVERQUOTA 0x00000400
+/*%
+ * Don't perform a fetch even if there are no address records available.
+ */
+#define DNS_ADBFIND_NOFETCH 0x00000800
+
+/*%
+ * The answers to queries come back as a list of these.
+ */
+struct dns_adbaddrinfo {
+ unsigned int magic; /*%< private */
+
+ isc_sockaddr_t sockaddr; /*%< [rw] */
+ unsigned int srtt; /*%< [rw] microsecs */
+
+ unsigned int flags; /*%< [rw] */
+ dns_adbentry_t *entry; /*%< private */
+ ISC_LINK(dns_adbaddrinfo_t) publink;
+};
+
+/*!<
+ * The event sent to the caller task is just a plain old isc_event_t. It
+ * contains no data other than a simple status, passed in the "type" field
+ * to indicate that another address resolved, or all partially resolved
+ * addresses have failed to resolve.
+ *
+ * "sender" is the dns_adbfind_t used to issue this query.
+ *
+ * This is simply a standard event, with the "type" set to:
+ *
+ *\li #DNS_EVENT_ADBMOREADDRESSES -- another address resolved.
+ *\li #DNS_EVENT_ADBNOMOREADDRESSES -- all pending addresses failed,
+ * were canceled, or otherwise will
+ * not be usable.
+ *\li #DNS_EVENT_ADBCANCELED -- The request was canceled by a
+ * 3rd party.
+ *\li #DNS_EVENT_ADBNAMEDELETED -- The name was deleted, so this request
+ * was canceled.
+ *
+ * In each of these cases, the addresses returned by the initial call
+ * to dns_adb_createfind() can still be used until they are no longer needed.
+ */
+
+/****
+**** FUNCTIONS
+****/
+
+isc_result_t
+dns_adb_create(isc_mem_t *mem, dns_view_t *view, isc_timermgr_t *tmgr,
+ isc_taskmgr_t *taskmgr, dns_adb_t **newadb);
+/*%<
+ * Create a new ADB.
+ *
+ * Notes:
+ *
+ *\li Generally, applications should not create an ADB directly, but
+ * should instead call dns_view_createresolver().
+ *
+ * Requires:
+ *
+ *\li 'mem' must be a valid memory context.
+ *
+ *\li 'view' be a pointer to a valid view.
+ *
+ *\li 'tmgr' be a pointer to a valid timer manager.
+ *
+ *\li 'taskmgr' be a pointer to a valid task manager.
+ *
+ *\li 'newadb' != NULL && '*newadb' == NULL.
+ *
+ * Returns:
+ *
+ *\li #ISC_R_SUCCESS after happiness.
+ *\li #ISC_R_NOMEMORY after resource allocation failure.
+ */
+
+void
+dns_adb_attach(dns_adb_t *adb, dns_adb_t **adbp);
+/*%
+ * Attach to an 'adb' to 'adbp'.
+ *
+ * Requires:
+ *\li 'adb' to be a valid dns_adb_t, created via dns_adb_create().
+ *\li 'adbp' to be a valid pointer to a *dns_adb_t which is initialized
+ * to NULL.
+ */
+
+void
+dns_adb_detach(dns_adb_t **adb);
+/*%
+ * Delete the ADB. Sets *ADB to NULL. Cancels any outstanding requests.
+ *
+ * Requires:
+ *
+ *\li 'adb' be non-NULL and '*adb' be a valid dns_adb_t, created via
+ * dns_adb_create().
+ */
+
+void
+dns_adb_whenshutdown(dns_adb_t *adb, isc_task_t *task, isc_event_t **eventp);
+/*%
+ * Send '*eventp' to 'task' when 'adb' has shutdown.
+ *
+ * Requires:
+ *
+ *\li '*adb' is a valid dns_adb_t.
+ *
+ *\li eventp != NULL && *eventp is a valid event.
+ *
+ * Ensures:
+ *
+ *\li *eventp == NULL
+ *
+ *\li The event's sender field is set to the value of adb when the event
+ * is sent.
+ */
+
+void
+dns_adb_shutdown(dns_adb_t *adb);
+/*%<
+ * Shutdown 'adb'.
+ *
+ * Requires:
+ *
+ * \li '*adb' is a valid dns_adb_t.
+ */
+
+isc_result_t
+dns_adb_createfind(dns_adb_t *adb, isc_task_t *task, isc_taskaction_t action,
+ void *arg, const dns_name_t *name, const dns_name_t *qname,
+ dns_rdatatype_t qtype, unsigned int options,
+ isc_stdtime_t now, dns_name_t *target, in_port_t port,
+ unsigned int depth, isc_counter_t *qc, dns_adbfind_t **find);
+/*%<
+ * Main interface for clients. The adb will look up the name given in
+ * "name" and will build up a list of found addresses, and perhaps start
+ * internal fetches to resolve names that are unknown currently.
+ *
+ * If other addresses resolve after this call completes, an event will
+ * be sent to the <task, taskaction, arg> with the sender of that event
+ * set to a pointer to the dns_adbfind_t returned by this function.
+ *
+ * If no events will be generated, the *find->result_v4 and/or result_v6
+ * members may be examined for address lookup status. The usual #ISC_R_SUCCESS,
+ * #ISC_R_FAILURE, #DNS_R_NXDOMAIN, and #DNS_R_NXRRSET are returned, along with
+ * #ISC_R_NOTFOUND meaning the ADB has not _yet_ found the values. In this
+ * latter case, retrying may produce more addresses.
+ *
+ * If events will be returned, the result_v[46] members are only valid
+ * when that event is actually returned.
+ *
+ * The list of addresses returned is unordered. The caller must impose
+ * any ordering required. The list will not contain "known bad" addresses,
+ * however. For instance, it will not return hosts that are known to be
+ * lame for the zone in question.
+ *
+ * The caller cannot (directly) modify the contents of the address list's
+ * fields other than the "link" field. All values can be read at any
+ * time, however.
+ *
+ * The "now" parameter is used only for determining which entries that
+ * have a specific time to live or expire time should be removed from
+ * the running database. If specified as zero, the current time will
+ * be retrieved and used.
+ *
+ * If 'target' is not NULL and 'name' is an alias (i.e. the name is
+ * CNAME'd or DNAME'd to another name), then 'target' will be updated with
+ * the domain name that 'name' is aliased to.
+ *
+ * All addresses returned will have the sockaddr's port set to 'port.'
+ * The caller may change them directly in the dns_adbaddrinfo_t since
+ * they are copies of the internal address only.
+ *
+ * XXXMLG Document options, especially the flags which control how
+ * events are sent.
+ *
+ * Requires:
+ *
+ *\li *adb be a valid isc_adb_t object.
+ *
+ *\li If events are to be sent, *task be a valid task,
+ * and isc_taskaction_t != NULL.
+ *
+ *\li *name is a valid dns_name_t.
+ *
+ *\li qname != NULL and *qname be a valid dns_name_t.
+ *
+ *\li target == NULL or target is a valid name with a buffer.
+ *
+ *\li find != NULL && *find == NULL.
+ *
+ * Returns:
+ *
+ *\li #ISC_R_SUCCESS Addresses might have been returned, and events will be
+ * delivered for unresolved addresses.
+ *\li #ISC_R_NOMORE Addresses might have been returned, but no events
+ * will ever be posted for this context. This is only
+ * returned if task != NULL.
+ *\li #ISC_R_NOMEMORY insufficient resources
+ *\li #DNS_R_ALIAS 'name' is an alias for another name.
+ *
+ * Calls, and returns error codes from:
+ *
+ *\li isc_stdtime_get()
+ *
+ * Notes:
+ *
+ *\li No internal reference to "name" exists after this function
+ * returns.
+ */
+
+void
+dns_adb_cancelfind(dns_adbfind_t *find);
+/*%<
+ * Cancels the find, and sends the event off to the caller.
+ *
+ * It is an error to call dns_adb_cancelfind() on a find where
+ * no event is wanted, or will ever be sent.
+ *
+ * Note:
+ *
+ *\li It is possible that the real completion event was posted just
+ * before the dns_adb_cancelfind() call was made. In this case,
+ * dns_adb_cancelfind() will do nothing. The event callback needs
+ * to be prepared to find this situation (i.e. result is valid but
+ * the caller expects it to be canceled).
+ *
+ * Requires:
+ *
+ *\li 'find' be a valid dns_adbfind_t pointer.
+ *
+ *\li events would have been posted to the task. This can be checked
+ * with (find->options & DNS_ADBFIND_WANTEVENT).
+ *
+ * Ensures:
+ *
+ *\li The event was posted to the task.
+ */
+
+void
+dns_adb_destroyfind(dns_adbfind_t **find);
+/*%<
+ * Destroys the find reference.
+ *
+ * Note:
+ *
+ *\li This can only be called after the event was delivered for a
+ * find. Additionally, the event MUST have been freed via
+ * isc_event_free() BEFORE this function is called.
+ *
+ * Requires:
+ *
+ *\li 'find' != NULL and *find be valid dns_adbfind_t pointer.
+ *
+ * Ensures:
+ *
+ *\li No "address found" events will be posted to the originating task
+ * after this function returns.
+ */
+
+void
+dns_adb_dump(dns_adb_t *adb, FILE *f);
+/*%<
+ * This function is only used for debugging. It will dump as much of the
+ * state of the running system as possible.
+ *
+ * Requires:
+ *
+ *\li adb be valid.
+ *
+ *\li f != NULL, and is a file open for writing.
+ */
+
+void
+dns_adb_dumpfind(dns_adbfind_t *find, FILE *f);
+/*%<
+ * This function is only used for debugging. Dump the data associated
+ * with a find.
+ *
+ * Requires:
+ *
+ *\li find is valid.
+ *
+ * \li f != NULL, and is a file open for writing.
+ */
+
+isc_result_t
+dns_adb_marklame(dns_adb_t *adb, dns_adbaddrinfo_t *addr,
+ const dns_name_t *qname, dns_rdatatype_t type,
+ isc_stdtime_t expire_time);
+/*%<
+ * Mark the given address as lame for the <qname,qtype>. expire_time should
+ * be set to the time when the entry should expire. That is, if it is to
+ * expire 10 minutes in the future, it should set it to (now + 10 * 60).
+ *
+ * Requires:
+ *
+ *\li adb be valid.
+ *
+ *\li addr be valid.
+ *
+ *\li qname be the qname used in the dns_adb_createfind() call.
+ *
+ * Returns:
+ *
+ *\li #ISC_R_SUCCESS -- all is well.
+ *\li #ISC_R_NOMEMORY -- could not mark address as lame.
+ */
+
+/*
+ * Reasonable defaults for RTT adjustments
+ *
+ * (Note: these values function both as scaling factors and as
+ * indicators of the type of RTT adjustment operation taking place.
+ * Adjusting the scaling factors is fine, as long as they all remain
+ * unique values.)
+ */
+#define DNS_ADB_RTTADJDEFAULT 7 /*%< default scale */
+#define DNS_ADB_RTTADJREPLACE 0 /*%< replace with our rtt */
+#define DNS_ADB_RTTADJAGE 10 /*%< age this rtt */
+
+void
+dns_adb_adjustsrtt(dns_adb_t *adb, dns_adbaddrinfo_t *addr, unsigned int rtt,
+ unsigned int factor);
+/*%<
+ * Mix the round trip time into the existing smoothed rtt.
+ *
+ * Requires:
+ *
+ *\li adb be valid.
+ *
+ *\li addr be valid.
+ *
+ *\li 0 <= factor <= 10
+ *
+ * Note:
+ *
+ *\li The srtt in addr will be updated to reflect the new global
+ * srtt value. This may include changes made by others.
+ */
+
+void
+dns_adb_agesrtt(dns_adb_t *adb, dns_adbaddrinfo_t *addr, isc_stdtime_t now);
+/*
+ * dns_adb_agesrtt is equivalent to dns_adb_adjustsrtt with factor
+ * equal to DNS_ADB_RTTADJAGE and the current time passed in.
+ *
+ * Requires:
+ *
+ *\li adb be valid.
+ *
+ *\li addr be valid.
+ *
+ * Note:
+ *
+ *\li The srtt in addr will be updated to reflect the new global
+ * srtt value. This may include changes made by others.
+ */
+
+void
+dns_adb_changeflags(dns_adb_t *adb, dns_adbaddrinfo_t *addr, unsigned int bits,
+ unsigned int mask);
+/*%
+ * Change Flags.
+ *
+ * Set the flags as given by:
+ *
+ *\li newflags = (oldflags & ~mask) | (bits & mask);
+ *
+ * Requires:
+ *
+ *\li adb be valid.
+ *
+ *\li addr be valid.
+ */
+
+void
+dns_adb_setudpsize(dns_adb_t *adb, dns_adbaddrinfo_t *addr, unsigned int size);
+/*%
+ * Update seen UDP response size. The largest seen will be returned by
+ * dns_adb_getudpsize().
+ *
+ * Requires:
+ *
+ *\li adb be valid.
+ *
+ *\li addr be valid.
+ */
+
+unsigned int
+dns_adb_getudpsize(dns_adb_t *adb, dns_adbaddrinfo_t *addr);
+/*%
+ * Return the largest seen UDP response size.
+ *
+ * Requires:
+ *
+ *\li adb be valid.
+ *
+ *\li addr be valid.
+ */
+
+void
+dns_adb_plainresponse(dns_adb_t *adb, dns_adbaddrinfo_t *addr);
+/*%
+ * Record a successful plain DNS response.
+ *
+ * Requires:
+ *
+ *\li adb be valid.
+ *
+ *\li addr be valid.
+ */
+
+void
+dns_adb_timeout(dns_adb_t *adb, dns_adbaddrinfo_t *addr);
+/*%
+ * Record a plain DNS UDP query failed.
+ *
+ * Requires:
+ *
+ *\li adb be valid.
+ *
+ *\li addr be valid.
+ */
+
+void
+dns_adb_ednsto(dns_adb_t *adb, dns_adbaddrinfo_t *addr);
+/*%
+ * Record a EDNS UDP query failed.
+ *
+ * Requires:
+ *
+ *\li adb be valid.
+ *
+ *\li addr be valid.
+ */
+
+isc_result_t
+dns_adb_findaddrinfo(dns_adb_t *adb, const isc_sockaddr_t *sa,
+ dns_adbaddrinfo_t **addrp, isc_stdtime_t now);
+/*%<
+ * Return a dns_adbaddrinfo_t that is associated with address 'sa'.
+ *
+ * Requires:
+ *
+ *\li adb is valid.
+ *
+ *\li sa is valid.
+ *
+ *\li addrp != NULL && *addrp == NULL
+ *
+ * Returns:
+ *\li #ISC_R_SUCCESS
+ *\li #ISC_R_NOMEMORY
+ *\li #ISC_R_SHUTTINGDOWN
+ */
+
+void
+dns_adb_freeaddrinfo(dns_adb_t *adb, dns_adbaddrinfo_t **addrp);
+/*%<
+ * Free a dns_adbaddrinfo_t allocated by dns_adb_findaddrinfo().
+ *
+ * Requires:
+ *
+ *\li adb is valid.
+ *
+ *\li *addrp is a valid dns_adbaddrinfo_t *.
+ */
+
+void
+dns_adb_flush(dns_adb_t *adb);
+/*%<
+ * Flushes all cached data from the adb.
+ *
+ * Requires:
+ *\li adb is valid.
+ */
+
+void
+dns_adb_setadbsize(dns_adb_t *adb, size_t size);
+/*%<
+ * Set a target memory size. If memory usage exceeds the target
+ * size entries will be removed before they would have expired on
+ * a random basis.
+ *
+ * If 'size' is 0 then memory usage is unlimited.
+ *
+ * Requires:
+ *\li 'adb' is valid.
+ */
+
+void
+dns_adb_flushname(dns_adb_t *adb, const dns_name_t *name);
+/*%<
+ * Flush 'name' from the adb cache.
+ *
+ * Requires:
+ *\li 'adb' is valid.
+ *\li 'name' is valid.
+ */
+
+void
+dns_adb_flushnames(dns_adb_t *adb, const dns_name_t *name);
+/*%<
+ * Flush 'name' and all subdomains from the adb cache.
+ *
+ * Requires:
+ *\li 'adb' is valid.
+ *\li 'name' is valid.
+ */
+
+void
+dns_adb_setcookie(dns_adb_t *adb, dns_adbaddrinfo_t *addr,
+ const unsigned char *cookie, size_t len);
+/*%<
+ * Record the COOKIE associated with this address. If
+ * cookie is NULL or len is zero the recorded COOKIE is cleared.
+ *
+ * Requires:
+ *\li 'adb' is valid.
+ *\li 'addr' is valid.
+ */
+
+size_t
+dns_adb_getcookie(dns_adb_t *adb, dns_adbaddrinfo_t *addr,
+ unsigned char *cookie, size_t len);
+/*
+ * Retrieve the saved COOKIE value and store it in 'cookie' which has
+ * size 'len'.
+ *
+ * Requires:
+ *\li 'adb' is valid.
+ *\li 'addr' is valid.
+ *
+ * Returns:
+ * The size of the cookie or zero if it doesn't fit in the buffer
+ * or it doesn't exist.
+ */
+
+void
+dns_adb_setquota(dns_adb_t *adb, uint32_t quota, uint32_t freq, double low,
+ double high, double discount);
+/*%<
+ * Set the baseline ADB quota, and configure parameters for the
+ * quota adjustment algorithm.
+ *
+ * If the number of fetches currently waiting for responses from this
+ * address exceeds the current quota, then additional fetches are spilled.
+ *
+ * 'quota' is the highest permissible quota; it will adjust itself
+ * downward in response to detected congestion.
+ *
+ * After every 'freq' fetches have either completed or timed out, an
+ * exponentially weighted moving average of the ratio of timeouts
+ * to responses is calculated. If the EWMA goes above a 'high'
+ * threshold, then the quota is adjusted down one step; if it drops
+ * below a 'low' threshold, then the quota is adjusted back up one
+ * step.
+ *
+ * The quota adjustment is based on the function (1 / 1 + (n/10)^(3/2)),
+ * for values of n from 0 to 99. It starts at 100% of the baseline
+ * quota, and descends after 100 steps to 2%.
+ *
+ * 'discount' represents the discount rate of the moving average. Higher
+ * values cause older values to be discounted sooner, providing a faster
+ * response to changes in the timeout ratio.
+ *
+ * Requires:
+ *\li 'adb' is valid.
+ */
+
+bool
+dns_adbentry_overquota(dns_adbentry_t *entry);
+/*%<
+ * Returns true if the specified ADB has too many active fetches.
+ *
+ * Requires:
+ *\li 'entry' is valid.
+ */
+
+void
+dns_adb_beginudpfetch(dns_adb_t *adb, dns_adbaddrinfo_t *addr);
+void
+dns_adb_endudpfetch(dns_adb_t *adb, dns_adbaddrinfo_t *addr);
+/*%
+ * Begin/end a UDP fetch on a particular address.
+ *
+ * These functions increment or decrement the fetch counter for
+ * the ADB entry so that the fetch quota can be enforced.
+ *
+ * Requires:
+ *
+ *\li adb be valid.
+ *
+ *\li addr be valid.
+ */
+
+ISC_LANG_ENDDECLS
diff --git a/lib/dns/include/dns/badcache.h b/lib/dns/include/dns/badcache.h
new file mode 100644
index 0000000..1021ba3
--- /dev/null
+++ b/lib/dns/include/dns/badcache.h
@@ -0,0 +1,149 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#pragma once
+
+/*****
+***** Module Info
+*****/
+
+/*! \file dns/badcache.h
+ * \brief
+ * Defines dns_badcache_t, the "bad cache" object.
+ *
+ * Notes:
+ *\li A bad cache object is a hash table of name/type tuples,
+ * indicating whether a given tuple known to be "bad" in some
+ * sense (e.g., queries for that name and type have been
+ * returning SERVFAIL). This is used for both the "bad server
+ * cache" in the resolver and for the "servfail cache" in
+ * the view.
+ *
+ * Reliability:
+ *
+ * Resources:
+ *
+ * Security:
+ *
+ * Standards:
+ */
+
+/***
+ *** Imports
+ ***/
+
+#include <inttypes.h>
+#include <stdbool.h>
+
+#include <dns/types.h>
+
+ISC_LANG_BEGINDECLS
+
+/***
+ *** Functions
+ ***/
+
+isc_result_t
+dns_badcache_init(isc_mem_t *mctx, unsigned int size, dns_badcache_t **bcp);
+/*%
+ * Allocate and initialize a badcache and store it in '*bcp'.
+ *
+ * Requires:
+ * \li mctx != NULL
+ * \li bcp != NULL
+ * \li *bcp == NULL
+ */
+
+void
+dns_badcache_destroy(dns_badcache_t **bcp);
+/*%
+ * Flush and then free badcache in 'bcp'. '*bcp' is set to NULL on return.
+ *
+ * Requires:
+ * \li '*bcp' to be a valid badcache
+ */
+
+void
+dns_badcache_add(dns_badcache_t *bc, const dns_name_t *name,
+ dns_rdatatype_t type, bool update, uint32_t flags,
+ isc_time_t *expire);
+/*%
+ * Adds a badcache entry to the badcache 'bc' for name 'name' and
+ * type 'type'. If an entry already exists, then it will be updated if
+ * 'update' is true. The entry will be stored with flags 'flags'
+ * and expiration date 'expire'.
+ *
+ * Requires:
+ * \li bc to be a valid badcache.
+ * \li name != NULL
+ * \li expire != NULL
+ */
+
+bool
+dns_badcache_find(dns_badcache_t *bc, const dns_name_t *name,
+ dns_rdatatype_t type, uint32_t *flagp, isc_time_t *now);
+/*%
+ * Returns true if a record is found in the badcache 'bc' matching
+ * 'name' and 'type', with an expiration date later than 'now'.
+ * If 'flagp' is not NULL, then '*flagp' is updated to the flags
+ * that were stored in the badcache entry. Returns false if
+ * no matching record is found.
+ *
+ * Requires:
+ * \li bc to be a valid badcache.
+ * \li name != NULL
+ * \li now != NULL
+ */
+
+void
+dns_badcache_flush(dns_badcache_t *bc);
+/*%
+ * Flush the entire bad cache.
+ *
+ * Requires:
+ * \li bc to be a valid badcache
+ */
+
+void
+dns_badcache_flushname(dns_badcache_t *bc, const dns_name_t *name);
+/*%
+ * Flush the bad cache of all entries at 'name'.
+ *
+ * Requires:
+ * \li bc to be a valid badcache
+ * \li name != NULL
+ */
+
+void
+dns_badcache_flushtree(dns_badcache_t *bc, const dns_name_t *name);
+/*%
+ * Flush the bad cache of all entries at or below 'name'.
+ *
+ * Requires:
+ * \li bc to be a valid badcache
+ * \li name != NULL
+ */
+
+void
+dns_badcache_print(dns_badcache_t *bc, const char *cachename, FILE *fp);
+/*%
+ * Print the contents of badcache 'bc' (headed by the title 'cachename')
+ * to file pointer 'fp'.
+ *
+ * Requires:
+ * \li bc to be a valid badcache
+ * \li cachename != NULL
+ * \li fp != NULL
+ */
+
+ISC_LANG_ENDDECLS
diff --git a/lib/dns/include/dns/bit.h b/lib/dns/include/dns/bit.h
new file mode 100644
index 0000000..dc7c195
--- /dev/null
+++ b/lib/dns/include/dns/bit.h
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#pragma once
+
+/*! \file dns/bit.h */
+
+#include <inttypes.h>
+
+typedef uint64_t dns_bitset_t;
+
+#define DNS_BIT_SET(bit, bitset) (*(bitset) |= ((dns_bitset_t)1 << (bit)))
+#define DNS_BIT_CLEAR(bit, bitset) (*(bitset) &= ~((dns_bitset_t)1 << (bit)))
+#define DNS_BIT_CHECK(bit, bitset) \
+ ((*(bitset) & ((dns_bitset_t)1 << (bit))) == ((dns_bitset_t)1 << (bit)))
diff --git a/lib/dns/include/dns/byaddr.h b/lib/dns/include/dns/byaddr.h
new file mode 100644
index 0000000..b92213b
--- /dev/null
+++ b/lib/dns/include/dns/byaddr.h
@@ -0,0 +1,149 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#pragma once
+
+/*****
+***** Module Info
+*****/
+
+/*! \file dns/byaddr.h
+ * \brief
+ * The byaddr module provides reverse lookup services for IPv4 and IPv6
+ * addresses.
+ *
+ * MP:
+ *\li The module ensures appropriate synchronization of data structures it
+ * creates and manipulates.
+ *
+ * Reliability:
+ *\li No anticipated impact.
+ *
+ * Resources:
+ *\li TBS
+ *
+ * Security:
+ *\li No anticipated impact.
+ *
+ * Standards:
+ *\li RFCs: 1034, 1035, 2181, TBS
+ *\li Drafts: TBS
+ */
+
+#include <isc/event.h>
+#include <isc/lang.h>
+
+#include <dns/types.h>
+
+ISC_LANG_BEGINDECLS
+
+/*%
+ * A 'dns_byaddrevent_t' is returned when a byaddr completes.
+ * The sender field will be set to the byaddr that completed. If 'result'
+ * is ISC_R_SUCCESS, then 'names' will contain a list of names associated
+ * with the address. The recipient of the event must not change the list
+ * and must not refer to any of the name data after the event is freed.
+ */
+typedef struct dns_byaddrevent {
+ ISC_EVENT_COMMON(struct dns_byaddrevent);
+ isc_result_t result;
+ dns_namelist_t names;
+} dns_byaddrevent_t;
+
+isc_result_t
+dns_byaddr_create(isc_mem_t *mctx, const isc_netaddr_t *address,
+ dns_view_t *view, unsigned int options, isc_task_t *task,
+ isc_taskaction_t action, void *arg, dns_byaddr_t **byaddrp);
+/*%<
+ * Find the domain name of 'address'.
+ *
+ * Notes:
+ *
+ *\li There is a reverse lookup format for IPv6 addresses, 'nibble'
+ *
+ *\li The 'nibble' format for that address is
+ *
+ * \code
+ * 1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.8.e.f.ip6.arpa.
+ * \endcode
+ *
+ * Requires:
+ *
+ *\li 'mctx' is a valid mctx.
+ *
+ *\li 'address' is a valid IPv4 or IPv6 address.
+ *
+ *\li 'view' is a valid view which has a resolver.
+ *
+ *\li 'task' is a valid task.
+ *
+ *\li byaddrp != NULL && *byaddrp == NULL
+ *
+ * Returns:
+ *
+ *\li #ISC_R_SUCCESS
+ *\li #ISC_R_NOMEMORY
+ *
+ *\li Any resolver-related error (e.g. #ISC_R_SHUTTINGDOWN) may also be
+ * returned.
+ */
+
+void
+dns_byaddr_cancel(dns_byaddr_t *byaddr);
+/*%<
+ * Cancel 'byaddr'.
+ *
+ * Notes:
+ *
+ *\li If 'byaddr' has not completed, post its #DNS_EVENT_BYADDRDONE
+ * event with a result code of #ISC_R_CANCELED.
+ *
+ * Requires:
+ *
+ *\li 'byaddr' is a valid byaddr.
+ */
+
+void
+dns_byaddr_destroy(dns_byaddr_t **byaddrp);
+/*%<
+ * Destroy 'byaddr'.
+ *
+ * Requires:
+ *
+ *\li '*byaddrp' is a valid byaddr.
+ *
+ *\li The caller has received the #DNS_EVENT_BYADDRDONE event (either because
+ * the byaddr completed or because dns_byaddr_cancel() was called).
+ *
+ * Ensures:
+ *
+ *\li *byaddrp == NULL.
+ */
+
+isc_result_t
+dns_byaddr_createptrname(const isc_netaddr_t *address, unsigned int options,
+ dns_name_t *name);
+/*%<
+ * Creates a name that would be used in a PTR query for this address. The
+ * nibble flag indicates that the 'nibble' format is to be used if an IPv6
+ * address is provided, instead of the 'bitstring' format. Since we dropped
+ * the support of the bitstring labels, it is expected that the flag is always
+ * set. 'options' are the same as for dns_byaddr_create().
+ *
+ * Requires:
+ *
+ * \li 'address' is a valid address.
+ * \li 'name' is a valid name with a dedicated buffer.
+ */
+
+ISC_LANG_ENDDECLS
diff --git a/lib/dns/include/dns/cache.h b/lib/dns/include/dns/cache.h
new file mode 100644
index 0000000..8fc9657
--- /dev/null
+++ b/lib/dns/include/dns/cache.h
@@ -0,0 +1,302 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#pragma once
+
+/*****
+***** Module Info
+*****/
+
+/*! \file dns/cache.h
+ * \brief
+ * Defines dns_cache_t, the cache object.
+ *
+ * Notes:
+ *\li A cache object contains DNS data of a single class.
+ * Multiple classes will be handled by creating multiple
+ * views, each with a different class and its own cache.
+ *
+ * MP:
+ *\li See notes at the individual functions.
+ *
+ * Reliability:
+ *
+ * Resources:
+ *
+ * Security:
+ *
+ * Standards:
+ */
+
+/***
+ *** Imports
+ ***/
+
+#include <stdbool.h>
+
+#include <isc/lang.h>
+#include <isc/stats.h>
+#include <isc/stdtime.h>
+
+#include <dns/types.h>
+
+ISC_LANG_BEGINDECLS
+
+/***
+ *** Functions
+ ***/
+isc_result_t
+dns_cache_create(isc_mem_t *cmctx, isc_mem_t *hmctx, isc_taskmgr_t *taskmgr,
+ isc_timermgr_t *timermgr, dns_rdataclass_t rdclass,
+ const char *cachename, const char *db_type,
+ unsigned int db_argc, char **db_argv, dns_cache_t **cachep);
+/*%<
+ * Create a new DNS cache.
+ *
+ * dns_cache_create2() will create a named cache.
+ *
+ * dns_cache_create3() will create a named cache using two separate memory
+ * contexts, one for cache data which can be cleaned and a separate one for
+ * memory allocated for the heap (which can grow without an upper limit and
+ * has no mechanism for shrinking).
+ *
+ * dns_cache_create() is a backward compatible version that internally
+ * specifies an empty cache name and a single memory context.
+ *
+ * Requires:
+ *
+ *\li 'cmctx' (and 'hmctx' if applicable) is a valid memory context.
+ *
+ *\li 'taskmgr' is a valid task manager and 'timermgr' is a valid timer
+ * manager, or both are NULL. If NULL, no periodic cleaning of the
+ * cache will take place.
+ *
+ *\li 'cachename' is a valid string. This must not be NULL.
+ *
+ *\li 'cachep' is a valid pointer, and *cachep == NULL
+ *
+ * Ensures:
+ *
+ *\li '*cachep' is attached to the newly created cache
+ *
+ * Returns:
+ *
+ *\li #ISC_R_SUCCESS
+ *\li #ISC_R_NOMEMORY
+ */
+
+void
+dns_cache_attach(dns_cache_t *cache, dns_cache_t **targetp);
+/*%<
+ * Attach *targetp to cache.
+ *
+ * Requires:
+ *
+ *\li 'cache' is a valid cache.
+ *
+ *\li 'targetp' points to a NULL dns_cache_t *.
+ *
+ * Ensures:
+ *
+ *\li *targetp is attached to cache.
+ */
+
+void
+dns_cache_detach(dns_cache_t **cachep);
+/*%<
+ * Detach *cachep from its cache.
+ *
+ * Requires:
+ *
+ *\li 'cachep' points to a valid cache.
+ *
+ * Ensures:
+ *
+ *\li *cachep is NULL.
+ *
+ *\li If '*cachep' is the last reference to the cache,
+ * all resources used by the cache will be freed
+ */
+
+void
+dns_cache_attachdb(dns_cache_t *cache, dns_db_t **dbp);
+/*%<
+ * Attach *dbp to the cache's database.
+ *
+ * Notes:
+ *
+ *\li This may be used to get a reference to the database for
+ * the purpose of cache lookups (XXX currently it is also
+ * the way to add data to the cache, but having a
+ * separate dns_cache_add() interface instead would allow
+ * more control over memory usage).
+ * The caller should call dns_db_detach() on the reference
+ * when it is no longer needed.
+ *
+ * Requires:
+ *
+ *\li 'cache' is a valid cache.
+ *
+ *\li 'dbp' points to a NULL dns_db *.
+ *
+ * Ensures:
+ *
+ *\li *dbp is attached to the database.
+ */
+
+isc_result_t
+dns_cache_clean(dns_cache_t *cache, isc_stdtime_t now);
+/*%<
+ * Force immediate cleaning of the cache, freeing all rdatasets
+ * whose TTL has expired as of 'now' and that have no pending
+ * references.
+ */
+
+const char *
+dns_cache_getname(dns_cache_t *cache);
+/*%<
+ * Get the cache name.
+ */
+
+void
+dns_cache_setcachesize(dns_cache_t *cache, size_t size);
+/*%<
+ * Set the maximum cache size. 0 means unlimited.
+ */
+
+size_t
+dns_cache_getcachesize(dns_cache_t *cache);
+/*%<
+ * Get the maximum cache size.
+ */
+
+void
+dns_cache_setservestalettl(dns_cache_t *cache, dns_ttl_t ttl);
+/*%<
+ * Sets the maximum length of time that cached answers may be retained
+ * past their normal TTL. Default value for the library is 0, disabling
+ * the use of stale data.
+ *
+ * Requires:
+ *\li 'cache' to be valid.
+ */
+
+dns_ttl_t
+dns_cache_getservestalettl(dns_cache_t *cache);
+/*%<
+ * Gets the maximum length of time that cached answers may be kept past
+ * normal expiry.
+ *
+ * Requires:
+ *\li 'cache' to be valid.
+ */
+
+void
+dns_cache_setservestalerefresh(dns_cache_t *cache, dns_ttl_t interval);
+/*%<
+ * Sets the length of time to wait before attempting to refresh a rrset
+ * if a previous attempt in doing so has failed.
+ * During this time window if stale rrset are available in cache they
+ * will be directly returned to client.
+ *
+ * Requires:
+ *\li 'cache' to be valid.
+ */
+
+dns_ttl_t
+dns_cache_getservestalerefresh(dns_cache_t *cache);
+/*%<
+ * Gets the 'stale-refresh-time' value, set by a previous call to
+ * 'dns_cache_setservestalerefresh'.
+ *
+ * Requires:
+ *\li 'cache' to be valid.
+ */
+
+isc_result_t
+dns_cache_flush(dns_cache_t *cache);
+/*%<
+ * Flushes all data from the cache.
+ *
+ * Returns:
+ *\li #ISC_R_SUCCESS
+ *\li #ISC_R_NOMEMORY
+ */
+
+isc_result_t
+dns_cache_flushnode(dns_cache_t *cache, const dns_name_t *name, bool tree);
+/*
+ * Flush a given name from the cache. If 'tree' is true, then
+ * also flush all names under 'name'.
+ *
+ * Requires:
+ *\li 'cache' to be valid.
+ *\li 'name' to be valid.
+ *
+ * Returns:
+ *\li #ISC_R_SUCCESS
+ *\li #ISC_R_NOMEMORY
+ *\li other error returns.
+ */
+
+isc_result_t
+dns_cache_flushname(dns_cache_t *cache, const dns_name_t *name);
+/*
+ * Flush a given name from the cache. Equivalent to
+ * dns_cache_flushpartial(cache, name, false).
+ *
+ * Requires:
+ *\li 'cache' to be valid.
+ *\li 'name' to be valid.
+ *
+ * Returns:
+ *\li #ISC_R_SUCCESS
+ *\li #ISC_R_NOMEMORY
+ *\li other error returns.
+ */
+
+isc_stats_t *
+dns_cache_getstats(dns_cache_t *cache);
+/*
+ * Return a pointer to the stats collection object for 'cache'
+ */
+
+void
+dns_cache_dumpstats(dns_cache_t *cache, FILE *fp);
+/*
+ * Dump cache statistics and status in text to 'fp'
+ */
+
+void
+dns_cache_updatestats(dns_cache_t *cache, isc_result_t result);
+/*
+ * Update cache statistics based on result code in 'result'
+ */
+
+#ifdef HAVE_LIBXML2
+int
+dns_cache_renderxml(dns_cache_t *cache, void *writer0);
+/*
+ * Render cache statistics and status in XML for 'writer'.
+ */
+#endif /* HAVE_LIBXML2 */
+
+#ifdef HAVE_JSON_C
+isc_result_t
+dns_cache_renderjson(dns_cache_t *cache, void *cstats0);
+/*
+ * Render cache statistics and status in JSON
+ */
+#endif /* HAVE_JSON_C */
+
+ISC_LANG_ENDDECLS
diff --git a/lib/dns/include/dns/callbacks.h b/lib/dns/include/dns/callbacks.h
new file mode 100644
index 0000000..e908a9f
--- /dev/null
+++ b/lib/dns/include/dns/callbacks.h
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#pragma once
+
+/*! \file dns/callbacks.h */
+
+/***
+ *** Imports
+ ***/
+
+#include <isc/lang.h>
+#include <isc/magic.h>
+
+#include <dns/types.h>
+
+ISC_LANG_BEGINDECLS
+
+/***
+ *** Types
+ ***/
+
+#define DNS_CALLBACK_MAGIC ISC_MAGIC('C', 'L', 'L', 'B')
+#define DNS_CALLBACK_VALID(cb) ISC_MAGIC_VALID(cb, DNS_CALLBACK_MAGIC)
+
+struct dns_rdatacallbacks {
+ unsigned int magic;
+
+ /*%
+ * dns_load_master calls this when it has rdatasets to commit.
+ */
+ dns_addrdatasetfunc_t add;
+
+ /*%
+ * dns_master_load*() call this when loading a raw zonefile,
+ * to pass back information obtained from the file header
+ */
+ dns_rawdatafunc_t rawdata;
+ dns_zone_t *zone;
+
+ /*%
+ * dns_load_master / dns_rdata_fromtext call this to issue a error.
+ */
+ void (*error)(struct dns_rdatacallbacks *, const char *, ...);
+ /*%
+ * dns_load_master / dns_rdata_fromtext call this to issue a warning.
+ */
+ void (*warn)(struct dns_rdatacallbacks *, const char *, ...);
+ /*%
+ * Private data handles for use by the above callback functions.
+ */
+ void *add_private;
+ void *error_private;
+ void *warn_private;
+};
+
+/***
+ *** Initialization
+ ***/
+
+void
+dns_rdatacallbacks_init(dns_rdatacallbacks_t *callbacks);
+/*%<
+ * Initialize 'callbacks'.
+ *
+ * \li 'magic' is set to DNS_CALLBACK_MAGIC
+ *
+ * \li 'error' and 'warn' are set to default callbacks that print the
+ * error message through the DNS library log context.
+ *
+ *\li All other elements are initialized to NULL.
+ *
+ * Requires:
+ * \li 'callbacks' is a valid dns_rdatacallbacks_t,
+ */
+
+void
+dns_rdatacallbacks_init_stdio(dns_rdatacallbacks_t *callbacks);
+/*%<
+ * Like dns_rdatacallbacks_init, but logs to stdio.
+ */
+
+ISC_LANG_ENDDECLS
diff --git a/lib/dns/include/dns/catz.h b/lib/dns/include/dns/catz.h
new file mode 100644
index 0000000..1401380
--- /dev/null
+++ b/lib/dns/include/dns/catz.h
@@ -0,0 +1,461 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#pragma once
+
+/*
+ * Define this for reference count tracing in the unit
+ */
+#undef DNS_CATZ_TRACE
+
+#include <inttypes.h>
+#include <stdbool.h>
+
+#include <isc/ht.h>
+#include <isc/lang.h>
+#include <isc/refcount.h>
+#include <isc/rwlock.h>
+#include <isc/time.h>
+#include <isc/timer.h>
+
+#include <dns/db.h>
+#include <dns/fixedname.h>
+#include <dns/ipkeylist.h>
+#include <dns/rdata.h>
+#include <dns/types.h>
+
+ISC_LANG_BEGINDECLS
+
+#define DNS_CATZ_ERROR_LEVEL ISC_LOG_WARNING
+#define DNS_CATZ_INFO_LEVEL ISC_LOG_INFO
+#define DNS_CATZ_DEBUG_LEVEL1 ISC_LOG_DEBUG(1)
+#define DNS_CATZ_DEBUG_LEVEL2 ISC_LOG_DEBUG(2)
+#define DNS_CATZ_DEBUG_LEVEL3 ISC_LOG_DEBUG(3)
+#define DNS_CATZ_DEBUG_QUIET (DNS_CATZ_DEBUG_LEVEL3 + 1)
+
+/*
+ * Catalog Zones functions and structures.
+ */
+
+/*
+ * Options for a member zone in a catalog
+ */
+struct dns_catz_entry_options {
+ /*
+ * Options that can be overridden in catalog zone
+ */
+ /* default-masters/default-primaries definition */
+ dns_ipkeylist_t masters;
+
+ /* both as text in config format, NULL if none */
+ isc_buffer_t *allow_query;
+ isc_buffer_t *allow_transfer;
+
+ /*
+ * Options that are only set in named.conf
+ */
+ /* zone-directory definition */
+ char *zonedir;
+
+ /* zone should not be stored on disk (no 'file' statement in def */
+ bool in_memory;
+ /*
+ * Minimal interval between catalog zone updates, if a new version
+ * of catalog zone is received before this time the update will be
+ * postponed. This is a global option for the whole catalog zone.
+ */
+ uint32_t min_update_interval;
+};
+
+void
+dns_catz_options_init(dns_catz_options_t *options);
+/*%<
+ * Initialize 'options' to NULL values.
+ *
+ * Requires:
+ * \li 'options' to be non NULL.
+ */
+
+void
+dns_catz_options_free(dns_catz_options_t *options, isc_mem_t *mctx);
+/*%<
+ * Free 'options' contents into 'mctx'. ('options' itself is not freed.)
+ *
+ * Requires:
+ * \li 'options' to be non NULL.
+ * \li 'mctx' to be a valid memory context.
+ */
+
+void
+dns_catz_options_copy(isc_mem_t *mctx, const dns_catz_options_t *opts,
+ dns_catz_options_t *nopts);
+/*%<
+ * Duplicate 'opts' into 'nopts', allocating space from 'mctx'.
+ *
+ * Requires:
+ * \li 'mctx' to be a valid memory context.
+ * \li 'options' to be non NULL and valid options.
+ * \li 'nopts' to be non NULL.
+ */
+
+void
+dns_catz_options_setdefault(isc_mem_t *mctx, const dns_catz_options_t *defaults,
+ dns_catz_options_t *opts);
+/*%<
+ * Replace empty values in 'opts' with values from 'defaults'
+ *
+ * Requires:
+ * \li 'mctx' to be a valid memory context.
+ * \li 'defaults' to be non NULL and valid options.
+ * \li 'opts' to be non NULL.
+ */
+
+dns_name_t *
+dns_catz_entry_getname(dns_catz_entry_t *entry);
+/*%<
+ * Get domain name for 'entry'
+ *
+ * Requires:
+ * \li 'entry' to be non NULL.
+ *
+ * Returns:
+ * \li domain name for entry.
+ */
+
+void
+dns_catz_entry_new(isc_mem_t *mctx, const dns_name_t *domain,
+ dns_catz_entry_t **nentryp);
+/*%<
+ * Allocate a new catz_entry on 'mctx', with the name 'domain'
+ *
+ * Requires:
+ * \li 'mctx' to be a valid memory context.
+ * \li 'domain' to be valid dns_name or NULL.
+ * \li 'nentryp' to be non NULL, *nentryp to be NULL.
+ *
+ * Returns:
+ * \li ISC_R_SUCCESS on success
+ * \li ISC_R_NOMEMORY on allocation failure
+ */
+
+void
+dns_catz_entry_copy(dns_catz_zone_t *catz, const dns_catz_entry_t *entry,
+ dns_catz_entry_t **nentryp);
+/*%<
+ * Allocate a new catz_entry and deep copy 'entry' into 'nentryp'.
+ *
+ * Requires:
+ * \li 'mctx' to be a valid memory context.
+ * \li 'entry' to be non NULL.
+ * \li 'nentryp' to be non NULL, *nentryp to be NULL.
+ *
+ * Returns:
+ * \li ISC_R_SUCCESS on success
+ * \li ISC_R_NOMEMORY on allocation failure
+ */
+
+void
+dns_catz_entry_attach(dns_catz_entry_t *entry, dns_catz_entry_t **entryp);
+/*%<
+ * Attach an entry
+ *
+ * Requires:
+ * \li 'entry' is a valid dns_catz_entry_t.
+ * \li 'entryp' is not NULL and '*entryp' is NULL.
+ */
+
+void
+dns_catz_entry_detach(dns_catz_zone_t *catz, dns_catz_entry_t **entryp);
+/*%<
+ * Detach an entry, free if no further references
+ *
+ * Requires:
+ * \li 'catz' is a valid dns_catz_zone_t.
+ * \li 'entryp' is not NULL and '*entryp' is not NULL.
+ */
+
+bool
+dns_catz_entry_validate(const dns_catz_entry_t *entry);
+/*%<
+ * Validate whether entry is correct.
+ * (NOT YET IMPLEMENTED: always returns true)
+ *
+ * Requires:
+ *\li 'entry' is a valid dns_catz_entry_t.
+ */
+
+bool
+dns_catz_entry_cmp(const dns_catz_entry_t *ea, const dns_catz_entry_t *eb);
+/*%<
+ * Deep compare two entries
+ *
+ * Requires:
+ * \li 'ea' is a valid dns_catz_entry_t.
+ * \li 'eb' is a valid dns_catz_entry_t.
+ *
+ * Returns:
+ * \li 'true' if entries are the same.
+ * \li 'false' if the entries differ.
+ */
+
+isc_result_t
+dns_catz_new_zone(dns_catz_zones_t *catzs, dns_catz_zone_t **catzp,
+ const dns_name_t *name);
+/*%<
+ * Allocate a new catz zone on catzs mctx
+ *
+ * Requires:
+ * \li 'catzs' is a valid dns_catz_zones_t.
+ * \li 'catzp' is not NULL and '*zonep' is NULL.
+ * \li 'name' is a valid dns_name_t.
+ *
+ */
+
+dns_name_t *
+dns_catz_zone_getname(dns_catz_zone_t *catz);
+/*%<
+ * Get catalog zone name
+ *
+ * Requires:
+ * \li 'catz' is a valid dns_catz_zone_t.
+ */
+
+dns_catz_options_t *
+dns_catz_zone_getdefoptions(dns_catz_zone_t *catz);
+/*%<
+ * Get default member zone options for catalog zone 'catz'
+ *
+ * Requires:
+ * \li 'catz' is a valid dns_catz_zone_t.
+ */
+
+void
+dns_catz_zone_resetdefoptions(dns_catz_zone_t *catz);
+/*%<
+ * Reset the default member zone options for catalog zone 'catz' to
+ * the default values.
+ *
+ * Requires:
+ * \li 'catz' is a valid dns_catz_zone_t.
+ */
+
+isc_result_t
+dns_catz_generate_masterfilename(dns_catz_zone_t *catz, dns_catz_entry_t *entry,
+ isc_buffer_t **buffer);
+/*%<
+ * Generate master file name and put it into *buffer (might be reallocated).
+ * The general format of the file name is:
+ * __catz__catalog.zone.name__member_zone_name.db
+ * But if it's too long it's shortened to:
+ * __catz__unique_hash_generated_from_the_above.db
+ *
+ * Requires:
+ * \li 'catz' is a valid dns_catz_zone_t.
+ * \li 'entry' is a valid dns_catz_entry_t.
+ * \li 'buffer' is not NULL and '*buffer' is not NULL.
+ */
+
+isc_result_t
+dns_catz_generate_zonecfg(dns_catz_zone_t *catz, dns_catz_entry_t *entry,
+ isc_buffer_t **buf);
+/*%<
+ * Generate a zone config entry (in text form) from dns_catz_entry and puts
+ * it into *buf. buf might be reallocated.
+ *
+ * Requires:
+ * \li 'catz' is a valid dns_catz_zone_t.
+ * \li 'entry' is a valid dns_catz_entry_t.
+ * \li 'buf' is not NULL and '*buf' is NULL.
+ *
+ */
+
+/* Methods provided by named to dynamically modify the member zones */
+/* xxxwpk TODO config! */
+typedef isc_result_t (*dns_catz_zoneop_fn_t)(dns_catz_entry_t *entry,
+ dns_catz_zone_t *origin,
+ dns_view_t *view,
+ isc_taskmgr_t *taskmgr,
+ void *udata);
+struct dns_catz_zonemodmethods {
+ dns_catz_zoneop_fn_t addzone;
+ dns_catz_zoneop_fn_t modzone;
+ dns_catz_zoneop_fn_t delzone;
+ void *udata;
+};
+
+isc_result_t
+dns_catz_new_zones(isc_mem_t *mctx, isc_taskmgr_t *taskmgr,
+ isc_timermgr_t *timermgr, dns_catz_zones_t **catzsp,
+ dns_catz_zonemodmethods_t *zmm);
+/*%<
+ * Allocate a new catz_zones object, a collection storing all catalog zones
+ * for a view.
+ *
+ * Requires:
+ * \li 'mctx' is not NULL.
+ * \li 'taskmgr' is not NULL.
+ * \li 'timermgr' is not NULL.
+ * \li 'catzsp' is not NULL and '*catzsp' is NULL.
+ * \li 'zmm' is not NULL.
+ *
+ */
+
+isc_result_t
+dns_catz_add_zone(dns_catz_zones_t *catzs, const dns_name_t *name,
+ dns_catz_zone_t **catzp);
+/*%<
+ * Allocate a new catz named 'name' and put it in 'catzs' collection.
+ *
+ * Requires:
+ * \li 'catzs' is a valid dns_catz_zones_t.
+ * \li 'name' is a valid dns_name_t.
+ * \li 'catzp' is not NULL and *catzp is NULL.
+ *
+ */
+
+dns_catz_zone_t *
+dns_catz_get_zone(dns_catz_zones_t *catzs, const dns_name_t *name);
+/*%<
+ * Returns a zone named 'name' from collection 'catzs'
+ *
+ * Requires:
+ * \li 'catzs' is a valid dns_catz_zones_t.
+ * \li 'name' is a valid dns_name_t.
+ */
+
+void
+dns_catz_catzs_set_view(dns_catz_zones_t *catzs, dns_view_t *view);
+/*%<
+ * Set a view for 'catzs'.
+ *
+ * Requires:
+ * \li 'catzs' is a valid dns_catz_zones_t.
+ * \li 'catzs->view' is NULL or 'catzs->view' == 'view'.
+ */
+
+isc_result_t
+dns_catz_dbupdate_callback(dns_db_t *db, void *fn_arg);
+/*%<
+ * Callback for update of catalog zone database.
+ * If there was no catalog zone update recently it launches an
+ * update_taskaction immediately.
+ * If there was an update recently it schedules update_taskaction for some time
+ * in the future.
+ * If there is an update scheduled it replaces old db version with a new one.
+ *
+ * Requires:
+ * \li 'db' is a valid database.
+ * \li 'fn_arg' is not NULL (casted to dns_catz_zones_t*).
+ */
+
+void
+dns_catz_dbupdate_unregister(dns_db_t *db, dns_catz_zones_t *catzs);
+/*%<
+ * Register the catalog zone database update notify callback.
+ *
+ * Requires:
+ * \li 'db' is a valid database.
+ * \li 'catzs' is valid.
+ */
+
+void
+dns_catz_dbupdate_register(dns_db_t *db, dns_catz_zones_t *catzs);
+/*%<
+ * Unregister the catalog zone database update notify callback.
+ *
+ * Requires:
+ * \li 'db' is a valid database.
+ * \li 'catzs' is valid.
+ */
+
+void
+dns_catz_prereconfig(dns_catz_zones_t *catzs);
+/*%<
+ * Called before reconfig, clears 'active' flag on all the zones in set
+ *
+ * Requires:
+ * \li 'catzs' is a valid dns_catz_zones_t.
+ *
+ */
+
+void
+dns_catz_postreconfig(dns_catz_zones_t *catzs);
+/*%<
+ * Called after reconfig, walks through all zones in set, removes those
+ * inactive and force reload of those with changed configuration.
+ *
+ * Requires:
+ * \li 'catzs' is a valid dns_catz_zones_t.
+ */
+
+void
+dns_catz_get_iterator(dns_catz_zone_t *catz, isc_ht_iter_t **itp);
+/*%<
+ * Get the hashtable iterator on catalog zone members, point '*itp' to it.
+ *
+ * Requires:
+ * \li 'catzs' is a valid dns_catz_zones_t.
+ * \li 'itp' is not NULL and '*itp' is NULL.
+ *
+ */
+
+void
+dns_catz_shutdown_catzs(dns_catz_zones_t *catzs);
+/*%<
+ * Shut down the catalog zones.
+ *
+ * Requires:
+ * \li 'catzs' is a valid dns_catz_zones_t.
+ *
+ */
+
+#ifdef DNS_CATZ_TRACE
+/* Compatibility macros */
+#define dns_catz_attach_catz(catz, catzp) \
+ dns_catz_zone__attach(catz, catzp, __func__, __FILE__, __LINE__)
+#define dns_catz_detach_catz(catzp) \
+ dns_catz_zone__detach(catzp, __func__, __FILE__, __LINE__)
+#define dns_catz_ref_catz(ptr) \
+ dns_catz_zone__ref(ptr, __func__, __FILE__, __LINE__)
+#define dns_catz_unref_catz(ptr) \
+ dns_catz_zone__unref(ptr, __func__, __FILE__, __LINE__)
+
+#define dns_catz_attach_catzs(catzs, catzsp) \
+ dns_catz_zones__attach(catzs, catzsp, __func__, __FILE__, __LINE__)
+#define dns_catz_detach_catzs(catzsp) \
+ dns_catz_zones__detach(catzsp, __func__, __FILE__, __LINE__)
+#define dns_catz_ref_catzs(ptr) \
+ dns_catz_zones__ref(ptr, __func__, __FILE__, __LINE__)
+#define dns_catz_unref_catzs(ptr) \
+ dns_catz_zones__unref(ptr, __func__, __FILE__, __LINE__)
+
+ISC_REFCOUNT_TRACE_DECL(dns_catz_zone);
+ISC_REFCOUNT_TRACE_DECL(dns_catz_zones);
+#else
+/* Compatibility macros */
+#define dns_catz_attach_catz(catz, catzp) dns_catz_zone_attach(catz, catzp)
+#define dns_catz_detach_catz(catzp) dns_catz_zone_detach(catzp)
+#define dns_catz_ref_catz(ptr) dns_catz_zone_ref(ptr)
+#define dns_catz_unref_catz(ptr) dns_catz_zone_unref(ptr)
+
+#define dns_catz_attach_catzs(catzs, catzsp) \
+ dns_catz_zones_attach(catzs, catzsp)
+#define dns_catz_detach_catzs(catzsp) dns_catz_zones_detach(catzsp)
+#define dns_catz_ref_catzs(ptr) dns_catz_zones_ref(ptr)
+#define dns_catz_unref_catzs(ptr) dns_catz_zones_unref(ptr)
+
+ISC_REFCOUNT_DECL(dns_catz_zone);
+ISC_REFCOUNT_DECL(dns_catz_zones);
+#endif /* DNS_CATZ_TRACE */
+
+ISC_LANG_ENDDECLS
diff --git a/lib/dns/include/dns/cert.h b/lib/dns/include/dns/cert.h
new file mode 100644
index 0000000..c1a0a50
--- /dev/null
+++ b/lib/dns/include/dns/cert.h
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#pragma once
+
+/*! \file dns/cert.h */
+
+#include <isc/lang.h>
+
+#include <dns/types.h>
+
+ISC_LANG_BEGINDECLS
+
+isc_result_t
+dns_cert_fromtext(dns_cert_t *certp, isc_textregion_t *source);
+/*%<
+ * Convert the text 'source' refers to into a certificate type.
+ * The text may contain either a mnemonic type name or a decimal type number.
+ *
+ * Requires:
+ *\li 'certp' is a valid pointer.
+ *
+ *\li 'source' is a valid text region.
+ *
+ * Returns:
+ *\li #ISC_R_SUCCESS on success
+ *\li #ISC_R_RANGE numeric type is out of range
+ *\li #DNS_R_UNKNOWN mnemonic type is unknown
+ */
+
+isc_result_t
+dns_cert_totext(dns_cert_t cert, isc_buffer_t *target);
+/*%<
+ * Put a textual representation of certificate type 'cert' into 'target'.
+ *
+ * Requires:
+ *\li 'cert' is a valid cert.
+ *
+ *\li 'target' is a valid text buffer.
+ *
+ * Ensures:
+ *\li If the result is success:
+ * The used space in 'target' is updated.
+ *
+ * Returns:
+ *\li #ISC_R_SUCCESS on success
+ *\li #ISC_R_NOSPACE target buffer is too small
+ */
+
+ISC_LANG_ENDDECLS
diff --git a/lib/dns/include/dns/client.h b/lib/dns/include/dns/client.h
new file mode 100644
index 0000000..ec70f92
--- /dev/null
+++ b/lib/dns/include/dns/client.h
@@ -0,0 +1,293 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#pragma once
+
+/*****
+***** Module Info
+*****/
+
+/*! \file
+ *
+ * \brief
+ * The DNS client module provides convenient programming interfaces to various
+ * DNS services, such as name resolution with or without DNSSEC validation or
+ * dynamic DNS update. This module is primarily expected to be used by other
+ * applications than BIND9-related ones that need such advanced DNS features.
+ *
+ * MP:
+ *\li In the typical usage of this module, application threads will not share
+ * the same data structures created and manipulated in this module.
+ * However, the module still ensures appropriate synchronization of such
+ * data structures.
+ *
+ * Resources:
+ *\li TBS
+ *
+ * Security:
+ *\li This module does not handle any low-level data directly, and so no
+ * security issue specific to this module is anticipated.
+ */
+
+#include <isc/event.h>
+#include <isc/sockaddr.h>
+
+#include <dns/tsig.h>
+#include <dns/types.h>
+
+#include <dst/dst.h>
+
+ISC_LANG_BEGINDECLS
+
+/***
+ *** Types
+ ***/
+
+/*%
+ * Optional flags for dns_client_(start)resolve.
+ */
+/*%< Do not return DNSSEC data (e.g. RRSIGS) with response. */
+#define DNS_CLIENTRESOPT_NODNSSEC 0x01
+/*%< Allow running external context. */
+#define DNS_CLIENTRESOPT_RESERVED 0x02
+/*%< Don't validate responses. */
+#define DNS_CLIENTRESOPT_NOVALIDATE 0x04
+/*%< Don't set the CD flag on upstream queries. */
+#define DNS_CLIENTRESOPT_NOCDFLAG 0x08
+/*%< Use TCP transport. */
+#define DNS_CLIENTRESOPT_TCP 0x10
+
+/*%
+ * View name used in dns_client.
+ */
+#define DNS_CLIENTVIEW_NAME "_dnsclient"
+
+/*%
+ * A dns_clientresevent_t is sent when name resolution performed by a client
+ * completes. 'result' stores the result code of the entire resolution
+ * procedure. 'vresult' specifically stores the result code of DNSSEC
+ * validation if it is performed. When name resolution successfully completes,
+ * 'answerlist' is typically non empty, containing answer names along with
+ * RRsets. It is the receiver's responsibility to free this list by calling
+ * dns_client_freeresanswer() before freeing the event structure.
+ */
+typedef struct dns_clientresevent {
+ ISC_EVENT_COMMON(struct dns_clientresevent);
+ isc_result_t result;
+ isc_result_t vresult;
+ dns_namelist_t answerlist;
+} dns_clientresevent_t; /* too long? */
+
+isc_result_t
+dns_client_create(isc_mem_t *mctx, isc_appctx_t *actx, isc_taskmgr_t *taskmgr,
+ isc_nm_t *nm, isc_timermgr_t *timermgr, unsigned int options,
+ dns_client_t **clientp, const isc_sockaddr_t *localaddr4,
+ const isc_sockaddr_t *localaddr6);
+/*%<
+ * Create a DNS client object with minimal internal resources, such as
+ * a default view for the IN class and IPv4/IPv6 dispatches for the view.
+ *
+ * dns_client_create() takes 'manager' arguments so that the caller can
+ * control the behavior of the client through the underlying event framework.
+ * 'localaddr4' and 'localaddr6' specify the local addresses to use for
+ * each address family; if both are set to NULL, then wildcard addresses
+ * will be used for both families. If only one is NULL, then the other
+ * address will be used as the local address, and the NULL protocol family
+ * will not be used.
+ *
+ * Requires:
+ *
+ *\li 'mctx' is a valid memory context.
+ *
+ *\li 'actx' is a valid application context.
+ *
+ *\li 'taskmgr' is a valid task manager.
+ *
+ *\li 'nm' is a valid network manager.
+ *
+ *\li 'timermgr' is a valid timer manager.
+ *
+ *\li clientp != NULL && *clientp == NULL.
+ *
+ * Returns:
+ *
+ *\li #ISC_R_SUCCESS On success.
+ *
+ *\li Anything else Failure.
+ */
+
+void
+dns_client_detach(dns_client_t **clientp);
+/*%<
+ * Detach 'client' and destroy it if there are no more references.
+ *
+ * Requires:
+ *
+ *\li '*clientp' is a valid client.
+ *
+ * Ensures:
+ *
+ *\li *clientp == NULL.
+ */
+
+isc_result_t
+dns_client_setservers(dns_client_t *client, dns_rdataclass_t rdclass,
+ const dns_name_t *name_space, isc_sockaddrlist_t *addrs);
+/*%<
+ * Specify a list of addresses of recursive name servers that the client will
+ * use for name resolution. A view for the 'rdclass' class must be created
+ * beforehand. If 'name_space' is non NULL, the specified server will be used
+ * if and only if the query name is a subdomain of 'name_space'. When servers
+ * for multiple 'name_space's are provided, and a query name is covered by
+ * more than one 'name_space', the servers for the best (longest) matching
+ * name_space will be used. If 'name_space' is NULL, it works as if
+ * dns_rootname (.) were specified.
+ *
+ * Requires:
+ *
+ *\li 'client' is a valid client.
+ *
+ *\li 'name_space' is NULL or a valid name.
+ *
+ *\li 'addrs' != NULL.
+ *
+ * Returns:
+ *
+ *\li #ISC_R_SUCCESS On success.
+ *
+ *\li Anything else Failure.
+ */
+
+isc_result_t
+dns_client_clearservers(dns_client_t *client, dns_rdataclass_t rdclass,
+ const dns_name_t *name_space);
+/*%<
+ * Remove configured recursive name servers for the 'rdclass' and 'name_space'
+ * from the client. See the description of dns_client_setservers() for
+ * the requirements about 'rdclass' and 'name_space'.
+ *
+ * Requires:
+ *
+ *\li 'client' is a valid client.
+ *
+ *\li 'name_space' is NULL or a valid name.
+ *
+ * Returns:
+ *
+ *\li #ISC_R_SUCCESS On success.
+ *
+ *\li Anything else Failure.
+ */
+
+isc_result_t
+dns_client_resolve(dns_client_t *client, const dns_name_t *name,
+ dns_rdataclass_t rdclass, dns_rdatatype_t type,
+ unsigned int options, dns_namelist_t *namelist);
+
+isc_result_t
+dns_client_startresolve(dns_client_t *client, const dns_name_t *name,
+ dns_rdataclass_t rdclass, dns_rdatatype_t type,
+ unsigned int options, isc_task_t *task,
+ isc_taskaction_t action, void *arg,
+ dns_clientrestrans_t **transp);
+/*%<
+ * Perform name resolution for 'name', 'rdclass', and 'type'.
+ *
+ * If any trusted keys are configured and the query name is considered to
+ * belong to a secure zone, these functions also validate the responses
+ * using DNSSEC by default. If the DNS_CLIENTRESOPT_NOVALIDATE flag is set
+ * in 'options', DNSSEC validation is disabled regardless of the configured
+ * trusted keys or the query name. With DNS_CLIENTRESOPT_NODNSSEC
+ * DNSSEC data is not returned with response. DNS_CLIENTRESOPT_NOCDFLAG
+ * disables the CD flag on queries, DNS_CLIENTRESOPT_TCP switches to
+ * the TCP (vs. UDP) transport.
+ *
+ * dns_client_resolve() provides a synchronous service. This function starts
+ * name resolution internally and blocks until it completes. On success,
+ * 'namelist' will contain a list of answer names, each of which has
+ * corresponding RRsets. The caller must provide a valid empty list, and
+ * is responsible for freeing the list content via dns_client_freeresanswer().
+ * If the name resolution fails due to an error in DNSSEC validation,
+ * dns_client_resolve() returns the result code indicating the validation
+ * error. Otherwise, it returns the result code of the entire resolution
+ * process, either success or failure.
+ *
+ * It is expected that the client object passed to dns_client_resolve() was
+ * created via dns_client_create() and has external managers and contexts.
+ *
+ * dns_client_startresolve() is an asynchronous version of dns_client_resolve()
+ * and does not block. When name resolution is completed, 'action' will be
+ * called with the argument of a 'dns_clientresevent_t' object, which contains
+ * the resulting list of answer names (on success). On return, '*transp' is
+ * set to an opaque transaction ID so that the caller can cancel this
+ * resolution process.
+ *
+ * Requires:
+ *
+ *\li 'client' is a valid client.
+ *
+ *\li 'addrs' != NULL.
+ *
+ *\li 'name' is a valid name.
+ *
+ *\li 'namelist' != NULL and is not empty.
+ *
+ *\li 'task' is a valid task.
+ *
+ *\li 'transp' != NULL && *transp == NULL;
+ *
+ * Returns:
+ *
+ *\li #ISC_R_SUCCESS On success.
+ *
+ *\li Anything else Failure.
+ */
+
+void
+dns_client_freeresanswer(dns_client_t *client, dns_namelist_t *namelist);
+/*%<
+ * Free resources allocated for the content of 'namelist'.
+ *
+ * Requires:
+ *
+ *\li 'client' is a valid client.
+ *
+ *\li 'namelist' != NULL.
+ */
+
+isc_result_t
+dns_client_addtrustedkey(dns_client_t *client, dns_rdataclass_t rdclass,
+ dns_rdatatype_t rdtype, const dns_name_t *keyname,
+ isc_buffer_t *keydatabuf);
+/*%<
+ * Add a DNSSEC trusted key for the 'rdclass' class. A view for the 'rdclass'
+ * class must be created beforehand. 'rdtype' is the type of the RR data
+ * for the key, either DNSKEY or DS. 'keyname' is the DNS name of the key,
+ * and 'keydatabuf' stores the RR data.
+ *
+ * Requires:
+ *
+ *\li 'client' is a valid client.
+ *
+ *\li 'keyname' is a valid name.
+ *
+ *\li 'keydatabuf' is a valid buffer.
+ *
+ * Returns:
+ *
+ *\li #ISC_R_SUCCESS On success.
+ *
+ *\li Anything else Failure.
+ */
+
+ISC_LANG_ENDDECLS
diff --git a/lib/dns/include/dns/clientinfo.h b/lib/dns/include/dns/clientinfo.h
new file mode 100644
index 0000000..b6dd601
--- /dev/null
+++ b/lib/dns/include/dns/clientinfo.h
@@ -0,0 +1,102 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#pragma once
+
+/*****
+***** Module Info
+*****/
+
+/*! \file dns/clientinfo.h
+ * \brief
+ * The DNS clientinfo interface allows libdns to retrieve information
+ * about the client from the caller.
+ *
+ * The clientinfo interface is used by the DNS DB and DLZ interfaces;
+ * it allows databases to modify their answers on the basis of information
+ * about the client, such as source IP address.
+ *
+ * dns_clientinfo_t contains a pointer to an opaque structure containing
+ * client information in some form. dns_clientinfomethods_t contains a
+ * list of methods which operate on that opaque structure to return
+ * potentially useful data. Both structures also contain versioning
+ * information.
+ */
+
+/*****
+***** Imports
+*****/
+
+#include <inttypes.h>
+
+#include <isc/sockaddr.h>
+#include <isc/types.h>
+
+#include <dns/ecs.h>
+
+ISC_LANG_BEGINDECLS
+
+/*****
+***** Types
+*****/
+
+#define DNS_CLIENTINFO_VERSION 3
+/*
+ * Any updates to this structure should also be applied in
+ * contrib/modules/dlz/dlz_minmal.h.
+ */
+typedef struct dns_clientinfo {
+ uint16_t version;
+ void *data;
+ void *dbversion;
+ dns_ecs_t ecs;
+} dns_clientinfo_t;
+
+typedef isc_result_t (*dns_clientinfo_sourceip_t)(dns_clientinfo_t *client,
+ isc_sockaddr_t **addrp);
+
+#define DNS_CLIENTINFOMETHODS_VERSION 2
+#define DNS_CLIENTINFOMETHODS_AGE 1
+
+/*
+ * Any updates to this structure should also be applied in
+ * contrib/modules/dlz/dlz_minmal.h.
+ */
+typedef struct dns_clientinfomethods {
+ uint16_t version;
+ uint16_t age;
+ dns_clientinfo_sourceip_t sourceip;
+} dns_clientinfomethods_t;
+
+/*****
+***** Methods
+*****/
+void
+dns_clientinfomethods_init(dns_clientinfomethods_t *methods,
+ dns_clientinfo_sourceip_t sourceip);
+
+void
+dns_clientinfo_init(dns_clientinfo_t *ci, void *data, void *versionp);
+/*%<
+ * Initialize a clientinfo object, setting the data to 'data' and the
+ * database version to 'versionp'. ECS data is initialized to 0/0/0.
+ */
+
+void
+dns_clientinfo_setecs(dns_clientinfo_t *ci, dns_ecs_t *ecs);
+/*%<
+ * Set the ECS client data associated with a clientinfo object 'ci'.
+ * If 'ecs' is NULL, initialize ci->ecs to 0/0/0; otherwise copy it.
+ */
+
+ISC_LANG_ENDDECLS
diff --git a/lib/dns/include/dns/compress.h b/lib/dns/include/dns/compress.h
new file mode 100644
index 0000000..5476dd9
--- /dev/null
+++ b/lib/dns/include/dns/compress.h
@@ -0,0 +1,299 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#pragma once
+
+#include <inttypes.h>
+#include <stdbool.h>
+
+#include <isc/lang.h>
+#include <isc/region.h>
+
+#include <dns/name.h>
+#include <dns/types.h>
+
+ISC_LANG_BEGINDECLS
+
+/*! \file dns/compress.h
+ * Direct manipulation of the structures is strongly discouraged.
+ *
+ * A name compression context handles compression of multiple DNS names
+ * in relation to a single DNS message. The context can be used to
+ * selectively turn on/off compression for specific names (depending on
+ * the RR type) by using \c dns_compress_setmethods(). Alternately,
+ * compression can be disabled completely using \c
+ * dns_compress_disable().
+ *
+ * \c dns_compress_setmethods() is intended for use by RDATA towire()
+ * implementations, whereas \c dns_compress_disable() is intended to be
+ * used by a nameserver's configuration manager.
+ */
+
+#define DNS_COMPRESS_NONE 0x00 /*%< no compression */
+#define DNS_COMPRESS_GLOBAL14 0x01 /*%< "normal" compression. */
+#define DNS_COMPRESS_ALL 0x01 /*%< all compression. */
+#define DNS_COMPRESS_CASESENSITIVE 0x02 /*%< case sensitive compression. */
+#define DNS_COMPRESS_ENABLED 0x04
+
+/*
+ * DNS_COMPRESS_TABLESIZE must be a power of 2. The compress code
+ * utilizes this assumption.
+ */
+#define DNS_COMPRESS_TABLEBITS 6
+#define DNS_COMPRESS_TABLESIZE (1U << DNS_COMPRESS_TABLEBITS)
+#define DNS_COMPRESS_TABLEMASK (DNS_COMPRESS_TABLESIZE - 1)
+#define DNS_COMPRESS_INITIALNODES 24
+#define DNS_COMPRESS_ARENA_SIZE 640
+
+typedef struct dns_compressnode dns_compressnode_t;
+
+struct dns_compressnode {
+ dns_compressnode_t *next;
+ uint16_t offset;
+ uint16_t count;
+ isc_region_t r;
+ dns_name_t name;
+};
+
+struct dns_compress {
+ unsigned int magic; /*%< Magic number. */
+ unsigned int allowed; /*%< Allowed methods. */
+ int edns; /*%< Edns version or -1. */
+ /*% Global compression table. */
+ dns_compressnode_t *table[DNS_COMPRESS_TABLESIZE];
+ /*% Preallocated arena for names. */
+ unsigned char arena[DNS_COMPRESS_ARENA_SIZE];
+ off_t arena_off;
+ /*% Preallocated nodes for the table. */
+ dns_compressnode_t initialnodes[DNS_COMPRESS_INITIALNODES];
+ uint16_t count; /*%< Number of nodes. */
+ isc_mem_t *mctx; /*%< Memory context. */
+};
+
+typedef enum {
+ DNS_DECOMPRESS_ANY, /*%< Any compression */
+ DNS_DECOMPRESS_STRICT, /*%< Allowed compression */
+ DNS_DECOMPRESS_NONE /*%< No compression */
+} dns_decompresstype_t;
+
+struct dns_decompress {
+ unsigned int magic; /*%< Magic number. */
+ unsigned int allowed; /*%< Allowed methods. */
+ int edns; /*%< Edns version or -1. */
+ dns_decompresstype_t type; /*%< Strict checking */
+};
+
+isc_result_t
+dns_compress_init(dns_compress_t *cctx, int edns, isc_mem_t *mctx);
+/*%<
+ * Initialise the compression context structure pointed to by
+ * 'cctx'. A freshly initialized context has name compression
+ * enabled, but no methods are set. Please use \c
+ * dns_compress_setmethods() to set a compression method.
+ *
+ * Requires:
+ * \li 'cctx' is a valid dns_compress_t structure.
+ * \li 'mctx' is an initialized memory context.
+ * Ensures:
+ * \li cctx->global is initialized.
+ *
+ * Returns:
+ * \li #ISC_R_SUCCESS
+ */
+
+void
+dns_compress_invalidate(dns_compress_t *cctx);
+
+/*%<
+ * Invalidate the compression structure pointed to by cctx.
+ *
+ * Requires:
+ *\li 'cctx' to be initialized.
+ */
+
+void
+dns_compress_setmethods(dns_compress_t *cctx, unsigned int allowed);
+
+/*%<
+ * Sets allowed compression methods.
+ *
+ * Requires:
+ *\li 'cctx' to be initialized.
+ */
+
+unsigned int
+dns_compress_getmethods(dns_compress_t *cctx);
+
+/*%<
+ * Gets allowed compression methods.
+ *
+ * Requires:
+ *\li 'cctx' to be initialized.
+ *
+ * Returns:
+ *\li allowed compression bitmap.
+ */
+
+void
+dns_compress_disable(dns_compress_t *cctx);
+/*%<
+ * Disables all name compression in the context. Once disabled,
+ * name compression cannot currently be re-enabled.
+ *
+ * Requires:
+ *\li 'cctx' to be initialized.
+ *
+ */
+
+void
+dns_compress_setsensitive(dns_compress_t *cctx, bool sensitive);
+
+/*
+ * Preserve the case of compressed domain names.
+ *
+ * Requires:
+ * 'cctx' to be initialized.
+ */
+
+bool
+dns_compress_getsensitive(dns_compress_t *cctx);
+/*
+ * Return whether case is to be preserved when compressing
+ * domain names.
+ *
+ * Requires:
+ * 'cctx' to be initialized.
+ */
+
+int
+dns_compress_getedns(dns_compress_t *cctx);
+
+/*%<
+ * Gets edns value.
+ *
+ * Requires:
+ *\li 'cctx' to be initialized.
+ *
+ * Returns:
+ *\li -1 .. 255
+ */
+
+bool
+dns_compress_findglobal(dns_compress_t *cctx, const dns_name_t *name,
+ dns_name_t *prefix, uint16_t *offset);
+/*%<
+ * Finds longest possible match of 'name' in the global compression table.
+ *
+ * Requires:
+ *\li 'cctx' to be initialized.
+ *\li 'name' to be a absolute name.
+ *\li 'prefix' to be initialized.
+ *\li 'offset' to point to an uint16_t.
+ *
+ * Ensures:
+ *\li 'prefix' and 'offset' are valid if true is returned.
+ *
+ * Returns:
+ *\li #true / #false
+ */
+
+void
+dns_compress_add(dns_compress_t *cctx, const dns_name_t *name,
+ const dns_name_t *prefix, uint16_t offset);
+/*%<
+ * Add compression pointers for 'name' to the compression table,
+ * not replacing existing pointers.
+ *
+ * Requires:
+ *\li 'cctx' initialized
+ *
+ *\li 'name' must be initialized and absolute, and must remain
+ * valid until the message compression is complete.
+ *
+ *\li 'prefix' must be a prefix returned by
+ * dns_compress_findglobal(), or the same as 'name'.
+ */
+
+void
+dns_compress_rollback(dns_compress_t *cctx, uint16_t offset);
+
+/*%<
+ * Remove any compression pointers from global table >= offset.
+ *
+ * Requires:
+ *\li 'cctx' is initialized.
+ */
+
+void
+dns_decompress_init(dns_decompress_t *dctx, int edns,
+ dns_decompresstype_t type);
+
+/*%<
+ * Initializes 'dctx'.
+ * Records 'edns' and 'type' into the structure.
+ *
+ * Requires:
+ *\li 'dctx' to be a valid pointer.
+ */
+
+void
+dns_decompress_invalidate(dns_decompress_t *dctx);
+
+/*%<
+ * Invalidates 'dctx'.
+ *
+ * Requires:
+ *\li 'dctx' to be initialized
+ */
+
+void
+dns_decompress_setmethods(dns_decompress_t *dctx, unsigned int allowed);
+
+/*%<
+ * Sets 'dctx->allowed' to 'allowed'.
+ *
+ * Requires:
+ *\li 'dctx' to be initialized
+ */
+
+unsigned int
+dns_decompress_getmethods(dns_decompress_t *dctx);
+
+/*%<
+ * Returns 'dctx->allowed'
+ *
+ * Requires:
+ *\li 'dctx' to be initialized
+ */
+
+int
+dns_decompress_edns(dns_decompress_t *dctx);
+
+/*%<
+ * Returns 'dctx->edns'
+ *
+ * Requires:
+ *\li 'dctx' to be initialized
+ */
+
+dns_decompresstype_t
+dns_decompress_type(dns_decompress_t *dctx);
+
+/*%<
+ * Returns 'dctx->type'
+ *
+ * Requires:
+ *\li 'dctx' to be initialized
+ */
+
+ISC_LANG_ENDDECLS
diff --git a/lib/dns/include/dns/db.h b/lib/dns/include/dns/db.h
new file mode 100644
index 0000000..9b53f04
--- /dev/null
+++ b/lib/dns/include/dns/db.h
@@ -0,0 +1,1759 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#pragma once
+
+/*****
+***** Module Info
+*****/
+
+/*! \file dns/db.h
+ * \brief
+ * The DNS DB interface allows named rdatasets to be stored and retrieved.
+ *
+ * The dns_db_t type is like a "virtual class". To actually use
+ * DBs, an implementation of the class is required.
+ *
+ * XXX more XXX
+ *
+ * MP:
+ * \li The module ensures appropriate synchronization of data structures it
+ * creates and manipulates.
+ *
+ * Reliability:
+ * \li No anticipated impact.
+ *
+ * Resources:
+ * \li TBS
+ *
+ * Security:
+ * \li No anticipated impact.
+ *
+ * Standards:
+ * \li None.
+ */
+
+/*****
+***** Imports
+*****/
+
+#include <inttypes.h>
+#include <stdbool.h>
+
+#include <isc/deprecated.h>
+#include <isc/lang.h>
+#include <isc/magic.h>
+#include <isc/stats.h>
+#include <isc/stdtime.h>
+
+#include <dns/clientinfo.h>
+#include <dns/fixedname.h>
+#include <dns/name.h>
+#include <dns/rdata.h>
+#include <dns/rdataset.h>
+#include <dns/types.h>
+
+ISC_LANG_BEGINDECLS
+
+/*%
+ * Tuning: external query load in packets per seconds.
+ */
+extern unsigned int dns_pps;
+
+/*****
+***** Types
+*****/
+
+typedef struct dns_dbmethods {
+ void (*attach)(dns_db_t *source, dns_db_t **targetp);
+ void (*detach)(dns_db_t **dbp);
+ isc_result_t (*beginload)(dns_db_t *db,
+ dns_rdatacallbacks_t *callbacks);
+ isc_result_t (*endload)(dns_db_t *db, dns_rdatacallbacks_t *callbacks);
+ isc_result_t (*dump)(dns_db_t *db, dns_dbversion_t *version,
+ const char *filename,
+ dns_masterformat_t masterformat);
+ void (*currentversion)(dns_db_t *db, dns_dbversion_t **versionp);
+ isc_result_t (*newversion)(dns_db_t *db, dns_dbversion_t **versionp);
+ void (*attachversion)(dns_db_t *db, dns_dbversion_t *source,
+ dns_dbversion_t **targetp);
+ void (*closeversion)(dns_db_t *db, dns_dbversion_t **versionp,
+ bool commit);
+ isc_result_t (*findnode)(dns_db_t *db, const dns_name_t *name,
+ bool create, dns_dbnode_t **nodep);
+ isc_result_t (*find)(dns_db_t *db, const dns_name_t *name,
+ dns_dbversion_t *version, dns_rdatatype_t type,
+ unsigned int options, isc_stdtime_t now,
+ dns_dbnode_t **nodep, dns_name_t *foundname,
+ dns_rdataset_t *rdataset,
+ dns_rdataset_t *sigrdataset);
+ isc_result_t (*findzonecut)(dns_db_t *db, const dns_name_t *name,
+ unsigned int options, isc_stdtime_t now,
+ dns_dbnode_t **nodep, dns_name_t *foundname,
+ dns_name_t *dcname,
+ dns_rdataset_t *rdataset,
+ dns_rdataset_t *sigrdataset);
+ void (*attachnode)(dns_db_t *db, dns_dbnode_t *source,
+ dns_dbnode_t **targetp);
+ void (*detachnode)(dns_db_t *db, dns_dbnode_t **targetp);
+ isc_result_t (*expirenode)(dns_db_t *db, dns_dbnode_t *node,
+ isc_stdtime_t now);
+ void (*printnode)(dns_db_t *db, dns_dbnode_t *node, FILE *out);
+ isc_result_t (*createiterator)(dns_db_t *db, unsigned int options,
+ dns_dbiterator_t **iteratorp);
+ isc_result_t (*findrdataset)(dns_db_t *db, dns_dbnode_t *node,
+ dns_dbversion_t *version,
+ dns_rdatatype_t type,
+ dns_rdatatype_t covers, isc_stdtime_t now,
+ dns_rdataset_t *rdataset,
+ dns_rdataset_t *sigrdataset);
+ isc_result_t (*allrdatasets)(dns_db_t *db, dns_dbnode_t *node,
+ dns_dbversion_t *version,
+ unsigned int options, isc_stdtime_t now,
+ dns_rdatasetiter_t **iteratorp);
+ isc_result_t (*addrdataset)(dns_db_t *db, dns_dbnode_t *node,
+ dns_dbversion_t *version, isc_stdtime_t now,
+ dns_rdataset_t *rdataset,
+ unsigned int options,
+ dns_rdataset_t *addedrdataset);
+ isc_result_t (*subtractrdataset)(dns_db_t *db, dns_dbnode_t *node,
+ dns_dbversion_t *version,
+ dns_rdataset_t *rdataset,
+ unsigned int options,
+ dns_rdataset_t *newrdataset);
+ isc_result_t (*deleterdataset)(dns_db_t *db, dns_dbnode_t *node,
+ dns_dbversion_t *version,
+ dns_rdatatype_t type,
+ dns_rdatatype_t covers);
+ bool (*issecure)(dns_db_t *db);
+ unsigned int (*nodecount)(dns_db_t *db, dns_dbtree_t);
+ bool (*ispersistent)(dns_db_t *db);
+ void (*overmem)(dns_db_t *db, bool overmem);
+ void (*settask)(dns_db_t *db, isc_task_t *);
+ isc_result_t (*getoriginnode)(dns_db_t *db, dns_dbnode_t **nodep);
+ void (*transfernode)(dns_db_t *db, dns_dbnode_t **sourcep,
+ dns_dbnode_t **targetp);
+ isc_result_t (*getnsec3parameters)(dns_db_t *db,
+ dns_dbversion_t *version,
+ dns_hash_t *hash, uint8_t *flags,
+ uint16_t *iterations,
+ unsigned char *salt,
+ size_t *salt_len);
+ isc_result_t (*findnsec3node)(dns_db_t *db, const dns_name_t *name,
+ bool create, dns_dbnode_t **nodep);
+ isc_result_t (*setsigningtime)(dns_db_t *db, dns_rdataset_t *rdataset,
+ isc_stdtime_t resign);
+ isc_result_t (*getsigningtime)(dns_db_t *db, dns_rdataset_t *rdataset,
+ dns_name_t *name);
+ void (*resigned)(dns_db_t *db, dns_rdataset_t *rdataset,
+ dns_dbversion_t *version);
+ bool (*isdnssec)(dns_db_t *db);
+ dns_stats_t *(*getrrsetstats)(dns_db_t *db);
+ void (*rpz_attach)(dns_db_t *db, void *rpzs, uint8_t rpz_num);
+ isc_result_t (*rpz_ready)(dns_db_t *db);
+ isc_result_t (*findnodeext)(dns_db_t *db, const dns_name_t *name,
+ bool create,
+ dns_clientinfomethods_t *methods,
+ dns_clientinfo_t *clientinfo,
+ dns_dbnode_t **nodep);
+ isc_result_t (*findext)(dns_db_t *db, const dns_name_t *name,
+ dns_dbversion_t *version, dns_rdatatype_t type,
+ unsigned int options, isc_stdtime_t now,
+ dns_dbnode_t **nodep, dns_name_t *foundname,
+ dns_clientinfomethods_t *methods,
+ dns_clientinfo_t *clientinfo,
+ dns_rdataset_t *rdataset,
+ dns_rdataset_t *sigrdataset);
+ isc_result_t (*setcachestats)(dns_db_t *db, isc_stats_t *stats);
+ size_t (*hashsize)(dns_db_t *db);
+ isc_result_t (*nodefullname)(dns_db_t *db, dns_dbnode_t *node,
+ dns_name_t *name);
+ isc_result_t (*getsize)(dns_db_t *db, dns_dbversion_t *version,
+ uint64_t *records, uint64_t *bytes);
+ isc_result_t (*setservestalettl)(dns_db_t *db, dns_ttl_t ttl);
+ isc_result_t (*getservestalettl)(dns_db_t *db, dns_ttl_t *ttl);
+ isc_result_t (*setservestalerefresh)(dns_db_t *db, uint32_t interval);
+ isc_result_t (*getservestalerefresh)(dns_db_t *db, uint32_t *interval);
+ isc_result_t (*setgluecachestats)(dns_db_t *db, isc_stats_t *stats);
+} dns_dbmethods_t;
+
+typedef isc_result_t (*dns_dbcreatefunc_t)(isc_mem_t *mctx,
+ const dns_name_t *name,
+ dns_dbtype_t type,
+ dns_rdataclass_t rdclass,
+ unsigned int argc, char *argv[],
+ void *driverarg, dns_db_t **dbp);
+
+typedef isc_result_t (*dns_dbupdate_callback_t)(dns_db_t *db, void *fn_arg);
+
+#define DNS_DB_MAGIC ISC_MAGIC('D', 'N', 'S', 'D')
+#define DNS_DB_VALID(db) ISC_MAGIC_VALID(db, DNS_DB_MAGIC)
+
+/*%
+ * This structure is actually just the common prefix of a DNS db
+ * implementation's version of a dns_db_t.
+ * \brief
+ * Direct use of this structure by clients is forbidden. DB implementations
+ * may change the structure. 'magic' must be DNS_DB_MAGIC for any of the
+ * dns_db_ routines to work. DB implementations must maintain all DB
+ * invariants.
+ */
+struct dns_db {
+ unsigned int magic;
+ unsigned int impmagic;
+ dns_dbmethods_t *methods;
+ uint16_t attributes;
+ dns_rdataclass_t rdclass;
+ dns_name_t origin;
+ isc_mem_t *mctx;
+ ISC_LIST(dns_dbonupdatelistener_t) update_listeners;
+};
+
+#define DNS_DBATTR_CACHE 0x01
+#define DNS_DBATTR_STUB 0x02
+
+struct dns_dbonupdatelistener {
+ dns_dbupdate_callback_t onupdate;
+ void *onupdate_arg;
+ ISC_LINK(dns_dbonupdatelistener_t) link;
+};
+
+/*@{*/
+/*%
+ * Options that can be specified for dns_db_find().
+ */
+#define DNS_DBFIND_GLUEOK 0x0001
+#define DNS_DBFIND_VALIDATEGLUE 0x0002
+#define DNS_DBFIND_NOWILD 0x0004
+#define DNS_DBFIND_PENDINGOK 0x0008
+#define DNS_DBFIND_NOEXACT 0x0010
+#define DNS_DBFIND_FORCENSEC 0x0020
+#define DNS_DBFIND_COVERINGNSEC 0x0040
+#define DNS_DBFIND_FORCENSEC3 0x0080
+#define DNS_DBFIND_ADDITIONALOK 0x0100
+#define DNS_DBFIND_NOZONECUT 0x0200
+
+/*
+ * DNS_DBFIND_STALEOK: This flag is set when BIND fails to refresh a RRset due
+ * to timeout (resolver-query-timeout). Its intent is to try to look for stale
+ * data in cache as a fallback, but only if stale answers are enabled in
+ * configuration.
+ */
+#define DNS_DBFIND_STALEOK 0x0400
+
+/*
+ * DNS_DBFIND_STALEENABLED: This flag is used as a hint to the database that
+ * it may use stale data. It is always set during query lookup if stale
+ * answers are enabled, but only effectively used during stale-refresh-time
+ * window. Also during this window, the resolver will not try to resolve the
+ * query, in other words no attempt to refresh the data in cache is made when
+ * the stale-refresh-time window is active.
+ */
+#define DNS_DBFIND_STALEENABLED 0x0800
+
+/*
+ * DNS_DBFIND_STALETIMEOUT: This flag is used when we want stale data from the
+ * database, but not due to a failure in resolution, it also doesn't require
+ * stale-refresh-time window timer to be active. As long as there is stale
+ * data available, it should be returned.
+ */
+#define DNS_DBFIND_STALETIMEOUT 0x1000
+
+/*
+ * DNS_DBFIND_STALESTART: This flag is used to activate stale-refresh-time
+ * window.
+ */
+#define DNS_DBFIND_STALESTART 0x2000
+/*@}*/
+
+/*@{*/
+/*%
+ * Options that can be specified for dns_db_addrdataset().
+ */
+#define DNS_DBADD_MERGE 0x01
+#define DNS_DBADD_FORCE 0x02
+#define DNS_DBADD_EXACT 0x04
+#define DNS_DBADD_EXACTTTL 0x08
+#define DNS_DBADD_PREFETCH 0x10
+/*@}*/
+
+/*%
+ * Options that can be specified for dns_db_subtractrdataset().
+ */
+#define DNS_DBSUB_EXACT 0x01
+#define DNS_DBSUB_WANTOLD 0x02
+
+/*@{*/
+/*%
+ * Iterator options
+ */
+#define DNS_DB_RELATIVENAMES 0x1
+#define DNS_DB_NSEC3ONLY 0x2
+#define DNS_DB_NONSEC3 0x4
+/*@}*/
+
+#define DNS_DB_STALEOK 0x01
+#define DNS_DB_EXPIREDOK 0x02
+
+/*****
+***** Methods
+*****/
+
+/***
+ *** Basic DB Methods
+ ***/
+
+isc_result_t
+dns_db_create(isc_mem_t *mctx, const char *db_type, const dns_name_t *origin,
+ dns_dbtype_t type, dns_rdataclass_t rdclass, unsigned int argc,
+ char *argv[], dns_db_t **dbp);
+/*%<
+ * Create a new database using implementation 'db_type'.
+ *
+ * Notes:
+ * \li All names in the database must be subdomains of 'origin' and in class
+ * 'rdclass'. The database makes its own copy of the origin, so the
+ * caller may do whatever they like with 'origin' and its storage once the
+ * call returns.
+ *
+ * \li DB implementation-specific parameters are passed using argc and argv.
+ *
+ * Requires:
+ *
+ * \li dbp != NULL and *dbp == NULL
+ *
+ * \li 'origin' is a valid absolute domain name.
+ *
+ * \li mctx is a valid memory context
+ *
+ * Ensures:
+ *
+ * \li A copy of 'origin' has been made for the databases use, and the
+ * caller is free to do whatever they want with the name and storage
+ * associated with 'origin'.
+ *
+ * Returns:
+ *
+ * \li #ISC_R_SUCCESS
+ * \li #ISC_R_NOMEMORY
+ * \li #ISC_R_NOTFOUND db_type not found
+ *
+ * \li Many other errors are possible, depending on what db_type was
+ * specified.
+ */
+
+void
+dns_db_attach(dns_db_t *source, dns_db_t **targetp);
+/*%<
+ * Attach *targetp to source.
+ *
+ * Requires:
+ *
+ * \li 'source' is a valid database.
+ *
+ * \li 'targetp' points to a NULL dns_db_t *.
+ *
+ * Ensures:
+ *
+ * \li *targetp is attached to source.
+ */
+
+void
+dns_db_detach(dns_db_t **dbp);
+/*%<
+ * Detach *dbp from its database.
+ *
+ * Requires:
+ *
+ * \li 'dbp' points to a valid database.
+ *
+ * Ensures:
+ *
+ * \li *dbp is NULL.
+ *
+ * \li If '*dbp' is the last reference to the database,
+ * all resources used by the database will be freed
+ */
+
+bool
+dns_db_iscache(dns_db_t *db);
+/*%<
+ * Does 'db' have cache semantics?
+ *
+ * Requires:
+ *
+ * \li 'db' is a valid database.
+ *
+ * Returns:
+ * \li #true 'db' has cache semantics
+ * \li #false otherwise
+ */
+
+bool
+dns_db_iszone(dns_db_t *db);
+/*%<
+ * Does 'db' have zone semantics?
+ *
+ * Requires:
+ *
+ * \li 'db' is a valid database.
+ *
+ * Returns:
+ * \li #true 'db' has zone semantics
+ * \li #false otherwise
+ */
+
+bool
+dns_db_isstub(dns_db_t *db);
+/*%<
+ * Does 'db' have stub semantics?
+ *
+ * Requires:
+ *
+ * \li 'db' is a valid database.
+ *
+ * Returns:
+ * \li #true 'db' has zone semantics
+ * \li #false otherwise
+ */
+
+bool
+dns_db_issecure(dns_db_t *db);
+/*%<
+ * Is 'db' secure?
+ *
+ * Requires:
+ *
+ * \li 'db' is a valid database with zone semantics.
+ *
+ * Returns:
+ * \li #true 'db' is secure.
+ * \li #false 'db' is not secure.
+ */
+
+bool
+dns_db_isdnssec(dns_db_t *db);
+/*%<
+ * Is 'db' secure or partially secure?
+ *
+ * Requires:
+ *
+ * \li 'db' is a valid database with zone semantics.
+ *
+ * Returns:
+ * \li #true 'db' is secure or is partially.
+ * \li #false 'db' is not secure.
+ */
+
+dns_name_t *
+dns_db_origin(dns_db_t *db);
+/*%<
+ * The origin of the database.
+ *
+ * Note: caller must not try to change this name.
+ *
+ * Requires:
+ *
+ * \li 'db' is a valid database.
+ *
+ * Returns:
+ *
+ * \li The origin of the database.
+ */
+
+dns_rdataclass_t
+dns_db_class(dns_db_t *db);
+/*%<
+ * The class of the database.
+ *
+ * Requires:
+ *
+ * \li 'db' is a valid database.
+ *
+ * Returns:
+ *
+ * \li The class of the database.
+ */
+
+isc_result_t
+dns_db_beginload(dns_db_t *db, dns_rdatacallbacks_t *callbacks);
+/*%<
+ * Begin loading 'db'.
+ *
+ * Requires:
+ *
+ * \li 'db' is a valid database.
+ *
+ * \li This is the first attempt to load 'db'.
+ *
+ * \li 'callbacks' is a pointer to an initialized dns_rdatacallbacks_t
+ * structure.
+ *
+ * Ensures:
+ *
+ * \li On success, callbacks->add will be a valid dns_addrdatasetfunc_t
+ * suitable for loading records into 'db' from a raw or text zone
+ * file. callbacks->add_private will be a valid DB load context
+ * which should be used as 'arg' when callbacks->add is called.
+ *
+ * Returns:
+ *
+ * \li #ISC_R_SUCCESS
+ * \li #ISC_R_NOMEMORY
+ *
+ * \li Other results are possible, depending upon the database
+ * implementation used, syntax errors in the master file, etc.
+ */
+
+isc_result_t
+dns_db_endload(dns_db_t *db, dns_rdatacallbacks_t *callbacks);
+/*%<
+ * Finish loading 'db'.
+ *
+ * Requires:
+ *
+ * \li 'db' is a valid database that is being loaded.
+ *
+ * \li 'callbacks' is a valid dns_rdatacallbacks_t structure.
+ *
+ * \li callbacks->add_private is not NULL and is a valid database load context.
+ *
+ * Ensures:
+ *
+ * \li 'callbacks' is returned to its state prior to calling dns_db_beginload()
+ *
+ * Returns:
+ *
+ * \li #ISC_R_SUCCESS
+ * \li #ISC_R_NOMEMORY
+ *
+ * \li Other results are possible, depending upon the database
+ * implementation used, syntax errors in the master file, etc.
+ */
+
+isc_result_t
+dns_db_load(dns_db_t *db, const char *filename, dns_masterformat_t format,
+ unsigned int options);
+/*%<
+ * Load master file 'filename' into 'db'.
+ *
+ * Notes:
+ * \li This routine is equivalent to calling
+ *
+ *\code
+ * dns_db_beginload();
+ * dns_master_loadfile();
+ * dns_db_endload();
+ *\endcode
+ *
+ * Requires:
+ *
+ * \li 'db' is a valid database.
+ *
+ * \li This is the first attempt to load 'db'.
+ *
+ * Returns:
+ *
+ * \li #ISC_R_SUCCESS
+ * \li #ISC_R_NOMEMORY
+ *
+ * \li Other results are possible, depending upon the database
+ * implementation used, syntax errors in the master file, etc.
+ */
+
+isc_result_t
+dns_db_dump(dns_db_t *db, dns_dbversion_t *version, const char *filename);
+/*%<
+ * Dump version 'version' of 'db' to master file 'filename'.
+ *
+ * Requires:
+ *
+ * \li 'db' is a valid database.
+ *
+ * \li 'version' is a valid version.
+ *
+ * Returns:
+ *
+ * \li #ISC_R_SUCCESS
+ * \li #ISC_R_NOMEMORY
+ *
+ * \li Other results are possible, depending upon the database
+ * implementation used, OS file errors, etc.
+ */
+
+/***
+ *** Version Methods
+ ***/
+
+void
+dns_db_currentversion(dns_db_t *db, dns_dbversion_t **versionp);
+/*%<
+ * Open the current version for reading.
+ *
+ * Requires:
+ *
+ * \li 'db' is a valid database with zone semantics.
+ *
+ * \li versionp != NULL && *verisonp == NULL
+ *
+ * Ensures:
+ *
+ * \li On success, '*versionp' is attached to the current version.
+ *
+ */
+
+isc_result_t
+dns_db_newversion(dns_db_t *db, dns_dbversion_t **versionp);
+/*%<
+ * Open a new version for reading and writing.
+ *
+ * Requires:
+ *
+ * \li 'db' is a valid database with zone semantics.
+ *
+ * \li versionp != NULL && *verisonp == NULL
+ *
+ * Ensures:
+ *
+ * \li On success, '*versionp' is attached to the current version.
+ *
+ * Returns:
+ *
+ * \li #ISC_R_SUCCESS
+ * \li #ISC_R_NOMEMORY
+ *
+ * \li Other results are possible, depending upon the database
+ * implementation used.
+ */
+
+void
+dns_db_attachversion(dns_db_t *db, dns_dbversion_t *source,
+ dns_dbversion_t **targetp);
+/*%<
+ * Attach '*targetp' to 'source'.
+ *
+ * Requires:
+ *
+ * \li 'db' is a valid database with zone semantics.
+ *
+ * \li source is a valid open version
+ *
+ * \li targetp != NULL && *targetp == NULL
+ *
+ * Ensures:
+ *
+ * \li '*targetp' is attached to source.
+ */
+
+void
+dns_db_closeversion(dns_db_t *db, dns_dbversion_t **versionp, bool commit);
+/*%<
+ * Close version '*versionp'.
+ *
+ * Note: if '*versionp' is a read-write version and 'commit' is true,
+ * then all changes made in the version will take effect, otherwise they
+ * will be rolled back. The value of 'commit' is ignored for read-only
+ * versions.
+ *
+ * Requires:
+ *
+ * \li 'db' is a valid database with zone semantics.
+ *
+ * \li '*versionp' refers to a valid version.
+ *
+ * \li If committing a writable version, then there must be no other
+ * outstanding references to the version (e.g. an active rdataset
+ * iterator).
+ *
+ * Ensures:
+ *
+ * \li *versionp == NULL
+ *
+ * \li If *versionp is a read-write version, and commit is true, then
+ * the version will become the current version. If !commit, then all
+ * changes made in the version will be undone, and the version will
+ * not become the current version.
+ */
+
+/***
+ *** Node Methods
+ ***/
+
+isc_result_t
+dns_db_findnode(dns_db_t *db, const dns_name_t *name, bool create,
+ dns_dbnode_t **nodep);
+
+isc_result_t
+dns_db_findnodeext(dns_db_t *db, const dns_name_t *name, bool create,
+ dns_clientinfomethods_t *methods,
+ dns_clientinfo_t *clientinfo, dns_dbnode_t **nodep);
+/*%<
+ * Find the node with name 'name'.
+ *
+ * dns_db_findnodeext() (findnode extended) also accepts parameters
+ * 'methods' and 'clientinfo', which, when provided, enable the database to
+ * retrieve information about the client from the caller, and modify its
+ * response on the basis of that information.
+ *
+ * Notes:
+ * \li If 'create' is true and no node with name 'name' exists, then
+ * such a node will be created.
+ *
+ * \li This routine is for finding or creating a node with the specified
+ * name. There are no partial matches. It is not suitable for use
+ * in building responses to ordinary DNS queries; clients which wish
+ * to do that should use dns_db_find() instead.
+ *
+ * Requires:
+ *
+ * \li 'db' is a valid database.
+ *
+ * \li 'name' is a valid, non-empty, absolute name.
+ *
+ * \li nodep != NULL && *nodep == NULL
+ *
+ * Ensures:
+ *
+ * \li On success, *nodep is attached to the node with name 'name'.
+ *
+ * Returns:
+ *
+ * \li #ISC_R_SUCCESS
+ * \li #ISC_R_NOTFOUND If !create and name not found.
+ * \li #ISC_R_NOMEMORY Can only happen if create is true.
+ *
+ * \li Other results are possible, depending upon the database
+ * implementation used.
+ */
+
+isc_result_t
+dns_db_find(dns_db_t *db, const dns_name_t *name, dns_dbversion_t *version,
+ dns_rdatatype_t type, unsigned int options, isc_stdtime_t now,
+ dns_dbnode_t **nodep, dns_name_t *foundname,
+ dns_rdataset_t *rdataset, dns_rdataset_t *sigrdataset);
+
+isc_result_t
+dns_db_findext(dns_db_t *db, const dns_name_t *name, dns_dbversion_t *version,
+ dns_rdatatype_t type, unsigned int options, isc_stdtime_t now,
+ dns_dbnode_t **nodep, dns_name_t *foundname,
+ dns_clientinfomethods_t *methods, dns_clientinfo_t *clientinfo,
+ dns_rdataset_t *rdataset, dns_rdataset_t *sigrdataset);
+/*%<
+ * Find the best match for 'name' and 'type' in version 'version' of 'db'.
+ *
+ * dns_db_findext() (find extended) also accepts parameters 'methods'
+ * and 'clientinfo', which when provided enable the database to retrieve
+ * information about the client from the caller, and modify its response
+ * on the basis of this information.
+ *
+ * Notes:
+ *
+ * \li If type == dns_rdataset_any, then rdataset will not be bound.
+ *
+ * \li If 'options' does not have #DNS_DBFIND_GLUEOK set, then no glue will
+ * be returned. For zone databases, glue is as defined in RFC2181.
+ * For cache databases, glue is any rdataset with a trust of
+ * dns_trust_glue.
+ *
+ * \li If 'options' does not have #DNS_DBFIND_ADDITIONALOK set, then no
+ * additional records will be returned. Only caches can have
+ * rdataset with trust dns_trust_additional.
+ *
+ * \li If 'options' does not have #DNS_DBFIND_PENDINGOK set, then no
+ * pending data will be returned. This option is only meaningful for
+ * cache databases.
+ *
+ * \li If the #DNS_DBFIND_NOWILD option is set, then wildcard matching will
+ * be disabled. This option is only meaningful for zone databases.
+ *
+ * \li If the #DNS_DBFIND_NOZONECUT option is set, the database is
+ * assumed to contain no zone cuts above 'name'. An implementation
+ * may therefore choose to search for a match beginning at 'name'
+ * rather than walking down the tree to check check for delegations.
+ * If #DNS_DBFIND_NOWILD is not set, wildcard matching will be
+ * attempted at each node starting at the direct ancestor of 'name'
+ * and working up to the zone origin. This option is only meaningful
+ * when querying redirect zones.
+ *
+ * \li If the #DNS_DBFIND_FORCENSEC option is set, the database is assumed to
+ * have NSEC records, and these will be returned when appropriate. This
+ * is only necessary when querying a database that was not secure
+ * when created.
+ *
+ * \li If the DNS_DBFIND_COVERINGNSEC option is set, then look for a
+ * NSEC record that potentially covers 'name' if a answer cannot
+ * be found. Note the returned NSEC needs to be checked to ensure
+ * that it is correct. This only affects answers returned from the
+ * cache.
+ *
+ * \li If the #DNS_DBFIND_FORCENSEC3 option is set, then we are looking
+ * in the NSEC3 tree and not the main tree. Without this option being
+ * set NSEC3 records will not be found.
+ *
+ * \li To respond to a query for SIG records, the caller should create a
+ * rdataset iterator and extract the signatures from each rdataset.
+ *
+ * \li Making queries of type ANY with #DNS_DBFIND_GLUEOK is not recommended,
+ * because the burden of determining whether a given rdataset is valid
+ * glue or not falls upon the caller.
+ *
+ * \li The 'now' field is ignored if 'db' is a zone database. If 'db' is a
+ * cache database, an rdataset will not be found unless it expires after
+ * 'now'. Any ANY query will not match unless at least one rdataset at
+ * the node expires after 'now'. If 'now' is zero, then the current time
+ * will be used.
+ *
+ * Requires:
+ *
+ * \li 'db' is a valid database.
+ *
+ * \li 'type' is not SIG, or a meta-RR type other than 'ANY' (e.g. 'OPT').
+ *
+ * \li 'nodep' is NULL, or nodep is a valid pointer and *nodep == NULL.
+ *
+ * \li 'foundname' is a valid name with a dedicated buffer.
+ *
+ * \li 'rdataset' is NULL, or is a valid unassociated rdataset.
+ *
+ * Ensures,
+ * on a non-error completion:
+ *
+ * \li If nodep != NULL, then it is bound to the found node.
+ *
+ * \li If foundname != NULL, then it contains the full name of the
+ * found node.
+ *
+ * \li If rdataset != NULL and type != dns_rdatatype_any, then
+ * rdataset is bound to the found rdataset.
+ *
+ * Non-error results are:
+ *
+ * \li #ISC_R_SUCCESS The desired node and type were
+ * found.
+ *
+ * \li #DNS_R_GLUE The desired node and type were
+ * found, but are glue. This
+ * result can only occur if
+ * the DNS_DBFIND_GLUEOK option
+ * is set. This result can only
+ * occur if 'db' is a zone
+ * database. If type ==
+ * dns_rdatatype_any, then the
+ * node returned may contain, or
+ * consist entirely of invalid
+ * glue (i.e. data occluded by a
+ * zone cut). The caller must
+ * take care not to return invalid
+ * glue to a client.
+ *
+ * \li #DNS_R_DELEGATION The data requested is beneath
+ * a zone cut. node, foundname,
+ * and rdataset reference the
+ * NS RRset of the zone cut.
+ * If 'db' is a cache database,
+ * then this is the deepest known
+ * delegation.
+ *
+ * \li #DNS_R_ZONECUT type == dns_rdatatype_any, and
+ * the desired node is a zonecut.
+ * The caller must take care not
+ * to return inappropriate glue
+ * to a client. This result can
+ * only occur if 'db' is a zone
+ * database and DNS_DBFIND_GLUEOK
+ * is set.
+ *
+ * \li #DNS_R_DNAME The data requested is beneath
+ * a DNAME. node, foundname,
+ * and rdataset reference the
+ * DNAME RRset.
+ *
+ * \li #DNS_R_CNAME The rdataset requested was not
+ * found, but there is a CNAME
+ * at the desired name. node,
+ * foundname, and rdataset
+ * reference the CNAME RRset.
+ *
+ * \li #DNS_R_NXDOMAIN The desired name does not
+ * exist.
+ *
+ * \li #DNS_R_NXRRSET The desired name exists, but
+ * the desired type does not.
+ *
+ * \li #ISC_R_NOTFOUND The desired name does not
+ * exist, and no delegation could
+ * be found. This result can only
+ * occur if 'db' is a cache
+ * database. The caller should
+ * use its nameserver(s) of last
+ * resort (e.g. root hints).
+ *
+ * \li #DNS_R_NCACHENXDOMAIN The desired name does not
+ * exist. 'node' is bound to the
+ * cache node with the desired
+ * name, and 'rdataset' contains
+ * the negative caching proof.
+ *
+ * \li #DNS_R_NCACHENXRRSET The desired type does not
+ * exist. 'node' is bound to the
+ * cache node with the desired
+ * name, and 'rdataset' contains
+ * the negative caching proof.
+ *
+ * \li #DNS_R_EMPTYNAME The name exists but there is
+ * no data at the name.
+ *
+ * \li #DNS_R_COVERINGNSEC The returned data is a NSEC
+ * that potentially covers 'name'.
+ *
+ * \li #DNS_R_EMPTYWILD The name is a wildcard without
+ * resource records.
+ *
+ * Error results:
+ *
+ * \li #ISC_R_NOMEMORY
+ *
+ * \li #DNS_R_BADDB Data that is required to be
+ * present in the DB, e.g. an NSEC
+ * record in a secure zone, is not
+ * present.
+ *
+ * \li Other results are possible, and should all be treated as
+ * errors.
+ */
+
+isc_result_t
+dns_db_findzonecut(dns_db_t *db, const dns_name_t *name, unsigned int options,
+ isc_stdtime_t now, dns_dbnode_t **nodep,
+ dns_name_t *foundname, dns_name_t *dcname,
+ dns_rdataset_t *rdataset, dns_rdataset_t *sigrdataset);
+/*%<
+ * Find the deepest known zonecut which encloses 'name' in 'db'.
+ *
+ * Notes:
+ *
+ * \li If the #DNS_DBFIND_NOEXACT option is set, then the zonecut returned
+ * (if any) will be the deepest known ancestor of 'name'.
+ *
+ * \li If 'now' is zero, then the current time will be used.
+ *
+ * Requires:
+ *
+ * \li 'db' is a valid database with cache semantics.
+ *
+ * \li 'nodep' is NULL, or nodep is a valid pointer and *nodep == NULL.
+ *
+ * \li 'foundname' is a valid name with a dedicated buffer.
+ *
+ * \li 'dcname' is a valid name with a dedicated buffer.
+ *
+ * \li 'rdataset' is NULL, or is a valid unassociated rdataset.
+ *
+ * Ensures, on a non-error completion:
+ *
+ * \li If nodep != NULL, then it is bound to the found node.
+ *
+ * \li If foundname != NULL, then it contains the full name of the
+ * found node.
+ *
+ * \li If dcname != NULL, then it contains the deepest cached name
+ * that exists in the database.
+ *
+ * \li If rdataset != NULL and type != dns_rdatatype_any, then
+ * rdataset is bound to the found rdataset.
+ *
+ * Non-error results are:
+ *
+ * \li #ISC_R_SUCCESS
+ *
+ * \li #ISC_R_NOTFOUND
+ *
+ * \li Other results are possible, and should all be treated as
+ * errors.
+ */
+
+void
+dns_db_attachnode(dns_db_t *db, dns_dbnode_t *source, dns_dbnode_t **targetp);
+/*%<
+ * Attach *targetp to source.
+ *
+ * Requires:
+ *
+ * \li 'db' is a valid database.
+ *
+ * \li 'source' is a valid node.
+ *
+ * \li 'targetp' points to a NULL dns_dbnode_t *.
+ *
+ * Ensures:
+ *
+ * \li *targetp is attached to source.
+ */
+
+void
+dns_db_detachnode(dns_db_t *db, dns_dbnode_t **nodep);
+/*%<
+ * Detach *nodep from its node.
+ *
+ * Requires:
+ *
+ * \li 'db' is a valid database.
+ *
+ * \li 'nodep' points to a valid node.
+ *
+ * Ensures:
+ *
+ * \li *nodep is NULL.
+ */
+
+void
+dns_db_transfernode(dns_db_t *db, dns_dbnode_t **sourcep,
+ dns_dbnode_t **targetp);
+/*%<
+ * Transfer a node between pointer.
+ *
+ * This is equivalent to calling dns_db_attachnode() then dns_db_detachnode().
+ *
+ * Requires:
+ *
+ * \li 'db' is a valid database.
+ *
+ * \li '*sourcep' is a valid node.
+ *
+ * \li 'targetp' points to a NULL dns_dbnode_t *.
+ *
+ * Ensures:
+ *
+ * \li '*sourcep' is NULL.
+ */
+
+isc_result_t
+dns_db_expirenode(dns_db_t *db, dns_dbnode_t *node, isc_stdtime_t now);
+/*%<
+ * Mark as stale all records at 'node' which expire at or before 'now'.
+ *
+ * Note: if 'now' is zero, then the current time will be used.
+ *
+ * Requires:
+ *
+ * \li 'db' is a valid cache database.
+ *
+ * \li 'node' is a valid node.
+ */
+
+void
+dns_db_printnode(dns_db_t *db, dns_dbnode_t *node, FILE *out);
+/*%<
+ * Print a textual representation of the contents of the node to
+ * 'out'.
+ *
+ * Note: this function is intended for debugging, not general use.
+ *
+ * Requires:
+ *
+ * \li 'db' is a valid database.
+ *
+ * \li 'node' is a valid node.
+ */
+
+/***
+ *** DB Iterator Creation
+ ***/
+
+isc_result_t
+dns_db_createiterator(dns_db_t *db, unsigned int options,
+ dns_dbiterator_t **iteratorp);
+/*%<
+ * Create an iterator for version 'version' of 'db'.
+ *
+ * Notes:
+ *
+ * \li One or more of the following options can be set.
+ * #DNS_DB_RELATIVENAMES
+ * #DNS_DB_NSEC3ONLY
+ * #DNS_DB_NONSEC3
+ *
+ * Requires:
+ *
+ * \li 'db' is a valid database.
+ *
+ * \li iteratorp != NULL && *iteratorp == NULL
+ *
+ * Ensures:
+ *
+ * \li On success, *iteratorp will be a valid database iterator.
+ *
+ * Returns:
+ *
+ * \li #ISC_R_SUCCESS
+ * \li #ISC_R_NOMEMORY
+ */
+
+/***
+ *** Rdataset Methods
+ ***/
+
+/*
+ * XXXRTH Should we check for glue and pending data in dns_db_findrdataset()?
+ */
+
+isc_result_t
+dns_db_findrdataset(dns_db_t *db, dns_dbnode_t *node, dns_dbversion_t *version,
+ dns_rdatatype_t type, dns_rdatatype_t covers,
+ isc_stdtime_t now, dns_rdataset_t *rdataset,
+ dns_rdataset_t *sigrdataset);
+
+/*%<
+ * Search for an rdataset of type 'type' at 'node' that are in version
+ * 'version' of 'db'. If found, make 'rdataset' refer to it.
+ *
+ * Notes:
+ *
+ * \li If 'version' is NULL, then the current version will be used.
+ *
+ * \li Care must be used when using this routine to build a DNS response:
+ * 'node' should have been found with dns_db_find(), not
+ * dns_db_findnode(). No glue checking is done. No checking for
+ * pending data is done.
+ *
+ * \li The 'now' field is ignored if 'db' is a zone database. If 'db' is a
+ * cache database, an rdataset will not be found unless it expires after
+ * 'now'. If 'now' is zero, then the current time will be used.
+ *
+ * Requires:
+ *
+ * \li 'db' is a valid database.
+ *
+ * \li 'node' is a valid node.
+ *
+ * \li 'rdataset' is a valid, disassociated rdataset.
+ *
+ * \li 'sigrdataset' is a valid, disassociated rdataset, or it is NULL.
+ *
+ * \li If 'covers' != 0, 'type' must be RRSIG.
+ *
+ * \li 'type' is not a meta-RR type such as 'ANY' or 'OPT'.
+ *
+ * Ensures:
+ *
+ * \li On success, 'rdataset' is associated with the found rdataset.
+ *
+ * Returns:
+ *
+ * \li #ISC_R_SUCCESS
+ * \li #ISC_R_NOTFOUND
+ *
+ * \li Other results are possible, depending upon the database
+ * implementation used.
+ */
+
+isc_result_t
+dns_db_allrdatasets(dns_db_t *db, dns_dbnode_t *node, dns_dbversion_t *version,
+ unsigned int options, isc_stdtime_t now,
+ dns_rdatasetiter_t **iteratorp);
+/*%<
+ * Make '*iteratorp' an rdataset iterator for all rdatasets at 'node' in
+ * version 'version' of 'db'.
+ *
+ * Notes:
+ *
+ * \li If 'version' is NULL, then the current version will be used.
+ *
+ * \li 'options' controls which rdatasets are selected when interating over
+ * the node.
+ * 'DNS_DB_STALEOK' return stale rdatasets as well as current rdatasets.
+ * 'DNS_DB_EXPIREDOK' return expired rdatasets as well as current
+ * rdatasets.
+ *
+ * \li The 'now' field is ignored if 'db' is a zone database. If 'db' is a
+ * cache database, an rdataset will not be found unless it expires after
+ * 'now'. Any ANY query will not match unless at least one rdataset at
+ * the node expires after 'now'. If 'now' is zero, then the current time
+ * will be used.
+ *
+ * Requires:
+ *
+ * \li 'db' is a valid database.
+ *
+ * \li 'node' is a valid node.
+ *
+ * \li iteratorp != NULL && *iteratorp == NULL
+ *
+ * Ensures:
+ *
+ * \li On success, '*iteratorp' is a valid rdataset iterator.
+ *
+ * Returns:
+ *
+ * \li #ISC_R_SUCCESS
+ * \li #ISC_R_NOTFOUND
+ *
+ * \li Other results are possible, depending upon the database
+ * implementation used.
+ */
+
+isc_result_t
+dns_db_addrdataset(dns_db_t *db, dns_dbnode_t *node, dns_dbversion_t *version,
+ isc_stdtime_t now, dns_rdataset_t *rdataset,
+ unsigned int options, dns_rdataset_t *addedrdataset);
+/*%<
+ * Add 'rdataset' to 'node' in version 'version' of 'db'.
+ *
+ * Notes:
+ *
+ * \li If the database has zone semantics, the #DNS_DBADD_MERGE option is set,
+ * and an rdataset of the same type as 'rdataset' already exists at
+ * 'node' then the contents of 'rdataset' will be merged with the existing
+ * rdataset. If the option is not set, then rdataset will replace any
+ * existing rdataset of the same type. If not merging and the
+ * #DNS_DBADD_FORCE option is set, then the data will update the database
+ * without regard to trust levels. If not forcing the data, then the
+ * rdataset will only be added if its trust level is >= the trust level of
+ * any existing rdataset. Forcing is only meaningful for cache databases.
+ * If #DNS_DBADD_EXACT is set then there must be no rdata in common between
+ * the old and new rdata sets. If #DNS_DBADD_EXACTTTL is set then both
+ * the old and new rdata sets must have the same ttl.
+ *
+ * \li The 'now' field is ignored if 'db' is a zone database. If 'db' is
+ * a cache database, then the added rdataset will expire no later than
+ * now + rdataset->ttl.
+ *
+ * \li If 'addedrdataset' is not NULL, then it will be attached to the
+ * resulting new rdataset in the database, or to the existing data if
+ * the existing data was better.
+ *
+ * Requires:
+ *
+ * \li 'db' is a valid database.
+ *
+ * \li 'node' is a valid node.
+ *
+ * \li 'rdataset' is a valid, associated rdataset with the same class
+ * as 'db'.
+ *
+ * \li 'addedrdataset' is NULL, or a valid, unassociated rdataset.
+ *
+ * \li The database has zone semantics and 'version' is a valid
+ * read-write version, or the database has cache semantics
+ * and version is NULL.
+ *
+ * \li If the database has cache semantics, the #DNS_DBADD_MERGE option must
+ * not be set.
+ *
+ * Returns:
+ *
+ * \li #ISC_R_SUCCESS
+ * \li #DNS_R_UNCHANGED The operation did not change
+ * anything. \li #ISC_R_NOMEMORY \li #DNS_R_NOTEXACT
+ *
+ * \li Other results are possible, depending upon the database
+ * implementation used.
+ */
+
+isc_result_t
+dns_db_subtractrdataset(dns_db_t *db, dns_dbnode_t *node,
+ dns_dbversion_t *version, dns_rdataset_t *rdataset,
+ unsigned int options, dns_rdataset_t *newrdataset);
+/*%<
+ * Remove any rdata in 'rdataset' from 'node' in version 'version' of
+ * 'db'.
+ *
+ * Notes:
+ *
+ * \li If 'newrdataset' is not NULL, then it will be attached to the
+ * resulting new rdataset in the database, unless the rdataset has
+ * become nonexistent. If DNS_DBSUB_EXACT is set then all elements
+ * of 'rdataset' must exist at 'node'.
+ *
+ *\li If DNS_DBSUB_WANTOLD is set and the entire rdataset was deleted
+ * then return the original rdatatset in newrdataset if that existed.
+ *
+ * Requires:
+ *
+ * \li 'db' is a valid database.
+ *
+ * \li 'node' is a valid node.
+ *
+ * \li 'rdataset' is a valid, associated rdataset with the same class
+ * as 'db'.
+ *
+ * \li 'newrdataset' is NULL, or a valid, unassociated rdataset.
+ *
+ * \li The database has zone semantics and 'version' is a valid
+ * read-write version.
+ *
+ * Returns:
+ *
+ * \li #ISC_R_SUCCESS
+ * \li #DNS_R_UNCHANGED The operation did not change
+ * anything. \li #DNS_R_NXRRSET All rdata of the same
+ *type as
+ * those in 'rdataset' have been deleted. \li #DNS_R_NOTEXACT
+ * Some part of 'rdataset' did not exist and DNS_DBSUB_EXACT was set.
+ *
+ * \li Other results are possible, depending upon the database
+ * implementation used.
+ */
+
+isc_result_t
+dns_db_deleterdataset(dns_db_t *db, dns_dbnode_t *node,
+ dns_dbversion_t *version, dns_rdatatype_t type,
+ dns_rdatatype_t covers);
+/*%<
+ * Make it so that no rdataset of type 'type' exists at 'node' in version
+ * version 'version' of 'db'.
+ *
+ * Notes:
+ *
+ * \li If 'type' is dns_rdatatype_any, then no rdatasets will exist in
+ * 'version' (provided that the dns_db_deleterdataset() isn't followed
+ * by one or more dns_db_addrdataset() calls).
+ *
+ * Requires:
+ *
+ * \li 'db' is a valid database.
+ *
+ * \li 'node' is a valid node.
+ *
+ * \li The database has zone semantics and 'version' is a valid
+ * read-write version, or the database has cache semantics
+ * and version is NULL.
+ *
+ * \li 'type' is not a meta-RR type, except for dns_rdatatype_any, which is
+ * allowed.
+ *
+ * \li If 'covers' != 0, 'type' must be SIG.
+ *
+ * Returns:
+ *
+ * \li #ISC_R_SUCCESS
+ * \li #DNS_R_UNCHANGED No rdatasets of 'type' existed
+ * before the operation was attempted.
+ *
+ * \li Other results are possible, depending upon the database
+ * implementation used.
+ */
+
+isc_result_t
+dns_db_getsoaserial(dns_db_t *db, dns_dbversion_t *ver, uint32_t *serialp);
+/*%<
+ * Get the current SOA serial number from a zone database.
+ *
+ * Requires:
+ * \li 'db' is a valid database with zone semantics.
+ * \li 'ver' is a valid version.
+ */
+
+void
+dns_db_overmem(dns_db_t *db, bool overmem);
+/*%<
+ * Enable / disable aggressive cache cleaning.
+ */
+
+unsigned int
+dns_db_nodecount(dns_db_t *db, dns_dbtree_t tree);
+/*%<
+ * Count the number of nodes in 'db' or its auxiliary trees.
+ *
+ * Requires:
+ *
+ * \li 'db' is a valid database.
+ *
+ * Returns:
+ * \li The number of nodes in the database
+ */
+
+size_t
+dns_db_hashsize(dns_db_t *db);
+/*%<
+ * For database implementations using a hash table, report the
+ * current number of buckets.
+ *
+ * Requires:
+ *
+ * \li 'db' is a valid database.
+ *
+ * Returns:
+ * \li The number of buckets in the database's hash table, or
+ * 0 if not implemented.
+ */
+
+void
+dns_db_settask(dns_db_t *db, isc_task_t *task);
+/*%<
+ * If task is set then the final detach maybe performed asynchronously.
+ *
+ * Requires:
+ * \li 'db' is a valid database.
+ * \li 'task' to be valid or NULL.
+ */
+
+bool
+dns_db_ispersistent(dns_db_t *db);
+/*%<
+ * Is 'db' persistent? A persistent database does not need to be loaded
+ * from disk or written to disk.
+ *
+ * Requires:
+ *
+ * \li 'db' is a valid database.
+ *
+ * Returns:
+ * \li #true 'db' is persistent.
+ * \li #false 'db' is not persistent.
+ */
+
+isc_result_t
+dns_db_register(const char *name, dns_dbcreatefunc_t create, void *driverarg,
+ isc_mem_t *mctx, dns_dbimplementation_t **dbimp);
+
+/*%<
+ * Register a new database implementation and add it to the list of
+ * supported implementations.
+ *
+ * Requires:
+ *
+ * \li 'name' is not NULL
+ * \li 'order' is a valid function pointer
+ * \li 'mctx' is a valid memory context
+ * \li dbimp != NULL && *dbimp == NULL
+ *
+ * Returns:
+ * \li #ISC_R_SUCCESS The registration succeeded
+ * \li #ISC_R_NOMEMORY Out of memory
+ * \li #ISC_R_EXISTS A database implementation with the same name exists
+ *
+ * Ensures:
+ *
+ * \li *dbimp points to an opaque structure which must be passed to
+ * dns_db_unregister().
+ */
+
+void
+dns_db_unregister(dns_dbimplementation_t **dbimp);
+/*%<
+ * Remove a database implementation from the list of supported
+ * implementations. No databases of this type can be active when this
+ * is called.
+ *
+ * Requires:
+ * \li dbimp != NULL && *dbimp == NULL
+ *
+ * Ensures:
+ *
+ * \li Any memory allocated in *dbimp will be freed.
+ */
+
+isc_result_t
+dns_db_getoriginnode(dns_db_t *db, dns_dbnode_t **nodep);
+/*%<
+ * Get the origin DB node corresponding to the DB's zone. This function
+ * should typically succeed unless the underlying DB implementation doesn't
+ * support the feature.
+ *
+ * Requires:
+ *
+ * \li 'db' is a valid zone database.
+ * \li 'nodep' != NULL && '*nodep' == NULL
+ *
+ * Ensures:
+ * \li On success, '*nodep' will point to the DB node of the zone's origin.
+ *
+ * Returns:
+ * \li #ISC_R_SUCCESS
+ * \li #ISC_R_NOTFOUND - the DB implementation does not support this feature.
+ */
+
+isc_result_t
+dns_db_getnsec3parameters(dns_db_t *db, dns_dbversion_t *version,
+ dns_hash_t *hash, uint8_t *flags,
+ uint16_t *iterations, unsigned char *salt,
+ size_t *salt_length);
+/*%<
+ * Get the NSEC3 parameters that are associated with this zone.
+ *
+ * Requires:
+ * \li 'db' is a valid zone database.
+ *
+ * Returns:
+ * \li #ISC_R_SUCCESS
+ * \li #ISC_R_NOTFOUND - the DB implementation does not support this feature
+ * or this zone does not have NSEC3 records.
+ */
+
+isc_result_t
+dns_db_getsize(dns_db_t *db, dns_dbversion_t *version, uint64_t *records,
+ uint64_t *xfrsize);
+/*%<
+ * On success if 'records' is not NULL, it is set to the number of records
+ * in the given version of the database. If 'xfrisize' is not NULL, it is
+ * set to the approximate number of bytes needed to transfer the records,
+ * counting name, TTL, type, class, and rdata for each RR. (This is meant
+ * to be a rough approximation of the size of a full zone transfer, though
+ * it does not take into account DNS message overhead or name compression.)
+ *
+ * Requires:
+ * \li 'db' is a valid zone database.
+ * \li 'version' is NULL or a valid version.
+ * \li 'records' is NULL or a pointer to return the record count in.
+ * \li 'xfrsize' is NULL or a pointer to return the byte count in.
+ *
+ * Returns:
+ * \li #ISC_R_SUCCESS
+ * \li #ISC_R_NOTIMPLEMENTED
+ */
+
+isc_result_t
+dns_db_findnsec3node(dns_db_t *db, const dns_name_t *name, bool create,
+ dns_dbnode_t **nodep);
+/*%<
+ * Find the NSEC3 node with name 'name'.
+ *
+ * Notes:
+ * \li If 'create' is true and no node with name 'name' exists, then
+ * such a node will be created.
+ *
+ * Requires:
+ *
+ * \li 'db' is a valid database.
+ *
+ * \li 'name' is a valid, non-empty, absolute name.
+ *
+ * \li nodep != NULL && *nodep == NULL
+ *
+ * Ensures:
+ *
+ * \li On success, *nodep is attached to the node with name 'name'.
+ *
+ * Returns:
+ *
+ * \li #ISC_R_SUCCESS
+ * \li #ISC_R_NOTFOUND If !create and name not found.
+ * \li #ISC_R_NOMEMORY Can only happen if create is true.
+ *
+ * \li Other results are possible, depending upon the database
+ * implementation used.
+ */
+
+isc_result_t
+dns_db_setsigningtime(dns_db_t *db, dns_rdataset_t *rdataset,
+ isc_stdtime_t resign);
+/*%<
+ * Sets the re-signing time associated with 'rdataset' to 'resign'.
+ *
+ * Requires:
+ * \li 'db' is a valid zone database.
+ * \li 'rdataset' is or is to be associated with 'db'.
+ * \li 'rdataset' is not pending removed from the heap via an
+ * uncommitted call to dns_db_resigned().
+ *
+ * Returns:
+ * \li #ISC_R_SUCCESS
+ * \li #ISC_R_NOMEMORY
+ * \li #ISC_R_NOTIMPLEMENTED - Not supported by this DB implementation.
+ */
+
+isc_result_t
+dns_db_getsigningtime(dns_db_t *db, dns_rdataset_t *rdataset, dns_name_t *name);
+/*%<
+ * Return the rdataset with the earliest signing time in the zone.
+ * Note: the rdataset is version agnostic.
+ *
+ * Requires:
+ * \li 'db' is a valid zone database.
+ * \li 'rdataset' to be initialized but not associated.
+ * \li 'name' to be NULL or have a buffer associated with it.
+ *
+ * Returns:
+ * \li #ISC_R_SUCCESS
+ * \li #ISC_R_NOTFOUND - No dataset exists.
+ */
+
+void
+dns_db_resigned(dns_db_t *db, dns_rdataset_t *rdataset,
+ dns_dbversion_t *version);
+/*%<
+ * Mark 'rdataset' as not being available to be returned by
+ * dns_db_getsigningtime(). If the changes associated with 'version'
+ * are committed this will be permanent. If the version is not committed
+ * this change will be rolled back when the version is closed. Until
+ * 'version' is either committed or rolled back, 'rdataset' can no longer
+ * be acted upon by dns_db_setsigningtime().
+ *
+ * Requires:
+ * \li 'db' is a valid zone database.
+ * \li 'rdataset' to be associated with 'db'.
+ * \li 'version' to be open for writing.
+ */
+
+dns_stats_t *
+dns_db_getrrsetstats(dns_db_t *db);
+/*%<
+ * Get statistics information counting RRsets stored in the DB, when available.
+ * The statistics may not be available depending on the DB implementation.
+ *
+ * Requires:
+ *
+ * \li 'db' is a valid database (cache only).
+ *
+ * Returns:
+ * \li when available, a pointer to a statistics object created by
+ * dns_rdatasetstats_create(); otherwise NULL.
+ */
+
+isc_result_t
+dns_db_setcachestats(dns_db_t *db, isc_stats_t *stats);
+/*%<
+ * Set the location in which to collect cache statistics.
+ * This option may not exist depending on the DB implementation.
+ *
+ * Requires:
+ *
+ * \li 'db' is a valid database (cache only).
+ *
+ * Returns:
+ * \li when available, a pointer to a statistics object created by
+ * dns_rdatasetstats_create(); otherwise NULL.
+ */
+
+void
+dns_db_rpz_attach(dns_db_t *db, void *rpzs, uint8_t rpz_num) ISC_DEPRECATED;
+/*%<
+ * Attach the response policy information for a view to a database for a
+ * zone for the view.
+ */
+
+isc_result_t
+dns_db_rpz_ready(dns_db_t *db) ISC_DEPRECATED;
+/*%<
+ * Finish loading a response policy zone.
+ */
+
+isc_result_t
+dns_db_updatenotify_register(dns_db_t *db, dns_dbupdate_callback_t fn,
+ void *fn_arg);
+/*%<
+ * Register a notify-on-update callback function to a database.
+ * Duplicate callbacks are suppressed.
+ *
+ * Requires:
+ *
+ * \li 'db' is a valid database
+ * \li 'fn' is not NULL
+ *
+ */
+
+isc_result_t
+dns_db_updatenotify_unregister(dns_db_t *db, dns_dbupdate_callback_t fn,
+ void *fn_arg);
+/*%<
+ * Unregister a notify-on-update callback.
+ *
+ * Requires:
+ *
+ * \li 'db' is a valid database
+ * \li 'db' has update callback registered
+ *
+ */
+
+isc_result_t
+dns_db_nodefullname(dns_db_t *db, dns_dbnode_t *node, dns_name_t *name);
+/*%<
+ * Get the name associated with a database node.
+ *
+ * Requires:
+ *
+ * \li 'db' is a valid database
+ * \li 'node' and 'name' are not NULL
+ */
+
+isc_result_t
+dns_db_setservestalettl(dns_db_t *db, dns_ttl_t ttl);
+/*%<
+ * Sets the maximum length of time that cached answers may be retained
+ * past their normal TTL. Default value for the library is 0, disabling
+ * the use of stale data.
+ *
+ * Requires:
+ * \li 'db' is a valid cache database.
+ * \li 'ttl' is the number of seconds to retain data past its normal expiry.
+ *
+ * Returns:
+ * \li #ISC_R_SUCCESS
+ * \li #ISC_R_NOTIMPLEMENTED - Not supported by this DB implementation.
+ */
+
+isc_result_t
+dns_db_getservestalettl(dns_db_t *db, dns_ttl_t *ttl);
+/*%<
+ * Gets maximum length of time that cached answers may be kept past
+ * normal TTL expiration.
+ *
+ * Requires:
+ * \li 'db' is a valid cache database.
+ * \li 'ttl' is the number of seconds to retain data past its normal expiry.
+ *
+ * Returns:
+ * \li #ISC_R_SUCCESS
+ * \li #ISC_R_NOTIMPLEMENTED - Not supported by this DB implementation.
+ */
+
+isc_result_t
+dns_db_setservestalerefresh(dns_db_t *db, uint32_t interval);
+/*%<
+ * Sets the length of time to wait before attempting to refresh a rrset
+ * if a previous attempt in doing so has failed.
+ * During this time window if stale rrset are available in cache they
+ * will be directly returned to client.
+ *
+ * Requires:
+ * \li 'db' is a valid cache database.
+ * \li 'interval' is number of seconds before attempting to refresh data.
+ *
+ * Returns:
+ * \li #ISC_R_SUCCESS
+ * \li #ISC_R_NOTIMPLEMENTED - Not supported by this DB implementation.
+ */
+
+isc_result_t
+dns_db_getservestalerefresh(dns_db_t *db, uint32_t *interval);
+/*%<
+ * Gets the length of time in which stale answers are directly returned from
+ * cache before attempting to refresh them, in case a previous attempt in
+ * doing so has failed.
+ *
+ * Requires:
+ * \li 'db' is a valid cache database.
+ * \li 'interval' is number of seconds before attempting to refresh data.
+ *
+ * Returns:
+ * \li #ISC_R_SUCCESS
+ * \li #ISC_R_NOTIMPLEMENTED - Not supported by this DB implementation.
+ */
+
+isc_result_t
+dns_db_setgluecachestats(dns_db_t *db, isc_stats_t *stats);
+/*%<
+ * Set the location in which to collect glue cache statistics.
+ * This option may not exist depending on the DB implementation.
+ *
+ * Requires:
+ *
+ * \li 'db' is a valid database (cache only).
+ *
+ * Returns:
+ * \li when available, a pointer to a statistics object created by
+ * dns_rdatasetstats_create(); otherwise NULL.
+ */
+
+ISC_LANG_ENDDECLS
diff --git a/lib/dns/include/dns/dbiterator.h b/lib/dns/include/dns/dbiterator.h
new file mode 100644
index 0000000..dcec59a
--- /dev/null
+++ b/lib/dns/include/dns/dbiterator.h
@@ -0,0 +1,290 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#pragma once
+
+/*****
+***** Module Info
+*****/
+
+/*! \file dns/dbiterator.h
+ * \brief
+ * The DNS DB Iterator interface allows iteration of all of the nodes in a
+ * database.
+ *
+ * The dns_dbiterator_t type is like a "virtual class". To actually use
+ * it, an implementation of the class is required. This implementation is
+ * supplied by the database.
+ *
+ * It is the client's responsibility to call dns_db_detachnode() on all
+ * nodes returned.
+ *
+ * XXX &lt;more&gt; 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 <stdbool.h>
+
+#include <isc/lang.h>
+#include <isc/magic.h>
+
+#include <dns/types.h>
+
+ISC_LANG_BEGINDECLS
+
+/*****
+***** Types
+*****/
+
+typedef struct dns_dbiteratormethods {
+ void (*destroy)(dns_dbiterator_t **iteratorp);
+ isc_result_t (*first)(dns_dbiterator_t *iterator);
+ isc_result_t (*last)(dns_dbiterator_t *iterator);
+ isc_result_t (*seek)(dns_dbiterator_t *iterator,
+ const dns_name_t *name);
+ isc_result_t (*prev)(dns_dbiterator_t *iterator);
+ isc_result_t (*next)(dns_dbiterator_t *iterator);
+ isc_result_t (*current)(dns_dbiterator_t *iterator,
+ dns_dbnode_t **nodep, dns_name_t *name);
+ isc_result_t (*pause)(dns_dbiterator_t *iterator);
+ isc_result_t (*origin)(dns_dbiterator_t *iterator, dns_name_t *name);
+} dns_dbiteratormethods_t;
+
+#define DNS_DBITERATOR_MAGIC ISC_MAGIC('D', 'N', 'S', 'I')
+#define DNS_DBITERATOR_VALID(dbi) ISC_MAGIC_VALID(dbi, DNS_DBITERATOR_MAGIC)
+/*%
+ * This structure is actually just the common prefix of a DNS db
+ * implementation's version of a dns_dbiterator_t.
+ *
+ * Clients may use the 'db' field of this structure. Except for that field,
+ * direct use of this structure by clients is forbidden. DB implementations
+ * may change the structure. 'magic' must be DNS_DBITERATOR_MAGIC for any of
+ * the dns_dbiterator routines to work. DB iterator implementations must
+ * maintain all DB iterator invariants.
+ */
+struct dns_dbiterator {
+ /* Unlocked. */
+ unsigned int magic;
+ dns_dbiteratormethods_t *methods;
+ dns_db_t *db;
+ bool relative_names;
+ bool cleaning;
+};
+
+void
+dns_dbiterator_destroy(dns_dbiterator_t **iteratorp);
+/*%<
+ * Destroy '*iteratorp'.
+ *
+ * Requires:
+ *
+ *\li '*iteratorp' is a valid iterator.
+ *
+ * Ensures:
+ *
+ *\li All resources used by the iterator are freed.
+ *
+ *\li *iteratorp == NULL.
+ */
+
+isc_result_t
+dns_dbiterator_first(dns_dbiterator_t *iterator);
+/*%<
+ * Move the node cursor to the first node in the database (if any).
+ *
+ * Requires:
+ *\li 'iterator' is a valid iterator.
+ *
+ * Returns:
+ *\li #ISC_R_SUCCESS
+ *\li #ISC_R_NOMORE There are no nodes in the database.
+ *
+ *\li Other results are possible, depending on the DB implementation.
+ */
+
+isc_result_t
+dns_dbiterator_last(dns_dbiterator_t *iterator);
+/*%<
+ * Move the node cursor to the last node in the database (if any).
+ *
+ * Requires:
+ *\li 'iterator' is a valid iterator.
+ *
+ * Returns:
+ *\li #ISC_R_SUCCESS
+ *\li #ISC_R_NOMORE There are no nodes in the database.
+ *
+ *\li Other results are possible, depending on the DB implementation.
+ */
+
+isc_result_t
+dns_dbiterator_seek(dns_dbiterator_t *iterator, const dns_name_t *name);
+/*%<
+ * Move the node cursor to the node with name 'name'.
+ *
+ * Requires:
+ *\li 'iterator' is a valid iterator.
+ *
+ *\li 'name' is a valid name.
+ *
+ * Returns:
+ *\li #ISC_R_SUCCESS
+ *\li #ISC_R_NOTFOUND
+ *\li #DNS_R_PARTIALMATCH
+ * (node is at name above requested named when name has children)
+ *
+ *\li Other results are possible, depending on the DB implementation.
+ */
+
+isc_result_t
+dns_dbiterator_prev(dns_dbiterator_t *iterator);
+/*%<
+ * Move the node cursor to the previous node in the database (if any).
+ *
+ * Requires:
+ *\li 'iterator' is a valid iterator.
+ *
+ * Returns:
+ *\li #ISC_R_SUCCESS
+ *\li #ISC_R_NOMORE There are no more nodes in the
+ * database.
+ *
+ *\li Other results are possible, depending on the DB implementation.
+ */
+
+isc_result_t
+dns_dbiterator_next(dns_dbiterator_t *iterator);
+/*%<
+ * Move the node cursor to the next node in the database (if any).
+ *
+ * Requires:
+ *\li 'iterator' is a valid iterator.
+ *
+ * Returns:
+ *\li #ISC_R_SUCCESS
+ *\li #ISC_R_NOMORE There are no more nodes in the
+ * database.
+ *
+ *\li Other results are possible, depending on the DB implementation.
+ */
+
+isc_result_t
+dns_dbiterator_current(dns_dbiterator_t *iterator, dns_dbnode_t **nodep,
+ dns_name_t *name);
+/*%<
+ * Return the current node.
+ *
+ * Notes:
+ *\li If 'name' is not NULL, it will be set to the name of the node.
+ *
+ * Requires:
+ *\li 'iterator' is a valid iterator.
+ *
+ *\li nodep != NULL && *nodep == NULL
+ *
+ *\li The node cursor of 'iterator' is at a valid location (i.e. the
+ * result of last call to a cursor movement command was ISC_R_SUCCESS).
+ *
+ *\li 'name' is NULL, or is a valid name with a dedicated buffer.
+ *
+ * Returns:
+ *
+ *\li #ISC_R_SUCCESS
+ *\li #DNS_R_NEWORIGIN If this iterator was created
+ * with 'relative_names' set to true, then #DNS_R_NEWORIGIN will be returned
+ *when
+ * the origin the names are relative to changes. This result can occur only
+ *when
+ *'name' is not NULL. This is also a successful result.
+ *
+ *\li Other results are possible, depending on the DB implementation.
+ */
+
+isc_result_t
+dns_dbiterator_pause(dns_dbiterator_t *iterator);
+/*%<
+ * Pause iteration.
+ *
+ * Calling a cursor movement method or dns_dbiterator_current() may cause
+ * database locks to be acquired. Rather than reacquire these locks every
+ * time one of these routines is called, the locks may simply be held.
+ * Calling dns_dbiterator_pause() releases any such locks. Iterator clients
+ * should call this routine any time they are not going to execute another
+ * iterator method in the immediate future.
+ *
+ * Requires:
+ *\li 'iterator' is a valid iterator.
+ *
+ * Ensures:
+ *\li Any database locks being held for efficiency of iterator access are
+ * released.
+ *
+ * Returns:
+ *\li #ISC_R_SUCCESS
+ *
+ *\li Other results are possible, depending on the DB implementation.
+ */
+
+isc_result_t
+dns_dbiterator_origin(dns_dbiterator_t *iterator, dns_name_t *name);
+/*%<
+ * Return the origin to which returned node names are relative.
+ *
+ * Requires:
+ *
+ *\li 'iterator' is a valid relative_names iterator.
+ *
+ *\li 'name' is a valid name with a dedicated buffer.
+ *
+ * Returns:
+ *
+ *\li #ISC_R_SUCCESS
+ *\li #ISC_R_NOSPACE
+ *
+ *\li Other results are possible, depending on the DB implementation.
+ */
+
+void
+dns_dbiterator_setcleanmode(dns_dbiterator_t *iterator, bool mode);
+/*%<
+ * Indicate that the given iterator is/is not cleaning the DB.
+ *
+ * Notes:
+ *\li When 'mode' is true,
+ *
+ * Requires:
+ *\li 'iterator' is a valid iterator.
+ */
+
+ISC_LANG_ENDDECLS
diff --git a/lib/dns/include/dns/diff.h b/lib/dns/include/dns/diff.h
new file mode 100644
index 0000000..e4677dd
--- /dev/null
+++ b/lib/dns/include/dns/diff.h
@@ -0,0 +1,279 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#pragma once
+
+/*****
+***** Module Info
+*****/
+
+/*! \file dns/diff.h
+ * \brief
+ * A diff is a convenience type representing a list of changes to be
+ * made to a database.
+ */
+
+/***
+ *** Imports
+ ***/
+
+#include <isc/lang.h>
+#include <isc/magic.h>
+
+#include <dns/name.h>
+#include <dns/rdata.h>
+#include <dns/types.h>
+
+/***
+ *** Types
+ ***/
+
+/*%
+ * A dns_difftuple_t represents a single RR being added or deleted.
+ * The RR type and class are in the 'rdata' member; the class is always
+ * the real one, not a DynDNS meta-class, so that the rdatas can be
+ * compared using dns_rdata_compare(). The TTL is significant
+ * even for deletions, because a deletion/addition pair cannot
+ * be canceled out if the TTL differs (it might be an explicit
+ * TTL update).
+ *
+ * Tuples are also used to represent complete RRs with owner
+ * names for a couple of other purposes, such as the
+ * individual RRs of a "RRset exists (value dependent)"
+ * prerequisite set. In this case, op==DNS_DIFFOP_EXISTS,
+ * and the TTL is ignored.
+ *
+ * DNS_DIFFOP_*RESIGN will cause the 'resign' attribute of the resulting
+ * RRset to be recomputed to be 'resign' seconds before the earliest RRSIG
+ * timeexpire.
+ */
+
+typedef enum {
+ DNS_DIFFOP_ADD = 0, /*%< Add an RR. */
+ DNS_DIFFOP_DEL = 1, /*%< Delete an RR. */
+ DNS_DIFFOP_EXISTS = 2, /*%< Assert RR existence. */
+ DNS_DIFFOP_ADDRESIGN = 4, /*%< ADD + RESIGN. */
+ DNS_DIFFOP_DELRESIGN = 5 /*%< DEL + RESIGN. */
+} dns_diffop_t;
+
+typedef struct dns_difftuple dns_difftuple_t;
+typedef ISC_LIST(dns_difftuple_t) dns_difftuplelist_t;
+
+#define DNS_DIFFTUPLE_MAGIC ISC_MAGIC('D', 'I', 'F', 'T')
+#define DNS_DIFFTUPLE_VALID(t) ISC_MAGIC_VALID(t, DNS_DIFFTUPLE_MAGIC)
+
+struct dns_difftuple {
+ unsigned int magic;
+ isc_mem_t *mctx;
+ dns_diffop_t op;
+ dns_name_t name;
+ dns_ttl_t ttl;
+ dns_rdata_t rdata;
+ ISC_LINK(dns_difftuple_t) link;
+ /* Variable-size name data and rdata follows. */
+};
+
+/*%
+ * A dns_diff_t represents a set of changes being applied to
+ * a zone. Diffs are also used to represent "RRset exists
+ * (value dependent)" prerequisites.
+ */
+typedef struct dns_diff dns_diff_t;
+
+#define DNS_DIFF_MAGIC ISC_MAGIC('D', 'I', 'F', 'F')
+#define DNS_DIFF_VALID(t) ISC_MAGIC_VALID(t, DNS_DIFF_MAGIC)
+
+struct dns_diff {
+ unsigned int magic;
+ isc_mem_t *mctx;
+ dns_difftuplelist_t tuples;
+};
+
+/* Type of comparison function for sorting diffs. */
+typedef int
+dns_diff_compare_func(const void *, const void *);
+
+/***
+ *** Functions
+ ***/
+
+ISC_LANG_BEGINDECLS
+
+/**************************************************************************/
+/*
+ * Manipulation of diffs and tuples.
+ */
+
+isc_result_t
+dns_difftuple_create(isc_mem_t *mctx, dns_diffop_t op, const dns_name_t *name,
+ dns_ttl_t ttl, dns_rdata_t *rdata, dns_difftuple_t **tp);
+/*%<
+ * Create a tuple. Deep copies are made of the name and rdata, so
+ * they need not remain valid after the call.
+ *
+ * Requires:
+ *\li *tp != NULL && *tp == NULL.
+ *
+ * Returns:
+ *\li ISC_R_SUCCESS
+ * \li ISC_R_NOMEMORY
+ */
+
+void
+dns_difftuple_free(dns_difftuple_t **tp);
+/*%<
+ * Free a tuple.
+ *
+ * Requires:
+ * \li **tp is a valid tuple.
+ *
+ * Ensures:
+ * \li *tp == NULL
+ * \li All memory used by the tuple is freed.
+ */
+
+isc_result_t
+dns_difftuple_copy(dns_difftuple_t *orig, dns_difftuple_t **copyp);
+/*%<
+ * Copy a tuple.
+ *
+ * Requires:
+ * \li 'orig' points to a valid tuple
+ *\li copyp != NULL && *copyp == NULL
+ */
+
+void
+dns_diff_init(isc_mem_t *mctx, dns_diff_t *diff);
+/*%<
+ * Initialize a diff.
+ *
+ * Requires:
+ * \li 'diff' points to an uninitialized dns_diff_t
+ * \li allocated by the caller.
+ *
+ * Ensures:
+ * \li '*diff' is a valid, empty diff.
+ */
+
+void
+dns_diff_clear(dns_diff_t *diff);
+/*%<
+ * Clear a diff, destroying all its tuples.
+ *
+ * Requires:
+ * \li 'diff' points to a valid dns_diff_t.
+ *
+ * Ensures:
+ * \li Any tuples in the diff are destroyed.
+ * The diff now empty, but it is still valid
+ * and may be reused without calling dns_diff_init
+ * again. The only memory used is that of the
+ * dns_diff_t structure itself.
+ *
+ * Notes:
+ * \li Managing the memory of the dns_diff_t structure itself
+ * is the caller's responsibility.
+ */
+
+void
+dns_diff_append(dns_diff_t *diff, dns_difftuple_t **tuple);
+/*%<
+ * Append a single tuple to a diff.
+ *
+ *\li 'diff' is a valid diff.
+ * \li '*tuple' is a valid tuple.
+ *
+ * Ensures:
+ *\li *tuple is NULL.
+ *\li The tuple has been freed, or will be freed when the diff is cleared.
+ */
+
+void
+dns_diff_appendminimal(dns_diff_t *diff, dns_difftuple_t **tuple);
+/*%<
+ * Append 'tuple' to 'diff', removing any duplicate
+ * or conflicting updates as needed to create a minimal diff.
+ *
+ * Requires:
+ *\li 'diff' is a minimal diff.
+ *
+ * Ensures:
+ *\li 'diff' is still a minimal diff.
+ * \li *tuple is NULL.
+ * \li The tuple has been freed, or will be freed when the diff is
+ * cleared.
+ *
+ */
+
+isc_result_t
+dns_diff_sort(dns_diff_t *diff, dns_diff_compare_func *compare);
+/*%<
+ * Sort 'diff' in-place according to the comparison function 'compare'.
+ */
+
+isc_result_t
+dns_diff_apply(dns_diff_t *diff, dns_db_t *db, dns_dbversion_t *ver);
+isc_result_t
+dns_diff_applysilently(dns_diff_t *diff, dns_db_t *db, dns_dbversion_t *ver);
+/*%<
+ * Apply 'diff' to the database 'db'.
+ *
+ * dns_diff_apply() logs warnings about updates with no effect or
+ * with inconsistent TTLs; dns_diff_applysilently() does not.
+ *
+ * For efficiency, the diff should be sorted by owner name.
+ * If it is not sorted, operation will still be correct,
+ * but less efficient.
+ *
+ * Requires:
+ *\li *diff is a valid diff (possibly empty), containing
+ * tuples of type #DNS_DIFFOP_ADD and/or
+ * For #DNS_DIFFOP_DEL tuples, the TTL is ignored.
+ *
+ */
+
+isc_result_t
+dns_diff_load(dns_diff_t *diff, dns_addrdatasetfunc_t addfunc,
+ void *add_private);
+/*%<
+ * Like dns_diff_apply, but for use when loading a new database
+ * instead of modifying an existing one. This bypasses the
+ * database transaction mechanisms.
+ *
+ * Requires:
+ *\li 'addfunc' is a valid dns_addradatasetfunc_t obtained from
+ * dns_db_beginload()
+ *
+ *\li 'add_private' points to a corresponding dns_dbload_t *
+ * (XXX why is it a void pointer, then?)
+ */
+
+isc_result_t
+dns_diff_print(dns_diff_t *diff, FILE *file);
+
+/*%<
+ * Print the differences to 'file' or if 'file' is NULL via the
+ * logging system.
+ *
+ * Require:
+ *\li 'diff' to be valid.
+ *\li 'file' to refer to a open file or NULL.
+ *
+ * Returns:
+ *\li #ISC_R_SUCCESS
+ *\li #ISC_R_NOMEMORY
+ *\li #ISC_R_UNEXPECTED
+ *\li any error from dns_rdataset_totext()
+ */
+
+ISC_LANG_ENDDECLS
diff --git a/lib/dns/include/dns/dispatch.h b/lib/dns/include/dns/dispatch.h
new file mode 100644
index 0000000..96be0f4
--- /dev/null
+++ b/lib/dns/include/dns/dispatch.h
@@ -0,0 +1,390 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#pragma once
+
+#include <isc/netmgr.h>
+
+/*****
+***** Module Info
+*****/
+
+/*! \file dns/dispatch.h
+ * \brief
+ * DNS Dispatch Management
+ * Shared UDP and single-use TCP dispatches for queries and responses.
+ *
+ * MP:
+ *
+ *\li All locking is performed internally to each dispatch.
+ * Restrictions apply to dns_dispatch_done().
+ *
+ * Reliability:
+ *
+ * Resources:
+ *
+ * Security:
+ *
+ *\li Depends on dns_message_t for prevention of buffer overruns.
+ *
+ * Standards:
+ *
+ *\li None.
+ */
+
+/***
+ *** Imports
+ ***/
+
+#include <inttypes.h>
+#include <stdbool.h>
+
+#include <isc/buffer.h>
+#include <isc/lang.h>
+#include <isc/mutex.h>
+#include <isc/netmgr.h>
+#include <isc/refcount.h>
+#include <isc/types.h>
+
+#include <dns/types.h>
+
+#undef DNS_DISPATCH_TRACE
+
+ISC_LANG_BEGINDECLS
+
+/*%
+ * This is a set of one or more dispatches which can be retrieved
+ * round-robin fashion.
+ */
+struct dns_dispatchset {
+ isc_mem_t *mctx;
+ dns_dispatch_t **dispatches;
+ int ndisp;
+ int cur;
+ isc_mutex_t lock;
+};
+
+/*
+ */
+#define DNS_DISPATCHOPT_FIXEDID 0x00000001U
+
+isc_result_t
+dns_dispatchmgr_create(isc_mem_t *mctx, isc_nm_t *nm, dns_dispatchmgr_t **mgrp);
+/*%<
+ * Creates a new dispatchmgr object, and sets the available ports
+ * to the default range (1024-65535).
+ *
+ * Requires:
+ *\li 'mctx' be a valid memory context.
+ *
+ *\li 'nm' is a valid network manager.
+
+ *\li mgrp != NULL && *mgrp == NULL
+ *
+ * Returns:
+ *\li ISC_R_SUCCESS -- all ok
+ *
+ *\li anything else -- failure
+ */
+
+#if DNS_DISPATCH_TRACE
+#define dns_dispatchmgr_ref(ptr) \
+ dns_dispatchmgr__ref(ptr, __func__, __FILE__, __LINE__)
+#define dns_dispatchmgr_unref(ptr) \
+ dns_dispatchmgr__unref(ptr, __func__, __FILE__, __LINE__)
+#define dns_dispatchmgr_attach(ptr, ptrp) \
+ dns_dispatchmgr__attach(ptr, ptrp, __func__, __FILE__, __LINE__)
+#define dns_dispatchmgr_detach(ptrp) \
+ dns_dispatchmgr__detach(ptrp, __func__, __FILE__, __LINE__)
+ISC_REFCOUNT_TRACE_DECL(dns_dispatchmgr);
+#else
+ISC_REFCOUNT_DECL(dns_dispatchmgr);
+#endif
+
+/*%<
+ * Attach/Detach to a dispatch manager.
+ */
+
+void
+dns_dispatchmgr_setblackhole(dns_dispatchmgr_t *mgr, dns_acl_t *blackhole);
+/*%<
+ * Sets the dispatcher's "blackhole list," a list of addresses that will
+ * be ignored by all dispatchers created by the dispatchmgr.
+ *
+ * Requires:
+ * \li mgrp is a valid dispatchmgr
+ * \li blackhole is a valid acl
+ */
+
+dns_acl_t *
+dns_dispatchmgr_getblackhole(dns_dispatchmgr_t *mgr);
+/*%<
+ * Gets a pointer to the dispatcher's current blackhole list,
+ * without incrementing its reference count.
+ *
+ * Requires:
+ *\li mgr is a valid dispatchmgr
+ * Returns:
+ *\li A pointer to the current blackhole list, or NULL.
+ */
+
+isc_result_t
+dns_dispatchmgr_setavailports(dns_dispatchmgr_t *mgr, isc_portset_t *v4portset,
+ isc_portset_t *v6portset);
+/*%<
+ * Sets a list of UDP ports that can be used for outgoing UDP messages.
+ *
+ * Requires:
+ *\li mgr is a valid dispatchmgr
+ *\li v4portset is NULL or a valid port set
+ *\li v6portset is NULL or a valid port set
+ */
+
+void
+dns_dispatchmgr_setstats(dns_dispatchmgr_t *mgr, isc_stats_t *stats);
+/*%<
+ * Sets statistics counter for the dispatchmgr. This function is expected to
+ * be called only on zone creation (when necessary).
+ * Once installed, it cannot be removed or replaced. Also, there is no
+ * interface to get the installed stats from the zone; the caller must keep the
+ * stats to reference (e.g. dump) it later.
+ *
+ * Requires:
+ *\li mgr is a valid dispatchmgr with no managed dispatch.
+ *\li stats is a valid statistics supporting resolver statistics counters
+ * (see dns/stats.h).
+ */
+
+isc_result_t
+dns_dispatch_createudp(dns_dispatchmgr_t *mgr, const isc_sockaddr_t *localaddr,
+ dns_dispatch_t **dispp);
+/*%<
+ * Create a new UDP dispatch.
+ *
+ * Requires:
+ *\li All pointer parameters be valid for their respective types.
+ *
+ *\li dispp != NULL && *disp == NULL
+ *
+ * Returns:
+ *\li ISC_R_SUCCESS -- success.
+ *
+ *\li Anything else -- failure.
+ */
+
+isc_result_t
+dns_dispatch_createtcp(dns_dispatchmgr_t *mgr, const isc_sockaddr_t *localaddr,
+ const isc_sockaddr_t *destaddr, dns_dispatch_t **dispp);
+/*%<
+ * Create a new TCP dns_dispatch.
+ *
+ * Requires:
+ *
+ *\li mgr is a valid dispatch manager.
+ *
+ *\li sock is a valid.
+ *
+ * Returns:
+ *\li ISC_R_SUCCESS -- success.
+ *
+ *\li Anything else -- failure.
+ */
+
+#if DNS_DISPATCH_TRACE
+#define dns_dispatch_ref(ptr) \
+ dns_dispatch__ref(ptr, __func__, __FILE__, __LINE__)
+#define dns_dispatch_unref(ptr) \
+ dns_dispatch__unref(ptr, __func__, __FILE__, __LINE__)
+#define dns_dispatch_attach(ptr, ptrp) \
+ dns_dispatch__attach(ptr, ptrp, __func__, __FILE__, __LINE__)
+#define dns_dispatch_detach(ptrp) \
+ dns_dispatch__detach(ptrp, __func__, __FILE__, __LINE__)
+ISC_REFCOUNT_TRACE_DECL(dns_dispatch);
+#else
+ISC_REFCOUNT_DECL(dns_dispatch);
+#endif
+/*%<
+ * Attach/Detach to a dispatch handle.
+ *
+ * Requires:
+ *\li disp is valid.
+ *
+ *\li dispp != NULL && *dispp == NULL
+ */
+
+isc_result_t
+dns_dispatch_connect(dns_dispentry_t *resp);
+/*%<
+ * Connect to the remote server configured in 'resp' and run the
+ * connect callback that was set up via dns_dispatch_add().
+ *
+ * Requires:
+ *\li 'resp' is valid.
+ */
+
+void
+dns_dispatch_send(dns_dispentry_t *resp, isc_region_t *r);
+/*%<
+ * Send region 'r' using the socket in 'resp', then run the specified
+ * callback.
+ *
+ * Requires:
+ *\li 'resp' is valid.
+ */
+
+void
+dns_dispatch_resume(dns_dispentry_t *resp, uint16_t timeout);
+/*%<
+ * Reset the read timeout in the socket associated with 'resp' and
+ * continue reading.
+ *
+ * Requires:
+ *\li 'resp' is valid.
+ */
+
+isc_result_t
+dns_dispatch_gettcp(dns_dispatchmgr_t *mgr, const isc_sockaddr_t *destaddr,
+ const isc_sockaddr_t *localaddr, dns_dispatch_t **dispp);
+/*
+ * Attempt to connect to a existing TCP connection.
+ */
+
+typedef void (*dispatch_cb_t)(isc_result_t eresult, isc_region_t *region,
+ void *cbarg);
+
+isc_result_t
+dns_dispatch_add(dns_dispatch_t *disp, unsigned int options,
+ unsigned int timeout, const isc_sockaddr_t *dest,
+ dispatch_cb_t connected, dispatch_cb_t sent,
+ dispatch_cb_t response, void *arg, dns_messageid_t *idp,
+ dns_dispentry_t **resp);
+/*%<
+ * Add a response entry for this dispatch.
+ *
+ * "*idp" is filled in with the assigned message ID, and *resp is filled in
+ * with the dispatch entry object.
+ *
+ * The 'connected' and 'sent' callbacks are run to inform the caller when
+ * the connect and send functions are complete. The 'timedout' callback
+ * is run to inform the caller that a read has timed out; it may optionally
+ * reset the read timer. The 'response' callback is run for recv results
+ * (response packets, timeouts, or cancellations).
+ *
+ * All the callback functions are sent 'arg' as a parameter.
+ *
+ * Requires:
+ *\li "idp" be non-NULL.
+ *
+ *\li "response" and "arg" be set as appropriate.
+ *
+ *\li "dest" be non-NULL and valid.
+ *
+ *\li "resp" be non-NULL and *resp be NULL
+ *
+ * Ensures:
+ *
+ *\li &lt;id, dest> is a unique tuple. That means incoming messages
+ * are identifiable.
+ *
+ * Returns:
+ *
+ *\li ISC_R_SUCCESS -- all is well.
+ *\li ISC_R_NOMEMORY -- memory could not be allocated.
+ *\li ISC_R_NOMORE -- no more message ids can be allocated
+ * for this destination.
+ */
+
+void
+dns_dispatch_done(dns_dispentry_t **respp);
+/*<
+ * Disconnect a dispatch response entry from its dispatch, cancel all
+ * pending connects and reads in a dispatch entry and shut it down.
+
+ *
+ * Requires:
+ *\li "resp" != NULL and "*resp" contain a value previously allocated
+ * by dns_dispatch_add();
+ */
+
+isc_result_t
+dns_dispatch_getlocaladdress(dns_dispatch_t *disp, isc_sockaddr_t *addrp);
+/*%<
+ * Return the local address for this dispatch.
+ * This currently only works for dispatches using UDP sockets.
+ *
+ * Requires:
+ *\li disp is valid.
+ *\li addrp to be non NULL.
+ *
+ * Returns:
+ *\li ISC_R_SUCCESS
+ *\li ISC_R_NOTIMPLEMENTED
+ */
+
+isc_result_t
+dns_dispentry_getlocaladdress(dns_dispentry_t *resp, isc_sockaddr_t *addrp);
+/*%<
+ * Return the local address for this dispatch entry.
+ *
+ * Requires:
+ *\li resp is valid.
+ *\li addrp to be non NULL.
+ *
+ * Returns:
+ *\li ISC_R_SUCCESS
+ *\li ISC_R_NOTIMPLEMENTED
+ */
+
+dns_dispatch_t *
+dns_dispatchset_get(dns_dispatchset_t *dset);
+/*%<
+ * Retrieve the next dispatch from dispatch set 'dset', and increment
+ * the round-robin counter.
+ *
+ * Requires:
+ *\li dset != NULL
+ */
+
+isc_result_t
+dns_dispatchset_create(isc_mem_t *mctx, dns_dispatch_t *source,
+ dns_dispatchset_t **dsetp, int n);
+/*%<
+ * Given a valid dispatch 'source', create a dispatch set containing
+ * 'n' UDP dispatches, with the remainder filled out by clones of the
+ * source.
+ *
+ * Requires:
+ *\li source is a valid UDP dispatcher
+ *\li dsetp != NULL, *dsetp == NULL
+ */
+
+void
+dns_dispatchset_destroy(dns_dispatchset_t **dsetp);
+/*%<
+ * Dereference all the dispatches in '*dsetp', free the dispatchset
+ * memory, and set *dsetp to NULL.
+ *
+ * Requires:
+ *\li dset is valid
+ */
+
+isc_result_t
+dns_dispatch_getnext(dns_dispentry_t *resp);
+/*%<
+ * Trigger the sending of the next item off the dispatch queue if present.
+ *
+ * Requires:
+ *\li resp is valid
+ */
+
+ISC_LANG_ENDDECLS
diff --git a/lib/dns/include/dns/dlz.h b/lib/dns/include/dns/dlz.h
new file mode 100644
index 0000000..4b61411
--- /dev/null
+++ b/lib/dns/include/dns/dlz.h
@@ -0,0 +1,333 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0 AND ISC
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/*
+ * Copyright (C) 2002 Stichting NLnet, Netherlands, stichting@nlnet.nl.
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the
+ * above copyright notice and this permission notice appear in all
+ * copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND STICHTING NLNET
+ * DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
+ * STICHTING NLNET BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR
+ * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
+ * OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+ * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE
+ * USE OR PERFORMANCE OF THIS SOFTWARE.
+ *
+ * The development of Dynamically Loadable Zones (DLZ) for Bind 9 was
+ * conceived and contributed by Rob Butler.
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the
+ * above copyright notice and this permission notice appear in all
+ * copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND ROB BUTLER
+ * DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
+ * ROB BUTLER BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR
+ * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
+ * OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+ * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE
+ * USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+/*! \file dns/dlz.h */
+
+#pragma once
+
+/*****
+***** Module Info
+*****/
+
+/*
+ * DLZ Interface
+ *
+ * The DLZ interface allows zones to be looked up using a driver instead of
+ * Bind's default in memory zone table.
+ *
+ *
+ * Reliability:
+ * No anticipated impact.
+ *
+ * Resources:
+ *
+ * Security:
+ * No anticipated impact.
+ *
+ * Standards:
+ * None.
+ */
+
+/*****
+***** Imports
+*****/
+
+#include <stdbool.h>
+
+#include <isc/lang.h>
+
+#include <dns/clientinfo.h>
+#include <dns/name.h>
+#include <dns/types.h>
+#include <dns/view.h>
+
+#include <dst/dst.h>
+
+ISC_LANG_BEGINDECLS
+
+/***
+ *** Types
+ ***/
+
+#define DNS_DLZ_MAGIC ISC_MAGIC('D', 'L', 'Z', 'D')
+#define DNS_DLZ_VALID(dlz) ISC_MAGIC_VALID(dlz, DNS_DLZ_MAGIC)
+
+typedef isc_result_t (*dns_dlzallowzonexfr_t)(void *driverarg, void *dbdata,
+ isc_mem_t *mctx,
+ dns_rdataclass_t rdclass,
+ const dns_name_t *name,
+ const isc_sockaddr_t *clientaddr,
+ dns_db_t **dbp);
+
+/*%<
+ * Method prototype. Drivers implementing the DLZ interface MUST
+ * supply an allow zone transfer method. This method is called when
+ * the DNS server is performing a zone transfer query. The driver's
+ * method should return ISC_R_SUCCESS and a database pointer to the
+ * name server if the zone is supported by the database, and zone
+ * transfer is allowed. If the view's transfer acl should be used,
+ * then the driver's method should return ISC_R_DEFAULT. Otherwise,
+ * it should return ISC_R_NOTFOUND if the zone is not supported by
+ * the database, or ISC_R_NOPERM if zone transfers are not allowed.
+ * If an error occurs, the result code should indicate the type of error.
+ */
+
+typedef isc_result_t (*dns_dlzcreate_t)(isc_mem_t *mctx, const char *dlzname,
+ unsigned int argc, char *argv[],
+ void *driverarg, void **dbdata);
+
+/*%<
+ * Method prototype. Drivers implementing the DLZ interface MUST
+ * supply a create method. This method is called when the DNS server
+ * is starting up and creating drivers for use later.
+ */
+
+typedef void (*dns_dlzdestroy_t)(void *driverarg, void **dbdata);
+
+/*%<
+ * Method prototype. Drivers implementing the DLZ interface MUST
+ * supply a destroy method. This method is called when the DNS server
+ * is shutting down and no longer needs the driver.
+ */
+
+typedef isc_result_t (*dns_dlzfindzone_t)(void *driverarg, void *dbdata,
+ isc_mem_t *mctx,
+ dns_rdataclass_t rdclass,
+ const dns_name_t *name,
+ dns_clientinfomethods_t *methods,
+ dns_clientinfo_t *clientinfo,
+ dns_db_t **dbp);
+
+/*%<
+ * Method prototype. Drivers implementing the DLZ interface MUST
+ * supply a find zone method. This method is called when the DNS
+ * server is performing a query. The find zone method will be called
+ * with the longest possible name first, and continue to be called
+ * with successively shorter domain names, until any of the following
+ * occur:
+ *
+ * \li 1) a match is found, and the function returns (ISC_R_SUCCESS)
+ *
+ * \li 2) a problem occurs, and the functions returns anything other
+ * than (ISC_R_NOTFOUND)
+ * \li 3) we run out of domain name labels. I.E. we have tried the
+ * shortest domain name
+ * \li 4) the number of labels in the domain name is less than
+ * min_labels for dns_dlzfindzone
+ *
+ * The driver's find zone method should return ISC_R_SUCCESS and a
+ * database pointer to the name server if the zone is supported by the
+ * database. Otherwise it will return ISC_R_NOTFOUND, and a null
+ * pointer if the zone is not supported. If an error occurs it should
+ * return a result code indicating the type of error.
+ */
+
+typedef isc_result_t (*dns_dlzconfigure_t)(void *driverarg, void *dbdata,
+ dns_view_t *view,
+ dns_dlzdb_t *dlzdb);
+/*%<
+ * Method prototype. Drivers implementing the DLZ interface may
+ * optionally supply a configure method. If supplied, this will be
+ * called immediately after the create method is called. The driver
+ * may call configuration functions during the configure call
+ */
+
+typedef bool (*dns_dlzssumatch_t)(const dns_name_t *signer,
+ const dns_name_t *name,
+ const isc_netaddr_t *tcpaddr,
+ dns_rdatatype_t type, const dst_key_t *key,
+ void *driverarg, void *dbdata);
+/*%<
+ * Method prototype. Drivers implementing the DLZ interface may
+ * optionally supply a ssumatch method. If supplied, this will be
+ * called to authorize update requests
+ */
+
+/*% the methods supplied by a DLZ driver */
+typedef struct dns_dlzmethods {
+ dns_dlzcreate_t create;
+ dns_dlzdestroy_t destroy;
+ dns_dlzfindzone_t findzone;
+ dns_dlzallowzonexfr_t allowzonexfr;
+ dns_dlzconfigure_t configure;
+ dns_dlzssumatch_t ssumatch;
+} dns_dlzmethods_t;
+
+/*% information about a DLZ driver */
+struct dns_dlzimplementation {
+ const char *name;
+ const dns_dlzmethods_t *methods;
+ isc_mem_t *mctx;
+ void *driverarg;
+ ISC_LINK(dns_dlzimplementation_t) link;
+};
+
+typedef isc_result_t (*dlzconfigure_callback_t)(dns_view_t *, dns_dlzdb_t *,
+ dns_zone_t *);
+
+/*% An instance of a DLZ driver */
+struct dns_dlzdb {
+ unsigned int magic;
+ isc_mem_t *mctx;
+ dns_dlzimplementation_t *implementation;
+ void *dbdata;
+ dlzconfigure_callback_t configure_callback;
+ bool search;
+ char *dlzname;
+ ISC_LINK(dns_dlzdb_t) link;
+ dns_ssutable_t *ssutable;
+};
+
+/***
+ *** Method declarations
+ ***/
+
+isc_result_t
+dns_dlzallowzonexfr(dns_view_t *view, const dns_name_t *name,
+ const isc_sockaddr_t *clientaddr, dns_db_t **dbp);
+
+/*%<
+ * This method is called when the DNS server is performing a zone
+ * transfer query. It will call the DLZ driver's allow zone transfer
+ * method.
+ */
+
+isc_result_t
+dns_dlzcreate(isc_mem_t *mctx, const char *dlzname, const char *drivername,
+ unsigned int argc, char *argv[], dns_dlzdb_t **dbp);
+
+/*%<
+ * This method is called when the DNS server is starting up and
+ * creating drivers for use later. It will search the DLZ driver list
+ * for 'drivername' and return a DLZ driver via dbp if a match is
+ * found. If the DLZ driver supplies a create method, this function
+ * will call it.
+ */
+
+void
+dns_dlzdestroy(dns_dlzdb_t **dbp);
+
+/*%<
+ * This method is called when the DNS server is shutting down and no
+ * longer needs the driver. If the DLZ driver supplies a destroy
+ * methods, this function will call it.
+ */
+
+isc_result_t
+dns_dlzregister(const char *drivername, const dns_dlzmethods_t *methods,
+ void *driverarg, isc_mem_t *mctx,
+ dns_dlzimplementation_t **dlzimp);
+
+/*%<
+ * Register a dynamically loadable zones (DLZ) driver for the database
+ * type 'drivername', implemented by the functions in '*methods'.
+ *
+ * dlzimp must point to a NULL dlz_implementation_t pointer. That is,
+ * dlzimp != NULL && *dlzimp == NULL. It will be assigned a value that
+ * will later be used to identify the driver when deregistering it.
+ */
+
+isc_result_t
+dns_dlzstrtoargv(isc_mem_t *mctx, char *s, unsigned int *argcp, char ***argvp);
+
+/*%<
+ * This method is called when the name server is starting up to parse
+ * the DLZ driver command line from named.conf. Basically it splits
+ * up a string into and argc / argv. The primary difference of this
+ * method is items between braces { } are considered only 1 word. for
+ * example the command line "this is { one grouped phrase } and this
+ * isn't" would be parsed into:
+ *
+ * \li argv[0]: "this"
+ * \li argv[1]: "is"
+ * \li argv{2]: " one grouped phrase "
+ * \li argv[3]: "and"
+ * \li argv[4]: "this"
+ * \li argv{5}: "isn't"
+ *
+ * braces should NOT be nested, more than one grouping in the command
+ * line is allowed. Notice, argv[2] has an extra space at the
+ * beginning and end. Extra spaces are not stripped between a
+ * grouping. You can do so in your driver if needed, or be sure not
+ * to put extra spaces before / after the braces.
+ */
+
+void
+dns_dlzunregister(dns_dlzimplementation_t **dlzimp);
+
+/*%<
+ * Removes the dlz driver from the list of registered dlz drivers.
+ * There must be no active dlz drivers of this type when this function
+ * is called.
+ */
+
+typedef isc_result_t
+dns_dlz_writeablezone_t(dns_view_t *view, dns_dlzdb_t *dlzdb,
+ const char *zone_name);
+dns_dlz_writeablezone_t dns_dlz_writeablezone;
+/*%<
+ * creates a writeable DLZ zone. Must be called from within the
+ * configure() method of a DLZ driver.
+ */
+
+isc_result_t
+dns_dlzconfigure(dns_view_t *view, dns_dlzdb_t *dlzdb,
+ dlzconfigure_callback_t callback);
+/*%<
+ * call a DLZ drivers configure method, if supplied
+ */
+
+bool
+dns_dlz_ssumatch(dns_dlzdb_t *dlzdatabase, const dns_name_t *signer,
+ const dns_name_t *name, const isc_netaddr_t *tcpaddr,
+ dns_rdatatype_t type, const dst_key_t *key);
+/*%<
+ * call a DLZ drivers ssumatch method, if supplied. Otherwise return false
+ */
+
+ISC_LANG_ENDDECLS
diff --git a/lib/dns/include/dns/dlz_dlopen.h b/lib/dns/include/dns/dlz_dlopen.h
new file mode 100644
index 0000000..a0059ff
--- /dev/null
+++ b/lib/dns/include/dns/dlz_dlopen.h
@@ -0,0 +1,156 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/*! \file dns/dlz_dlopen.h */
+
+#pragma once
+
+#include <inttypes.h>
+#include <stdbool.h>
+
+#include <dns/sdlz.h>
+
+ISC_LANG_BEGINDECLS
+
+/*
+ * This header provides a minimal set of defines and typedefs needed
+ * for the entry points of an external DLZ module for bind9.
+ */
+
+#define DLZ_DLOPEN_VERSION 3
+#define DLZ_DLOPEN_AGE 0
+
+/*
+ * dlz_dlopen_version() is required for all DLZ external drivers. It
+ * should return DLZ_DLOPEN_VERSION
+ */
+typedef int
+dlz_dlopen_version_t(unsigned int *flags);
+
+/*
+ * dlz_dlopen_create() is required for all DLZ external drivers.
+ */
+typedef isc_result_t
+dlz_dlopen_create_t(const char *dlzname, unsigned int argc, char *argv[],
+ void **dbdata, ...);
+
+/*
+ * dlz_dlopen_destroy() is optional, and will be called when the
+ * driver is unloaded if supplied
+ */
+typedef void
+dlz_dlopen_destroy_t(void *dbdata);
+
+/*
+ * dlz_dlopen_findzonedb() is required for all DLZ external drivers
+ */
+typedef isc_result_t
+dlz_dlopen_findzonedb_t(void *dbdata, const char *name,
+ dns_clientinfomethods_t *methods,
+ dns_clientinfo_t *clientinfo);
+
+/*
+ * dlz_dlopen_lookup() is required for all DLZ external drivers
+ */
+typedef isc_result_t
+dlz_dlopen_lookup_t(const char *zone, const char *name, void *dbdata,
+ dns_sdlzlookup_t *lookup, dns_clientinfomethods_t *methods,
+ dns_clientinfo_t *clientinfo);
+
+/*
+ * dlz_dlopen_authority is optional() if dlz_dlopen_lookup()
+ * supplies authority information for the dns record
+ */
+typedef isc_result_t
+dlz_dlopen_authority_t(const char *zone, void *dbdata,
+ dns_sdlzlookup_t *lookup);
+
+/*
+ * dlz_dlopen_allowzonexfr() is optional, and should be supplied if
+ * you want to support zone transfers
+ */
+typedef isc_result_t
+dlz_dlopen_allowzonexfr_t(void *dbdata, const char *name, const char *client);
+
+/*
+ * dlz_dlopen_allnodes() is optional, but must be supplied if supply a
+ * dlz_dlopen_allowzonexfr() function
+ */
+typedef isc_result_t
+dlz_dlopen_allnodes_t(const char *zone, void *dbdata,
+ dns_sdlzallnodes_t *allnodes);
+
+/*
+ * dlz_dlopen_newversion() is optional. It should be supplied if you
+ * want to support dynamic updates.
+ */
+typedef isc_result_t
+dlz_dlopen_newversion_t(const char *zone, void *dbdata, void **versionp);
+
+/*
+ * dlz_closeversion() is optional, but must be supplied if you supply
+ * a dlz_newversion() function
+ */
+typedef void
+dlz_dlopen_closeversion_t(const char *zone, bool commit, void *dbdata,
+ void **versionp);
+
+/*
+ * dlz_dlopen_configure() is optional, but must be supplied if you
+ * want to support dynamic updates
+ */
+typedef isc_result_t
+dlz_dlopen_configure_t(dns_view_t *view, dns_dlzdb_t *dlzdb, void *dbdata);
+
+/*
+ * dlz_dlopen_setclientcallback() is optional, but must be supplied if you
+ * want to retrieve information about the client (e.g., source address)
+ * before sending a replay.
+ */
+typedef isc_result_t
+dlz_dlopen_setclientcallback_t(dns_view_t *view, void *dbdata);
+
+/*
+ * dlz_dlopen_ssumatch() is optional, but must be supplied if you want
+ * to support dynamic updates
+ */
+typedef bool
+dlz_dlopen_ssumatch_t(const char *signer, const char *name, const char *tcpaddr,
+ const char *type, const char *key, uint32_t keydatalen,
+ unsigned char *keydata, void *dbdata);
+
+/*
+ * dlz_dlopen_addrdataset() is optional, but must be supplied if you
+ * want to support dynamic updates
+ */
+typedef isc_result_t
+dlz_dlopen_addrdataset_t(const char *name, const char *rdatastr, void *dbdata,
+ void *version);
+
+/*
+ * dlz_dlopen_subrdataset() is optional, but must be supplied if you
+ * want to support dynamic updates
+ */
+typedef isc_result_t
+dlz_dlopen_subrdataset_t(const char *name, const char *rdatastr, void *dbdata,
+ void *version);
+
+/*
+ * dlz_dlopen_delrdataset() is optional, but must be supplied if you
+ * want to support dynamic updates
+ */
+typedef isc_result_t
+dlz_dlopen_delrdataset_t(const char *name, const char *type, void *dbdata,
+ void *version);
+
+ISC_LANG_ENDDECLS
diff --git a/lib/dns/include/dns/dns64.h b/lib/dns/include/dns/dns64.h
new file mode 100644
index 0000000..8dd0103
--- /dev/null
+++ b/lib/dns/include/dns/dns64.h
@@ -0,0 +1,192 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#pragma once
+
+#include <stdbool.h>
+
+#include <isc/lang.h>
+
+#include <dns/types.h>
+
+ISC_LANG_BEGINDECLS
+
+/*
+ * dns_dns64_create() flags.
+ */
+#define DNS_DNS64_RECURSIVE_ONLY \
+ 0x01 /* If set then this record \
+ * only applies to recursive \
+ * queries. \
+ */
+#define DNS_DNS64_BREAK_DNSSEC \
+ 0x02 /* If set then still perform \
+ * DNSSEC synthesis even \
+ * though the result would \
+ * fail validation. \
+ */
+
+/*
+ * dns_dns64_aaaaok() and dns_dns64_aaaafroma() flags.
+ */
+#define DNS_DNS64_RECURSIVE 0x01 /* Recursive query. */
+#define DNS_DNS64_DNSSEC 0x02 /* DNSSEC sensitive query. */
+
+isc_result_t
+dns_dns64_create(isc_mem_t *mctx, const isc_netaddr_t *prefix,
+ unsigned int prefixlen, const isc_netaddr_t *suffix,
+ dns_acl_t *client, dns_acl_t *mapped, dns_acl_t *excluded,
+ unsigned int flags, dns_dns64_t **dns64);
+/*
+ * Create a dns64 record which is used to identify the set of clients
+ * it applies to and how to perform the DNS64 synthesis.
+ *
+ * 'prefix' and 'prefixlen' defined the leading bits of the AAAA records
+ * to be synthesised. 'suffix' defines the bits after the A records bits.
+ * If suffix is NULL zeros will be used for these bits. 'client' defines
+ * for which clients this record applies. If 'client' is NULL then all
+ * clients apply. 'mapped' defines which A records are candidated for
+ * mapping. If 'mapped' is NULL then all A records will be mapped.
+ * 'excluded' defines which AAAA are to be treated as non-existent for the
+ * purposed of determining whether to perform synthesis. If 'excluded' is
+ * NULL then no AAAA records prevent synthesis.
+ *
+ * If DNS_DNS64_RECURSIVE_ONLY is set then the record will only match if
+ * DNS_DNS64_RECURSIVE is set when calling dns_dns64_aaaaok() and
+ * dns_dns64_aaaafroma().
+ *
+ * If DNS_DNS64_BREAK_DNSSEC is set then the record will still apply if
+ * DNS_DNS64_DNSSEC is set when calling dns_dns64_aaaaok() and
+ * dns_dns64_aaaafroma() otherwise the record will be ignored.
+ *
+ * Requires:
+ * 'mctx' to be valid.
+ * 'prefix' to be valid and the address family to AF_INET6.
+ * 'prefixlen' to be one of 32, 40, 48, 56, 72 and 96.
+ * the bits not covered by prefixlen in prefix to
+ * be zero.
+ * 'suffix' to be NULL or the address family be set to AF_INET6
+ * and the leading 'prefixlen' + 32 bits of the 'suffix'
+ * to be zero. If 'prefixlen' is 40, 48 or 56 then the
+ * the leading 'prefixlen' + 40 bits of 'suffix' must be
+ * zero.
+ * 'client' to be NULL or a valid acl.
+ * 'mapped' to be NULL or a valid acl.
+ * 'excluded' to be NULL or a valid acl.
+ *
+ * Returns:
+ * ISC_R_SUCCESS
+ * ISC_R_NOMEMORY
+ */
+
+void
+dns_dns64_destroy(dns_dns64_t **dns64p);
+/*
+ * Destroys a dns64 record.
+ *
+ * Requires the record to not be linked.
+ */
+
+isc_result_t
+dns_dns64_aaaafroma(const dns_dns64_t *dns64, const isc_netaddr_t *reqaddr,
+ const dns_name_t *reqsigner, dns_aclenv_t *env,
+ unsigned int flags, unsigned char *a, unsigned char *aaaa);
+/*
+ * dns_dns64_aaaafroma() determines whether to perform a DNS64 address
+ * synthesis from 'a' based on 'dns64', 'reqaddr', 'reqsigner', 'env',
+ * 'flags' and 'aaaa'. If synthesis is performed then the result is
+ * written to '*aaaa'.
+ *
+ * The synthesised address will be of the form:
+ *
+ * <prefix bits><a bits><suffix bits>
+ *
+ * If <a bits> straddle bits 64-71 of the AAAA record, then 8 zero bits will
+ * be inserted at bits 64-71.
+ *
+ * Requires:
+ * 'dns64' to be valid.
+ * 'reqaddr' to be valid.
+ * 'reqsigner' to be NULL or valid.
+ * 'env' to be valid.
+ * 'a' to point to a IPv4 address in network order.
+ * 'aaaa' to point to a IPv6 address buffer in network order.
+ *
+ * Returns:
+ * ISC_R_SUCCESS if synthesis was performed.
+ * DNS_R_DISALLOWED if there is no match.
+ */
+
+dns_dns64_t *
+dns_dns64_next(dns_dns64_t *dns64);
+/*
+ * Return the next dns64 record in the list.
+ */
+
+void
+dns_dns64_append(dns_dns64list_t *list, dns_dns64_t *dns64);
+/*
+ * Append the dns64 record to the list.
+ */
+
+void
+dns_dns64_unlink(dns_dns64list_t *list, dns_dns64_t *dns64);
+/*
+ * Unlink the dns64 record from the list.
+ */
+
+bool
+dns_dns64_aaaaok(const dns_dns64_t *dns64, const isc_netaddr_t *reqaddr,
+ const dns_name_t *reqsigner, dns_aclenv_t *env,
+ unsigned int flags, dns_rdataset_t *rdataset, bool *aaaaok,
+ size_t aaaaoklen);
+/*
+ * Determine if there are any non-excluded AAAA records in from the
+ * matching dns64 records in the list starting at 'dns64'. If there
+ * is a non-excluded address return true. If all addresses are
+ * excluded in the matched records return false. If no records
+ * match then return true.
+ *
+ * If aaaaok is defined then dns_dns64_aaaaok() return a array of which
+ * addresses in 'rdataset' were deemed to not be exclude by any matching
+ * record. If there are no matching records then all entries are set
+ * to true.
+ *
+ * Requires
+ * 'rdataset' to be valid and to be for type AAAA and class IN.
+ * 'aaaaoklen' must match the number of records in 'rdataset'
+ * if 'aaaaok' in non NULL.
+ */
+
+isc_result_t
+dns_dns64_findprefix(dns_rdataset_t *rdataset, isc_netprefix_t *prefix,
+ size_t *len);
+/*
+ * Look through 'rdataset' for AAAA pairs which define encoded DNS64 prefixes.
+ * 'len' should be set to the number of entries in 'prefix' and returns
+ * the number of prefixes discovered. This may be bigger than those that
+ * can fit in 'prefix'.
+ *
+ * Requires
+ * 'rdataset' to be valid and to be for type AAAA and class IN.
+ * 'prefix' to be non NULL.
+ * 'len' to be non NULL and non zero.
+ *
+ * Returns
+ * ISC_R_SUCCESS
+ * ISC_R_NOSPACE if there are more prefixes discovered than can fit
+ * into 'prefix'.
+ * ISC_R_NOTFOUND no prefixes where found.
+ */
+
+ISC_LANG_ENDDECLS
diff --git a/lib/dns/include/dns/dnsrps.h b/lib/dns/include/dns/dnsrps.h
new file mode 100644
index 0000000..15066f0
--- /dev/null
+++ b/lib/dns/include/dns/dnsrps.h
@@ -0,0 +1,112 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#pragma once
+
+#include <inttypes.h>
+#include <stdbool.h>
+
+#include <isc/lang.h>
+
+#include <dns/types.h>
+
+#ifdef USE_DNSRPS
+
+#include <dns/librpz.h>
+#include <dns/rpz.h>
+
+/*
+ * Error message if dlopen(librpz) failed.
+ */
+extern librpz_emsg_t librpz_lib_open_emsg;
+
+/*
+ * These shim BIND9 database, node, and rdataset are handles on RRs from librpz.
+ *
+ * All of these structures are used by a single thread and so need no locks.
+ *
+ * rpsdb_t holds the state for a set of RPZ queries.
+ *
+ * rpsnode_t is a link to the rpsdb_t for the set of RPZ queries
+ * and a flag saying whether it is pretending to be a node with RRs for
+ * the qname or the node with the SOA for the zone containing the rewritten
+ * RRs or justifying NXDOMAIN.
+ */
+typedef struct {
+ uint8_t unused;
+} rpsnode_t;
+typedef struct rpsdb {
+ dns_db_t common;
+ int ref_cnt;
+ librpz_result_id_t hit_id;
+ librpz_result_t result;
+ librpz_rsp_t *rsp;
+ librpz_domain_buf_t origin_buf;
+ const dns_name_t *qname;
+ rpsnode_t origin_node;
+ rpsnode_t data_node;
+} rpsdb_t;
+
+/*
+ * Convert a dnsrps policy to a classic BIND9 RPZ policy.
+ */
+dns_rpz_policy_t
+dns_dnsrps_2policy(librpz_policy_t rps_policy);
+
+/*
+ * Convert a dnsrps trigger to a classic BIND9 RPZ rewrite or trigger type.
+ */
+dns_rpz_type_t
+dns_dnsrps_trig2type(librpz_trig_t trig);
+
+/*
+ * Convert a classic BIND9 RPZ rewrite or trigger type to a librpz trigger type.
+ */
+librpz_trig_t
+dns_dnsrps_type2trig(dns_rpz_type_t type);
+
+/*
+ * Start dnsrps for the entire server.
+ */
+isc_result_t
+dns_dnsrps_server_create(void);
+
+/*
+ * Stop dnsrps for the entire server.
+ */
+void
+dns_dnsrps_server_destroy(void);
+
+/*
+ * Ready dnsrps for a view.
+ */
+isc_result_t
+dns_dnsrps_view_init(dns_rpz_zones_t *new, char *rps_cstr);
+
+/*
+ * Connect to and start the dnsrps daemon, dnsrpzd.
+ */
+isc_result_t
+dns_dnsrps_connect(dns_rpz_zones_t *rpzs);
+
+/*
+ * Get ready to try dnsrps rewriting.
+ */
+isc_result_t
+dns_dnsrps_rewrite_init(librpz_emsg_t *emsg, dns_rpz_st_t *st,
+ dns_rpz_zones_t *rpzs, const dns_name_t *qname,
+ isc_mem_t *mctx, bool have_rd);
+
+#endif /* USE_DNSRPS */
+
+ISC_LANG_ENDDECLS
diff --git a/lib/dns/include/dns/dnssec.h b/lib/dns/include/dns/dnssec.h
new file mode 100644
index 0000000..6add7d5
--- /dev/null
+++ b/lib/dns/include/dns/dnssec.h
@@ -0,0 +1,399 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#pragma once
+
+/*! \file dns/dnssec.h */
+
+#include <stdbool.h>
+
+#include <isc/lang.h>
+#include <isc/stats.h>
+#include <isc/stdtime.h>
+
+#include <dns/diff.h>
+#include <dns/types.h>
+
+#include <dst/dst.h>
+
+ISC_LANG_BEGINDECLS
+
+extern isc_stats_t *dns_dnssec_stats;
+
+/*%< Maximum number of keys supported in a zone. */
+#define DNS_MAXZONEKEYS 32
+
+/*
+ * Indicates how the signer found this key: in the key repository, at the
+ * zone apex, or specified by the user.
+ */
+typedef enum {
+ dns_keysource_unknown,
+ dns_keysource_repository,
+ dns_keysource_zoneapex,
+ dns_keysource_user
+} dns_keysource_t;
+
+/*
+ * A DNSSEC key and hints about its intended use gleaned from metadata
+ */
+struct dns_dnsseckey {
+ dst_key_t *key;
+ bool hint_publish; /*% metadata says to publish */
+ bool force_publish; /*% publish regardless of metadata
+ * */
+ bool hint_sign; /*% metadata says to sign with this
+ * key */
+ bool force_sign; /*% sign with key regardless of
+ * metadata */
+ bool hint_revoke; /*% metadata says revoke key */
+ bool hint_remove; /*% metadata says *don't* publish */
+ bool is_active; /*% key is already active */
+ bool first_sign; /*% key is newly becoming active */
+ bool purge; /*% remove key files */
+ unsigned int prepublish; /*% how long until active? */
+ dns_keysource_t source; /*% how the key was found */
+ bool ksk; /*% this is a key-signing key */
+ bool zsk; /*% this is a zone-signing key */
+ bool legacy; /*% this is old-style key with no
+ * metadata (possibly generated by
+ * an older version of BIND9) and
+ * should be ignored when searching
+ * for keys to import into the zone */
+ unsigned int index; /*% position in list */
+ ISC_LINK(dns_dnsseckey_t) link;
+};
+
+isc_result_t
+dns_dnssec_keyfromrdata(const dns_name_t *name, const dns_rdata_t *rdata,
+ isc_mem_t *mctx, dst_key_t **key);
+/*%<
+ * Creates a DST key from a DNS record. Basically a wrapper around
+ * dst_key_fromdns().
+ *
+ * Requires:
+ *\li 'name' is not NULL
+ *\li 'rdata' is not NULL
+ *\li 'mctx' is not NULL
+ *\li 'key' is not NULL
+ *\li '*key' is NULL
+ *
+ * Returns:
+ *\li #ISC_R_SUCCESS
+ *\li #ISC_R_NOMEMORY
+ *\li DST_R_INVALIDPUBLICKEY
+ *\li various errors from dns_name_totext
+ */
+
+isc_result_t
+dns_dnssec_sign(const dns_name_t *name, dns_rdataset_t *set, dst_key_t *key,
+ isc_stdtime_t *inception, isc_stdtime_t *expire,
+ isc_mem_t *mctx, isc_buffer_t *buffer, dns_rdata_t *sigrdata);
+/*%<
+ * Generates a RRSIG record covering this rdataset. This has no effect
+ * on existing RRSIG records.
+ *
+ * Requires:
+ *\li 'name' (the owner name of the record) is a valid name
+ *\li 'set' is a valid rdataset
+ *\li 'key' is a valid key
+ *\li 'inception' is not NULL
+ *\li 'expire' is not NULL
+ *\li 'mctx' is not NULL
+ *\li 'buffer' is not NULL
+ *\li 'sigrdata' is not NULL
+ *
+ * Returns:
+ *\li #ISC_R_SUCCESS
+ *\li #ISC_R_NOMEMORY
+ *\li #ISC_R_NOSPACE
+ *\li #DNS_R_INVALIDTIME - the expiration is before the inception
+ *\li #DNS_R_KEYUNAUTHORIZED - the key cannot sign this data (either
+ * it is not a zone key or its flags prevent
+ * authentication)
+ *\li DST_R_*
+ */
+
+isc_result_t
+dns_dnssec_verify(const dns_name_t *name, dns_rdataset_t *set, dst_key_t *key,
+ bool ignoretime, unsigned int maxbits, isc_mem_t *mctx,
+ dns_rdata_t *sigrdata, dns_name_t *wild);
+/*%<
+ * Verifies the RRSIG record covering this rdataset signed by a specific
+ * key. This does not determine if the key's owner is authorized to sign
+ * this record, as this requires a resolver or database.
+ * If 'ignoretime' is true, temporal validity will not be checked.
+ *
+ * 'maxbits' specifies the maximum number of rsa exponent bits accepted.
+ *
+ * Requires:
+ *\li 'name' (the owner name of the record) is a valid name
+ *\li 'set' is a valid rdataset
+ *\li 'key' is a valid key
+ *\li 'mctx' is not NULL
+ *\li 'sigrdata' is a valid rdata containing a SIG record
+ *\li 'wild' if non-NULL then is a valid and has a buffer.
+ *
+ * Returns:
+ *\li #ISC_R_SUCCESS
+ *\li #ISC_R_NOMEMORY
+ *\li #DNS_R_FROMWILDCARD - the signature is valid and is from
+ * a wildcard expansion. dns_dnssec_verify2() only.
+ * 'wild' contains the name of the wildcard if non-NULL.
+ *\li #DNS_R_SIGINVALID - the signature fails to verify
+ *\li #DNS_R_SIGEXPIRED - the signature has expired
+ *\li #DNS_R_SIGFUTURE - the signature's validity period has not begun
+ *\li #DNS_R_KEYUNAUTHORIZED - the key cannot sign this data (either
+ * it is not a zone key or its flags prevent
+ * authentication)
+ *\li DST_R_*
+ */
+
+/*@{*/
+isc_result_t
+dns_dnssec_findzonekeys(dns_db_t *db, dns_dbversion_t *ver, dns_dbnode_t *node,
+ const dns_name_t *name, const char *directory,
+ isc_stdtime_t now, isc_mem_t *mctx,
+ unsigned int maxkeys, dst_key_t **keys,
+ unsigned int *nkeys);
+
+/*%<
+ * Finds a set of zone keys.
+ * XXX temporary - this should be handled in dns_zone_t.
+ */
+/*@}*/
+
+bool
+dns_dnssec_keyactive(dst_key_t *key, isc_stdtime_t now);
+/*%<
+ *
+ * Returns true if 'key' is active as of the time specified
+ * in 'now' (i.e., if the activation date has passed, inactivation or
+ * deletion date has not yet been reached, and the key is not revoked
+ * -- or if it is a legacy key without metadata). Otherwise returns
+ * false.
+ *
+ * Requires:
+ *\li 'key' is a valid key
+ */
+
+isc_result_t
+dns_dnssec_signmessage(dns_message_t *msg, dst_key_t *key);
+/*%<
+ * Signs a message with a SIG(0) record. This is implicitly called by
+ * dns_message_renderend() if msg->sig0key is not NULL.
+ *
+ * Requires:
+ *\li 'msg' is a valid message
+ *\li 'key' is a valid key that can be used for signing
+ *
+ * Returns:
+ *\li #ISC_R_SUCCESS
+ *\li #ISC_R_NOMEMORY
+ *\li DST_R_*
+ */
+
+isc_result_t
+dns_dnssec_verifymessage(isc_buffer_t *source, dns_message_t *msg,
+ dst_key_t *key);
+/*%<
+ * Verifies a message signed by a SIG(0) record. This is not
+ * called implicitly by dns_message_parse(). If dns_message_signer()
+ * is called before dns_dnssec_verifymessage(), it will return
+ * #DNS_R_NOTVERIFIEDYET. dns_dnssec_verifymessage() will set
+ * the verified_sig0 flag in msg if the verify succeeds, and
+ * the sig0status field otherwise.
+ *
+ * Requires:
+ *\li 'source' is a valid buffer containing the unparsed message
+ *\li 'msg' is a valid message
+ *\li 'key' is a valid key
+ *
+ * Returns:
+ *\li #ISC_R_SUCCESS
+ *\li #ISC_R_NOMEMORY
+ *\li #ISC_R_NOTFOUND - no SIG(0) was found
+ *\li #DNS_R_SIGINVALID - the SIG record is not well-formed or
+ * was not generated by the key.
+ *\li DST_R_*
+ */
+
+bool
+dns_dnssec_selfsigns(dns_rdata_t *rdata, const dns_name_t *name,
+ dns_rdataset_t *rdataset, dns_rdataset_t *sigrdataset,
+ bool ignoretime, isc_mem_t *mctx);
+
+bool
+dns_dnssec_signs(dns_rdata_t *rdata, const dns_name_t *name,
+ dns_rdataset_t *rdataset, dns_rdataset_t *sigrdataset,
+ bool ignoretime, isc_mem_t *mctx);
+/*%<
+ * Verify that 'rdataset' is validly signed in 'sigrdataset' by
+ * the key in 'rdata'.
+ *
+ * dns_dnssec_selfsigns() requires that rdataset be a DNSKEY or KEY
+ * rrset. dns_dnssec_signs() works on any rrset.
+ */
+
+isc_result_t
+dns_dnsseckey_create(isc_mem_t *mctx, dst_key_t **dstkey,
+ dns_dnsseckey_t **dkp);
+/*%<
+ * Create and initialize a dns_dnsseckey_t structure.
+ *
+ * Requires:
+ *\li 'dkp' is not NULL and '*dkp' is NULL.
+ *
+ * Returns:
+ *\li #ISC_R_SUCCESS
+ *\li #ISC_R_NOMEMORY
+ */
+
+void
+dns_dnsseckey_destroy(isc_mem_t *mctx, dns_dnsseckey_t **dkp);
+/*%<
+ * Reclaim a dns_dnsseckey_t structure.
+ *
+ * Requires:
+ *\li 'dkp' is not NULL and '*dkp' is not NULL.
+ *
+ * Ensures:
+ *\li '*dkp' is NULL.
+ */
+
+void
+dns_dnssec_get_hints(dns_dnsseckey_t *key, isc_stdtime_t now);
+/*%<
+ * Get hints on DNSSEC key whether this key can be published
+ * and/or is active. Timing metadata is compared to 'now'.
+ *
+ * Requires:
+ *\li 'key' is a pointer to a DNSSEC key and is not NULL.
+ */
+
+isc_result_t
+dns_dnssec_findmatchingkeys(const dns_name_t *origin, const char *directory,
+ isc_stdtime_t now, isc_mem_t *mctx,
+ dns_dnsseckeylist_t *keylist);
+/*%<
+ * Search 'directory' for K* key files matching the name in 'origin'.
+ * Append all such keys, along with use hints gleaned from their
+ * metadata, onto 'keylist'. Skip any unsupported algorithms.
+ *
+ * Requires:
+ *\li 'keylist' is not NULL
+ *
+ * Returns:
+ *\li #ISC_R_SUCCESS
+ *\li #ISC_R_NOTFOUND
+ *\li #ISC_R_NOMEMORY
+ *\li any error returned by dns_name_totext(), isc_dir_open(), or
+ * dst_key_fromnamedfile()
+ *
+ * Ensures:
+ *\li On error, keylist is unchanged
+ */
+
+isc_result_t
+dns_dnssec_keylistfromrdataset(const dns_name_t *origin, const char *directory,
+ isc_mem_t *mctx, dns_rdataset_t *keyset,
+ dns_rdataset_t *keysigs, dns_rdataset_t *soasigs,
+ bool savekeys, bool publickey,
+ dns_dnsseckeylist_t *keylist);
+/*%<
+ * Append the contents of a DNSKEY rdataset 'keyset' to 'keylist'.
+ * Omit duplicates. If 'publickey' is false, search 'directory' for
+ * matching key files, and load the private keys that go with
+ * the public ones. If 'savekeys' is true, mark the keys so
+ * they will not be deleted or inactivated regardless of metadata.
+ *
+ * 'keysigs' and 'soasigs', if not NULL and associated, contain the
+ * RRSIGS for the DNSKEY and SOA records respectively and are used to mark
+ * whether a key is already active in the zone.
+ */
+
+isc_result_t
+dns_dnssec_updatekeys(dns_dnsseckeylist_t *keys, dns_dnsseckeylist_t *newkeys,
+ dns_dnsseckeylist_t *removed, const dns_name_t *origin,
+ dns_ttl_t hint_ttl, dns_diff_t *diff, isc_mem_t *mctx,
+ void (*report)(const char *, ...)
+ ISC_FORMAT_PRINTF(1, 2));
+/*%<
+ * Update the list of keys in 'keys' with new key information in 'newkeys'.
+ *
+ * For each key in 'newkeys', see if it has a match in 'keys'.
+ * - If not, and if the metadata says the key should be published:
+ * add it to 'keys', and place a dns_difftuple into 'diff' so
+ * the key can be added to the DNSKEY set. If the metadata says it
+ * should be active, set the first_sign flag.
+ * - If so, and if the metadata says it should be removed:
+ * remove it from 'keys', and place a dns_difftuple into 'diff' so
+ * the key can be removed from the DNSKEY set. if 'removed' is non-NULL,
+ * copy the key into that list; otherwise destroy it.
+ * - Otherwise, make sure keys has current metadata.
+ *
+ * 'hint_ttl' is the TTL to use for the DNSKEY RRset if there is no
+ * existing RRset, and if none of the keys to be added has a default TTL
+ * (in which case we would use the shortest one). If the TTL is longer
+ * than the time until a new key will be activated, then we have to delay
+ * the key's activation.
+ *
+ * 'report' points to a function for reporting status.
+ *
+ * On completion, any remaining keys in 'newkeys' are freed.
+ */
+
+isc_result_t
+dns_dnssec_syncupdate(dns_dnsseckeylist_t *keys, dns_dnsseckeylist_t *rmkeys,
+ dns_rdataset_t *cds, dns_rdataset_t *cdnskey,
+ isc_stdtime_t now, dns_ttl_t hint_ttl, dns_diff_t *diff,
+ isc_mem_t *mctx);
+/*%<
+ * Update the CDS and CDNSKEY RRsets, adding and removing keys as needed.
+ *
+ * Returns:
+ *\li ISC_R_SUCCESS
+ *\li Other values indicate error
+ */
+
+isc_result_t
+dns_dnssec_syncdelete(dns_rdataset_t *cds, dns_rdataset_t *cdnskey,
+ dns_name_t *origin, dns_rdataclass_t zclass,
+ dns_ttl_t ttl, dns_diff_t *diff, isc_mem_t *mctx,
+ bool expect_cds_delete, bool expect_cdnskey_delete);
+/*%<
+ * Add or remove the CDS DELETE record and the CDNSKEY DELETE record.
+ * If 'expect_cds_delete' is true, the CDS DELETE record should be present.
+ * Otherwise, the CDS DELETE record must be removed from the RRsets (if
+ * present). If 'expect_cdnskey_delete' is true, the CDNSKEY DELETE record
+ * should be present. Otherwise, the CDNSKEY DELETE record must be removed
+ * from the RRsets (if present).
+ *
+ * Returns:
+ *\li ISC_R_SUCCESS
+ *\li Other values indicate error
+ */
+
+isc_result_t
+dns_dnssec_matchdskey(dns_name_t *name, dns_rdata_t *dsrdata,
+ dns_rdataset_t *keyset, dns_rdata_t *keyrdata);
+/*%<
+ * Given a DS rdata and a DNSKEY RRset, find the DNSKEY rdata that matches
+ * the DS, and place it in 'keyrdata'.
+ *
+ * Returns:
+ *\li ISC_R_SUCCESS
+ *\li ISC_R_NOTFOUND
+ *\li Other values indicate error
+ */
+ISC_LANG_ENDDECLS
diff --git a/lib/dns/include/dns/dnstap.h b/lib/dns/include/dns/dnstap.h
new file mode 100644
index 0000000..ed2c199
--- /dev/null
+++ b/lib/dns/include/dns/dnstap.h
@@ -0,0 +1,393 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#pragma once
+
+/*****
+***** Module Info
+*****/
+
+/*! \file
+ * \brief
+ * The dt (dnstap) module provides fast passive logging of DNS messages.
+ * Protocol Buffers. The protobuf schema for Dnstap messages is in the
+ * file dnstap.proto, which is compiled to dnstap.pb-c.c and dnstap.pb-c.h.
+ */
+
+#include <inttypes.h>
+#include <stdbool.h>
+
+struct fstrm_iothr_options;
+
+#include <isc/log.h>
+#include <isc/refcount.h>
+#include <isc/region.h>
+#include <isc/sockaddr.h>
+#include <isc/time.h>
+#include <isc/types.h>
+
+#include <dns/name.h>
+#include <dns/rdataclass.h>
+#include <dns/rdatatype.h>
+#include <dns/types.h>
+
+/*%
+ * Dnstap message types:
+ *
+ * STUB QUERY: SQ
+ * STUB RESPONSE: SR
+ * CLIENT QUERY: CQ
+ * CLIENT RESPONSE: CR
+ * AUTH QUERY: AQ
+ * AUTH RESPONSE: AR
+ * RESOLVER QUERY: RQ
+ * RESOLVER RESPONSE: RR
+ * FORWARDER QUERY: FQ
+ * FORWARDER RESPONSE: FR
+ */
+
+#define DNS_DTTYPE_SQ 0x0001
+#define DNS_DTTYPE_SR 0x0002
+#define DNS_DTTYPE_CQ 0x0004
+#define DNS_DTTYPE_CR 0x0008
+#define DNS_DTTYPE_AQ 0x0010
+#define DNS_DTTYPE_AR 0x0020
+#define DNS_DTTYPE_RQ 0x0040
+#define DNS_DTTYPE_RR 0x0080
+#define DNS_DTTYPE_FQ 0x0100
+#define DNS_DTTYPE_FR 0x0200
+#define DNS_DTTYPE_TQ 0x0400
+#define DNS_DTTYPE_TR 0x0800
+#define DNS_DTTYPE_UQ 0x1000
+#define DNS_DTTYPE_UR 0x2000
+
+#define DNS_DTTYPE_QUERY \
+ (DNS_DTTYPE_SQ | DNS_DTTYPE_CQ | DNS_DTTYPE_AQ | DNS_DTTYPE_RQ | \
+ DNS_DTTYPE_FQ | DNS_DTTYPE_TQ | DNS_DTTYPE_UQ)
+#define DNS_DTTYPE_RESPONSE \
+ (DNS_DTTYPE_SR | DNS_DTTYPE_CR | DNS_DTTYPE_AR | DNS_DTTYPE_RR | \
+ DNS_DTTYPE_FR | DNS_DTTYPE_TR | DNS_DTTYPE_UR)
+#define DNS_DTTYPE_ALL (DNS_DTTYPE_QUERY | DNS_DTTYPE_RESPONSE)
+
+typedef enum {
+ dns_dtmode_none = 0,
+ dns_dtmode_file,
+ dns_dtmode_unix
+} dns_dtmode_t;
+
+typedef struct dns_dthandle dns_dthandle_t;
+
+#ifdef HAVE_DNSTAP
+struct dns_dtdata {
+ isc_mem_t *mctx;
+
+ void *frame;
+
+ bool query;
+ bool tcp;
+ dns_dtmsgtype_t type;
+
+ isc_time_t qtime;
+ isc_time_t rtime;
+
+ isc_region_t qaddr;
+ isc_region_t raddr;
+
+ uint32_t qport;
+ uint32_t rport;
+
+ isc_region_t msgdata;
+ dns_message_t *msg;
+
+ char namebuf[DNS_NAME_FORMATSIZE];
+ char typebuf[DNS_RDATATYPE_FORMATSIZE];
+ char classbuf[DNS_RDATACLASS_FORMATSIZE];
+};
+#endif /* HAVE_DNSTAP */
+
+isc_result_t
+dns_dt_create(isc_mem_t *mctx, dns_dtmode_t mode, const char *path,
+ struct fstrm_iothr_options **foptp, isc_task_t *reopen_task,
+ dns_dtenv_t **envp);
+/*%<
+ * Create and initialize the dnstap environment.
+ *
+ * There should be a single global dnstap environment for the server;
+ * copies of it will be attached to each view.
+ *
+ * Notes:
+ *
+ *\li 'path' refers to a UNIX domain socket by default. It may
+ * optionally be prepended with "socket:" or "file:". If prepended
+ * with "file:", then dnstap logs are sent to a file instead of a
+ * socket.
+ *
+ *\li '*foptp' set the options for fstrm_iothr_init(). '*foptp' must have
+ * have had the number of input queues set and this should be set
+ * to the number of worker threads. Additionally the queue model
+ * should also be set. Other options may be set if desired.
+ * If dns_dt_create succeeds the *foptp is set to NULL.
+ *
+ *\li 'reopen_task' needs to be set to the task in the context of which
+ * dns_dt_reopen() will be called. This is not an optional parameter:
+ * using dns_dt_create() (which sets 'reopen_task' to NULL) is only
+ * allowed in unit tests.
+ *
+ * Requires:
+ *
+ *\li 'mctx' is a valid memory context.
+ *
+ *\li 'path' is a valid C string.
+ *
+ *\li 'foptp' is non NULL.
+ *
+ *\li envp != NULL && *envp == NULL
+ *
+ * Returns:
+ *
+ *\li #ISC_R_SUCCESS
+ *\li #ISC_R_NOMEMORY
+ *
+ *\li Other errors are possible.
+ */
+
+isc_result_t
+dns_dt_setupfile(dns_dtenv_t *env, uint64_t max_size, int rolls,
+ isc_log_rollsuffix_t suffix);
+/*%<
+ * Sets up the dnstap logfile limits.
+ *
+ * 'max_size' is the size a log file may grow before it is rolled
+ *
+ * 'rolls' is the number of rolled files to retain.
+ *
+ * 'suffix' is the logfile suffix setting, increment or timestamp.
+ *
+ * Requires:
+ *
+ *\li 'env' is a valid dnstap environment.
+ *
+ * Returns:
+ *\li #ISC_R_SUCCESS on success
+ *\li #ISC_R_INVALIDFILE if dnstap is set to use a UNIX domain socket
+ */
+
+isc_result_t
+dns_dt_reopen(dns_dtenv_t *env, int roll);
+/*%<
+ * Reopens files established by dns_dt_create2().
+ *
+ * If 'roll' is non-negative and 'env->mode' is dns_dtmode_file,
+ * then the file is automatically rolled over before reopening.
+ * The value of 'roll' indicates the number of backup log files to
+ * keep. If 'roll' is negative, or if 'env->mode' is dns_dtmode_unix,
+ * then the channel is simply reopened.
+ *
+ * Note: dns_dt_reopen() uses task-exclusive mode and must be run in the
+ * context of env->reopen_task.
+ *
+ * Requires:
+ *\li 'env' is a valid dnstap environment.
+ */
+
+isc_result_t
+dns_dt_setidentity(dns_dtenv_t *env, const char *identity);
+isc_result_t
+dns_dt_setversion(dns_dtenv_t *env, const char *version);
+/*%<
+ * Set the "identity" and "version" strings to be sent in dnstap messages.
+ *
+ * Requires:
+ *
+ *\li 'env' is a valid dnstap environment.
+ */
+
+void
+dns_dt_attach(dns_dtenv_t *source, dns_dtenv_t **destp);
+/*%<
+ * Attach '*destp' to 'source', incrementing the reference counter.
+ *
+ * Requires:
+ *
+ *\li 'source' is a valid dnstap environment.
+ *
+ *\li 'destp' is not NULL and '*destp' is NULL.
+ *
+ *\li *destp is attached to source.
+ */
+
+void
+dns_dt_detach(dns_dtenv_t **envp);
+/*%<
+ * Detach '*envp', decrementing the reference counter.
+ *
+ * Requires:
+ *
+ *\li '*envp' is a valid dnstap environment.
+ *
+ * Ensures:
+ *
+ *\li '*envp' will be destroyed when the number of references reaches zero.
+ *
+ *\li '*envp' is NULL.
+ */
+
+isc_result_t
+dns_dt_getstats(dns_dtenv_t *env, isc_stats_t **statsp);
+/*%<
+ * Attach to the stats struct if it exists.
+ *
+ * Requires:
+ *
+ *\li 'env' is a valid dnstap environment.
+ *
+ *\li 'statsp' is non NULL and '*statsp' is NULL.
+ *
+ * Returns:
+ *
+ *\li ISC_R_SUCCESS
+ *
+ *\li ISC_R_NOTFOUND
+ */
+
+void
+dns_dt_send(dns_view_t *view, dns_dtmsgtype_t msgtype, isc_sockaddr_t *qaddr,
+ isc_sockaddr_t *dstaddr, bool tcp, isc_region_t *zone,
+ isc_time_t *qtime, isc_time_t *rtime, isc_buffer_t *buf);
+/*%<
+ * Sends a dnstap message to the log, if 'msgtype' is one of the message
+ * types represented in 'view->dttypes'.
+ *
+ * Parameters are: 'qaddr' (query address, i.e, the address of the
+ * query initiator); 'raddr' (response address, i.e., the address of
+ * the query responder); 'tcp' (boolean indicating whether the transaction
+ * was over TCP); 'zone' (the authoritative zone or bailiwick, in
+ * uncompressed wire format), 'qtime' and 'rtime' (query and response
+ * times; if NULL, they are set to the current time); and 'buf' (the
+ * DNS message being logged, in wire format).
+ *
+ * Requires:
+ *
+ *\li 'view' is a valid view, and 'view->dtenv' is NULL or is a
+ * valid dnstap environment.
+ */
+
+isc_result_t
+dns_dt_parse(isc_mem_t *mctx, isc_region_t *src, dns_dtdata_t **destp);
+/*%<
+ * Converts a raw dnstap frame in 'src' to a parsed dnstap data structure
+ * in '*destp'.
+ *
+ * Requires:
+ *\li 'src' is not NULL
+ *
+ *\li 'destp' is not NULL and '*destp' points to a valid buffer.
+ *
+ * Returns:
+ *\li #ISC_R_SUCCESS on success
+ *
+ *\li Other errors are possible.
+ */
+
+isc_result_t
+dns_dt_datatotext(dns_dtdata_t *d, isc_buffer_t **dest);
+/*%<
+ * Converts a parsed dnstap data structure 'd' to text, storing
+ * the result in the buffer 'dest'. If 'dest' points to a dynamically
+ * allocated buffer, then it may be reallocated as needed.
+ *
+ * (XXX: add a 'long_form' option to generate a detailed listing of
+ * dnstap data instead * of a one-line summary.)
+ *
+ * Requires:
+ *\li 'd' is not NULL
+ *
+ *\li 'dest' is not NULL and '*dest' points to a valid buffer.
+ *
+ * Returns:
+ *\li #ISC_R_SUCCESS on success
+ *\li #ISC_R_NOSPACE if buffer is not dynamic and runs out of space
+ *\li #ISC_R_NOMEMORY if buffer is dynamic but memory could not be allocated
+ *
+ *\li Other errors are possible.
+ */
+
+void
+dns_dtdata_free(dns_dtdata_t **dp);
+/*%<
+ * Frees the specified dns_dtdata structure and all its members,
+ * and sets *dp to NULL.
+ */
+
+isc_result_t
+dns_dt_open(const char *filename, dns_dtmode_t mode, isc_mem_t *mctx,
+ dns_dthandle_t **handlep);
+/*%<
+ * Opens a dnstap framestream at 'filename' and stores a pointer to the
+ * reader object in a dns_dthandle_t structure.
+ *
+ * The caller is responsible for allocating the handle structure.
+ *
+ * (XXX: Currently only file readers are supported, not unix-domain socket
+ * readers.)
+ *
+ * Requires:
+ *
+ *\li 'filename' is not NULL.
+ *
+ *\li 'handlep' is not NULL and '*handlep' is NULL.
+ *
+ *\li '*mctx' is not a valid memory context.
+ *
+ * Returns:
+ *
+ *\li #ISC_R_SUCCESS on success
+ *\li #ISC_R_NOTIMPLEMENTED if 'mode' is not dns_dtmode_file. (XXX)
+ *\li #ISC_R_NOMEMORY if the fstrm library was unable to allocate a
+ * reader or options structure
+ *\li #ISC_R_FAILURE if 'filename' could not be opened.
+ *\li #DNS_R_BADDNSTAP if 'filename' does not contain a dnstap
+ * framestream.
+ */
+
+isc_result_t
+dns_dt_getframe(dns_dthandle_t *handle, uint8_t **bufp, size_t *sizep);
+/*%<
+ * Read a dnstap frame from the framstream reader in 'handle', storing
+ * a pointer to it in '*bufp' and its size in '*sizep'.
+ *
+ * Requires:
+ *
+ *\li 'handle' is not NULL
+ *\li 'bufp' is not NULL
+ *\li 'sizep' is not NULL
+ *
+ * Ensures:
+ * \li if returning ISC_R_SUCCESS then '*bufp' is not NULL
+ *
+ * Returns:
+ *
+ *\li #ISC_R_SUCCESS on success
+ *\li #ISC_R_NOMORE at the end of the frame stream
+ *\li #ISC_R_FAILURE for any other failure
+ */
+
+void
+dns_dt_close(dns_dthandle_t **handlep);
+/*%<
+ * Closes the dnstap file referenced by 'handle'.
+ *
+ * Requires:
+ *
+ *\li '*handlep' is not NULL
+ */
diff --git a/lib/dns/include/dns/ds.h b/lib/dns/include/dns/ds.h
new file mode 100644
index 0000000..629729b
--- /dev/null
+++ b/lib/dns/include/dns/ds.h
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#pragma once
+
+#include <isc/lang.h>
+
+#include <dns/rdatastruct.h>
+#include <dns/types.h>
+
+#define DNS_DSDIGEST_SHA1 (1)
+#define DNS_DSDIGEST_SHA256 (2)
+#define DNS_DSDIGEST_GOST (3)
+#define DNS_DSDIGEST_SHA384 (4)
+
+/*
+ * Assuming SHA-384 digest type.
+ */
+#define DNS_DS_BUFFERSIZE (52)
+
+ISC_LANG_BEGINDECLS
+
+isc_result_t
+dns_ds_fromkeyrdata(const dns_name_t *owner, dns_rdata_t *key,
+ dns_dsdigest_t digest_type, unsigned char *digest,
+ dns_rdata_ds_t *dsrdata);
+/*%<
+ * Build a DS rdata structure from a key.
+ *
+ * Requires:
+ *\li key Points to a valid DNSKEY or CDNSKEY record.
+ *\li buffer Points to a buffer of at least
+ * #ISC_MAX_MD_SIZE bytes.
+ */
+
+isc_result_t
+dns_ds_buildrdata(dns_name_t *owner, dns_rdata_t *key,
+ dns_dsdigest_t digest_type, unsigned char *buffer,
+ dns_rdata_t *rdata);
+/*%<
+ * Similar to dns_ds_fromkeyrdata(), but copies the DS into a
+ * dns_rdata object.
+ *
+ * Requires:
+ *\li key Points to a valid DNSKEY or CDNSKEY record.
+ *\li buffer Points to a buffer of at least
+ * #DNS_DS_BUFFERSIZE bytes.
+ *\li rdata Points to an initialized dns_rdata_t.
+ *
+ * Ensures:
+ * \li *rdata Contains a valid DS rdata. The 'data' member refers
+ * to 'buffer'.
+ */
+
+ISC_LANG_ENDDECLS
diff --git a/lib/dns/include/dns/dsdigest.h b/lib/dns/include/dns/dsdigest.h
new file mode 100644
index 0000000..3b3898b
--- /dev/null
+++ b/lib/dns/include/dns/dsdigest.h
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#pragma once
+
+/*! \file dns/dsdigest.h */
+
+#include <isc/lang.h>
+
+#include <dns/types.h>
+
+ISC_LANG_BEGINDECLS
+
+isc_result_t
+dns_dsdigest_fromtext(dns_dsdigest_t *dsdigestp, isc_textregion_t *source);
+/*%<
+ * Convert the text 'source' refers to into a DS digest type value.
+ * The text may contain either a mnemonic digest name or a decimal
+ * digest number.
+ *
+ * Requires:
+ *\li 'dsdigestp' is a valid pointer.
+ *
+ *\li 'source' is a valid text region.
+ *
+ * Returns:
+ *\li ISC_R_SUCCESS on success
+ *\li ISC_R_RANGE numeric type is out of range
+ *\li DNS_R_UNKNOWN mnemonic type is unknown
+ */
+
+isc_result_t
+dns_dsdigest_totext(dns_dsdigest_t dsdigest, isc_buffer_t *target);
+/*%<
+ * Put a textual representation of the DS digest type 'dsdigest'
+ * into 'target'.
+ *
+ * Requires:
+ *\li 'dsdigest' is a valid dsdigest.
+ *
+ *\li 'target' is a valid text buffer.
+ *
+ * Ensures,
+ * if the result is success:
+ *\li The used space in 'target' is updated.
+ *
+ * Returns:
+ *\li ISC_R_SUCCESS on success
+ *\li ISC_R_NOSPACE target buffer is too small
+ */
+
+#define DNS_DSDIGEST_FORMATSIZE 20
+void
+dns_dsdigest_format(dns_dsdigest_t typ, char *cp, unsigned int size);
+/*%<
+ * Wrapper for dns_dsdigest_totext(), writing text into 'cp'
+ */
+
+ISC_LANG_ENDDECLS
diff --git a/lib/dns/include/dns/dyndb.h b/lib/dns/include/dns/dyndb.h
new file mode 100644
index 0000000..3cb32c2
--- /dev/null
+++ b/lib/dns/include/dns/dyndb.h
@@ -0,0 +1,159 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#pragma once
+
+#include <stdbool.h>
+
+#include <isc/types.h>
+
+#include <dns/types.h>
+
+ISC_LANG_BEGINDECLS
+
+/*!
+ * \brief
+ * Context for initializing a dyndb module.
+ *
+ * This structure passes global server data to which a dyndb
+ * module will need access -- the server memory context, hash
+ * initializer, log context, etc. The structure doesn't persist
+ * beyond configuring the dyndb module. The module's register function
+ * should attach to all reference-counted variables and its destroy
+ * function should detach from them.
+ */
+struct dns_dyndbctx {
+ unsigned int magic;
+ const void *hashinit;
+ isc_mem_t *mctx;
+ isc_log_t *lctx;
+ dns_view_t *view;
+ dns_zonemgr_t *zmgr;
+ isc_task_t *task;
+ isc_timermgr_t *timermgr;
+ const bool *refvar; /* unused, but retained for API compatibility */
+};
+
+#define DNS_DYNDBCTX_MAGIC ISC_MAGIC('D', 'd', 'b', 'c')
+#define DNS_DYNDBCTX_VALID(d) ISC_MAGIC_VALID(d, DNS_DYNDBCTX_MAGIC)
+
+/*
+ * API version
+ *
+ * When the API changes, increment DNS_DYNDB_VERSION. If the
+ * change is backward-compatible (e.g., adding a new function call
+ * but not changing or removing an old one), increment DNS_DYNDB_AGE;
+ * if not, set DNS_DYNDB_AGE to 0.
+ */
+#ifndef DNS_DYNDB_VERSION
+#define DNS_DYNDB_VERSION 1
+#define DNS_DYNDB_AGE 0
+#endif /* ifndef DNS_DYNDB_VERSION */
+
+typedef isc_result_t
+dns_dyndb_register_t(isc_mem_t *mctx, const char *name, const char *parameters,
+ const char *file, unsigned long line,
+ const dns_dyndbctx_t *dctx, void **instp);
+/*%
+ * Called when registering a new driver instance. 'name' must be unique.
+ * 'parameters' contains the driver configuration text. 'dctx' is the
+ * initialization context set up in dns_dyndb_createctx().
+ *
+ * '*instp' will be set to the driver instance handle if the function
+ * is successful.
+ *
+ * Returns:
+ *\li #ISC_R_SUCCESS
+ *\li #ISC_R_NOMEMORY
+ *\li Other errors are possible
+ */
+
+typedef void
+dns_dyndb_destroy_t(void **instp);
+/*%
+ * Destroy a driver instance. Dereference any reference-counted
+ * variables passed in 'dctx' and 'inst' in the register function.
+ *
+ * \c *instp must be set to \c NULL by the function before it returns.
+ */
+
+typedef int
+dns_dyndb_version_t(unsigned int *flags);
+/*%
+ * Return the API version number a dyndb module was compiled with.
+ *
+ * If the returned version number is no greater than than
+ * DNS_DYNDB_VERSION, and no less than DNS_DYNDB_VERSION - DNS_DYNDB_AGE,
+ * then the module is API-compatible with named.
+ *
+ * 'flags' is currently unused and may be NULL, but could be used in
+ * the future to pass back driver capabilities or other information.
+ */
+
+isc_result_t
+dns_dyndb_load(const char *libname, const char *name, const char *parameters,
+ const char *file, unsigned long line, isc_mem_t *mctx,
+ const dns_dyndbctx_t *dctx);
+/*%
+ * Load a dyndb module.
+ *
+ * This loads a dyndb module using dlopen() or equivalent, calls its register
+ * function (see dns_dyndb_register_t above), and if successful, adds
+ * the instance handle to a list of dyndb instances so it can be cleaned
+ * up later.
+ *
+ * 'file' and 'line' can be used to indicate the name of the file and
+ * the line number from which the parameters were taken, so that logged
+ * error messages, if any, will display the correct locations.
+ *
+ * Returns:
+ *\li #ISC_R_SUCCESS
+ *\li #ISC_R_NOMEMORY
+ *\li Other errors are possible
+ */
+
+void
+dns_dyndb_cleanup(bool exiting);
+/*%
+ * Shut down and destroy all running dyndb modules.
+ *
+ * 'exiting' indicates whether the server is shutting down,
+ * as opposed to merely being reconfigured.
+ */
+
+isc_result_t
+dns_dyndb_createctx(isc_mem_t *mctx, const void *hashinit, isc_log_t *lctx,
+ dns_view_t *view, dns_zonemgr_t *zmgr, isc_task_t *task,
+ isc_timermgr_t *tmgr, dns_dyndbctx_t **dctxp);
+/*%
+ * Create a dyndb initialization context structure, with
+ * pointers to structures in the server that the dyndb module will
+ * need to access (view, zone manager, memory context, hash initializer,
+ * etc). This structure is expected to last only until all dyndb
+ * modules have been loaded and initialized; after that it will be
+ * destroyed with dns_dyndb_destroyctx().
+ *
+ * Returns:
+ *\li #ISC_R_SUCCESS
+ *\li #ISC_R_NOMEMORY
+ *\li Other errors are possible
+ */
+
+void
+dns_dyndb_destroyctx(dns_dyndbctx_t **dctxp);
+/*%
+ * Destroys a dyndb initialization context structure; all
+ * reference-counted members are detached and the structure is freed.
+ */
+
+ISC_LANG_ENDDECLS
diff --git a/lib/dns/include/dns/ecs.h b/lib/dns/include/dns/ecs.h
new file mode 100644
index 0000000..f9d5e88
--- /dev/null
+++ b/lib/dns/include/dns/ecs.h
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#pragma once
+
+#include <inttypes.h>
+
+#include <isc/lang.h>
+#include <isc/netaddr.h>
+#include <isc/types.h>
+
+#include <dns/rdatatype.h>
+#include <dns/types.h>
+
+/*%
+ * Maximum scope values for IPv4 and IPv6.
+ */
+#ifndef ECS_MAX_V4_SCOPE
+#define ECS_MAX_V4_SCOPE 24
+#endif
+
+#ifndef ECS_MAX_V6_SCOPE
+#define ECS_MAX_V6_SCOPE 56
+#endif
+
+/*
+ * Any updates to this structure should also be applied in
+ * contrib/modules/dlz/dlz_minmal.h.
+ */
+struct dns_ecs {
+ isc_netaddr_t addr;
+ uint8_t source;
+ uint8_t scope;
+};
+
+/* <address>/NNN/NNN */
+#define DNS_ECS_FORMATSIZE (ISC_NETADDR_FORMATSIZE + 9)
+
+ISC_LANG_BEGINDECLS
+
+void
+dns_ecs_init(dns_ecs_t *ecs);
+/*%<
+ * Initialize a DNS ECS structure.
+ *
+ * Requires:
+ * \li 'ecs' is not NULL and points to a valid dns_ecs structure.
+ */
+
+bool
+dns_ecs_equals(const dns_ecs_t *ecs1, const dns_ecs_t *ecs2);
+/*%<
+ * Determine whether two ECS address prefixes are equal (except the
+ * scope prefix-length field).
+ *
+ * 'ecs1->source' must exactly match 'ecs2->source'; the address families
+ * must match; and the first 'ecs1->source' bits of the addresses must
+ * match. Subsequent address bits and the 'scope' values are ignored.
+ */
+
+void
+dns_ecs_format(const dns_ecs_t *ecs, char *buf, size_t size);
+/*%<
+ * Format an ECS record as text. Result is guaranteed to be null-terminated.
+ *
+ * Requires:
+ * \li 'ecs' is not NULL.
+ * \li 'buf' is not NULL.
+ * \li 'size' is at least DNS_ECS_FORMATSIZE
+ */
+ISC_LANG_ENDDECLS
diff --git a/lib/dns/include/dns/edns.h b/lib/dns/include/dns/edns.h
new file mode 100644
index 0000000..f015ff8
--- /dev/null
+++ b/lib/dns/include/dns/edns.h
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#pragma once
+
+/*%
+ * The maximum version on EDNS supported by this build.
+ */
+#define DNS_EDNS_VERSION 0
+#ifdef DRAFT_ANDREWS_EDNS1
+#undef DNS_EDNS_VERSION
+/*
+ * Warning: this currently disables sending COOKIE requests in resolver.c
+ */
+#define DNS_EDNS_VERSION 1 /* draft-andrews-edns1 */
+#endif /* ifdef DRAFT_ANDREWS_EDNS1 */
diff --git a/lib/dns/include/dns/events.h b/lib/dns/include/dns/events.h
new file mode 100644
index 0000000..d3d08e0
--- /dev/null
+++ b/lib/dns/include/dns/events.h
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#pragma once
+
+#include <isc/eventclass.h>
+
+/*! \file dns/events.h
+ * \brief
+ * Registry of DNS event numbers.
+ */
+
+#define DNS_EVENT_FETCHCONTROL (ISC_EVENTCLASS_DNS + 0)
+#define DNS_EVENT_FETCHDONE (ISC_EVENTCLASS_DNS + 1)
+#define DNS_EVENT_VIEWRESSHUTDOWN (ISC_EVENTCLASS_DNS + 2)
+#define DNS_EVENT_VIEWADBSHUTDOWN (ISC_EVENTCLASS_DNS + 3)
+#define DNS_EVENT_UPDATE (ISC_EVENTCLASS_DNS + 4)
+#define DNS_EVENT_UPDATEDONE (ISC_EVENTCLASS_DNS + 5)
+#define DNS_EVENT_DISPATCH (ISC_EVENTCLASS_DNS + 6)
+#define DNS_EVENT_TCPMSG (ISC_EVENTCLASS_DNS + 7)
+#define DNS_EVENT_ADBMOREADDRESSES (ISC_EVENTCLASS_DNS + 8)
+#define DNS_EVENT_ADBNOMOREADDRESSES (ISC_EVENTCLASS_DNS + 9)
+#define DNS_EVENT_ADBCANCELED (ISC_EVENTCLASS_DNS + 10)
+#define DNS_EVENT_ADBNAMEDELETED (ISC_EVENTCLASS_DNS + 11)
+#define DNS_EVENT_ADBSHUTDOWN (ISC_EVENTCLASS_DNS + 12)
+#define DNS_EVENT_ADBEXPIRED (ISC_EVENTCLASS_DNS + 13)
+#define DNS_EVENT_ADBCONTROL (ISC_EVENTCLASS_DNS + 14)
+#define DNS_EVENT_CACHECLEAN (ISC_EVENTCLASS_DNS + 15)
+#define DNS_EVENT_BYADDRDONE (ISC_EVENTCLASS_DNS + 16)
+#define DNS_EVENT_ZONECONTROL (ISC_EVENTCLASS_DNS + 17)
+#define DNS_EVENT_DBDESTROYED (ISC_EVENTCLASS_DNS + 18)
+#define DNS_EVENT_VALIDATORDONE (ISC_EVENTCLASS_DNS + 19)
+#define DNS_EVENT_REQUESTDONE (ISC_EVENTCLASS_DNS + 20)
+#define DNS_EVENT_VALIDATORSTART (ISC_EVENTCLASS_DNS + 21)
+#define DNS_EVENT_VIEWREQSHUTDOWN (ISC_EVENTCLASS_DNS + 22)
+#define DNS_EVENT_NOTIFYSENDTOADDR (ISC_EVENTCLASS_DNS + 23)
+#define DNS_EVENT_ZONE (ISC_EVENTCLASS_DNS + 24)
+#define DNS_EVENT_ZONESTARTXFRIN (ISC_EVENTCLASS_DNS + 25)
+#define DNS_EVENT_MASTERQUANTUM (ISC_EVENTCLASS_DNS + 26)
+#define DNS_EVENT_CACHEOVERMEM (ISC_EVENTCLASS_DNS + 27)
+#define DNS_EVENT_MASTERNEXTZONE (ISC_EVENTCLASS_DNS + 28)
+#define DNS_EVENT_IOREADY (ISC_EVENTCLASS_DNS + 29)
+#define DNS_EVENT_LOOKUPDONE (ISC_EVENTCLASS_DNS + 30)
+#define DNS_EVENT_RBTDEADNODES (ISC_EVENTCLASS_DNS + 31)
+#define DNS_EVENT_DISPATCHCONTROL (ISC_EVENTCLASS_DNS + 32)
+#define DNS_EVENT_REQUESTCONTROL (ISC_EVENTCLASS_DNS + 33)
+#define DNS_EVENT_DUMPQUANTUM (ISC_EVENTCLASS_DNS + 34)
+/* #define DNS_EVENT_IMPORTRECVDONE (ISC_EVENTCLASS_DNS + 35) */
+#define DNS_EVENT_FREESTORAGE (ISC_EVENTCLASS_DNS + 36)
+#define DNS_EVENT_VIEWACACHESHUTDOWN (ISC_EVENTCLASS_DNS + 37)
+#define DNS_EVENT_ACACHECONTROL (ISC_EVENTCLASS_DNS + 38)
+#define DNS_EVENT_ACACHECLEAN (ISC_EVENTCLASS_DNS + 39)
+#define DNS_EVENT_ACACHEOVERMEM (ISC_EVENTCLASS_DNS + 40)
+#define DNS_EVENT_RBTPRUNE (ISC_EVENTCLASS_DNS + 41)
+#define DNS_EVENT_MANAGEKEYS (ISC_EVENTCLASS_DNS + 42)
+#define DNS_EVENT_CLIENTRESDONE (ISC_EVENTCLASS_DNS + 43)
+#define DNS_EVENT_CLIENTREQDONE (ISC_EVENTCLASS_DNS + 44)
+#define DNS_EVENT_ADBGROWENTRIES (ISC_EVENTCLASS_DNS + 45)
+#define DNS_EVENT_ADBGROWNAMES (ISC_EVENTCLASS_DNS + 46)
+#define DNS_EVENT_ZONESECURESERIAL (ISC_EVENTCLASS_DNS + 47)
+#define DNS_EVENT_ZONESECUREDB (ISC_EVENTCLASS_DNS + 48)
+#define DNS_EVENT_ZONELOAD (ISC_EVENTCLASS_DNS + 49)
+#define DNS_EVENT_KEYDONE (ISC_EVENTCLASS_DNS + 50)
+#define DNS_EVENT_SETNSEC3PARAM (ISC_EVENTCLASS_DNS + 51)
+#define DNS_EVENT_SETSERIAL (ISC_EVENTCLASS_DNS + 52)
+#define DNS_EVENT_CATZUPDATED (ISC_EVENTCLASS_DNS + 53)
+#define DNS_EVENT_CATZADDZONE (ISC_EVENTCLASS_DNS + 54)
+#define DNS_EVENT_CATZMODZONE (ISC_EVENTCLASS_DNS + 55)
+#define DNS_EVENT_CATZDELZONE (ISC_EVENTCLASS_DNS + 56)
+#define DNS_EVENT_RPZUPDATED (ISC_EVENTCLASS_DNS + 57)
+#define DNS_EVENT_STARTUPDATE (ISC_EVENTCLASS_DNS + 58)
+#define DNS_EVENT_TRYSTALE (ISC_EVENTCLASS_DNS + 59)
+#define DNS_EVENT_ZONEFLUSH (ISC_EVENTCLASS_DNS + 60)
+#define DNS_EVENT_CHECKDSSENDTOADDR (ISC_EVENTCLASS_DNS + 61)
+
+#define DNS_EVENT_FIRSTEVENT (ISC_EVENTCLASS_DNS + 0)
+#define DNS_EVENT_LASTEVENT (ISC_EVENTCLASS_DNS + 65535)
diff --git a/lib/dns/include/dns/fixedname.h b/lib/dns/include/dns/fixedname.h
new file mode 100644
index 0000000..97155e7
--- /dev/null
+++ b/lib/dns/include/dns/fixedname.h
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#pragma once
+
+/*****
+***** Module Info
+*****/
+
+/*! \file dns/fixedname.h
+ * \brief
+ * Fixed-size Names
+ *
+ * dns_fixedname_t is a convenience type containing a name, an offsets
+ * table, and a dedicated buffer big enough for the longest possible
+ * name. This is typically used for stack-allocated names.
+ *
+ * MP:
+ *\li The caller must ensure any required synchronization.
+ *
+ * Reliability:
+ *\li No anticipated impact.
+ *
+ * Resources:
+ *\li Per dns_fixedname_t:
+ *\code
+ * sizeof(dns_name_t) + sizeof(dns_offsets_t) +
+ * sizeof(isc_buffer_t) + 255 bytes + structure padding
+ *\endcode
+ *
+ * Security:
+ *\li No anticipated impact.
+ *
+ * Standards:
+ *\li None.
+ */
+
+/*****
+***** Imports
+*****/
+
+#include <isc/buffer.h>
+#include <isc/lang.h>
+
+#include <dns/name.h>
+
+/*****
+***** Types
+*****/
+
+struct dns_fixedname {
+ dns_name_t name;
+ dns_offsets_t offsets;
+ isc_buffer_t buffer;
+ unsigned char data[DNS_NAME_MAXWIRE];
+};
+
+ISC_LANG_BEGINDECLS
+
+void
+dns_fixedname_init(dns_fixedname_t *fixed);
+
+void
+dns_fixedname_invalidate(dns_fixedname_t *fixed);
+
+dns_name_t *
+dns_fixedname_name(dns_fixedname_t *fixed);
+
+dns_name_t *
+dns_fixedname_initname(dns_fixedname_t *fixed);
+
+ISC_LANG_ENDDECLS
diff --git a/lib/dns/include/dns/forward.h b/lib/dns/include/dns/forward.h
new file mode 100644
index 0000000..f43fce7
--- /dev/null
+++ b/lib/dns/include/dns/forward.h
@@ -0,0 +1,123 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#pragma once
+
+/*! \file dns/forward.h */
+
+#include <isc/lang.h>
+#include <isc/result.h>
+#include <isc/sockaddr.h>
+
+#include <dns/types.h>
+
+ISC_LANG_BEGINDECLS
+
+struct dns_forwarder {
+ isc_sockaddr_t addr;
+ ISC_LINK(dns_forwarder_t) link;
+};
+
+typedef ISC_LIST(struct dns_forwarder) dns_forwarderlist_t;
+
+struct dns_forwarders {
+ dns_forwarderlist_t fwdrs;
+ dns_fwdpolicy_t fwdpolicy;
+};
+
+isc_result_t
+dns_fwdtable_create(isc_mem_t *mctx, dns_fwdtable_t **fwdtablep);
+/*%<
+ * Creates a new forwarding table.
+ *
+ * Requires:
+ * \li mctx is a valid memory context.
+ * \li fwdtablep != NULL && *fwdtablep == NULL
+ *
+ * Returns:
+ * \li #ISC_R_SUCCESS
+ * \li #ISC_R_NOMEMORY
+ */
+
+isc_result_t
+dns_fwdtable_addfwd(dns_fwdtable_t *fwdtable, const dns_name_t *name,
+ dns_forwarderlist_t *fwdrs, dns_fwdpolicy_t policy);
+isc_result_t
+dns_fwdtable_add(dns_fwdtable_t *fwdtable, const dns_name_t *name,
+ isc_sockaddrlist_t *addrs, dns_fwdpolicy_t policy);
+/*%<
+ * Adds an entry to the forwarding table. The entry associates
+ * a domain with a list of forwarders and a forwarding policy. The
+ * addrs/fwdrs list is copied if not empty, so the caller should free
+ * its copy.
+ *
+ * Requires:
+ * \li fwdtable is a valid forwarding table.
+ * \li name is a valid name
+ * \li addrs/fwdrs is a valid list of isc_sockaddr/dns_forwarder
+ * structures, which may be empty.
+ *
+ * Returns:
+ * \li #ISC_R_SUCCESS
+ * \li #ISC_R_NOMEMORY
+ */
+
+isc_result_t
+dns_fwdtable_delete(dns_fwdtable_t *fwdtable, const dns_name_t *name);
+/*%<
+ * Removes an entry for 'name' from the forwarding table. If an entry
+ * that exactly matches 'name' does not exist, ISC_R_NOTFOUND will be returned.
+ *
+ * Requires:
+ * \li fwdtable is a valid forwarding table.
+ * \li name is a valid name
+ *
+ * Returns:
+ * \li #ISC_R_SUCCESS
+ * \li #ISC_R_NOTFOUND
+ * \li #ISC_R_NOSPACE
+ */
+
+isc_result_t
+dns_fwdtable_find(dns_fwdtable_t *fwdtable, const dns_name_t *name,
+ dns_name_t *foundname, dns_forwarders_t **forwardersp);
+/*%<
+ * Finds a domain in the forwarding table. The closest matching parent
+ * domain is returned.
+ *
+ * Requires:
+ * \li fwdtable is a valid forwarding table.
+ * \li name is a valid name
+ * \li forwardersp != NULL && *forwardersp == NULL
+ * \li foundname to be NULL or a valid name with buffer.
+ *
+ * Returns:
+ * \li #ISC_R_SUCCESS Success
+ * \li #DNS_R_PARTIALMATCH Superdomain found with data
+ * \li #ISC_R_NOTFOUND No match
+ * \li #ISC_R_NOSPACE Concatenating nodes to form foundname failed
+ */
+
+void
+dns_fwdtable_destroy(dns_fwdtable_t **fwdtablep);
+/*%<
+ * Destroys a forwarding table.
+ *
+ * Requires:
+ * \li fwtablep != NULL && *fwtablep != NULL
+ *
+ * Ensures:
+ * \li all memory associated with the forwarding table is freed.
+ */
+
+ISC_LANG_ENDDECLS
diff --git a/lib/dns/include/dns/geoip.h b/lib/dns/include/dns/geoip.h
new file mode 100644
index 0000000..0d44f6a
--- /dev/null
+++ b/lib/dns/include/dns/geoip.h
@@ -0,0 +1,111 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#pragma once
+
+/*****
+***** Module Info
+*****/
+
+/*! \file dns/geoip.h
+ * \brief
+ * GeoIP/GeoIP2 data types and function prototypes.
+ */
+
+#if defined(HAVE_GEOIP2)
+
+/***
+ *** Imports
+ ***/
+
+#include <stdbool.h>
+
+#include <isc/lang.h>
+#include <isc/magic.h>
+#include <isc/netaddr.h>
+#include <isc/refcount.h>
+
+#include <dns/iptable.h>
+#include <dns/name.h>
+#include <dns/types.h>
+
+/***
+ *** Types
+ ***/
+
+typedef enum {
+ dns_geoip_countrycode,
+ dns_geoip_countrycode3,
+ dns_geoip_countryname,
+ dns_geoip_continentcode,
+ dns_geoip_continent,
+ dns_geoip_region,
+ dns_geoip_regionname,
+ dns_geoip_country_code,
+ dns_geoip_country_code3,
+ dns_geoip_country_name,
+ dns_geoip_country_continentcode,
+ dns_geoip_country_continent,
+ dns_geoip_region_countrycode,
+ dns_geoip_region_code,
+ dns_geoip_region_name,
+ dns_geoip_city_countrycode,
+ dns_geoip_city_countrycode3,
+ dns_geoip_city_countryname,
+ dns_geoip_city_region,
+ dns_geoip_city_regionname,
+ dns_geoip_city_name,
+ dns_geoip_city_postalcode,
+ dns_geoip_city_metrocode,
+ dns_geoip_city_areacode,
+ dns_geoip_city_continentcode,
+ dns_geoip_city_continent,
+ dns_geoip_city_timezonecode,
+ dns_geoip_isp_name,
+ dns_geoip_org_name,
+ dns_geoip_as_asnum,
+ dns_geoip_domain_name,
+ dns_geoip_netspeed_id
+} dns_geoip_subtype_t;
+
+typedef struct dns_geoip_elem {
+ dns_geoip_subtype_t subtype;
+ void *db;
+ union {
+ char as_string[256];
+ int as_int;
+ };
+} dns_geoip_elem_t;
+
+struct dns_geoip_databases {
+ void *country; /* GeoIP2-Country or GeoLite2-Country */
+ void *city; /* GeoIP2-CIty or GeoLite2-City */
+ void *domain; /* GeoIP2-Domain */
+ void *isp; /* GeoIP2-ISP */
+ void *as; /* GeoIP2-ASN or GeoLite2-ASN */
+};
+
+/***
+ *** Functions
+ ***/
+
+ISC_LANG_BEGINDECLS
+
+bool
+dns_geoip_match(const isc_netaddr_t *reqaddr,
+ const dns_geoip_databases_t *geoip,
+ const dns_geoip_elem_t *elt);
+
+ISC_LANG_ENDDECLS
+
+#endif /* HAVE_GEOIP2 */
diff --git a/lib/dns/include/dns/ipkeylist.h b/lib/dns/include/dns/ipkeylist.h
new file mode 100644
index 0000000..50f229c
--- /dev/null
+++ b/lib/dns/include/dns/ipkeylist.h
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#pragma once
+
+#include <inttypes.h>
+
+#include <isc/types.h>
+
+#include <dns/types.h>
+
+/*%
+ * A structure holding a list of addresses and keys. Used to store
+ * primaries for a secondary zone, created by parsing config options.
+ */
+struct dns_ipkeylist {
+ isc_sockaddr_t *addrs;
+ dns_name_t **keys;
+ dns_name_t **tlss;
+ dns_name_t **labels;
+ uint32_t count;
+ uint32_t allocated;
+};
+
+void
+dns_ipkeylist_init(dns_ipkeylist_t *ipkl);
+/*%<
+ * Reset ipkl to empty state
+ *
+ * Requires:
+ *\li 'ipkl' to be non NULL.
+ */
+
+void
+dns_ipkeylist_clear(isc_mem_t *mctx, dns_ipkeylist_t *ipkl);
+/*%<
+ * Free `ipkl` contents using `mctx`.
+ *
+ * After this call, `ipkl` is a freshly cleared structure with all
+ * pointers set to `NULL` and count set to 0.
+ *
+ * Requires:
+ *\li 'mctx' to be a valid memory context.
+ *\li 'ipkl' to be non NULL.
+ */
+
+isc_result_t
+dns_ipkeylist_copy(isc_mem_t *mctx, const dns_ipkeylist_t *src,
+ dns_ipkeylist_t *dst);
+/*%<
+ * Deep copy `src` into empty `dst`, allocating `dst`'s contents.
+ *
+ * Requires:
+ *\li 'mctx' to be a valid memory context.
+ *\li 'src' to be non NULL
+ *\li 'dst' to be non NULL and point to an empty \ref dns_ipkeylist_t
+ * with all pointers set to `NULL` and count set to 0.
+ *
+ * Returns:
+ *\li #ISC_R_SUCCESS -- success
+ *\li any other value -- failure
+ */
+isc_result_t
+dns_ipkeylist_resize(isc_mem_t *mctx, dns_ipkeylist_t *ipkl, unsigned int n);
+/*%<
+ * Resize ipkl to contain n elements. Size (count) is not changed, and the
+ * added space is zeroed.
+ *
+ * Requires:
+ * \li 'mctx' to be a valid memory context.
+ * \li 'ipk' to be non NULL
+ * \li 'n' >= ipkl->count
+ *
+ * Returns:
+ * \li #ISC_R_SUCCESS if success
+ * \li #ISC_R_NOMEMORY if there's no memory, ipkeylist is left untouched
+ */
diff --git a/lib/dns/include/dns/iptable.h b/lib/dns/include/dns/iptable.h
new file mode 100644
index 0000000..5ad667b
--- /dev/null
+++ b/lib/dns/include/dns/iptable.h
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#pragma once
+
+#include <inttypes.h>
+#include <stdbool.h>
+
+#include <isc/lang.h>
+#include <isc/magic.h>
+#include <isc/radix.h>
+
+#include <dns/types.h>
+
+struct dns_iptable {
+ unsigned int magic;
+ isc_mem_t *mctx;
+ isc_refcount_t refcount;
+ isc_radix_tree_t *radix;
+ ISC_LINK(dns_iptable_t) nextincache;
+};
+
+#define DNS_IPTABLE_MAGIC ISC_MAGIC('T', 'a', 'b', 'l')
+#define DNS_IPTABLE_VALID(a) ISC_MAGIC_VALID(a, DNS_IPTABLE_MAGIC)
+
+/***
+ *** Functions
+ ***/
+
+ISC_LANG_BEGINDECLS
+
+isc_result_t
+dns_iptable_create(isc_mem_t *mctx, dns_iptable_t **target);
+/*
+ * Create a new IP table and the underlying radix structure
+ */
+
+isc_result_t
+dns_iptable_addprefix(dns_iptable_t *tab, const isc_netaddr_t *addr,
+ uint16_t bitlen, bool pos);
+/*
+ * Add an IP prefix to an existing IP table
+ */
+
+isc_result_t
+dns_iptable_merge(dns_iptable_t *tab, dns_iptable_t *source, bool pos);
+/*
+ * Merge one IP table into another one.
+ */
+
+void
+dns_iptable_attach(dns_iptable_t *source, dns_iptable_t **target);
+
+void
+dns_iptable_detach(dns_iptable_t **tabp);
+
+ISC_LANG_ENDDECLS
diff --git a/lib/dns/include/dns/journal.h b/lib/dns/include/dns/journal.h
new file mode 100644
index 0000000..05f904a
--- /dev/null
+++ b/lib/dns/include/dns/journal.h
@@ -0,0 +1,338 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#pragma once
+
+/*****
+***** Module Info
+*****/
+
+/*! \file dns/journal.h
+ * \brief
+ * Database journaling.
+ */
+
+/***
+ *** Imports
+ ***/
+
+#include <inttypes.h>
+#include <stdbool.h>
+
+#include <isc/lang.h>
+#include <isc/magic.h>
+
+#include <dns/diff.h>
+#include <dns/name.h>
+#include <dns/rdata.h>
+#include <dns/types.h>
+
+/***
+ *** Defines.
+ ***/
+#define DNS_JOURNALOPT_RESIGN 0x00000001
+
+#define DNS_JOURNAL_READ 0x00000000 /* false */
+#define DNS_JOURNAL_CREATE 0x00000001 /* true */
+#define DNS_JOURNAL_WRITE 0x00000002
+
+#define DNS_JOURNAL_SIZE_MAX INT32_MAX
+#define DNS_JOURNAL_SIZE_MIN 4096
+
+/*% Print transaction header data */
+#define DNS_JOURNAL_PRINTXHDR 0x0001
+
+/*% Rewrite whole journal file instead of compacting */
+#define DNS_JOURNAL_COMPACTALL 0x0001
+#define DNS_JOURNAL_VERSION1 0x0002
+
+/***
+ *** Types
+ ***/
+
+/*%
+ * A dns_journal_t represents an open journal file. This is an opaque type.
+ *
+ * A particular dns_journal_t object may be opened for writing, in which case
+ * it can be used for writing transactions to a journal file, or it can be
+ * opened for reading, in which case it can be used for reading transactions
+ * from (iterating over) a journal file. A single dns_journal_t object may
+ * not be used for both purposes.
+ */
+typedef struct dns_journal dns_journal_t;
+
+/***
+ *** Functions
+ ***/
+
+ISC_LANG_BEGINDECLS
+
+/**************************************************************************/
+
+isc_result_t
+dns_db_createsoatuple(dns_db_t *db, dns_dbversion_t *ver, isc_mem_t *mctx,
+ dns_diffop_t op, dns_difftuple_t **tp);
+/*!< brief
+ * Create a diff tuple for the current database SOA.
+ * XXX this probably belongs somewhere else.
+ */
+
+/*@{*/
+#define DNS_SERIAL_GT(a, b) ((int)(((a) - (b)) & 0xFFFFFFFF) > 0)
+#define DNS_SERIAL_GE(a, b) ((int)(((a) - (b)) & 0xFFFFFFFF) >= 0)
+/*!< brief
+ * Compare SOA serial numbers. DNS_SERIAL_GT(a, b) returns true iff
+ * a is "greater than" b where "greater than" is as defined in RFC1982.
+ * DNS_SERIAL_GE(a, b) returns true iff a is "greater than or equal to" b.
+ */
+/*@}*/
+
+/**************************************************************************/
+/*
+ * Journal object creation and destruction.
+ */
+
+isc_result_t
+dns_journal_open(isc_mem_t *mctx, const char *filename, unsigned int mode,
+ dns_journal_t **journalp);
+/*%<
+ * Open the journal file 'filename' and create a dns_journal_t object for it.
+ *
+ * DNS_JOURNAL_CREATE open the journal for reading and writing and create
+ * the journal if it does not exist.
+ * DNS_JOURNAL_WRITE open the journal for reading and writing.
+ * DNS_JOURNAL_READ open the journal for reading only.
+ */
+
+void
+dns_journal_destroy(dns_journal_t **journalp);
+/*%<
+ * Destroy a dns_journal_t, closing any open files and freeing its memory.
+ */
+
+/**************************************************************************/
+/*
+ * Writing transactions to journals.
+ */
+
+isc_result_t
+dns_journal_begin_transaction(dns_journal_t *j);
+/*%<
+ * Prepare to write a new transaction to the open journal file 'j'.
+ *
+ * Requires:
+ * \li 'j' is open for writing.
+ */
+
+isc_result_t
+dns_journal_writediff(dns_journal_t *j, dns_diff_t *diff);
+/*%<
+ * Write 'diff' to the current transaction of journal file 'j'.
+ *
+ * Requires:
+ * \li 'j' is open for writing and dns_journal_begin_transaction()
+ * has been called.
+ *
+ *\li 'diff' is a full or partial, correctly ordered IXFR
+ * difference sequence.
+ */
+
+isc_result_t
+dns_journal_commit(dns_journal_t *j);
+/*%<
+ * Commit the current transaction of journal file 'j'.
+ *
+ * Requires:
+ * \li 'j' is open for writing and dns_journal_begin_transaction()
+ * has been called.
+ *
+ * \li dns_journal_writediff() has been called one or more times
+ * to form a complete, correctly ordered IXFR difference
+ * sequence.
+ */
+
+isc_result_t
+dns_journal_write_transaction(dns_journal_t *j, dns_diff_t *diff);
+/*%
+ * Write a complete transaction at once to a journal file,
+ * sorting it if necessary, and commit it. Equivalent to calling
+ * dns_diff_sort(), dns_journal_begin_transaction(),
+ * dns_journal_writediff(), and dns_journal_commit().
+ *
+ * Requires:
+ *\li 'j' is open for writing.
+ *
+ * \li 'diff' contains exactly one SOA deletion, one SOA addition
+ * with a greater serial number, and possibly other changes,
+ * in arbitrary order.
+ */
+
+/**************************************************************************/
+/*
+ * Reading transactions from journals.
+ */
+
+bool
+dns_journal_empty(dns_journal_t *j);
+/*<
+ * Find out if a journal is empty.
+ */
+
+bool
+dns_journal_recovered(dns_journal_t *j);
+/*<
+ * Find out if the journal could be opened using old journal format
+ */
+
+uint32_t
+dns_journal_first_serial(dns_journal_t *j);
+uint32_t
+dns_journal_last_serial(dns_journal_t *j);
+/*%<
+ * Get the first and last addressable serial number in the journal.
+ */
+
+isc_result_t
+dns_journal_iter_init(dns_journal_t *j, uint32_t begin_serial,
+ uint32_t end_serial, size_t *xfrsizep);
+/*%<
+ * Prepare to iterate over the transactions that will bring the database
+ * from SOA serial number 'begin_serial' to 'end_serial'.
+ *
+ * If 'xfrsizep' is not NULL, then on success it will be set to the
+ * total size of all records in the iteration (excluding headers). This
+ * is meant to be a rough approximation of the size of an incremental
+ * zone transfer, though it does not account for DNS message overhead
+ * or name compression.)
+ *
+ * Returns:
+ *\li ISC_R_SUCCESS
+ *\li ISC_R_RANGE begin_serial is outside the addressable range.
+ *\li ISC_R_NOTFOUND begin_serial is within the range of addressable
+ * serial numbers covered by the journal, but
+ * this particular serial number does not exist.
+ */
+
+/*@{*/
+isc_result_t
+dns_journal_first_rr(dns_journal_t *j);
+isc_result_t
+dns_journal_next_rr(dns_journal_t *j);
+/*%<
+ * Position the iterator at the first/next RR in a journal
+ * transaction sequence established using dns_journal_iter_init().
+ *
+ * Requires:
+ * \li dns_journal_iter_init() has been called.
+ *
+ */
+/*@}*/
+
+void
+dns_journal_current_rr(dns_journal_t *j, dns_name_t **name, uint32_t *ttl,
+ dns_rdata_t **rdata);
+/*%<
+ * Get the name, ttl, and rdata of the current journal RR.
+ *
+ * Requires:
+ * \li The last call to dns_journal_first_rr() or dns_journal_next_rr()
+ * returned ISC_R_SUCCESS.
+ */
+
+/**************************************************************************/
+/*
+ * Database roll-forward.
+ */
+
+isc_result_t
+dns_journal_rollforward(dns_journal_t *j, dns_db_t *db, unsigned int options);
+/*%<
+ * Roll forward (play back) the journal file "filename" into the
+ * database "db". This should be called when the server starts
+ * after a shutdown or crash.
+ *
+ * Requires:
+ *\li 'journal' is a valid journal
+ *\li 'db' is a valid database which does not have a version
+ * open for writing.
+ *
+ * Returns:
+ *\li ISC_R_NOTFOUND when current serial in not in journal.
+ *\li ISC_R_RANGE when current serial in not in journals range.
+ *\li DNS_R_UPTODATE when the database was already up to date.
+ *\li ISC_R_SUCCESS journal has been applied successfully to the
+ * database without any issues.
+ *
+ * others
+ */
+
+isc_result_t
+dns_journal_print(isc_mem_t *mctx, uint32_t flags, const char *filename,
+ FILE *file);
+/* For debugging not general use */
+
+isc_result_t
+dns_db_diff(isc_mem_t *mctx, dns_db_t *dba, dns_dbversion_t *dbvera,
+ dns_db_t *dbb, dns_dbversion_t *dbverb,
+ const char *journal_filename);
+
+isc_result_t
+dns_db_diffx(dns_diff_t *diff, dns_db_t *dba, dns_dbversion_t *dbvera,
+ dns_db_t *dbb, dns_dbversion_t *dbverb,
+ const char *journal_filename);
+/*%<
+ * Compare the databases 'dba' and 'dbb' and generate a diff/journal
+ * entry containing the changes to make 'dba' from 'dbb' (note
+ * the order). This journal entry will consist of a single,
+ * possibly very large transaction. Append the journal
+ * entry to the journal file specified by 'journal_filename' if
+ * non-NULL.
+ */
+
+isc_result_t
+dns_journal_compact(isc_mem_t *mctx, char *filename, uint32_t serial,
+ uint32_t flags, uint32_t target_size);
+/*%<
+ * Attempt to compact the journal if it is greater that 'target_size'.
+ * Changes from 'serial' onwards will be preserved. Changes prior than
+ * that may be dropped in order to get the journal below `target_size`.
+ *
+ * If 'flags' includes DNS_JOURNAL_COMPACTALL, the entire journal is copied.
+ * In this case, `serial` is ignored. This flag is used when upgrading or
+ * downgrading the format version of the journal. If 'flags' also includes
+ * DNS_JOURNAL_VERSION1, then the journal is copied out in the original
+ * format used prior to BIND 9.16.12; otherwise it is copied in the
+ * current format.
+ *
+ * If _COMPACTALL is not in use, and the journal file exists and is
+ * non-empty, then 'serial' must exist in the journal.
+ *
+ * Returns:
+ *\li ISC_R_SUCCESS
+ *\li ISC_R_RANGE serial is outside the range existing in the journal
+ *
+ * Other errors may be returned from file operations.
+ */
+
+bool
+dns_journal_get_sourceserial(dns_journal_t *j, uint32_t *sourceserial);
+void
+dns_journal_set_sourceserial(dns_journal_t *j, uint32_t sourceserial);
+/*%<
+ * Get and set source serial.
+ *
+ * Returns:
+ * true if sourceserial has previously been set.
+ */
+
+ISC_LANG_ENDDECLS
diff --git a/lib/dns/include/dns/kasp.h b/lib/dns/include/dns/kasp.h
new file mode 100644
index 0000000..4f560a0
--- /dev/null
+++ b/lib/dns/include/dns/kasp.h
@@ -0,0 +1,716 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#pragma once
+
+/*****
+***** Module Info
+*****/
+
+/*! \file dns/kasp.h
+ * \brief
+ * DNSSEC Key and Signing Policy (KASP)
+ *
+ * A "kasp" is a DNSSEC policy, that determines how a zone should be
+ * signed and maintained.
+ */
+
+#include <isc/lang.h>
+#include <isc/magic.h>
+#include <isc/mutex.h>
+#include <isc/refcount.h>
+
+#include <dns/types.h>
+
+ISC_LANG_BEGINDECLS
+
+/* Stores a KASP key */
+struct dns_kasp_key {
+ isc_mem_t *mctx;
+
+ /* Locked by themselves. */
+ isc_refcount_t references;
+
+ /* Under owner's locking control. */
+ ISC_LINK(struct dns_kasp_key) link;
+
+ /* Configuration */
+ uint32_t lifetime;
+ uint8_t algorithm;
+ int length;
+ uint8_t role;
+};
+
+struct dns_kasp_nsec3param {
+ uint8_t saltlen;
+ uint8_t algorithm;
+ uint8_t iterations;
+ bool optout;
+};
+
+/* Stores a DNSSEC policy */
+struct dns_kasp {
+ unsigned int magic;
+ isc_mem_t *mctx;
+ char *name;
+
+ /* Internals. */
+ isc_mutex_t lock;
+ bool frozen;
+
+ /* Locked by themselves. */
+ isc_refcount_t references;
+
+ /* Under owner's locking control. */
+ ISC_LINK(struct dns_kasp) link;
+
+ /* Configuration: signatures */
+ uint32_t signatures_refresh;
+ uint32_t signatures_validity;
+ uint32_t signatures_validity_dnskey;
+
+ /* Configuration: Keys */
+ dns_kasp_keylist_t keys;
+ dns_ttl_t dnskey_ttl;
+
+ /* Configuration: Denial of existence */
+ bool nsec3;
+ dns_kasp_nsec3param_t nsec3param;
+
+ /* Configuration: Timings */
+ uint32_t publish_safety;
+ uint32_t retire_safety;
+ uint32_t purge_keys;
+
+ /* Zone settings */
+ dns_ttl_t zone_max_ttl;
+ uint32_t zone_propagation_delay;
+
+ /* Parent settings */
+ dns_ttl_t parent_ds_ttl;
+ uint32_t parent_propagation_delay;
+};
+
+#define DNS_KASP_MAGIC ISC_MAGIC('K', 'A', 'S', 'P')
+#define DNS_KASP_VALID(kasp) ISC_MAGIC_VALID(kasp, DNS_KASP_MAGIC)
+
+/* Defaults */
+#define DNS_KASP_SIG_REFRESH "P5D"
+#define DNS_KASP_SIG_VALIDITY "P14D"
+#define DNS_KASP_SIG_VALIDITY_DNSKEY "P14D"
+#define DNS_KASP_KEY_TTL "3600"
+#define DNS_KASP_DS_TTL "86400"
+#define DNS_KASP_PUBLISH_SAFETY "3600"
+#define DNS_KASP_PURGE_KEYS "P90D"
+#define DNS_KASP_RETIRE_SAFETY "3600"
+#define DNS_KASP_ZONE_MAXTTL "86400"
+#define DNS_KASP_ZONE_PROPDELAY "300"
+#define DNS_KASP_PARENT_PROPDELAY "3600"
+
+/* Key roles */
+#define DNS_KASP_KEY_ROLE_KSK 0x01
+#define DNS_KASP_KEY_ROLE_ZSK 0x02
+
+isc_result_t
+dns_kasp_create(isc_mem_t *mctx, const char *name, dns_kasp_t **kaspp);
+/*%<
+ * Create a KASP.
+ *
+ * Requires:
+ *
+ *\li 'mctx' is a valid memory context.
+ *
+ *\li 'name' is a valid C string.
+ *
+ *\li kaspp != NULL && *kaspp == NULL
+ *
+ * Returns:
+ *
+ *\li #ISC_R_SUCCESS
+ *\li #ISC_R_NOMEMORY
+ *
+ *\li Other errors are possible.
+ */
+
+void
+dns_kasp_attach(dns_kasp_t *source, dns_kasp_t **targetp);
+/*%<
+ * Attach '*targetp' to 'source'.
+ *
+ * Requires:
+ *
+ *\li 'source' is a valid, frozen kasp.
+ *
+ *\li 'targetp' points to a NULL dns_kasp_t *.
+ *
+ * Ensures:
+ *
+ *\li *targetp is attached to source.
+ *
+ *\li While *targetp is attached, the kasp will not shut down.
+ */
+
+void
+dns_kasp_detach(dns_kasp_t **kaspp);
+/*%<
+ * Detach KASP.
+ *
+ * Requires:
+ *
+ *\li 'kaspp' points to a valid dns_kasp_t *
+ *
+ * Ensures:
+ *
+ *\li *kaspp is NULL.
+ */
+
+void
+dns_kasp_freeze(dns_kasp_t *kasp);
+/*%<
+ * Freeze kasp. No changes can be made to kasp configuration while frozen.
+ *
+ * Requires:
+ *
+ *\li 'kasp' is a valid, unfrozen kasp.
+ *
+ * Ensures:
+ *
+ *\li 'kasp' is frozen.
+ */
+
+void
+dns_kasp_thaw(dns_kasp_t *kasp);
+/*%<
+ * Thaw kasp.
+ *
+ * Requires:
+ *
+ *\li 'kasp' is a valid, frozen kasp.
+ *
+ * Ensures:
+ *
+ *\li 'kasp' is no longer frozen.
+ */
+
+const char *
+dns_kasp_getname(dns_kasp_t *kasp);
+/*%<
+ * Get kasp name.
+ *
+ * Requires:
+ *
+ *\li 'kasp' is a valid kasp.
+ *
+ * Returns:
+ *
+ *\li name of 'kasp'.
+ */
+
+uint32_t
+dns_kasp_signdelay(dns_kasp_t *kasp);
+/*%<
+ * Get the delay that is needed to ensure that all existing RRsets have been
+ * re-signed with a successor key. This is the signature validity minus the
+ * signature refresh time (that indicates how far before signature expiry an
+ * RRSIG should be refreshed).
+ *
+ * Requires:
+ *
+ *\li 'kasp' is a valid, frozen kasp.
+ *
+ * Returns:
+ *
+ *\li signature refresh interval.
+ */
+
+uint32_t
+dns_kasp_sigrefresh(dns_kasp_t *kasp);
+/*%<
+ * Get signature refresh interval.
+ *
+ * Requires:
+ *
+ *\li 'kasp' is a valid, frozen kasp.
+ *
+ * Returns:
+ *
+ *\li signature refresh interval.
+ */
+
+void
+dns_kasp_setsigrefresh(dns_kasp_t *kasp, uint32_t value);
+/*%<
+ * Set signature refresh interval.
+ *
+ * Requires:
+ *
+ *\li 'kasp' is a valid, thawed kasp.
+ */
+
+uint32_t
+dns_kasp_sigvalidity(dns_kasp_t *kasp);
+uint32_t
+dns_kasp_sigvalidity_dnskey(dns_kasp_t *kasp);
+/*%<
+ * Get signature validity.
+ *
+ * Requires:
+ *
+ *\li 'kasp' is a valid, frozen kasp.
+ *
+ * Returns:
+ *
+ *\li signature validity.
+ */
+
+void
+dns_kasp_setsigvalidity(dns_kasp_t *kasp, uint32_t value);
+void
+dns_kasp_setsigvalidity_dnskey(dns_kasp_t *kasp, uint32_t value);
+/*%<
+ * Set signature validity.
+ *
+ * Requires:
+ *
+ *\li 'kasp' is a valid, thawed kasp.
+ */
+
+dns_ttl_t
+dns_kasp_dnskeyttl(dns_kasp_t *kasp);
+/*%<
+ * Get DNSKEY TTL.
+ *
+ * Requires:
+ *
+ *\li 'kasp' is a valid, frozen kasp.
+ *
+ * Returns:
+ *
+ *\li DNSKEY TTL.
+ */
+
+void
+dns_kasp_setdnskeyttl(dns_kasp_t *kasp, dns_ttl_t ttl);
+/*%<
+ * Set DNSKEY TTL.
+ *
+ * Requires:
+ *
+ *\li 'kasp' is a valid, thawed kasp.
+ */
+
+uint32_t
+dns_kasp_purgekeys(dns_kasp_t *kasp);
+/*%<
+ * Get purge keys interval.
+ *
+ * Requires:
+ *
+ *\li 'kasp' is a valid, frozen kasp.
+ *
+ * Returns:
+ *
+ *\li Purge keys interval.
+ */
+
+void
+dns_kasp_setpurgekeys(dns_kasp_t *kasp, uint32_t value);
+/*%<
+ * Set purge keys interval.
+ *
+ * Requires:
+ *
+ *\li 'kasp' is a valid, thawed kasp.
+ */
+
+uint32_t
+dns_kasp_publishsafety(dns_kasp_t *kasp);
+/*%<
+ * Get publish safety interval.
+ *
+ * Requires:
+ *
+ *\li 'kasp' is a valid, frozen kasp.
+ *
+ * Returns:
+ *
+ *\li Publish safety interval.
+ */
+
+void
+dns_kasp_setpublishsafety(dns_kasp_t *kasp, uint32_t value);
+/*%<
+ * Set publish safety interval.
+ *
+ * Requires:
+ *
+ *\li 'kasp' is a valid, thawed kasp.
+ */
+
+uint32_t
+dns_kasp_retiresafety(dns_kasp_t *kasp);
+/*%<
+ * Get retire safety interval.
+ *
+ * Requires:
+ *
+ *\li 'kasp' is a valid, frozen kasp.
+ *
+ * Returns:
+ *
+ *\li Retire safety interval.
+ */
+
+void
+dns_kasp_setretiresafety(dns_kasp_t *kasp, uint32_t value);
+/*%<
+ * Set retire safety interval.
+ *
+ * Requires:
+ *
+ *\li 'kasp' is a valid, thawed kasp.
+ */
+
+dns_ttl_t
+dns_kasp_zonemaxttl(dns_kasp_t *kasp, bool fallback);
+/*%<
+ * Get maximum zone TTL. If 'fallback' is true, return a default maximum TTL
+ * if the maximum zone TTL is set to unlimited (value 0). Fallback should be
+ * used if determining key rollover timings in keymgr.c
+ *
+ * Requires:
+ *
+ *\li 'kasp' is a valid, frozen kasp.
+ *
+ * Returns:
+ *
+ *\li Maximum zone TTL.
+ */
+
+void
+dns_kasp_setzonemaxttl(dns_kasp_t *kasp, dns_ttl_t ttl);
+/*%<
+ * Set maximum zone TTL.
+ *
+ * Requires:
+ *
+ *\li 'kasp' is a valid, thawed kasp.
+ */
+
+uint32_t
+dns_kasp_zonepropagationdelay(dns_kasp_t *kasp);
+/*%<
+ * Get zone propagation delay.
+ *
+ * Requires:
+ *
+ *\li 'kasp' is a valid, frozen kasp.
+ *
+ * Returns:
+ *
+ *\li Zone propagation delay.
+ */
+
+void
+dns_kasp_setzonepropagationdelay(dns_kasp_t *kasp, uint32_t value);
+/*%<
+ * Set zone propagation delay.
+ *
+ * Requires:
+ *
+ *\li 'kasp' is a valid, thawed kasp.
+ */
+
+dns_ttl_t
+dns_kasp_dsttl(dns_kasp_t *kasp);
+/*%<
+ * Get DS TTL (should match that of the parent DS record).
+ *
+ * Requires:
+ *
+ *\li 'kasp' is a valid, frozen kasp.
+ *
+ * Returns:
+ *
+ *\li Expected parent DS TTL.
+ */
+
+void
+dns_kasp_setdsttl(dns_kasp_t *kasp, dns_ttl_t ttl);
+/*%<
+ * Set DS TTL.
+ *
+ * Requires:
+ *
+ *\li 'kasp' is a valid, thawed kasp.
+ */
+
+uint32_t
+dns_kasp_parentpropagationdelay(dns_kasp_t *kasp);
+/*%<
+ * Get parent zone propagation delay.
+ *
+ * Requires:
+ *
+ *\li 'kasp' is a valid, frozen kasp.
+ *
+ * Returns:
+ *
+ *\li Parent zone propagation delay.
+ */
+
+void
+dns_kasp_setparentpropagationdelay(dns_kasp_t *kasp, uint32_t value);
+/*%<
+ * Set parent propagation delay.
+ *
+ * Requires:
+ *
+ *\li 'kasp' is a valid, thawed kasp.
+ */
+
+isc_result_t
+dns_kasplist_find(dns_kasplist_t *list, const char *name, dns_kasp_t **kaspp);
+/*%<
+ * Search for a kasp with name 'name' in 'list'.
+ * If found, '*kaspp' is (strongly) attached to it.
+ *
+ * Requires:
+ *
+ *\li 'kaspp' points to a NULL dns_kasp_t *.
+ *
+ * Returns:
+ *
+ *\li #ISC_R_SUCCESS A matching kasp was found.
+ *\li #ISC_R_NOTFOUND No matching kasp was found.
+ */
+
+dns_kasp_keylist_t
+dns_kasp_keys(dns_kasp_t *kasp);
+/*%<
+ * Get the list of kasp keys.
+ *
+ * Requires:
+ *
+ *\li 'kasp' is a valid, frozen kasp.
+ *
+ * Returns:
+ *
+ *\li #ISC_R_SUCCESS
+ *\li #ISC_R_NOMEMORY
+ *
+ *\li Other errors are possible.
+ */
+
+bool
+dns_kasp_keylist_empty(dns_kasp_t *kasp);
+/*%<
+ * Check if the keylist is empty.
+ *
+ * Requires:
+ *
+ *\li 'kasp' is a valid kasp.
+ *
+ * Returns:
+ *
+ *\li true if the keylist is empty, false otherwise.
+ */
+
+void
+dns_kasp_addkey(dns_kasp_t *kasp, dns_kasp_key_t *key);
+/*%<
+ * Add a key.
+ *
+ * Requires:
+ *
+ *\li 'kasp' is a valid, thawed kasp.
+ *\li 'key' is not NULL.
+ */
+
+isc_result_t
+dns_kasp_key_create(dns_kasp_t *kasp, dns_kasp_key_t **keyp);
+/*%<
+ * Create a key inside a KASP.
+ *
+ * Requires:
+ *
+ *\li 'kasp' is a valid kasp.
+ *
+ *\li keyp != NULL && *keyp == NULL
+ *
+ * Returns:
+ *
+ *\li #ISC_R_SUCCESS
+ *\li #ISC_R_NOMEMORY
+ *
+ *\li Other errors are possible.
+ */
+
+void
+dns_kasp_key_destroy(dns_kasp_key_t *key);
+/*%<
+ * Destroy a KASP key.
+ *
+ * Requires:
+ *
+ *\li key != NULL
+ */
+
+uint32_t
+dns_kasp_key_algorithm(dns_kasp_key_t *key);
+/*%<
+ * Get the key algorithm.
+ *
+ * Requires:
+ *
+ *\li key != NULL
+ *
+ * Returns:
+ *
+ *\li Key algorithm.
+ */
+
+unsigned int
+dns_kasp_key_size(dns_kasp_key_t *key);
+/*%<
+ * Get the key size.
+ *
+ * Requires:
+ *
+ *\li key != NULL
+ *
+ * Returns:
+ *
+ *\li Configured key size, or default key size for key algorithm if no size
+ * configured.
+ */
+
+uint32_t
+dns_kasp_key_lifetime(dns_kasp_key_t *key);
+/*%<
+ * The lifetime of this key (how long may this key be active?)
+ *
+ * Requires:
+ *
+ *\li key != NULL
+ *
+ * Returns:
+ *
+ *\li Lifetime of key.
+ *
+ */
+
+bool
+dns_kasp_key_ksk(dns_kasp_key_t *key);
+/*%<
+ * Does this key act as a KSK?
+ *
+ * Requires:
+ *
+ *\li key != NULL
+ *
+ * Returns:
+ *
+ *\li True, if the key role has DNS_KASP_KEY_ROLE_KSK set.
+ *\li False, otherwise.
+ *
+ */
+
+bool
+dns_kasp_key_zsk(dns_kasp_key_t *key);
+/*%<
+ * Does this key act as a ZSK?
+ *
+ * Requires:
+ *
+ *\li key != NULL
+ *
+ * Returns:
+ *
+ *\li True, if the key role has DNS_KASP_KEY_ROLE_ZSK set.
+ *\li False, otherwise.
+ *
+ */
+
+bool
+dns_kasp_nsec3(dns_kasp_t *kasp);
+/*%<
+ * Return true if NSEC3 chain should be used.
+ *
+ * Requires:
+ *
+ *\li 'kasp' is a valid, frozen kasp.
+ *
+ */
+
+uint8_t
+dns_kasp_nsec3iter(dns_kasp_t *kasp);
+/*%<
+ * The number of NSEC3 iterations to use.
+ *
+ * Requires:
+ *
+ *\li 'kasp' is a valid, frozen kasp.
+ *\li 'kasp->nsec3' is true.
+ *
+ */
+
+uint8_t
+dns_kasp_nsec3flags(dns_kasp_t *kasp);
+/*%<
+ * The NSEC3 flags field value.
+ *
+ * Requires:
+ *
+ *\li 'kasp' is a valid, frozen kasp.
+ *\li 'kasp->nsec3' is true.
+ *
+ */
+
+uint8_t
+dns_kasp_nsec3saltlen(dns_kasp_t *kasp);
+/*%<
+ * The NSEC3 salt length.
+ *
+ * Requires:
+ *
+ *\li 'kasp' is a valid, frozen kasp.
+ *\li 'kasp->nsec3' is true.
+ *
+ */
+
+void
+dns_kasp_setnsec3(dns_kasp_t *kasp, bool nsec3);
+/*%<
+ * Set to use NSEC3 if 'nsec3' is 'true', otherwise policy will use NSEC.
+ *
+ * Requires:
+ *
+ *\li 'kasp' is a valid, unfrozen kasp.
+ *
+ */
+
+void
+dns_kasp_setnsec3param(dns_kasp_t *kasp, uint8_t iter, bool optout,
+ uint8_t saltlen);
+/*%<
+ * Set the desired NSEC3 parameters.
+ *
+ * Requires:
+ *
+ *\li 'kasp' is a valid, unfrozen kasp.
+ *\li 'kasp->nsec3' is true.
+ *
+ */
+
+ISC_LANG_ENDDECLS
diff --git a/lib/dns/include/dns/keydata.h b/lib/dns/include/dns/keydata.h
new file mode 100644
index 0000000..fba86d4
--- /dev/null
+++ b/lib/dns/include/dns/keydata.h
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#pragma once
+
+/*****
+***** Module Info
+*****/
+
+/*! \file dns/keydata.h
+ * \brief
+ * KEYDATA utilities.
+ */
+
+/***
+ *** Imports
+ ***/
+
+#include <inttypes.h>
+
+#include <isc/lang.h>
+#include <isc/types.h>
+
+#include <dns/rdatastruct.h>
+#include <dns/types.h>
+
+ISC_LANG_BEGINDECLS
+
+isc_result_t
+dns_keydata_todnskey(dns_rdata_keydata_t *keydata, dns_rdata_dnskey_t *dnskey,
+ isc_mem_t *mctx);
+
+isc_result_t
+dns_keydata_fromdnskey(dns_rdata_keydata_t *keydata, dns_rdata_dnskey_t *dnskey,
+ uint32_t refresh, uint32_t addhd, uint32_t removehd,
+ isc_mem_t *mctx);
+
+ISC_LANG_ENDDECLS
diff --git a/lib/dns/include/dns/keyflags.h b/lib/dns/include/dns/keyflags.h
new file mode 100644
index 0000000..51f6cee
--- /dev/null
+++ b/lib/dns/include/dns/keyflags.h
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#pragma once
+
+/*! \file dns/keyflags.h */
+
+#include <isc/lang.h>
+
+#include <dns/types.h>
+
+ISC_LANG_BEGINDECLS
+
+isc_result_t
+dns_keyflags_fromtext(dns_keyflags_t *flagsp, isc_textregion_t *source);
+/*%<
+ * Convert the text 'source' refers to into a DNSSEC KEY flags value.
+ * The text may contain either a set of flag mnemonics separated by
+ * vertical bars or a decimal flags value. For compatibility with
+ * older versions of BIND and the DNSSEC signer, octal values
+ * prefixed with a zero and hexadecimal values prefixed with "0x"
+ * are also accepted.
+ *
+ * Requires:
+ *\li 'flagsp' is a valid pointer.
+ *
+ *\li 'source' is a valid text region.
+ *
+ * Returns:
+ *\li ISC_R_SUCCESS on success
+ *\li ISC_R_RANGE numeric flag value is out of range
+ *\li DNS_R_UNKNOWN mnemonic flag is unknown
+ */
+
+ISC_LANG_ENDDECLS
diff --git a/lib/dns/include/dns/keymgr.h b/lib/dns/include/dns/keymgr.h
new file mode 100644
index 0000000..bf08fbb
--- /dev/null
+++ b/lib/dns/include/dns/keymgr.h
@@ -0,0 +1,131 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#pragma once
+
+/*! \file dns/keymgr.h */
+
+#include <isc/lang.h>
+#include <isc/stdtime.h>
+
+#include <dns/types.h>
+
+#include <dst/dst.h>
+
+ISC_LANG_BEGINDECLS
+
+isc_result_t
+dns_keymgr_run(const dns_name_t *origin, dns_rdataclass_t rdclass,
+ const char *directory, isc_mem_t *mctx,
+ dns_dnsseckeylist_t *keyring, dns_dnsseckeylist_t *dnskeys,
+ dns_kasp_t *kasp, isc_stdtime_t now, isc_stdtime_t *nexttime);
+/*%<
+ * Manage keys in 'keyring' and update timing data according to 'kasp' policy.
+ * Create new keys for 'origin' if necessary in 'directory'. Append all such
+ * keys, along with use hints gleaned from their metadata, onto 'keyring'.
+ *
+ * Update key states and store changes back to disk. Store when to run next
+ * in 'nexttime'.
+ *
+ * Requires:
+ *\li 'origin' is a valid FQDN.
+ *\li 'mctx' is a valid memory context.
+ *\li 'keyring' is not NULL.
+ *\li 'kasp' is not NULL.
+ *
+ * Returns:
+ *\li #ISC_R_SUCCESS
+ *\li any error returned by dst_key_generate(), isc_dir_open(),
+ * dst_key_to_file(), or dns_dnsseckey_create().
+ *
+ * Ensures:
+ *\li On error, keypool is unchanged
+ */
+
+isc_result_t
+dns_keymgr_checkds(dns_kasp_t *kasp, dns_dnsseckeylist_t *keyring,
+ const char *directory, isc_stdtime_t now, isc_stdtime_t when,
+ bool dspublish);
+isc_result_t
+dns_keymgr_checkds_id(dns_kasp_t *kasp, dns_dnsseckeylist_t *keyring,
+ const char *directory, isc_stdtime_t now,
+ isc_stdtime_t when, bool dspublish, dns_keytag_t id,
+ unsigned int algorithm);
+/*%<
+ * Check DS for one key in 'keyring'. The key must have the KSK role.
+ * If 'dspublish' is set to true, set the DS Publish time to 'now'.
+ * If 'dspublish' is set to false, set the DS Removed time to 'now'.
+ * If a specific key 'id' is given it must match the keytag.
+ * If the 'algorithm' is non-zero, it must match the key's algorithm.
+ * The result is stored in the key state file.
+ *
+ * Requires:
+ *\li 'kasp' is not NULL.
+ *\li 'keyring' is not NULL.
+ *
+ * Returns:
+ *\li #ISC_R_SUCCESS (No error).
+ *\li #DNS_R_NOKEYMATCH (No matching keys found).
+ *\li #DNS_R_TOOMANYKEYS (More than one matching keys found).
+ *
+ */
+
+isc_result_t
+dns_keymgr_rollover(dns_kasp_t *kasp, dns_dnsseckeylist_t *keyring,
+ const char *directory, isc_stdtime_t now,
+ isc_stdtime_t when, dns_keytag_t id,
+ unsigned int algorithm);
+/*%<
+ * Rollover key with given 'id'. If the 'algorithm' is non-zero, it must
+ * match the key's algorithm. The changes are stored in the key state file.
+ *
+ * A rollover means adjusting the key metadata so that keymgr will start the
+ * actual rollover on the next run. Update the 'inactive' time and adjust
+ * key lifetime to match the 'when' to rollover time.
+ *
+ * The 'when' time may be in the past. In that case keymgr will roll the
+ * key as soon as possible.
+ *
+ * The 'when' time may be in the future. This may extend the lifetime,
+ * overriding the default lifetime from the policy.
+ *
+ * Requires:
+ *\li 'kasp' is not NULL.
+ *\li 'keyring' is not NULL.
+ *
+ * Returns:
+ *\li #ISC_R_SUCCESS (No error).
+ *\li #DNS_R_NOKEYMATCH (No matching keys found).
+ *\li #DNS_R_TOOMANYKEYS (More than one matching keys found).
+ *\li #DNS_R_KEYNOTACTIVE (Key is not active).
+ *
+ */
+
+void
+dns_keymgr_status(dns_kasp_t *kasp, dns_dnsseckeylist_t *keyring,
+ isc_stdtime_t now, char *out, size_t out_len);
+/*%<
+ * Retrieve the status of given 'kasp' policy and keys in the
+ * 'keyring' and store the printable output in the 'out' buffer.
+ *
+ * Requires:
+ *\li 'kasp' is not NULL.
+ *\li 'keyring' is not NULL.
+ *\li 'out' is not NULL.
+ *
+ * Returns:
+ *\li Printable status in 'out'.
+ *
+ */
+
+ISC_LANG_ENDDECLS
diff --git a/lib/dns/include/dns/keytable.h b/lib/dns/include/dns/keytable.h
new file mode 100644
index 0000000..6095a85
--- /dev/null
+++ b/lib/dns/include/dns/keytable.h
@@ -0,0 +1,351 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#pragma once
+
+/*****
+***** Module Info
+*****/
+
+/*! \file
+ * \brief
+ * The keytable module provides services for storing and retrieving DNSSEC
+ * trusted keys, as well as the ability to find the deepest matching key
+ * for a given domain name.
+ *
+ * MP:
+ *\li The module ensures appropriate synchronization of data structures it
+ * creates and manipulates.
+ *
+ * Resources:
+ *\li TBS
+ *
+ * Security:
+ *\li No anticipated impact.
+ */
+
+#include <stdbool.h>
+
+#include <isc/lang.h>
+#include <isc/magic.h>
+#include <isc/refcount.h>
+#include <isc/rwlock.h>
+#include <isc/stdtime.h>
+
+#include <dns/rdatastruct.h>
+#include <dns/types.h>
+
+#include <dst/dst.h>
+
+ISC_LANG_BEGINDECLS
+
+typedef void (*dns_keytable_callback_t)(const dns_name_t *name, void *fn_arg);
+
+isc_result_t
+dns_keytable_create(isc_mem_t *mctx, dns_keytable_t **keytablep);
+/*%<
+ * Create a keytable.
+ *
+ * Requires:
+ *
+ *\li 'mctx' is a valid memory context.
+ *
+ *\li keytablep != NULL && *keytablep == NULL
+ *
+ * Ensures:
+ *
+ *\li On success, *keytablep is a valid, empty key table.
+ *
+ * Returns:
+ *
+ *\li ISC_R_SUCCESS
+ *
+ *\li Any other result indicates failure.
+ */
+
+void
+dns_keytable_attach(dns_keytable_t *source, dns_keytable_t **targetp);
+/*%<
+ * Attach *targetp to source.
+ *
+ * Requires:
+ *
+ *\li 'source' is a valid keytable.
+ *
+ *\li 'targetp' points to a NULL dns_keytable_t *.
+ *
+ * Ensures:
+ *
+ *\li *targetp is attached to source.
+ */
+
+void
+dns_keytable_detach(dns_keytable_t **keytablep);
+/*%<
+ * Detach *keytablep from its keytable.
+ *
+ * Requires:
+ *
+ *\li 'keytablep' points to a valid keytable.
+ *
+ * Ensures:
+ *
+ *\li *keytablep is NULL.
+ *
+ *\li If '*keytablep' is the last reference to the keytable,
+ * all resources used by the keytable will be freed
+ */
+
+isc_result_t
+dns_keytable_add(dns_keytable_t *keytable, bool managed, bool initial,
+ dns_name_t *name, dns_rdata_ds_t *ds,
+ dns_keytable_callback_t callback, void *callback_arg);
+/*%<
+ * Add a key to 'keytable'. The keynode associated with 'name'
+ * is updated with the DS specified in 'ds'.
+ *
+ * The value of keynode->managed is set to 'managed', and the
+ * value of keynode->initial is set to 'initial'. (Note: 'initial'
+ * should only be used when adding managed-keys from configuration.
+ * This indicates the key is in "initializing" state, and has not yet
+ * been confirmed with a key refresh query. Once a key refresh query
+ * has validated, we update the keynode with initial == false.)
+ *
+ * Notes:
+ *
+ *\li If the key already exists in the table, adding it again
+ * has no effect and ISC_R_SUCCESS is returned.
+ *
+ * Requires:
+ *
+ *\li 'keytable' points to a valid keytable.
+ *\li 'ds' is not NULL.
+ *\li if 'initial' is true then 'managed' must also be true.
+ *
+ * Returns:
+ *
+ *\li ISC_R_SUCCESS
+ *\li ISC_R_EXISTS
+ *
+ *\li Any other result indicates failure.
+ */
+
+isc_result_t
+dns_keytable_marksecure(dns_keytable_t *keytable, const dns_name_t *name);
+/*%<
+ * Add a null key to 'keytable' for name 'name'. This marks the
+ * name as a secure domain, but doesn't supply any key data to allow the
+ * domain to be validated. (Used when automated trust anchor management
+ * has gotten broken by a zone misconfiguration; for example, when the
+ * active key has been revoked but the stand-by key was still in its 30-day
+ * waiting period for validity.)
+ *
+ * Notes:
+ *
+ *\li If a key already exists in the table, ISC_R_EXISTS is
+ * returned and nothing is done.
+ *
+ * Requires:
+ *
+ *\li 'keytable' points to a valid keytable.
+ *
+ *\li keyp != NULL && *keyp is a valid dst_key_t *.
+ *
+ * Returns:
+ *
+ *\li ISC_R_SUCCESS
+ *\li ISC_R_EXISTS
+ *
+ *\li Any other result indicates failure.
+ */
+
+isc_result_t
+dns_keytable_delete(dns_keytable_t *keytable, const dns_name_t *keyname,
+ dns_keytable_callback_t callback, void *callback_arg);
+/*%<
+ * Delete all trust anchors from 'keytable' matching name 'keyname'
+ *
+ * Requires:
+ *
+ *\li 'keytable' points to a valid keytable.
+ *
+ *\li 'name' is not NULL
+ *
+ * Returns:
+ *
+ *\li ISC_R_SUCCESS
+ *
+ *\li Any other result indicates failure.
+ */
+
+isc_result_t
+dns_keytable_deletekey(dns_keytable_t *keytable, const dns_name_t *keyname,
+ dns_rdata_dnskey_t *dnskey);
+/*%<
+ * Remove the trust anchor matching the name 'keyname' and the DNSKEY
+ * rdata struct 'dnskey' from 'keytable'.
+ *
+ * Requires:
+ *
+ *\li 'keytable' points to a valid keytable.
+ *\li 'dnskey' is not NULL
+ *
+ * Returns:
+ *
+ *\li ISC_R_SUCCESS
+ *
+ *\li Any other result indicates failure.
+ */
+
+isc_result_t
+dns_keytable_find(dns_keytable_t *keytable, const dns_name_t *keyname,
+ dns_keynode_t **keynodep);
+/*%<
+ * Search for the first instance of a trust anchor named 'name' in
+ * 'keytable', without regard to keyid and algorithm.
+ *
+ * Requires:
+ *
+ *\li 'keytable' is a valid keytable.
+ *
+ *\li 'name' is a valid absolute name.
+ *
+ *\li keynodep != NULL && *keynodep == NULL
+ *
+ * Returns:
+ *
+ *\li ISC_R_SUCCESS
+ *\li ISC_R_NOTFOUND
+ *
+ *\li Any other result indicates an error.
+ */
+
+isc_result_t
+dns_keytable_finddeepestmatch(dns_keytable_t *keytable, const dns_name_t *name,
+ dns_name_t *foundname);
+/*%<
+ * Search for the deepest match of 'name' in 'keytable'.
+ *
+ * Requires:
+ *
+ *\li 'keytable' is a valid keytable.
+ *
+ *\li 'name' is a valid absolute name.
+ *
+ *\li 'foundname' is a name with a dedicated buffer.
+ *
+ * Returns:
+ *
+ *\li ISC_R_SUCCESS
+ *\li ISC_R_NOTFOUND
+ *
+ *\li Any other result indicates an error.
+ */
+
+void
+dns_keytable_detachkeynode(dns_keytable_t *keytable, dns_keynode_t **keynodep);
+/*%<
+ * Detach a keynode found via dns_keytable_find().
+ *
+ * Requires:
+ *
+ *\li *keynodep is a valid keynode returned by a call to dns_keytable_find().
+ *
+ * Ensures:
+ *
+ *\li *keynodep == NULL
+ */
+
+isc_result_t
+dns_keytable_issecuredomain(dns_keytable_t *keytable, const dns_name_t *name,
+ dns_name_t *foundname, bool *wantdnssecp);
+/*%<
+ * Is 'name' at or beneath a trusted key?
+ *
+ * Requires:
+ *
+ *\li 'keytable' is a valid keytable.
+ *
+ *\li 'name' is a valid absolute name.
+ *
+ *\li 'foundanme' is NULL or is a pointer to an initialized dns_name_t
+ *
+ *\li '*wantsdnssecp' is a valid bool.
+ *
+ * Ensures:
+ *
+ *\li On success, *wantsdnssecp will be true if and only if 'name'
+ * is at or beneath a trusted key. If 'foundname' is not NULL, then
+ * it will be updated to contain the name of the closest enclosing
+ * trust anchor.
+ *
+ * Returns:
+ *
+ *\li ISC_R_SUCCESS
+ *
+ *\li Any other result is an error.
+ */
+
+isc_result_t
+dns_keytable_dump(dns_keytable_t *keytable, FILE *fp);
+/*%<
+ * Dump the keytable on fp.
+ */
+
+isc_result_t
+dns_keytable_totext(dns_keytable_t *keytable, isc_buffer_t **buf);
+/*%<
+ * Dump the keytable to buffer at 'buf'
+ */
+
+bool
+dns_keynode_dsset(dns_keynode_t *keynode, dns_rdataset_t *rdataset);
+/*%<
+ * Clone the DS RRset associated with 'keynode' into 'rdataset' if
+ * it exists. 'dns_rdataset_disassociate(rdataset)' needs to be
+ * called when done.
+ *
+ * Returns:
+ *\li true if there is a DS RRset.
+ *\li false if there isn't DS RRset.
+ *
+ * Requires:
+ *\li 'keynode' is valid.
+ *\li 'rdataset' is valid or NULL.
+ */
+
+bool
+dns_keynode_managed(dns_keynode_t *keynode);
+/*%<
+ * Is this flagged as a managed key?
+ */
+
+bool
+dns_keynode_initial(dns_keynode_t *keynode);
+/*%<
+ * Is this flagged as an initializing key?
+ */
+
+void
+dns_keynode_trust(dns_keynode_t *keynode);
+/*%<
+ * Sets keynode->initial to false in order to mark the key as
+ * trusted: no longer an initializing key.
+ */
+
+isc_result_t
+dns_keytable_forall(dns_keytable_t *keytable,
+ void (*func)(dns_keytable_t *, dns_keynode_t *,
+ dns_name_t *, void *),
+ void *arg);
+ISC_LANG_ENDDECLS
diff --git a/lib/dns/include/dns/keyvalues.h b/lib/dns/include/dns/keyvalues.h
new file mode 100644
index 0000000..2155266
--- /dev/null
+++ b/lib/dns/include/dns/keyvalues.h
@@ -0,0 +1,104 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#pragma once
+
+/*! \file dns/keyvalues.h */
+
+/*
+ * Flags field of the KEY RR rdata
+ */
+#define DNS_KEYFLAG_TYPEMASK 0xC000 /*%< Mask for "type" bits */
+#define DNS_KEYTYPE_AUTHCONF 0x0000 /*%< Key usable for both */
+#define DNS_KEYTYPE_CONFONLY 0x8000 /*%< Key usable for confidentiality */
+#define DNS_KEYTYPE_AUTHONLY 0x4000 /*%< Key usable for authentication */
+#define DNS_KEYTYPE_NOKEY 0xC000 /*%< No key usable for either; no key */
+#define DNS_KEYTYPE_NOAUTH DNS_KEYTYPE_CONFONLY
+#define DNS_KEYTYPE_NOCONF DNS_KEYTYPE_AUTHONLY
+
+#define DNS_KEYFLAG_RESERVED2 0x2000 /*%< reserved - must be zero */
+#define DNS_KEYFLAG_EXTENDED 0x1000 /*%< key has extended flags */
+#define DNS_KEYFLAG_RESERVED4 0x0800 /*%< reserved - must be zero */
+#define DNS_KEYFLAG_RESERVED5 0x0400 /*%< reserved - must be zero */
+#define DNS_KEYFLAG_OWNERMASK 0x0300 /*%< these bits determine the type */
+#define DNS_KEYOWNER_USER 0x0000 /*%< key is assoc. with user */
+#define DNS_KEYOWNER_ENTITY 0x0200 /*%< key is assoc. with entity eg host */
+#define DNS_KEYOWNER_ZONE 0x0100 /*%< key is zone key */
+#define DNS_KEYOWNER_RESERVED 0x0300 /*%< reserved meaning */
+#define DNS_KEYFLAG_REVOKE 0x0080 /*%< key revoked (per rfc5011) */
+#define DNS_KEYFLAG_RESERVED9 0x0040 /*%< reserved - must be zero */
+#define DNS_KEYFLAG_RESERVED10 0x0020 /*%< reserved - must be zero */
+#define DNS_KEYFLAG_RESERVED11 0x0010 /*%< reserved - must be zero */
+#define DNS_KEYFLAG_SIGNATORYMASK \
+ 0x000F /*%< key can sign RR's of same name \
+ */
+
+#define DNS_KEYFLAG_RESERVEDMASK \
+ (DNS_KEYFLAG_RESERVED2 | DNS_KEYFLAG_RESERVED4 | \
+ DNS_KEYFLAG_RESERVED5 | DNS_KEYFLAG_RESERVED9 | \
+ DNS_KEYFLAG_RESERVED10 | DNS_KEYFLAG_RESERVED11)
+#define DNS_KEYFLAG_KSK 0x0001 /*%< key signing key */
+
+#define DNS_KEYFLAG_RESERVEDMASK2 0xFFFF /*%< no bits defined here */
+
+/* The Algorithm field of the KEY and SIG RR's is an integer, {1..254} */
+#define DNS_KEYALG_RSAMD5 1 /*%< RSA with MD5 */
+#define DNS_KEYALG_RSA 1 /*%< Used just for tagging */
+#define DNS_KEYALG_DH 2 /*%< Diffie Hellman KEY */
+#define DNS_KEYALG_DSA 3 /*%< DSA KEY */
+#define DNS_KEYALG_NSEC3DSA 6
+#define DNS_KEYALG_DSS DNS_ALG_DSA
+#define DNS_KEYALG_ECC 4
+#define DNS_KEYALG_RSASHA1 5
+#define DNS_KEYALG_NSEC3RSASHA1 7
+#define DNS_KEYALG_RSASHA256 8
+#define DNS_KEYALG_RSASHA512 10
+#define DNS_KEYALG_ECCGOST 12
+#define DNS_KEYALG_ECDSA256 13
+#define DNS_KEYALG_ECDSA384 14
+#define DNS_KEYALG_ED25519 15
+#define DNS_KEYALG_ED448 16
+#define DNS_KEYALG_INDIRECT 252
+#define DNS_KEYALG_PRIVATEDNS 253
+#define DNS_KEYALG_PRIVATEOID 254 /*%< Key begins with OID giving alg */
+#define DNS_KEYALG_MAX 255
+
+/* Protocol values */
+#define DNS_KEYPROTO_RESERVED 0
+#define DNS_KEYPROTO_TLS 1
+#define DNS_KEYPROTO_EMAIL 2
+#define DNS_KEYPROTO_DNSSEC 3
+#define DNS_KEYPROTO_IPSEC 4
+#define DNS_KEYPROTO_ANY 255
+
+/* Signatures */
+#define DNS_SIG_RSAMINBITS 512 /*%< Size of a mod or exp in bits */
+#define DNS_SIG_RSAMAXBITS 2552
+/* Total of binary mod and exp */
+#define DNS_SIG_RSAMAXBYTES ((DNS_SIG_RSAMAXBITS + 7 / 8) * 2 + 3)
+/*%< Max length of text sig block */
+#define DNS_SIG_RSAMAXBASE64 (((DNS_SIG_RSAMAXBYTES + 2) / 3) * 4)
+#define DNS_SIG_RSAMINSIZE ((DNS_SIG_RSAMINBITS + 7) / 8)
+#define DNS_SIG_RSAMAXSIZE ((DNS_SIG_RSAMAXBITS + 7) / 8)
+
+#define DNS_SIG_ECDSA256SIZE 64
+#define DNS_SIG_ECDSA384SIZE 96
+
+#define DNS_KEY_ECDSA256SIZE 64
+#define DNS_KEY_ECDSA384SIZE 96
+
+#define DNS_SIG_ED25519SIZE 64
+#define DNS_SIG_ED448SIZE 114
+
+#define DNS_KEY_ED25519SIZE 32
+#define DNS_KEY_ED448SIZE 57
diff --git a/lib/dns/include/dns/librpz.h b/lib/dns/include/dns/librpz.h
new file mode 100644
index 0000000..8fdbe16
--- /dev/null
+++ b/lib/dns/include/dns/librpz.h
@@ -0,0 +1,945 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/*
+ * Define the interface from a DNS resolver to the Response Policy Zone
+ * library, librpz.
+ *
+ * This file should be included only the interface functions between the
+ * resolver and librpz to avoid name space pollution.
+ *
+ * Copyright (c) 2016-2017 Farsight Security, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * version 1.2.12
+ */
+
+#pragma once
+
+#include <inttypes.h>
+#include <stdarg.h>
+#include <stdbool.h>
+#include <stdio.h>
+
+#include <arpa/nameser.h>
+#include <netinet/in.h>
+#include <sys/types.h>
+
+/*
+ * Allow either ordinary or dlopen() linking.
+ */
+#ifdef LIBRPZ_INTERNAL
+#define LIBDEF(t, s) extern t s;
+#define LIBDEF_F(f) LIBDEF(librpz_##f##_t, librpz_##f)
+#else /* ifdef LIBRPZ_INTERNAL */
+#define LIBDEF(t, s)
+#define LIBDEF_F(f)
+#endif /* ifdef LIBRPZ_INTERNAL */
+
+/*
+ * Response Policy Zone triggers.
+ * Comparisons of trigger precedences require
+ * LIBRPZ_TRIG_CLIENT_IP < LIBRPZ_TRIG_QNAME < LIBRPZ_TRIG_IP
+ * < LIBRPZ_TRIG_NSDNAME < LIBRPZ_TRIG_NSIP}
+ */
+typedef enum {
+ LIBRPZ_TRIG_BAD = 0,
+ LIBRPZ_TRIG_CLIENT_IP = 1,
+ LIBRPZ_TRIG_QNAME = 2,
+ LIBRPZ_TRIG_IP = 3,
+ LIBRPZ_TRIG_NSDNAME = 4,
+ LIBRPZ_TRIG_NSIP = 5
+} librpz_trig_t;
+#define LIBRPZ_TRIG_SIZE 3 /* sizeof librpz_trig_t in bits */
+typedef uint8_t librpz_tbit_t; /* one bit for each of the TRIGS_NUM
+ * trigger types */
+
+/*
+ * Response Policy Zone Actions or policies
+ */
+typedef enum {
+ LIBRPZ_POLICY_UNDEFINED = 0, /* an empty entry or no decision yet */
+ LIBRPZ_POLICY_DELETED = 1, /* placeholder for a deleted policy */
+
+ LIBRPZ_POLICY_PASSTHRU = 2, /* 'passthru': do not rewrite */
+ LIBRPZ_POLICY_DROP = 3, /* 'drop': do not respond */
+ LIBRPZ_POLICY_TCP_ONLY = 4, /* 'tcp-only': answer UDP with TC=1 */
+ LIBRPZ_POLICY_NXDOMAIN = 5, /* 'nxdomain': answer with NXDOMAIN */
+ LIBRPZ_POLICY_NODATA = 6, /* 'nodata': answer with ANCOUNT=0 */
+ LIBRPZ_POLICY_RECORD = 7, /* rewrite with the policy's RR */
+
+ /* only in client configurations to override the zone */
+ LIBRPZ_POLICY_GIVEN, /* 'given': what policy record says */
+ LIBRPZ_POLICY_DISABLED, /* at most log */
+ LIBRPZ_POLICY_CNAME, /* answer with 'cname x' */
+} librpz_policy_t;
+#define LIBRPZ_POLICY_BITS 4
+
+/*
+ * Special policies that appear as targets of CNAMEs
+ * NXDOMAIN is signaled by a CNAME with a "." target.
+ * NODATA is signaled by a CNAME with a "*." target.
+ */
+#define LIBRPZ_RPZ_PREFIX "rpz-"
+#define LIBRPZ_RPZ_PASSTHRU LIBRPZ_RPZ_PREFIX "passthru"
+#define LIBRPZ_RPZ_DROP LIBRPZ_RPZ_PREFIX "drop"
+#define LIBRPZ_RPZ_TCP_ONLY LIBRPZ_RPZ_PREFIX "tcp-only"
+
+typedef uint16_t librpz_dznum_t; /* dnsrpzd zone # in [0,DZNUM_MAX] */
+typedef uint8_t librpz_cznum_t; /* client zone # in [0,CZNUM_MAX] */
+
+/*
+ * CIDR block
+ */
+typedef struct librpz_prefix {
+ union {
+ struct in_addr in;
+ struct in6_addr in6;
+ } addr;
+ uint8_t family;
+ uint8_t len;
+} librpz_prefix_t;
+
+/*
+ * A domain
+ */
+typedef uint8_t librpz_dsize_t;
+typedef struct librpz_domain {
+ librpz_dsize_t size; /* of only .d */
+ uint8_t d[0]; /* variable length wire format */
+} librpz_domain_t;
+
+/*
+ * A maximal domain buffer
+ */
+typedef struct librpz_domain_buf {
+ librpz_dsize_t size;
+ uint8_t d[NS_MAXCDNAME];
+} librpz_domain_buf_t;
+
+/*
+ * A resource record without the owner name.
+ * C compilers say that sizeof(librpz_rr_t)=12 instead of 10.
+ */
+typedef struct {
+ uint16_t type; /* network byte order */
+ uint16_t class; /* network byte order */
+ uint32_t ttl; /* network byte order */
+ uint16_t rdlength; /* network byte order */
+ uint8_t rdata[0]; /* variable length */
+} librpz_rr_t;
+
+/*
+ * The database file might be mapped with different starting addresses
+ * by concurrent clients (resolvers), and so all pointers are offsets.
+ */
+typedef uint32_t librpz_idx_t;
+#define LIBRPZ_IDX_NULL 0
+#define LIBRPZ_IDX_MIN 1
+#define LIBRPZ_IDX_BAD ((librpz_idx_t)-1)
+/**
+ * Partial decoded results of a set of RPZ queries for a single DNS response
+ * or iteration through the mapped file.
+ */
+typedef int16_t librpz_result_id_t;
+typedef struct librpz_result {
+ librpz_idx_t next_rr;
+ librpz_result_id_t hit_id; /* trigger ID from resolver */
+ librpz_policy_t zpolicy; /* policy from zone */
+ librpz_policy_t policy; /* adjusted by client configuration */
+ librpz_dznum_t dznum; /* dnsrpzd zone number */
+ librpz_cznum_t cznum; /* librpz client zone number */
+ librpz_trig_t trig : LIBRPZ_TRIG_SIZE;
+ bool log : 1; /* log rewrite given librpz_log_level
+ * */
+} librpz_result_t;
+
+/**
+ * librpz trace or log levels.
+ */
+typedef enum {
+ LIBRPZ_LOG_FATAL = 0, /* always print fatal errors */
+ LIBRPZ_LOG_ERROR = 1, /* errors have this level */
+ LIBRPZ_LOG_TRACE1 = 2, /* big events such as dnsrpzd starts */
+ LIBRPZ_LOG_TRACE2 = 3, /* smaller dnsrpzd zone transfers */
+ LIBRPZ_LOG_TRACE3 = 4, /* librpz hits */
+ LIBRPZ_LOG_TRACE4 = 5, /* librpz lookups */
+ LIBRPZ_LOG_INVALID = 999,
+} librpz_log_level_t;
+typedef librpz_log_level_t(librpz_log_level_val_t)(librpz_log_level_t level);
+LIBDEF_F(log_level_val)
+
+/**
+ * Logging function that can be supplied by the resolver.
+ * @param level is one of librpz_log_level_t
+ * @param ctx is for use by the resolver's logging system.
+ * NULL mean a context-free message.
+ */
+typedef void(librpz_log_fnc_t)(librpz_log_level_t level, void *ctx,
+ const char *buf);
+
+/**
+ * Point librpz logging functions to the resolver's choice.
+ */
+typedef void(librpz_set_log_t)(librpz_log_fnc_t *new_log, const char *prog_nm);
+LIBDEF_F(set_log)
+
+/**
+ * librpz error messages are put in these buffers.
+ * Use a structure instead of naked char* to let the compiler check the length.
+ * A function defined with "foo(char buf[120])" can be called with
+ * "char sbuf[2]; foo(sbuf)" and suffer a buffer overrun.
+ */
+typedef struct {
+ char c[120];
+} librpz_emsg_t;
+
+#ifdef LIBRPZ_HAVE_ATTR
+#define LIBRPZ_UNUSED __attribute__((unused))
+#define LIBRPZ_PF(f, l) __attribute__((format(printf, f, l)))
+#define LIBRPZ_NORET __attribute__((__noreturn__))
+#else /* ifdef LIBRPZ_HAVE_ATTR */
+#define LIBRPZ_UNUSED
+#define LIBRPZ_PF(f, l)
+#define LIBRPZ_NORET
+#endif /* ifdef LIBRPZ_HAVE_ATTR */
+
+typedef bool(librpz_parse_log_opt_t)(librpz_emsg_t *emsg, const char *arg);
+LIBDEF_F(parse_log_opt)
+
+typedef void(librpz_vpemsg_t)(librpz_emsg_t *emsg, const char *p, va_list args);
+LIBDEF_F(vpemsg)
+typedef void(librpz_pemsg_t)(librpz_emsg_t *emsg, const char *p, ...)
+ LIBRPZ_PF(2, 3);
+LIBDEF_F(pemsg)
+
+typedef void(librpz_vlog_t)(librpz_log_level_t level, void *ctx, const char *p,
+ va_list args);
+LIBDEF_F(vlog)
+typedef void(librpz_log_t)(librpz_log_level_t level, void *ctx, const char *p,
+ ...) LIBRPZ_PF(3, 4);
+LIBDEF_F(log)
+
+typedef void(librpz_fatal_t)(int ex_code, const char *p, ...) LIBRPZ_PF(2, 3);
+extern void
+librpz_fatal(int ex_code, const char *p, ...) LIBRPZ_PF(2, 3) LIBRPZ_NORET;
+
+typedef void(librpz_rpz_assert_t)(const char *file, unsigned line,
+ const char *p, ...) LIBRPZ_PF(3, 4);
+extern void
+librpz_rpz_assert(const char *file, unsigned line, const char *p, ...)
+ LIBRPZ_PF(3, 4) LIBRPZ_NORET;
+
+typedef void(librpz_rpz_vassert_t)(const char *file, uint line, const char *p,
+ va_list args);
+extern void
+librpz_rpz_vassert(const char *file, uint line, const char *p,
+ va_list args) LIBRPZ_NORET;
+
+/*
+ * As far as clients are concerned, all relative pointers or indexes in a
+ * version of the mapped file except trie node parent pointers remain valid
+ * forever. A client must release a version so that it can be garbage
+ * collected by the file system. When dnsrpzd needs to expand the file,
+ * it copies the old file to a new, larger file. Clients can continue
+ * using the old file.
+ *
+ * Versions can also appear in a single file. Old nodes and trie values
+ * within the file are not destroyed until all clients using the version
+ * that contained the old values release the version.
+ *
+ * A client is marked as using version by connecting to the daemon. It is
+ * marked as using all subsequent versions. A client releases all versions
+ * by closing the connection or a range of versions by updating is slot
+ * in the shared memory version table.
+ *
+ * As far as clients are concerned, there are the following possible librpz
+ * failures:
+ * - malloc() or other fatal internal librpz problems indicated by
+ * a failing return from a librpz function
+ * All operations will fail until client handle is destroyed and
+ * recreated with librpz_client_detach() and librpz_client_create().
+ * - corrupt database detected by librpz code, corrupt database detected
+ * by dnsrpzd, or disconnection from the daemon.
+ * Current operations will fail.
+ *
+ * Clients assume that the file has already been unlinked before
+ * the corrupt flag is set so that they do not race with the server
+ * over the corruption of a single file. A client that finds the
+ * corrupt set knows that dnsrpzd has already crashed with
+ * abort() and is restarting. The client can re-connect to dnsrpzd
+ * and retransmit its configuration, backing off as usual if anything
+ * goes wrong.
+ *
+ * Searches of the database by a client do not need locks against dnsrpzd or
+ * other clients, but a lock is used to protect changes to the connection
+ * by competing threads in the client. The client provides functions
+ * to serialize the concurrent use of any single client handle.
+ * Functions that do nothing are appropriate for applications that are
+ * not "threaded" or that do not share client handles among threads.
+ * Otherwise, functions must be provided to librpz_clientcreate().
+ * Something like the following works with pthreads:
+ *
+ * static void
+ * lock(void *mutex) { assert(pthread_mutex_lock(mutex) == 0); }
+ *
+ * static void
+ * unlock(void *mutex) { assert(pthread_mutex_unlock(mutex) == 0); }
+ *
+ * static void
+ * mutex_destroy(void *mutex) { assert(pthread_mutex_destroy(mutex) == 0); }
+ *
+ *
+ *
+ * At every instant, all of the data and pointers in the mapped file are valid.
+ * Changes to trie node or other data are always made so that it and
+ * all pointers in and to it remain valid for a time. Old versions are
+ * eventually discarded.
+ *
+ * Dnsrpzd periodically defines a new version by setting aside all changes
+ * made since the previous version was defined. Subsequent changes
+ * made (only!) by dnsrpzd will be part of the next version.
+ *
+ * To discard an old version, dnsrpzd must know that all clients have stopped
+ * using that version. Clients do that by using part of the mapped file
+ * to tell dnsrpzd the oldest version that each client is using.
+ * Dnsrpzd assigns each connecting client an entry in the cversions array
+ * in the mapped file. The client puts version numbers into that entry
+ * to signal to dnsrpzd which versions that can be discarded.
+ * Dnsrpzd is free, as far as that client is concerned, to discard all
+ * numerically smaller versions. A client can disclaim all versions with
+ * the version number VERSIONS_ALL or 0.
+ *
+ * The race between a client changing its entry and dnsrpzd discarding a
+ * version is resolved by allowing dnsrpzd to discard all versions
+ * smaller or equal to the client's version number. If dnsrpzd is in
+ * the midst of discarding or about to discard version N when the
+ * client asserts N, no harm is done. The client depends only on
+ * the consistency of version N+1.
+ *
+ * This version mechanism depends in part on not being exercised too frequently
+ * Version numbers are 32 bits long and dnsrpzd creates new versions
+ * at most once every 30 seconds.
+ */
+
+/*
+ * Lock functions for concurrent use of a single librpz_client_t client handle.
+ */
+typedef void(librpz_mutex_t)(void *mutex);
+
+/*
+ * List of connections to dnsrpzd daemons.
+ */
+typedef struct librpz_clist librpz_clist_t;
+
+/*
+ * Client's handle on dnsrpzd.
+ */
+typedef struct librpz_client librpz_client_t;
+
+/**
+ * Create the list of connections to the dnsrpzd daemon.
+ * @param[out] emsg: error message
+ * @param lock: start exclusive access to the client handle
+ * @param unlock: end exclusive access to the client handle
+ * @param mutex_destroy: release the lock
+ * @param mutex: pointer to the lock for the client handle
+ * @param log_ctx: NULL or resolver's context log messages
+ */
+typedef librpz_clist_t *(librpz_clist_create_t)(librpz_emsg_t *emsg,
+ librpz_mutex_t *lock,
+ librpz_mutex_t *unlock,
+ librpz_mutex_t *mutex_destroy,
+ void *mutex, void *log_ctx);
+LIBDEF_F(clist_create)
+
+/**
+ * Release the list of dnsrpzd connections.
+ */
+typedef void(librpz_clist_detach_t)(librpz_clist_t **clistp);
+LIBDEF_F(clist_detach)
+
+/**
+ * Create a librpz client handle.
+ * @param[out] emsg: error message
+ * @param clist: of dnsrpzd connections
+ * @param cstr: string of configuration settings separated by ';' or '\n'
+ * @param use_expired: true to not ignore expired zones
+ * @return client handle or NULL if the handle could not be created
+ */
+typedef librpz_client_t *(librpz_client_create_t)(librpz_emsg_t *emsg,
+ librpz_clist_t *clist,
+ const char *cstr,
+ bool use_expired);
+LIBDEF_F(client_create)
+
+/**
+ * Start (if necessary) dnsrpzd and connect to it.
+ * @param[out] emsg: error message
+ * @param client handle
+ * @param optional: true if it is ok if starting the daemon is not allowed
+ */
+typedef bool(librpz_connect_t)(librpz_emsg_t *emsg, librpz_client_t *client,
+ bool optional);
+LIBDEF_F(connect)
+
+/**
+ * Start to destroy a librpz client handle.
+ * It will not be destroyed until the last set of RPZ queries represented
+ * by a librpz_rsp_t ends.
+ * @param client handle to be released
+ * @return false on error
+ */
+typedef void(librpz_client_detach_t)(librpz_client_t **clientp);
+LIBDEF_F(client_detach)
+
+/**
+ * State for a set of RPZ queries for a single DNS response
+ * or for listing the database.
+ */
+typedef struct librpz_rsp librpz_rsp_t;
+
+/**
+ * Start a set of RPZ queries for a single DNS response.
+ * @param[out] emsg: error message for false return or *rspp=NULL
+ * @param[out] rspp created context or NULL
+ * @param[out] min_ns_dotsp: NULL or pointer to configured MIN-NS-DOTS value
+ * @param client state
+ * @param have_rd: RD=1 in the DNS request
+ * @param have_do: DO=1 in the DNS request
+ * @return false on error
+ */
+typedef bool(librpz_rsp_create_t)(librpz_emsg_t *emsg, librpz_rsp_t **rspp,
+ int *min_ns_dotsp, librpz_client_t *client,
+ bool have_rd, bool have_do);
+LIBDEF_F(rsp_create)
+
+/**
+ * Finish RPZ work for a DNS response.
+ */
+typedef void(librpz_rsp_detach_t)(librpz_rsp_t **rspp);
+LIBDEF_F(rsp_detach)
+
+/**
+ * Get the final, accumulated result of a set of RPZ queries.
+ * Yield LIBRPZ_POLICY_UNDEFINED if
+ * - there were no hits,
+ * - there was a dispositive hit, be we have not recursed and are required
+ * to recurse so that evil DNS authorities will not know we are using RPZ
+ * - we have a hit and have recursed, but later data such as NSIP could
+ * override
+ * @param[out] emsg
+ * @param[out] result describes the hit
+ * or result->policy=LIBRPZ_POLICY_UNDEFINED without a hit
+ * @param[out] result: current policy rewrite values
+ * @param recursed: recursion has now been done even if it was not done
+ * when the hit was found
+ * @param[in,out] rsp state from librpz_itr_start()
+ * @return false on error
+ */
+typedef bool(librpz_rsp_result_t)(librpz_emsg_t *emsg, librpz_result_t *result,
+ bool recursed, const librpz_rsp_t *rsp);
+LIBDEF_F(rsp_result)
+
+/**
+ * Might looking for a trigger be worthwhile?
+ * @param trig: look for this type of trigger
+ * @param ipv6: true if trig is LIBRPZ_TRIG_CLIENT_IP, LIBRPZ_TRIG_IP,
+ * or LIBRPZ_TRIG_NSIP and the IP address is IPv6
+ * @return: true if looking could be worthwhile
+ */
+typedef bool(librpz_have_trig_t)(librpz_trig_t trig, bool ipv6,
+ const librpz_rsp_t *rsp);
+LIBDEF_F(have_trig)
+
+/**
+ * Might looking for NSDNAME and NSIP triggers be worthwhile?
+ * @return: true if looking could be worthwhile
+ */
+typedef bool(librpz_have_ns_trig_t)(const librpz_rsp_t *rsp);
+LIBDEF_F(have_ns_trig)
+
+/**
+ * Convert the found client IP trie key to a CIDR block
+ * @param[out] emsg
+ * @param[out] prefix trigger
+ * @param[in,out] rsp state from librpz_itr_start()
+ * @return false on error
+ */
+typedef bool(librpz_rsp_clientip_prefix_t)(librpz_emsg_t *emsg,
+ librpz_prefix_t *prefix,
+ librpz_rsp_t *rsp);
+LIBDEF_F(rsp_clientip_prefix)
+
+/**
+ * Compute the owner name of the found or result trie key, usually to log it.
+ * An IP address key might be returned as 8.0.0.0.127.rpz-client-ip.
+ * example.com. might be a qname trigger. example.com.rpz-nsdname. could
+ * be an NSDNAME trigger.
+ * @param[out] emsg
+ * @param[out] owner domain
+ * @param[in,out] rsp state from librpz_itr_start()
+ * @return false on error
+ */
+typedef bool(librpz_rsp_domain_t)(librpz_emsg_t *emsg,
+ librpz_domain_buf_t *owner,
+ librpz_rsp_t *rsp);
+LIBDEF_F(rsp_domain)
+
+/**
+ * Get the next RR of the LIBRPZ_POLICY_RECORD result after an initial use of
+ * librpz_rsp_result() or librpz_itr_node() or after a previous use of
+ * librpz_rsp_rr(). The RR is in uncompressed wire format including type,
+ * class, ttl and length in network byte order.
+ * @param[out] emsg
+ * @param[out] typep: optional host byte order record type or ns_t_invalid (0)
+ * @param[out] classp: class such as ns_c_in
+ * @param[out] ttlp: TTL
+ * @param[out] rrp: optional malloc() buffer containing the next RR or
+ * NULL after the last RR
+ * @param[out] result: current policy rewrite values
+ * @param qname: used construct a wildcard CNAME
+ * @param qname_size
+ * @param[in,out] rsp state from librpz_itr_start()
+ * @return false on error
+ */
+typedef bool(librpz_rsp_rr_t)(librpz_emsg_t *emsg, uint16_t *typep,
+ uint16_t *classp, uint32_t *ttlp,
+ librpz_rr_t **rrp, librpz_result_t *result,
+ const uint8_t *qname, size_t qname_size,
+ librpz_rsp_t *rsp);
+LIBDEF_F(rsp_rr)
+
+/**
+ * Get the next RR of the LIBRPZ_POLICY_RECORD result.
+ * @param[out] emsg
+ * @param[out] ttlp: TTL
+ * @param[out] rrp: malloc() buffer with SOA RR without owner name
+ * @param[out] result: current policy rewrite values
+ * @param[out] origin: SOA owner name
+ * @param[out] origin_size
+ * @param[in,out] rsp state from librpz_itr_start()
+ * @return false on error
+ */
+typedef bool(librpz_rsp_soa_t)(librpz_emsg_t *emsg, uint32_t *ttlp,
+ librpz_rr_t **rrp, librpz_domain_buf_t *origin,
+ librpz_result_t *result, librpz_rsp_t *rsp);
+LIBDEF_F(rsp_soa)
+
+/**
+ * Get the SOA serial number for a policy zone to compare with a known value
+ * to check whether a zone transfer is complete.
+ */
+typedef bool(librpz_soa_serial_t)(librpz_emsg_t *emsg, uint32_t *serialp,
+ const char *domain_nm, librpz_rsp_t *rsp);
+LIBDEF_F(soa_serial)
+
+/**
+ * Save the current policy checking state.
+ * @param[out] emsg
+ * @param[in,out] rsp state from librpz_itr_start()
+ * @return false on error
+ */
+typedef bool(librpz_rsp_push_t)(librpz_emsg_t *emsg, librpz_rsp_t *rsp);
+LIBDEF_F(rsp_push)
+#define LIBRPZ_RSP_STACK_DEPTH 3
+
+/**
+ * Restore the previous policy checking state.
+ * @param[out] emsg
+ * @param[out] result: NULL or restored policy rewrite values
+ * @param[in,out] rsp state from librpz_itr_start()
+ * @return false on error
+ */
+typedef bool(librpz_rsp_pop_t)(librpz_emsg_t *emsg, librpz_result_t *result,
+ librpz_rsp_t *rsp);
+LIBDEF_F(rsp_pop)
+
+/**
+ * Discard the most recently save policy checking state.
+ * @param[out] emsg
+ * @param[out] result: NULL or restored policy rewrite values
+ * @return false on error
+ */
+typedef bool(librpz_rsp_pop_discard_t)(librpz_emsg_t *emsg, librpz_rsp_t *rsp);
+LIBDEF_F(rsp_pop_discard)
+
+/**
+ * Disable a zone.
+ * @param[out] emsg
+ * @param znum
+ * @param[in,out] rsp state from librpz_itr_start()
+ * @return false on error
+ */
+typedef bool(librpz_rsp_forget_zone_t)(librpz_emsg_t *emsg, librpz_cznum_t znum,
+ librpz_rsp_t *rsp);
+LIBDEF_F(rsp_forget_zone)
+
+/**
+ * Apply RPZ to an IP address.
+ * @param[out] emsg
+ * @param addr: address to check
+ * @param ipv6: true for 16 byte IPv6 instead of 4 byte IPv4
+ * @param trig LIBRPZ_TRIG_CLIENT_IP, LIBRPZ_TRIG_IP, or LIBRPZ_TRIG_NSIP
+ * @param hit_id: caller chosen
+ * @param recursed: recursion has been done
+ * @param[in,out] rsp state from librpz_itr_start()
+ * @return false on error
+ */
+typedef bool(librpz_ck_ip_t)(librpz_emsg_t *emsg, const void *addr, uint family,
+ librpz_trig_t trig, librpz_result_id_t hit_id,
+ bool recursed, librpz_rsp_t *rsp);
+LIBDEF_F(ck_ip)
+
+/**
+ * Apply RPZ to a wire-format domain.
+ * @param[out] emsg
+ * @param domain in wire format
+ * @param domain_size
+ * @param trig LIBRPZ_TRIG_QNAME or LIBRPZ_TRIG_NSDNAME
+ * @param hit_id: caller chosen
+ * @param recursed: recursion has been done
+ * @param[in,out] rsp state from librpz_itr_start()
+ * @return false on error
+ */
+typedef bool(librpz_ck_domain_t)(librpz_emsg_t *emsg, const uint8_t *domain,
+ size_t domain_size, librpz_trig_t trig,
+ librpz_result_id_t hit_id, bool recursed,
+ librpz_rsp_t *rsp);
+LIBDEF_F(ck_domain)
+
+/**
+ * Ask dnsrpzd to refresh a zone.
+ * @param[out] emsg error message
+ * @param librpz_domain_t domain to refresh
+ * @param client context
+ * @return false after error
+ */
+typedef bool(librpz_zone_refresh_t)(librpz_emsg_t *emsg, const char *domain,
+ librpz_rsp_t *rsp);
+LIBDEF_F(zone_refresh)
+
+/**
+ * Get a string describing the database
+ * @param license: include the license
+ * @param cfiles: include the configuration file names
+ * @param listens: include the local notify IP addresses
+ * @param[out] emsg error message if the result is null
+ * @param client context
+ * @return malloc'ed string or NULL after error
+ */
+typedef char *(librpz_db_info_t)(librpz_emsg_t *emsg, bool license, bool cfiles,
+ bool listens, librpz_rsp_t *rsp);
+LIBDEF_F(db_info)
+
+/**
+ * Start a context for listing the nodes and/or zones in the mapped file
+ * @param[out] emsg: error message for false return or *rspp=NULL
+ * @param[out] rspp: created context or NULL
+ * @param client context
+ * @return false after error
+ */
+typedef bool(librpz_itr_start_t)(librpz_emsg_t *emsg, librpz_rsp_t **rspp,
+ librpz_client_t *client);
+LIBDEF_F(itr_start)
+
+/**
+ * Get mapped file memory allocation statistics.
+ * @param[out] emsg: error message
+ * @param rsp state from librpz_itr_start()
+ * @return malloc'ed string or NULL after error
+ */
+typedef char *(librpz_mf_stats_t)(librpz_emsg_t *emsg, librpz_rsp_t *rsp);
+LIBDEF_F(mf_stats)
+
+/**
+ * Get versions currently used by clients.
+ * @param[out] emsg: error message
+ * @param[in,out] rsp: state from librpz_itr_start()
+ * @return malloc'ed string or NULL after error
+ */
+typedef char *(librpz_vers_stats_t)(librpz_emsg_t *emsg, librpz_rsp_t *rsp);
+LIBDEF_F(vers_stats)
+
+/**
+ * Allocate a string describing the next zone or "" after the last zone.
+ * @param[out] emsg
+ * @param all_zones to list all instead of only requested zones
+ * @param[in,out] rsp state from librpz_rsp_start()
+ * @return malloc'ed string or NULL after error
+ */
+typedef char *(librpz_itr_zone_t)(librpz_emsg_t *emsg, bool all_zones,
+ librpz_rsp_t *rsp);
+LIBDEF_F(itr_zone)
+
+/**
+ * Describe the next trie node while dumping the database.
+ * @param[out] emsg
+ * @param[out] result describes node
+ * or result->policy=LIBRPZ_POLICY_UNDEFINED after the last node.
+ * @param all_zones to list all instead of only requested zones
+ * @param[in,out] rsp state from librpz_itr_start()
+ * @return: false on error
+ */
+typedef bool(librpz_itr_node_t)(librpz_emsg_t *emsg, librpz_result_t *result,
+ bool all_zones, librpz_rsp_t *rsp);
+LIBDEF_F(itr_node)
+
+/**
+ * RPZ policy to string with a backup buffer of POLICY2STR_SIZE size
+ */
+typedef const char *(librpz_policy2str_t)(librpz_policy_t policy, char *buf,
+ size_t buf_size);
+#define POLICY2STR_SIZE sizeof("policy xxxxxx")
+LIBDEF_F(policy2str)
+
+/**
+ * Trigger type to string.
+ */
+typedef const char *(librpz_trig2str_t)(librpz_trig_t trig);
+LIBDEF_F(trig2str)
+
+/**
+ * Convert a number of seconds to a zone file duration string
+ */
+typedef const char *(librpz_secs2str_t)(time_t secs, char *buf,
+ size_t buf_size);
+#define SECS2STR_SIZE sizeof("1234567w7d24h59m59s")
+LIBDEF_F(secs2str)
+
+/**
+ * Parse a duration with 's', 'm', 'h', 'd', and 'w' units.
+ */
+typedef bool(librpz_str2secs_t)(librpz_emsg_t *emsg, time_t *val,
+ const char *str0);
+LIBDEF_F(str2secs)
+
+/**
+ * Translate selected rtypes to strings
+ */
+typedef const char *(librpz_rtype2str_t)(uint type, char *buf, size_t buf_size);
+#define RTYPE2STR_SIZE sizeof("type xxxxx")
+LIBDEF_F(rtype2str)
+
+/**
+ * Local version of ns_name_ntop() for portability.
+ */
+typedef int(librpz_domain_ntop_t)(const u_char *src, char *dst, size_t dstsiz);
+LIBDEF_F(domain_ntop)
+
+/**
+ * Local version of ns_name_pton().
+ */
+typedef int(librpz_domain_pton2_t)(const char *src, u_char *dst, size_t dstsiz,
+ size_t *dstlen, bool lower);
+LIBDEF_F(domain_pton2)
+
+typedef union socku socku_t;
+typedef socku_t *(librpz_mk_inet_su_t)(socku_t *su, const struct in_addr *addrp,
+ in_port_t port);
+LIBDEF_F(mk_inet_su)
+
+typedef socku_t *(librpz_mk_inet6_su_t)(socku_t *su,
+ const struct in6_addr *addrp,
+ uint32_t scope_id, in_port_t port);
+LIBDEF_F(mk_inet6_su)
+
+typedef bool(librpz_str2su_t)(socku_t *sup, const char *str);
+LIBDEF_F(str2su)
+
+typedef char *(librpz_su2str_t)(char *str, size_t str_len, const socku_t *su);
+LIBDEF_F(su2str)
+#define SU2STR_SIZE (INET6_ADDRSTRLEN + 1 + 6 + 1)
+
+/**
+ * default path to dnsrpzd
+ */
+LIBDEF(const char *, librpz_dnsrpzd_path)
+
+#undef LIBDEF
+
+/*
+ * This is the dlopen() interface to librpz.
+ */
+typedef const struct {
+ const char *dnsrpzd_path;
+ const char *version;
+ librpz_parse_log_opt_t *parse_log_opt;
+ librpz_log_level_val_t *log_level_val;
+ librpz_set_log_t *set_log;
+ librpz_vpemsg_t *vpemsg;
+ librpz_pemsg_t *pemsg;
+ librpz_vlog_t *vlog;
+ librpz_log_t *log;
+ librpz_fatal_t *fatal LIBRPZ_NORET;
+ librpz_rpz_assert_t *rpz_assert LIBRPZ_NORET;
+ librpz_rpz_vassert_t *rpz_vassert LIBRPZ_NORET;
+ librpz_clist_create_t *clist_create;
+ librpz_clist_detach_t *clist_detach;
+ librpz_client_create_t *client_create;
+ librpz_connect_t *connect;
+ librpz_client_detach_t *client_detach;
+ librpz_rsp_create_t *rsp_create;
+ librpz_rsp_detach_t *rsp_detach;
+ librpz_rsp_result_t *rsp_result;
+ librpz_have_trig_t *have_trig;
+ librpz_have_ns_trig_t *have_ns_trig;
+ librpz_rsp_clientip_prefix_t *rsp_clientip_prefix;
+ librpz_rsp_domain_t *rsp_domain;
+ librpz_rsp_rr_t *rsp_rr;
+ librpz_rsp_soa_t *rsp_soa;
+ librpz_soa_serial_t *soa_serial;
+ librpz_rsp_push_t *rsp_push;
+ librpz_rsp_pop_t *rsp_pop;
+ librpz_rsp_pop_discard_t *rsp_pop_discard;
+ librpz_rsp_forget_zone_t *rsp_forget_zone;
+ librpz_ck_ip_t *ck_ip;
+ librpz_ck_domain_t *ck_domain;
+ librpz_zone_refresh_t *zone_refresh;
+ librpz_db_info_t *db_info;
+ librpz_itr_start_t *itr_start;
+ librpz_mf_stats_t *mf_stats;
+ librpz_vers_stats_t *vers_stats;
+ librpz_itr_zone_t *itr_zone;
+ librpz_itr_node_t *itr_node;
+ librpz_policy2str_t *policy2str;
+ librpz_trig2str_t *trig2str;
+ librpz_secs2str_t *secs2str;
+ librpz_str2secs_t *str2secs;
+ librpz_rtype2str_t *rtype2str;
+ librpz_domain_ntop_t *domain_ntop;
+ librpz_domain_pton2_t *domain_pton2;
+ librpz_mk_inet_su_t *mk_inet_su;
+ librpz_mk_inet6_su_t *mk_inet6_su;
+ librpz_str2su_t *str2su;
+ librpz_su2str_t *su2str;
+} librpz_0_t;
+extern librpz_0_t librpz_def_0;
+
+/*
+ * Future versions can be upward compatible by defining LIBRPZ_DEF as
+ * librpz_X_t.
+ */
+#define LIBRPZ_DEF librpz_def_0
+#define LIBRPZ_DEF_STR "librpz_def_0"
+
+typedef librpz_0_t librpz_t;
+extern librpz_t *librpz;
+
+#if LIBRPZ_LIB_OPEN == 2
+#include <dlfcn.h>
+
+/**
+ * link-load librpz
+ * @param[out] emsg: error message
+ * @param[in,out] dl_handle: NULL or pointer to new dlopen handle
+ * @param[in] path: librpz.so path
+ * @return address of interface structure or NULL on failure
+ */
+static inline librpz_t *
+librpz_lib_open(librpz_emsg_t *emsg, void **dl_handle, const char *path) {
+ void *handle;
+ librpz_t *new_librpz;
+
+ emsg->c[0] = '\0';
+
+ /*
+ * Close a previously opened handle on librpz.so.
+ */
+ if (dl_handle != NULL && *dl_handle != NULL) {
+ if (dlclose(*dl_handle) != 0) {
+ snprintf(emsg->c, sizeof(librpz_emsg_t),
+ "dlopen(NULL): %s", dlerror());
+ return (NULL);
+ }
+ *dl_handle = NULL;
+ }
+
+ /*
+ * First try the main executable of the process in case it was
+ * linked to librpz.
+ * Do not worry if we cannot search the main executable of the process.
+ */
+ handle = dlopen(NULL, RTLD_NOW | RTLD_LOCAL);
+ if (handle != NULL) {
+ new_librpz = dlsym(handle, LIBRPZ_DEF_STR);
+ if (new_librpz != NULL) {
+ if (dl_handle != NULL) {
+ *dl_handle = handle;
+ }
+ return (new_librpz);
+ }
+ if (dlclose(handle) != 0) {
+ snprintf(emsg->c, sizeof(librpz_emsg_t),
+ "dlsym(NULL, " LIBRPZ_DEF_STR "): %s",
+ dlerror());
+ return (NULL);
+ }
+ }
+
+ if (path == NULL || path[0] == '\0') {
+ snprintf(emsg->c, sizeof(librpz_emsg_t),
+ "librpz not linked and no dlopen() path provided");
+ return (NULL);
+ }
+
+ handle = dlopen(path, RTLD_NOW | RTLD_LOCAL);
+ if (handle == NULL) {
+ snprintf(emsg->c, sizeof(librpz_emsg_t), "dlopen(%s): %s", path,
+ dlerror());
+ return (NULL);
+ }
+ new_librpz = dlsym(handle, LIBRPZ_DEF_STR);
+ if (new_librpz != NULL) {
+ if (dl_handle != NULL) {
+ *dl_handle = handle;
+ }
+ return (new_librpz);
+ }
+ snprintf(emsg->c, sizeof(librpz_emsg_t),
+ "dlsym(%s, " LIBRPZ_DEF_STR "): %s", path, dlerror());
+ dlclose(handle);
+ return (NULL);
+}
+#elif defined(LIBRPZ_LIB_OPEN)
+/*
+ * Statically link to the librpz.so DSO on systems without dlopen()
+ */
+static inline librpz_t *
+librpz_lib_open(librpz_emsg_t *emsg, void **dl_handle, const char *path) {
+ (void)(path);
+
+ if (dl_handle != NULL) {
+ *dl_handle = NULL;
+ }
+
+#if LIBRPZ_LIB_OPEN == 1
+ emsg->c[0] = '\0';
+ return (&LIBRPZ_DEF);
+#else /* if LIBRPZ_LIB_OPEN == 1 */
+ snprintf(emsg->c, sizeof(librpz_emsg_t),
+ "librpz not available via ./configure");
+ return (NULL);
+#endif /* LIBRPZ_LIB_OPEN */
+}
+#endif /* LIBRPZ_LIB_OPEN */
diff --git a/lib/dns/include/dns/log.h b/lib/dns/include/dns/log.h
new file mode 100644
index 0000000..03b97ac
--- /dev/null
+++ b/lib/dns/include/dns/log.h
@@ -0,0 +1,112 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/*! \file dns/log.h
+ */
+
+#pragma once
+
+#include <isc/lang.h>
+#include <isc/log.h>
+
+extern isc_log_t *dns_lctx;
+extern isc_logcategory_t dns_categories[];
+extern isc_logmodule_t dns_modules[];
+
+#define DNS_LOGCATEGORY_NOTIFY (&dns_categories[0])
+#define DNS_LOGCATEGORY_DATABASE (&dns_categories[1])
+#define DNS_LOGCATEGORY_SECURITY (&dns_categories[2])
+/* DNS_LOGCATEGORY_CONFIG superseded by CFG_LOGCATEGORY_CONFIG */
+#define DNS_LOGCATEGORY_DNSSEC (&dns_categories[4])
+#define DNS_LOGCATEGORY_RESOLVER (&dns_categories[5])
+#define DNS_LOGCATEGORY_XFER_IN (&dns_categories[6])
+#define DNS_LOGCATEGORY_XFER_OUT (&dns_categories[7])
+#define DNS_LOGCATEGORY_DISPATCH (&dns_categories[8])
+#define DNS_LOGCATEGORY_LAME_SERVERS (&dns_categories[9])
+#define DNS_LOGCATEGORY_DELEGATION_ONLY (&dns_categories[10])
+#define DNS_LOGCATEGORY_EDNS_DISABLED (&dns_categories[11])
+#define DNS_LOGCATEGORY_RPZ (&dns_categories[12])
+#define DNS_LOGCATEGORY_RRL (&dns_categories[13])
+#define DNS_LOGCATEGORY_CNAME (&dns_categories[14])
+#define DNS_LOGCATEGORY_SPILL (&dns_categories[15])
+#define DNS_LOGCATEGORY_DNSTAP (&dns_categories[16])
+#define DNS_LOGCATEGORY_ZONELOAD (&dns_categories[17])
+#define DNS_LOGCATEGORY_NSID (&dns_categories[18])
+#define DNS_LOGCATEGORY_RPZ_PASSTHRU (&dns_categories[19])
+
+/* Backwards compatibility. */
+#define DNS_LOGCATEGORY_GENERAL ISC_LOGCATEGORY_GENERAL
+
+#define DNS_LOGMODULE_DB (&dns_modules[0])
+#define DNS_LOGMODULE_RBTDB (&dns_modules[1])
+#define DNS_LOGMODULE_RBT (&dns_modules[2])
+#define DNS_LOGMODULE_RDATA (&dns_modules[3])
+#define DNS_LOGMODULE_MASTER (&dns_modules[4])
+#define DNS_LOGMODULE_MESSAGE (&dns_modules[5])
+#define DNS_LOGMODULE_CACHE (&dns_modules[6])
+#define DNS_LOGMODULE_CONFIG (&dns_modules[7])
+#define DNS_LOGMODULE_RESOLVER (&dns_modules[8])
+#define DNS_LOGMODULE_ZONE (&dns_modules[9])
+#define DNS_LOGMODULE_JOURNAL (&dns_modules[10])
+#define DNS_LOGMODULE_ADB (&dns_modules[11])
+#define DNS_LOGMODULE_XFER_IN (&dns_modules[12])
+#define DNS_LOGMODULE_XFER_OUT (&dns_modules[13])
+#define DNS_LOGMODULE_ACL (&dns_modules[14])
+#define DNS_LOGMODULE_VALIDATOR (&dns_modules[15])
+#define DNS_LOGMODULE_DISPATCH (&dns_modules[16])
+#define DNS_LOGMODULE_REQUEST (&dns_modules[17])
+#define DNS_LOGMODULE_MASTERDUMP (&dns_modules[18])
+#define DNS_LOGMODULE_TSIG (&dns_modules[19])
+#define DNS_LOGMODULE_TKEY (&dns_modules[20])
+#define DNS_LOGMODULE_SDB (&dns_modules[21])
+#define DNS_LOGMODULE_DIFF (&dns_modules[22])
+#define DNS_LOGMODULE_HINTS (&dns_modules[23])
+#define DNS_LOGMODULE_UNUSED1 (&dns_modules[24])
+#define DNS_LOGMODULE_DLZ (&dns_modules[25])
+#define DNS_LOGMODULE_DNSSEC (&dns_modules[26])
+#define DNS_LOGMODULE_CRYPTO (&dns_modules[27])
+#define DNS_LOGMODULE_PACKETS (&dns_modules[28])
+#define DNS_LOGMODULE_NTA (&dns_modules[29])
+#define DNS_LOGMODULE_DYNDB (&dns_modules[30])
+#define DNS_LOGMODULE_DNSTAP (&dns_modules[31])
+#define DNS_LOGMODULE_SSU (&dns_modules[32])
+
+ISC_LANG_BEGINDECLS
+
+void
+dns_log_init(isc_log_t *lctx);
+/*%
+ * Make the libdns categories and modules available for use with the
+ * ISC logging library.
+ *
+ * Requires:
+ *\li lctx is a valid logging context.
+ *
+ *\li dns_log_init() is called only once.
+ *
+ * Ensures:
+ * \li The categories and modules defined above are available for
+ * use by isc_log_usechannnel() and isc_log_write().
+ */
+
+void
+dns_log_setcontext(isc_log_t *lctx);
+/*%
+ * Make the libdns library use the provided context for logging internal
+ * messages.
+ *
+ * Requires:
+ *\li lctx is a valid logging context.
+ */
+
+ISC_LANG_ENDDECLS
diff --git a/lib/dns/include/dns/lookup.h b/lib/dns/include/dns/lookup.h
new file mode 100644
index 0000000..f89e93d
--- /dev/null
+++ b/lib/dns/include/dns/lookup.h
@@ -0,0 +1,128 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#pragma once
+
+/*****
+***** Module Info
+*****/
+
+/*! \file dns/lookup.h
+ * \brief
+ * The lookup module performs simple DNS lookups. It implements
+ * the full resolver algorithm, both looking for local data and
+ * resolving external names as necessary.
+ *
+ * MP:
+ *\li The module ensures appropriate synchronization of data structures it
+ * creates and manipulates.
+ *
+ * Reliability:
+ *\li No anticipated impact.
+ *
+ * Resources:
+ *\li TBS
+ *
+ * Security:
+ *\li No anticipated impact.
+ *
+ * Standards:
+ *\li RFCs: 1034, 1035, 2181, TBS
+ *\li Drafts: TBS
+ */
+
+#include <isc/event.h>
+#include <isc/lang.h>
+
+#include <dns/types.h>
+
+ISC_LANG_BEGINDECLS
+
+/*%
+ * A 'dns_lookupevent_t' is returned when a lookup completes.
+ * The sender field will be set to the lookup that completed. If 'result'
+ * is ISC_R_SUCCESS, then 'names' will contain a list of names associated
+ * with the address. The recipient of the event must not change the list
+ * and must not refer to any of the name data after the event is freed.
+ */
+typedef struct dns_lookupevent {
+ ISC_EVENT_COMMON(struct dns_lookupevent);
+ isc_result_t result;
+ dns_name_t *name;
+ dns_rdataset_t *rdataset;
+ dns_rdataset_t *sigrdataset;
+ dns_db_t *db;
+ dns_dbnode_t *node;
+} dns_lookupevent_t;
+
+isc_result_t
+dns_lookup_create(isc_mem_t *mctx, const dns_name_t *name, dns_rdatatype_t type,
+ dns_view_t *view, unsigned int options, isc_task_t *task,
+ isc_taskaction_t action, void *arg, dns_lookup_t **lookupp);
+/*%<
+ * Finds the rrsets matching 'name' and 'type'.
+ *
+ * Requires:
+ *
+ *\li 'mctx' is a valid mctx.
+ *
+ *\li 'name' is a valid name.
+ *
+ *\li 'view' is a valid view which has a resolver.
+ *
+ *\li 'task' is a valid task.
+ *
+ *\li lookupp != NULL && *lookupp == NULL
+ *
+ * Returns:
+ *
+ *\li ISC_R_SUCCESS
+ *\li ISC_R_NOMEMORY
+ *
+ *\li Any resolver-related error (e.g. ISC_R_SHUTTINGDOWN) may also be
+ * returned.
+ */
+
+void
+dns_lookup_cancel(dns_lookup_t *lookup);
+/*%<
+ * Cancel 'lookup'.
+ *
+ * Notes:
+ *
+ *\li If 'lookup' has not completed, post its LOOKUPDONE event with a
+ * result code of ISC_R_CANCELED.
+ *
+ * Requires:
+ *
+ *\li 'lookup' is a valid lookup.
+ */
+
+void
+dns_lookup_destroy(dns_lookup_t **lookupp);
+/*%<
+ * Destroy 'lookup'.
+ *
+ * Requires:
+ *
+ *\li '*lookupp' is a valid lookup.
+ *
+ *\li The caller has received the LOOKUPDONE event (either because the
+ * lookup completed or because dns_lookup_cancel() was called).
+ *
+ * Ensures:
+ *
+ *\li *lookupp == NULL.
+ */
+
+ISC_LANG_ENDDECLS
diff --git a/lib/dns/include/dns/master.h b/lib/dns/include/dns/master.h
new file mode 100644
index 0000000..26e67a0
--- /dev/null
+++ b/lib/dns/include/dns/master.h
@@ -0,0 +1,259 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#pragma once
+
+/*! \file dns/master.h */
+
+/***
+ *** Imports
+ ***/
+
+#include <inttypes.h>
+#include <stdio.h>
+
+#include <isc/lang.h>
+
+#include <dns/types.h>
+
+/*
+ * Flags to be passed in the 'options' argument in the functions below.
+ */
+#define DNS_MASTER_AGETTL 0x00000001 /*%< Age the ttl based on $DATE. */
+#define DNS_MASTER_MANYERRORS \
+ 0x00000002 /*%< Continue processing on errors. \
+ */
+#define DNS_MASTER_NOINCLUDE 0x00000004 /*%< Disallow $INCLUDE directives. */
+#define DNS_MASTER_ZONE 0x00000008 /*%< Loading a zone master file. */
+#define DNS_MASTER_HINT 0x00000010 /*%< Loading a hint master file. */
+#define DNS_MASTER_SECONDARY 0x00000020 /*%< Secondary master file. */
+#define DNS_MASTER_CHECKNS \
+ 0x00000040 /*%< \
+ * Check NS records to see \
+ * if they are an address \
+ */
+#define DNS_MASTER_FATALNS \
+ 0x00000080 /*%< \
+ * Treat DNS_MASTER_CHECKNS \
+ * matches as fatal \
+ */
+#define DNS_MASTER_CHECKNAMES 0x00000100
+#define DNS_MASTER_CHECKNAMESFAIL 0x00000200
+#define DNS_MASTER_CHECKWILDCARD \
+ 0x00000400 /* Check for internal wildcards. \
+ */
+#define DNS_MASTER_CHECKMX 0x00000800
+#define DNS_MASTER_CHECKMXFAIL 0x00001000
+
+#define DNS_MASTER_RESIGN 0x00002000
+#define DNS_MASTER_KEY 0x00004000 /*%< Loading a key zone master file. */
+#define DNS_MASTER_NOTTL 0x00008000 /*%< Don't require ttl. */
+#define DNS_MASTER_CHECKTTL 0x00010000 /*%< Check max-zone-ttl */
+
+ISC_LANG_BEGINDECLS
+
+/*
+ * Structures that implement the "raw" format for master dump.
+ * These are provided for a reference purpose only; in the actual
+ * encoding, we directly read/write each field so that the encoded data
+ * is always "packed", regardless of the hardware architecture.
+ */
+#define DNS_RAWFORMAT_VERSION 1
+
+/*
+ * Flags to indicate the status of the data in the raw file header
+ */
+#define DNS_MASTERRAW_COMPAT 0x01
+#define DNS_MASTERRAW_SOURCESERIALSET 0x02
+#define DNS_MASTERRAW_LASTXFRINSET 0x04
+
+/* Common header */
+struct dns_masterrawheader {
+ uint32_t format; /* must be
+ * dns_masterformat_raw */
+ uint32_t version; /* compatibility for future
+ * extensions */
+ uint32_t dumptime; /* timestamp on creation
+ * (currently unused) */
+ uint32_t flags; /* Flags */
+ uint32_t sourceserial; /* Source serial number (used
+ * by inline-signing zones) */
+ uint32_t lastxfrin; /* timestamp of last transfer
+ * (used by secondary zones) */
+};
+
+/* The structure for each RRset */
+typedef struct {
+ uint32_t totallen; /* length of the data for this
+ * RRset, including the
+ * "header" part */
+ dns_rdataclass_t rdclass; /* 16-bit class */
+ dns_rdatatype_t type; /* 16-bit type */
+ dns_rdatatype_t covers; /* same as type */
+ dns_ttl_t ttl; /* 32-bit TTL */
+ uint32_t nrdata; /* number of RRs in this set */
+ /* followed by encoded owner name, and then rdata */
+} dns_masterrawrdataset_t;
+
+/*
+ * Method prototype: a callback to register each include file as
+ * it is encountered.
+ */
+typedef void (*dns_masterincludecb_t)(const char *file, void *arg);
+
+/***
+ *** Function
+ ***/
+
+isc_result_t
+dns_master_loadfile(const char *master_file, dns_name_t *top,
+ dns_name_t *origin, dns_rdataclass_t zclass,
+ unsigned int options, uint32_t resign,
+ dns_rdatacallbacks_t *callbacks,
+ dns_masterincludecb_t include_cb, void *include_arg,
+ isc_mem_t *mctx, dns_masterformat_t format,
+ dns_ttl_t maxttl);
+
+isc_result_t
+dns_master_loadstream(FILE *stream, dns_name_t *top, dns_name_t *origin,
+ dns_rdataclass_t zclass, unsigned int options,
+ dns_rdatacallbacks_t *callbacks, isc_mem_t *mctx);
+
+isc_result_t
+dns_master_loadbuffer(isc_buffer_t *buffer, dns_name_t *top, dns_name_t *origin,
+ dns_rdataclass_t zclass, unsigned int options,
+ dns_rdatacallbacks_t *callbacks, isc_mem_t *mctx);
+
+isc_result_t
+dns_master_loadlexer(isc_lex_t *lex, dns_name_t *top, dns_name_t *origin,
+ dns_rdataclass_t zclass, unsigned int options,
+ dns_rdatacallbacks_t *callbacks, isc_mem_t *mctx);
+
+isc_result_t
+dns_master_loadfileinc(const char *master_file, dns_name_t *top,
+ dns_name_t *origin, dns_rdataclass_t zclass,
+ unsigned int options, uint32_t resign,
+ dns_rdatacallbacks_t *callbacks, isc_task_t *task,
+ dns_loaddonefunc_t done, void *done_arg,
+ dns_loadctx_t **ctxp, dns_masterincludecb_t include_cb,
+ void *include_arg, isc_mem_t *mctx,
+ dns_masterformat_t format, uint32_t maxttl);
+
+isc_result_t
+dns_master_loadstreaminc(FILE *stream, dns_name_t *top, dns_name_t *origin,
+ dns_rdataclass_t zclass, unsigned int options,
+ dns_rdatacallbacks_t *callbacks, isc_task_t *task,
+ dns_loaddonefunc_t done, void *done_arg,
+ dns_loadctx_t **ctxp, isc_mem_t *mctx);
+
+isc_result_t
+dns_master_loadbufferinc(isc_buffer_t *buffer, dns_name_t *top,
+ dns_name_t *origin, dns_rdataclass_t zclass,
+ unsigned int options, dns_rdatacallbacks_t *callbacks,
+ isc_task_t *task, dns_loaddonefunc_t done,
+ void *done_arg, dns_loadctx_t **ctxp, isc_mem_t *mctx);
+
+isc_result_t
+dns_master_loadlexerinc(isc_lex_t *lex, dns_name_t *top, dns_name_t *origin,
+ dns_rdataclass_t zclass, unsigned int options,
+ dns_rdatacallbacks_t *callbacks, isc_task_t *task,
+ dns_loaddonefunc_t done, void *done_arg,
+ dns_loadctx_t **ctxp, isc_mem_t *mctx);
+
+/*%<
+ * Loads a RFC1305 master file from a file, stream, buffer, or existing
+ * lexer into rdatasets and then calls 'callbacks->commit' to commit the
+ * rdatasets. Rdata memory belongs to dns_master_load and will be
+ * reused / released when the callback completes. dns_load_master will
+ * abort if callbacks->commit returns any value other than ISC_R_SUCCESS.
+ *
+ * If 'DNS_MASTER_AGETTL' is set and the master file contains one or more
+ * $DATE directives, the TTLs of the data will be aged accordingly.
+ *
+ * 'callbacks->commit' is assumed to call 'callbacks->error' or
+ * 'callbacks->warn' to generate any error messages required.
+ *
+ * 'done' is called with 'done_arg' and a result code when the loading
+ * is completed or has failed. If the initial setup fails 'done' is
+ * not called.
+ *
+ * 'resign' the number of seconds before a RRSIG expires that it should
+ * be re-signed. 0 is used if not provided.
+ *
+ * Requires:
+ *\li 'master_file' points to a valid string.
+ *\li 'lexer' points to a valid lexer.
+ *\li 'top' points to a valid name.
+ *\li 'origin' points to a valid name.
+ *\li 'callbacks->commit' points to a valid function.
+ *\li 'callbacks->error' points to a valid function.
+ *\li 'callbacks->warn' points to a valid function.
+ *\li 'mctx' points to a valid memory context.
+ *\li 'task' and 'done' to be valid.
+ *\li 'lmgr' to be valid.
+ *\li 'ctxp != NULL && ctxp == NULL'.
+ *
+ * Returns:
+ *\li ISC_R_SUCCESS upon successfully loading the master file.
+ *\li ISC_R_SEENINCLUDE upon successfully loading the master file with
+ * a $INCLUDE statement.
+ *\li ISC_R_NOMEMORY out of memory.
+ *\li ISC_R_UNEXPECTEDEND expected to be able to read a input token and
+ * there was not one.
+ *\li ISC_R_UNEXPECTED
+ *\li DNS_R_NOOWNER failed to specify a ownername.
+ *\li DNS_R_NOTTL failed to specify a ttl.
+ *\li DNS_R_BADCLASS record class did not match zone class.
+ *\li DNS_R_CONTINUE load still in progress (dns_master_load*inc() only).
+ *\li Any dns_rdata_fromtext() error code.
+ *\li Any error code from callbacks->commit().
+ */
+
+void
+dns_loadctx_detach(dns_loadctx_t **ctxp);
+/*%<
+ * Detach from the load context.
+ *
+ * Requires:
+ *\li '*ctxp' to be valid.
+ *
+ * Ensures:
+ *\li '*ctxp == NULL'
+ */
+
+void
+dns_loadctx_attach(dns_loadctx_t *source, dns_loadctx_t **target);
+/*%<
+ * Attach to the load context.
+ *
+ * Requires:
+ *\li 'source' to be valid.
+ *\li 'target != NULL && *target == NULL'.
+ */
+
+void
+dns_loadctx_cancel(dns_loadctx_t *ctx);
+/*%<
+ * Cancel loading the zone file associated with this load context.
+ *
+ * Requires:
+ *\li 'ctx' to be valid
+ */
+
+void
+dns_master_initrawheader(dns_masterrawheader_t *header);
+/*%<
+ * Initializes the header for a raw master file, setting all
+ * values to zero.
+ */
+ISC_LANG_ENDDECLS
diff --git a/lib/dns/include/dns/masterdump.h b/lib/dns/include/dns/masterdump.h
new file mode 100644
index 0000000..056780b
--- /dev/null
+++ b/lib/dns/include/dns/masterdump.h
@@ -0,0 +1,359 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#pragma once
+
+/*! \file dns/masterdump.h */
+
+/***
+ *** Imports
+ ***/
+
+#include <stdio.h>
+
+#include <isc/lang.h>
+
+#include <dns/types.h>
+
+/***
+ *** Types
+ ***/
+
+typedef struct dns_master_style dns_master_style_t;
+
+/***
+ *** Definitions
+ ***/
+
+/*
+ * Flags affecting master file formatting. Flags 0x0000FFFF
+ * define the formatting of the rdata part and are defined in
+ * rdata.h.
+ */
+
+/*% Omit the owner name when possible. */
+#define DNS_STYLEFLAG_OMIT_OWNER 0x000010000ULL
+
+/*%
+ * Omit the TTL when possible. If DNS_STYLEFLAG_TTL is
+ * also set, this means no TTLs are ever printed
+ * because $TTL directives are generated before every
+ * change in the TTL. In this case, no columns need to
+ * be reserved for the TTL. Master files generated with
+ * these options will be rejected by BIND 4.x because it
+ * does not recognize the $TTL directive.
+ *
+ * If DNS_STYLEFLAG_TTL is not also set, the TTL will be
+ * omitted when it is equal to the previous TTL.
+ * This is correct according to RFC1035, but the
+ * TTLs may be silently misinterpreted by older
+ * versions of BIND which use the SOA MINTTL as a
+ * default TTL value.
+ */
+#define DNS_STYLEFLAG_OMIT_TTL 0x000020000ULL
+
+/*% Omit the class when possible. */
+#define DNS_STYLEFLAG_OMIT_CLASS 0x000040000ULL
+
+/*% Output $TTL directives. */
+#define DNS_STYLEFLAG_TTL 0x000080000ULL
+
+/*%
+ * Output $ORIGIN directives and print owner names relative to
+ * the origin when possible.
+ */
+#define DNS_STYLEFLAG_REL_OWNER 0x000100000ULL
+
+/*% Print domain names in RR data in relative form when possible.
+ * For this to take effect, DNS_STYLEFLAG_REL_OWNER must also be set. */
+#define DNS_STYLEFLAG_REL_DATA 0x000200000ULL
+
+/*% Print the trust level of each rdataset. */
+#define DNS_STYLEFLAG_TRUST 0x000400000ULL
+
+/*% Print negative caching entries. */
+#define DNS_STYLEFLAG_NCACHE 0x000800000ULL
+
+/*% Never print the TTL. */
+#define DNS_STYLEFLAG_NO_TTL 0x001000000ULL
+
+/*% Never print the CLASS. */
+#define DNS_STYLEFLAG_NO_CLASS 0x002000000ULL
+
+/*% Report re-signing time. */
+#define DNS_STYLEFLAG_RESIGN 0x004000000ULL
+
+/*% Don't printout the cryptographic parts of DNSSEC records. */
+#define DNS_STYLEFLAG_NOCRYPTO 0x008000000ULL
+
+/*% Comment out data by prepending with ";" */
+#define DNS_STYLEFLAG_COMMENTDATA 0x010000000ULL
+
+/*% Print TTL with human-readable units. */
+#define DNS_STYLEFLAG_TTL_UNITS 0x020000000ULL
+
+/*% Indent output. */
+#define DNS_STYLEFLAG_INDENT 0x040000000ULL
+
+/*% Output in YAML style. */
+#define DNS_STYLEFLAG_YAML 0x080000000ULL
+
+/*% Print ECS cache entries as comments (reserved for future use). */
+#define DNS_STYLEFLAG_ECSCACHE 0x100000000ULL
+
+/*% Print expired cache entries. */
+#define DNS_STYLEFLAG_EXPIRED 0x200000000ULL
+
+ISC_LANG_BEGINDECLS
+
+/***
+ *** Constants
+ ***/
+
+/*%
+ * The default master file style.
+ *
+ * This uses $TTL directives to avoid the need to dedicate a
+ * tab stop for the TTL. The class is only printed for the first
+ * rrset in the file and shares a tab stop with the RR type.
+ */
+extern const dns_master_style_t dns_master_style_default;
+
+/*%
+ * A master file style that dumps zones to a very generic format easily
+ * imported/checked with external tools.
+ */
+extern const dns_master_style_t dns_master_style_full;
+
+/*%
+ * A master file style that prints explicit TTL values on each
+ * record line, never using $TTL statements. The TTL has a tab
+ * stop of its own, but the class and type share one.
+ */
+extern const dns_master_style_t dns_master_style_explicitttl;
+
+/*%
+ * A master style format designed for cache files. It prints explicit TTL
+ * values on each record line and never uses $ORIGIN or relative names.
+ */
+extern const dns_master_style_t dns_master_style_cache;
+
+/*%
+ * A master style format designed for cache files. The same as above but
+ * this also prints expired entries.
+ */
+extern const dns_master_style_t dns_master_style_cache_with_expired;
+
+/*%
+ * A master style that prints name, ttl, class, type, and value on
+ * every line. Similar to explicitttl above, but more verbose.
+ * Intended for generating master files which can be easily parsed
+ * by perl scripts and similar applications.
+ */
+extern const dns_master_style_t dns_master_style_simple;
+
+/*%
+ * The style used for debugging, "dig" output, etc.
+ */
+extern const dns_master_style_t dns_master_style_debug;
+
+/*%
+ * Similar to dns_master_style_debug but data is prepended with ";"
+ */
+extern const dns_master_style_t dns_master_style_comment;
+
+/*%
+ * Similar to dns_master_style_debug but data is indented with "\t" (tab)
+ */
+extern const dns_master_style_t dns_master_style_indent;
+
+/*%
+ * The style used for dumping "key" zones.
+ */
+extern const dns_master_style_t dns_master_style_keyzone;
+
+/*%
+ * YAML-compatible output
+ */
+extern const dns_master_style_t dns_master_style_yaml;
+
+/***
+ *** Functions
+ ***/
+
+void
+dns_dumpctx_attach(dns_dumpctx_t *source, dns_dumpctx_t **target);
+/*%<
+ * Attach to a dump context.
+ *
+ * Require:
+ *\li 'source' to be valid.
+ *\li 'target' to be non NULL and '*target' to be NULL.
+ */
+
+void
+dns_dumpctx_detach(dns_dumpctx_t **dctxp);
+/*%<
+ * Detach from a dump context.
+ *
+ * Require:
+ *\li 'dctxp' to point to a valid dump context.
+ *
+ * Ensures:
+ *\li '*dctxp' is NULL.
+ */
+
+void
+dns_dumpctx_cancel(dns_dumpctx_t *dctx);
+/*%<
+ * Cancel a in progress dump.
+ *
+ * Require:
+ *\li 'dctx' to be valid.
+ */
+
+dns_dbversion_t *
+dns_dumpctx_version(dns_dumpctx_t *dctx);
+/*%<
+ * Return the version handle (if any) of the database being dumped.
+ *
+ * Require:
+ *\li 'dctx' to be valid.
+ */
+
+dns_db_t *
+dns_dumpctx_db(dns_dumpctx_t *dctx);
+/*%<
+ * Return the database being dumped.
+ *
+ * Require:
+ *\li 'dctx' to be valid.
+ */
+
+/*@{*/
+isc_result_t
+dns_master_dumptostreamasync(isc_mem_t *mctx, dns_db_t *db,
+ dns_dbversion_t *version,
+ const dns_master_style_t *style, FILE *f,
+ isc_task_t *task, dns_dumpdonefunc_t done,
+ void *done_arg, dns_dumpctx_t **dctxp);
+
+isc_result_t
+dns_master_dumptostream(isc_mem_t *mctx, dns_db_t *db, dns_dbversion_t *version,
+ const dns_master_style_t *style,
+ dns_masterformat_t format,
+ dns_masterrawheader_t *header, FILE *f);
+/*%<
+ * Dump the database 'db' to the steam 'f' in the specified format by
+ * 'format'. If the format is dns_masterformat_text (the RFC1035 format),
+ * 'style' specifies the file style (e.g., &dns_master_style_default).
+ *
+ * If 'format' is dns_masterformat_raw, then 'header' can contain
+ * information to be written to the file header.
+ *
+ * Temporary dynamic memory may be allocated from 'mctx'.
+ *
+ * Require:
+ *\li 'task' to be valid.
+ *\li 'done' to be non NULL.
+ *\li 'dctxp' to be non NULL && '*dctxp' to be NULL.
+ *
+ * Returns:
+ *\li ISC_R_SUCCESS
+ *\li ISC_R_NOMEMORY
+ *\li Any database or rrset iterator error.
+ *\li Any dns_rdata_totext() error code.
+ */
+/*@}*/
+
+/*@{*/
+
+isc_result_t
+dns_master_dumpasync(isc_mem_t *mctx, dns_db_t *db, dns_dbversion_t *version,
+ const dns_master_style_t *style, const char *filename,
+ isc_task_t *task, dns_dumpdonefunc_t done, void *done_arg,
+ dns_dumpctx_t **dctxp, dns_masterformat_t format,
+ dns_masterrawheader_t *header);
+
+isc_result_t
+dns_master_dump(isc_mem_t *mctx, dns_db_t *db, dns_dbversion_t *version,
+ const dns_master_style_t *style, const char *filename,
+ dns_masterformat_t format, dns_masterrawheader_t *header);
+
+/*%<
+ * Dump the database 'db' to the file 'filename' in the specified format by
+ * 'format'. If the format is dns_masterformat_text (the RFC1035 format),
+ * 'style' specifies the file style (e.g., &dns_master_style_default).
+ *
+ * If 'format' is dns_masterformat_raw, then 'header' can contain
+ * information to be written to the file header.
+ *
+ * Temporary dynamic memory may be allocated from 'mctx'.
+ *
+ * Returns:
+ *\li ISC_R_SUCCESS
+ *\li ISC_R_NOMEMORY
+ *\li Any database or rrset iterator error.
+ *\li Any dns_rdata_totext() error code.
+ */
+/*@}*/
+
+isc_result_t
+dns_master_rdatasettotext(const dns_name_t *owner_name,
+ dns_rdataset_t *rdataset,
+ const dns_master_style_t *style, dns_indent_t *indent,
+ isc_buffer_t *target);
+/*%<
+ * Convert 'rdataset' to text format, storing the result in 'target'.
+ *
+ * Notes:
+ *\li The rdata cursor position will be changed.
+ *
+ * Requires:
+ *\li 'rdataset' is a valid non-question rdataset.
+ *
+ *\li 'rdataset' is not empty.
+ */
+
+isc_result_t
+dns_master_questiontotext(const dns_name_t *owner_name,
+ dns_rdataset_t *rdataset,
+ const dns_master_style_t *style,
+ isc_buffer_t *target);
+
+isc_result_t
+dns_master_dumpnodetostream(isc_mem_t *mctx, dns_db_t *db,
+ dns_dbversion_t *version, dns_dbnode_t *node,
+ const dns_name_t *name,
+ const dns_master_style_t *style, FILE *f);
+
+isc_result_t
+dns_master_dumpnode(isc_mem_t *mctx, dns_db_t *db, dns_dbversion_t *version,
+ dns_dbnode_t *node, const dns_name_t *name,
+ const dns_master_style_t *style, const char *filename);
+
+dns_masterstyle_flags_t
+dns_master_styleflags(const dns_master_style_t *style);
+
+isc_result_t
+dns_master_stylecreate(dns_master_style_t **style,
+ dns_masterstyle_flags_t flags, unsigned int ttl_column,
+ unsigned int class_column, unsigned int type_column,
+ unsigned int rdata_column, unsigned int line_length,
+ unsigned int tab_width, unsigned int split_width,
+ isc_mem_t *mctx);
+
+void
+dns_master_styledestroy(dns_master_style_t **style, isc_mem_t *mctx);
+
+ISC_LANG_ENDDECLS
diff --git a/lib/dns/include/dns/message.h b/lib/dns/include/dns/message.h
new file mode 100644
index 0000000..b5d9a5a
--- /dev/null
+++ b/lib/dns/include/dns/message.h
@@ -0,0 +1,1567 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#pragma once
+
+/***
+ *** Imports
+ ***/
+
+#include <inttypes.h>
+#include <stdbool.h>
+
+#include <isc/lang.h>
+#include <isc/magic.h>
+#include <isc/refcount.h>
+
+#include <dns/compress.h>
+#include <dns/masterdump.h>
+#include <dns/types.h>
+
+#include <dst/dst.h>
+
+/*! \file dns/message.h
+ * \brief Message Handling Module
+ *
+ * How this beast works:
+ *
+ * When a dns message is received in a buffer, dns_message_parse() is called
+ * on the memory region. Various items are checked including the format
+ * of the message (if counts are right, if counts consume the entire sections,
+ * and if sections consume the entire message) and known pseudo-RRs in the
+ * additional data section are analyzed and removed.
+ *
+ * TSIG checking is also done at this layer, and any DNSSEC transaction
+ * signatures should also be checked here.
+ *
+ * Notes on using the gettemp*() and puttemp*() functions:
+ *
+ * These functions return items (names, rdatasets, etc) allocated from some
+ * internal state of the dns_message_t.
+ *
+ * Names and rdatasets must be put back into the dns_message_t in
+ * one of two ways. Assume a name was allocated via
+ * dns_message_gettempname():
+ *
+ *\li (1) insert it into a section, using dns_message_addname().
+ *
+ *\li (2) return it to the message using dns_message_puttempname().
+ *
+ * The same applies to rdatasets.
+ *
+ * On the other hand, offsets, rdatalists and rdatas allocated using
+ * dns_message_gettemp*() will always be freed automatically
+ * when the message is reset or destroyed; calling dns_message_puttemp*()
+ * on rdatalists and rdatas is optional and serves only to enable the item
+ * to be reused multiple times during the lifetime of the message; offsets
+ * cannot be reused.
+ *
+ * Buffers allocated using isc_buffer_allocate() can be automatically freed
+ * as well by giving the buffer to the message using dns_message_takebuffer().
+ * Doing this will cause the buffer to be freed using isc_buffer_free()
+ * when the section lists are cleared, such as in a reset or in a destroy.
+ * Since the buffer itself exists until the message is destroyed, this sort
+ * of code can be written:
+ *
+ * \code
+ * buffer = isc_buffer_allocate(mctx, 512);
+ * name = NULL;
+ * result = dns_message_gettempname(message, &name);
+ * result = dns_name_fromtext(name, &source, dns_rootname, 0, buffer);
+ * dns_message_takebuffer(message, &buffer);
+ * \endcode
+ *
+ *
+ * TODO:
+ *
+ * XXX Needed: ways to set and retrieve EDNS information, add rdata to a
+ * section, move rdata from one section to another, remove rdata, etc.
+ */
+
+#define DNS_MESSAGEFLAG_QR 0x8000U
+#define DNS_MESSAGEFLAG_AA 0x0400U
+#define DNS_MESSAGEFLAG_TC 0x0200U
+#define DNS_MESSAGEFLAG_RD 0x0100U
+#define DNS_MESSAGEFLAG_RA 0x0080U
+#define DNS_MESSAGEFLAG_AD 0x0020U
+#define DNS_MESSAGEFLAG_CD 0x0010U
+
+/*%< EDNS0 extended message flags */
+#define DNS_MESSAGEEXTFLAG_DO 0x8000U
+
+/*%< EDNS0 extended OPT codes */
+#define DNS_OPT_LLQ 1 /*%< LLQ opt code */
+#define DNS_OPT_NSID 3 /*%< NSID opt code */
+#define DNS_OPT_CLIENT_SUBNET 8 /*%< client subnet opt code */
+#define DNS_OPT_EXPIRE 9 /*%< EXPIRE opt code */
+#define DNS_OPT_COOKIE 10 /*%< COOKIE opt code */
+#define DNS_OPT_TCP_KEEPALIVE 11 /*%< TCP keepalive opt code */
+#define DNS_OPT_PAD 12 /*%< PAD opt code */
+#define DNS_OPT_KEY_TAG 14 /*%< Key tag opt code */
+#define DNS_OPT_EDE 15 /*%< Extended DNS Error opt code */
+#define DNS_OPT_CLIENT_TAG 16 /*%< Client tag opt code */
+#define DNS_OPT_SERVER_TAG 17 /*%< Server tag opt code */
+
+/*%< Experimental options [65001...65534] as per RFC6891 */
+
+/*%<
+ * The maximum number of EDNS options we allow to set. Reserve space for the
+ * options we know about. Extended DNS Errors may occur multiple times, but we
+ * will set only one per message (for now).
+ */
+#define DNS_EDNSOPTIONS 8
+
+/*%< EDNS0 extended DNS errors */
+#define DNS_EDE_OTHER 0 /*%< Other Error */
+#define DNS_EDE_DNSKEYALG 1 /*%< Unsupported DNSKEY Algorithm */
+#define DNS_EDE_DSDIGESTTYPE 2 /*%< Unsupported DS Digest Type */
+#define DNS_EDE_STALEANSWER 3 /*%< Stale Answer */
+#define DNS_EDE_FORGEDANSWER 4 /*%< Forged Answer */
+#define DNS_EDE_DNSSECINDETERMINATE 5 /*%< DNSSEC Indeterminate */
+#define DNS_EDE_DNSSECBOGUS 6 /*%< DNSSEC Bogus */
+#define DNS_EDE_SIGNATUREEXPIRED 7 /*%< Signature Expired */
+#define DNS_EDE_SIGNATURENOTYETVALID 8 /*%< Signature Not Yet Valid */
+#define DNS_EDE_DNSKEYMISSING 9 /*%< DNSKEY Missing */
+#define DNS_EDE_RRSIGSMISSING 10 /*%< RRSIGs Missing */
+#define DNS_EDE_NOZONEKEYBITSET 11 /*%< No Zone Key Bit Set */
+#define DNS_EDE_NSECMISSING 12 /*%< NSEC Missing */
+#define DNS_EDE_CACHEDERROR 13 /*%< Cached Error */
+#define DNS_EDE_NOTREADY 14 /*%< Not Ready */
+#define DNS_EDE_BLOCKED 15 /*%< Blocked */
+#define DNS_EDE_CENSORED 16 /*%< Censored */
+#define DNS_EDE_FILTERED 17 /*%< Filtered */
+#define DNS_EDE_PROHIBITED 18 /*%< Prohibited */
+#define DNS_EDE_STALENXANSWER 19 /*%< Stale NXDomain Answer */
+#define DNS_EDE_NOTAUTH 20 /*%< Not Authoritative */
+#define DNS_EDE_NOTSUPPORTED 21 /*%< Not Supported */
+#define DNS_EDE_NOREACHABLEAUTH 22 /*%< No Reachable Authority */
+#define DNS_EDE_NETWORKERROR 23 /*%< Network Error */
+#define DNS_EDE_INVALIDDATA 24 /*%< Invalid Data */
+
+/*
+ * From RFC 8914:
+ * Because long EXTRA-TEXT fields may trigger truncation (which is undesirable
+ * given the supplemental nature of EDE), implementers and operators creating
+ * EDE options SHOULD avoid lengthy EXTRA-TEXT contents.
+ *
+ * Following this advice we limit the EXTRA-TEXT length to 64 characters.
+ */
+#define DNS_EDE_EXTRATEXT_LEN 64
+
+#define DNS_MESSAGE_REPLYPRESERVE (DNS_MESSAGEFLAG_RD | DNS_MESSAGEFLAG_CD)
+#define DNS_MESSAGEEXTFLAG_REPLYPRESERVE (DNS_MESSAGEEXTFLAG_DO)
+
+#define DNS_MESSAGE_HEADERLEN 12 /*%< 6 uint16_t's */
+
+#define DNS_MESSAGE_MAGIC ISC_MAGIC('M', 'S', 'G', '@')
+#define DNS_MESSAGE_VALID(msg) ISC_MAGIC_VALID(msg, DNS_MESSAGE_MAGIC)
+
+/*
+ * Ordering here matters. DNS_SECTION_ANY must be the lowest and negative,
+ * and DNS_SECTION_MAX must be one greater than the last used section.
+ */
+typedef int dns_section_t;
+#define DNS_SECTION_ANY (-1)
+#define DNS_SECTION_QUESTION 0
+#define DNS_SECTION_ANSWER 1
+#define DNS_SECTION_AUTHORITY 2
+#define DNS_SECTION_ADDITIONAL 3
+#define DNS_SECTION_MAX 4
+
+typedef int dns_pseudosection_t;
+#define DNS_PSEUDOSECTION_ANY (-1)
+#define DNS_PSEUDOSECTION_OPT 0
+#define DNS_PSEUDOSECTION_TSIG 1
+#define DNS_PSEUDOSECTION_SIG0 2
+#define DNS_PSEUDOSECTION_MAX 3
+
+typedef int dns_messagetextflag_t;
+#define DNS_MESSAGETEXTFLAG_NOCOMMENTS 0x0001
+#define DNS_MESSAGETEXTFLAG_NOHEADERS 0x0002
+#define DNS_MESSAGETEXTFLAG_ONESOA 0x0004
+#define DNS_MESSAGETEXTFLAG_OMITSOA 0x0008
+
+/*
+ * Dynamic update names for these sections.
+ */
+#define DNS_SECTION_ZONE DNS_SECTION_QUESTION
+#define DNS_SECTION_PREREQUISITE DNS_SECTION_ANSWER
+#define DNS_SECTION_UPDATE DNS_SECTION_AUTHORITY
+
+/*
+ * These tell the message library how the created dns_message_t will be used.
+ */
+#define DNS_MESSAGE_INTENTUNKNOWN 0 /*%< internal use only */
+#define DNS_MESSAGE_INTENTPARSE 1 /*%< parsing messages */
+#define DNS_MESSAGE_INTENTRENDER 2 /*%< rendering */
+
+/*
+ * Control behavior of parsing
+ */
+#define DNS_MESSAGEPARSE_PRESERVEORDER 0x0001 /*%< preserve rdata order */
+#define DNS_MESSAGEPARSE_BESTEFFORT \
+ 0x0002 /*%< return a message if a \
+ * recoverable parse error \
+ * occurs */
+#define DNS_MESSAGEPARSE_CLONEBUFFER \
+ 0x0004 /*%< save a copy of the \
+ * source buffer */
+#define DNS_MESSAGEPARSE_IGNORETRUNCATION \
+ 0x0008 /*%< truncation errors are \
+ * not fatal. */
+
+/*
+ * Control behavior of rendering
+ */
+#define DNS_MESSAGERENDER_ORDERED 0x0001 /*%< don't change order */
+#define DNS_MESSAGERENDER_PARTIAL 0x0002 /*%< allow a partial rdataset */
+#define DNS_MESSAGERENDER_OMITDNSSEC 0x0004 /*%< omit DNSSEC records */
+#define DNS_MESSAGERENDER_PREFER_A \
+ 0x0008 /*%< prefer A records in \
+ * additional section. */
+#define DNS_MESSAGERENDER_PREFER_AAAA \
+ 0x0010 /*%< prefer AAAA records in \
+ * additional section. */
+/* Obsolete: DNS_MESSAGERENDER_FILTER_AAAA 0x0020 */
+
+typedef struct dns_msgblock dns_msgblock_t;
+
+struct dns_sortlist_arg {
+ dns_aclenv_t *env;
+ dns_acl_t *acl;
+ const dns_aclelement_t *element;
+};
+
+typedef struct dns_minttl {
+ bool is_set;
+ dns_ttl_t ttl;
+} dns_minttl_t;
+
+struct dns_message {
+ /* public from here down */
+ unsigned int magic;
+ isc_refcount_t refcount;
+
+ dns_messageid_t id;
+ unsigned int flags;
+ dns_rcode_t rcode;
+ dns_opcode_t opcode;
+ dns_rdataclass_t rdclass;
+
+ /* 4 real, 1 pseudo */
+ unsigned int counts[DNS_SECTION_MAX];
+
+ /* private from here down */
+ dns_namelist_t sections[DNS_SECTION_MAX];
+ dns_name_t *cursors[DNS_SECTION_MAX];
+ dns_rdataset_t *opt;
+ dns_rdataset_t *sig0;
+ dns_rdataset_t *tsig;
+
+ int state;
+ unsigned int from_to_wire : 2;
+ unsigned int header_ok : 1;
+ unsigned int question_ok : 1;
+ unsigned int tcp_continuation : 1;
+ unsigned int verified_sig : 1;
+ unsigned int verify_attempted : 1;
+ unsigned int free_query : 1;
+ unsigned int free_saved : 1;
+ unsigned int cc_ok : 1;
+ unsigned int cc_bad : 1;
+ unsigned int cc_echoed : 1;
+ unsigned int tkey : 1;
+ unsigned int rdclass_set : 1;
+ unsigned int fuzzing : 1;
+
+ unsigned int opt_reserved;
+ unsigned int sig_reserved;
+ unsigned int reserved; /* reserved space (render) */
+
+ uint16_t padding;
+ unsigned int padding_off;
+
+ isc_buffer_t *buffer;
+ dns_compress_t *cctx;
+
+ isc_mem_t *mctx;
+ isc_mempool_t *namepool;
+ isc_mempool_t *rdspool;
+
+ isc_bufferlist_t scratchpad;
+ isc_bufferlist_t cleanup;
+
+ ISC_LIST(dns_msgblock_t) rdatas;
+ ISC_LIST(dns_msgblock_t) rdatalists;
+ ISC_LIST(dns_msgblock_t) offsets;
+
+ ISC_LIST(dns_rdata_t) freerdata;
+ ISC_LIST(dns_rdatalist_t) freerdatalist;
+
+ dns_rcode_t tsigstatus;
+ dns_rcode_t querytsigstatus;
+ dns_name_t *tsigname; /* Owner name of TSIG, if any
+ * */
+ dns_rdataset_t *querytsig;
+ dns_tsigkey_t *tsigkey;
+ dst_context_t *tsigctx;
+ int sigstart;
+ int timeadjust;
+
+ dns_name_t *sig0name; /* Owner name of SIG0, if any
+ * */
+ dst_key_t *sig0key;
+ dns_rcode_t sig0status;
+ isc_region_t query;
+ isc_region_t saved;
+
+ /*
+ * Time to be used when fuzzing.
+ */
+ isc_stdtime_t fuzztime;
+
+ dns_rdatasetorderfunc_t order;
+ dns_sortlist_arg_t order_arg;
+
+ dns_indent_t indent;
+
+ dns_minttl_t minttl[DNS_SECTION_MAX];
+};
+
+struct dns_ednsopt {
+ uint16_t code;
+ uint16_t length;
+ unsigned char *value;
+};
+
+/***
+ *** Functions
+ ***/
+
+ISC_LANG_BEGINDECLS
+
+void
+dns_message_create(isc_mem_t *mctx, unsigned int intent, dns_message_t **msgp);
+
+/*%<
+ * Create msg structure.
+ *
+ * This function will allocate some internal blocks of memory that are
+ * expected to be needed for parsing or rendering nearly any type of message.
+ *
+ * Requires:
+ *\li 'mctx' be a valid memory context.
+ *
+ *\li 'msgp' be non-null and '*msg' be NULL.
+ *
+ *\li 'intent' must be one of DNS_MESSAGE_INTENTPARSE or
+ * #DNS_MESSAGE_INTENTRENDER.
+ *
+ * Ensures:
+ *\li The data in "*msg" is set to indicate an unused and empty msg
+ * structure.
+ *
+ * Returns:
+ *\li #ISC_R_NOMEMORY -- out of memory
+ *\li #ISC_R_SUCCESS -- success
+ */
+
+void
+dns_message_reset(dns_message_t *msg, unsigned int intent);
+/*%<
+ * Reset a message structure to default state. All internal lists are freed
+ * or reset to a default state as well. This is simply a more efficient
+ * way to call dns_message_detach() (assuming last reference is hold),
+ * followed by dns_message_create(), since it avoid many memory allocations.
+ *
+ * If any data loanouts (buffers, names, rdatas, etc) were requested,
+ * the caller must no longer use them after this call.
+ *
+ * The intended next use of the message will be 'intent'.
+ *
+ * Requires:
+ *
+ *\li 'msg' be valid.
+ *
+ *\li 'intent' is DNS_MESSAGE_INTENTPARSE or DNS_MESSAGE_INTENTRENDER
+ */
+
+void
+dns_message_attach(dns_message_t *source, dns_message_t **target);
+/*%<
+ * Attach to message 'source'.
+ *
+ * Requires:
+ *\li 'source' to be a valid message.
+ *\li 'target' to be non NULL and '*target' to be NULL.
+ */
+
+void
+dns_message_detach(dns_message_t **messagep);
+/*%<
+ * Detach *messagep from its message.
+ * list.
+ *
+ * Requires:
+ *\li '*messagep' to be a valid message.
+ */
+
+isc_result_t
+dns_message_sectiontotext(dns_message_t *msg, dns_section_t section,
+ const dns_master_style_t *style,
+ dns_messagetextflag_t flags, isc_buffer_t *target);
+
+isc_result_t
+dns_message_pseudosectiontotext(dns_message_t *msg, dns_pseudosection_t section,
+ const dns_master_style_t *style,
+ dns_messagetextflag_t flags,
+ isc_buffer_t *target);
+/*%<
+ * Convert section 'section' or 'pseudosection' of message 'msg' to
+ * a cleartext representation
+ *
+ * Notes:
+ * \li See dns_message_totext for meanings of flags.
+ *
+ * Requires:
+ *
+ *\li 'msg' is a valid message.
+ *
+ *\li 'style' is a valid master dump style.
+ *
+ *\li 'target' is a valid buffer.
+ *
+ *\li 'section' is a valid section label.
+ *
+ * Ensures:
+ *
+ *\li If the result is success:
+ * The used space in 'target' is updated.
+ *
+ * Returns:
+ *
+ *\li #ISC_R_SUCCESS
+ *\li #ISC_R_NOSPACE
+ *\li #ISC_R_NOMORE
+ *
+ *\li Note: On error return, *target may be partially filled with data.
+ */
+
+isc_result_t
+dns_message_headertotext(dns_message_t *msg, const dns_master_style_t *style,
+ dns_messagetextflag_t flags, isc_buffer_t *target);
+/*%<
+ * Convert the header section of message 'msg' to a cleartext
+ * representation. This is called from dns_message_totext().
+ *
+ * Notes on flags:
+ *\li If #DNS_MESSAGETEXTFLAG_NOHEADERS is set, header lines will be
+ * suppressed and this function is a no-op.
+ *
+ * Requires:
+ *
+ *\li 'msg' is a valid message.
+ *
+ *\li 'target' is a valid buffer.
+ *
+ * Ensures:
+ *
+ *\li If the result is success:
+ * The used space in 'target' is updated.
+ *
+ * Returns:
+ *
+ *\li #ISC_R_SUCCESS
+ *\li #ISC_R_NOSPACE
+ *\li #ISC_R_NOMORE
+ *
+ *\li Note: On error return, *target may be partially filled with data.
+ */
+
+isc_result_t
+dns_message_totext(dns_message_t *msg, const dns_master_style_t *style,
+ dns_messagetextflag_t flags, isc_buffer_t *target);
+/*%<
+ * Convert all sections of message 'msg' to a cleartext representation
+ *
+ * Notes on flags:
+ *\li If #DNS_MESSAGETEXTFLAG_NOCOMMENTS is cleared, lines beginning with
+ * ";;" will be emitted indicating section name.
+ *\li If #DNS_MESSAGETEXTFLAG_NOHEADERS is cleared, header lines will be
+ * emitted.
+ *\li If #DNS_MESSAGETEXTFLAG_ONESOA is set then only print the first
+ * SOA record in the answer section.
+ *\li If *#DNS_MESSAGETEXTFLAG_OMITSOA is set don't print any SOA records
+ * in the answer section.
+ *
+ * The SOA flags are useful for suppressing the display of the second
+ * SOA record in an AXFR by setting #DNS_MESSAGETEXTFLAG_ONESOA on the
+ * first message in an AXFR stream and #DNS_MESSAGETEXTFLAG_OMITSOA on
+ * subsequent messages.
+ *
+ * Requires:
+ *
+ *\li 'msg' is a valid message.
+ *
+ *\li 'style' is a valid master dump style.
+ *
+ *\li 'target' is a valid buffer.
+ *
+ * Ensures:
+ *
+ *\li If the result is success:
+ * The used space in 'target' is updated.
+ *
+ * Returns:
+ *
+ *\li #ISC_R_SUCCESS
+ *\li #ISC_R_NOSPACE
+ *\li #ISC_R_NOMORE
+ *
+ *\li Note: On error return, *target may be partially filled with data.
+ */
+
+isc_result_t
+dns_message_parse(dns_message_t *msg, isc_buffer_t *source,
+ unsigned int options);
+/*%<
+ * Parse raw wire data in 'source' as a DNS message.
+ *
+ * OPT records are detected and stored in the pseudo-section "opt".
+ * TSIGs are detected and stored in the pseudo-section "tsig".
+ *
+ * If #DNS_MESSAGEPARSE_PRESERVEORDER is set, or if the opcode of the message
+ * is UPDATE, a separate dns_name_t object will be created for each RR in the
+ * message. Each such dns_name_t will have a single rdataset containing the
+ * single RR, and the order of the RRs in the message is preserved.
+ * Otherwise, only one dns_name_t object will be created for each unique
+ * owner name in the section, and each such dns_name_t will have a list
+ * of rdatasets. To access the names and their data, use
+ * dns_message_firstname() and dns_message_nextname().
+ *
+ * If #DNS_MESSAGEPARSE_BESTEFFORT is set, errors in message content will
+ * not be considered FORMERRs. If the entire message can be parsed, it
+ * will be returned and DNS_R_RECOVERABLE will be returned.
+ *
+ * If #DNS_MESSAGEPARSE_IGNORETRUNCATION is set then return as many complete
+ * RR's as possible, DNS_R_RECOVERABLE will be returned.
+ *
+ * OPT and TSIG records are always handled specially, regardless of the
+ * 'preserve_order' setting.
+ *
+ * Requires:
+ *\li "msg" be valid.
+ *
+ *\li "buffer" be a wire format buffer.
+ *
+ * Ensures:
+ *\li The buffer's data format is correct.
+ *
+ *\li The buffer's contents verify as correct regarding header bits, buffer
+ * and rdata sizes, etc.
+ *
+ * Returns:
+ *\li #ISC_R_SUCCESS -- all is well
+ *\li #ISC_R_NOMEMORY -- no memory
+ *\li #DNS_R_RECOVERABLE -- the message parsed properly, but contained
+ * errors.
+ *\li Many other errors possible XXXMLG
+ */
+
+isc_result_t
+dns_message_renderbegin(dns_message_t *msg, dns_compress_t *cctx,
+ isc_buffer_t *buffer);
+/*%<
+ * Begin rendering on a message. Only one call can be made to this function
+ * per message.
+ *
+ * The compression context is "owned" by the message library until
+ * dns_message_renderend() is called. It must be invalidated by the caller.
+ *
+ * The buffer is "owned" by the message library until dns_message_renderend()
+ * is called.
+ *
+ * Requires:
+ *
+ *\li 'msg' be valid.
+ *
+ *\li 'cctx' be valid.
+ *
+ *\li 'buffer' is a valid buffer.
+ *
+ * Side Effects:
+ *
+ *\li The buffer is cleared before it is used.
+ *
+ * Returns:
+ *\li #ISC_R_SUCCESS -- all is well
+ *\li #ISC_R_NOSPACE -- output buffer is too small
+ */
+
+isc_result_t
+dns_message_renderchangebuffer(dns_message_t *msg, isc_buffer_t *buffer);
+/*%<
+ * Reset the buffer. This can be used after growing the old buffer
+ * on a ISC_R_NOSPACE return from most of the render functions.
+ *
+ * On successful completion, the old buffer is no longer used by the
+ * library. The new buffer is owned by the library until
+ * dns_message_renderend() is called.
+ *
+ * Requires:
+ *
+ *\li 'msg' be valid.
+ *
+ *\li dns_message_renderbegin() was called.
+ *
+ *\li buffer != NULL.
+ *
+ * Returns:
+ *\li #ISC_R_NOSPACE -- new buffer is too small
+ *\li #ISC_R_SUCCESS -- all is well.
+ */
+
+isc_result_t
+dns_message_renderreserve(dns_message_t *msg, unsigned int space);
+/*%<
+ * XXXMLG should use size_t rather than unsigned int once the buffer
+ * API is cleaned up
+ *
+ * Reserve "space" bytes in the given buffer.
+ *
+ * Requires:
+ *
+ *\li 'msg' be valid.
+ *
+ *\li dns_message_renderbegin() was called.
+ *
+ * Returns:
+ *\li #ISC_R_SUCCESS -- all is well.
+ *\li #ISC_R_NOSPACE -- not enough free space in the buffer.
+ */
+
+void
+dns_message_renderrelease(dns_message_t *msg, unsigned int space);
+/*%<
+ * XXXMLG should use size_t rather than unsigned int once the buffer
+ * API is cleaned up
+ *
+ * Release "space" bytes in the given buffer that was previously reserved.
+ *
+ * Requires:
+ *
+ *\li 'msg' be valid.
+ *
+ *\li 'space' is less than or equal to the total amount of space reserved
+ * via prior calls to dns_message_renderreserve().
+ *
+ *\li dns_message_renderbegin() was called.
+ */
+
+isc_result_t
+dns_message_rendersection(dns_message_t *msg, dns_section_t section,
+ unsigned int options);
+/*%<
+ * Render all names, rdatalists, etc from the given section at the
+ * specified priority or higher.
+ *
+ * Requires:
+ *\li 'msg' be valid.
+ *
+ *\li 'section' be a valid section.
+ *
+ *\li dns_message_renderbegin() was called.
+ *
+ * Returns:
+ *\li #ISC_R_SUCCESS -- all records were written, and there are
+ * no more records for this section.
+ *\li #ISC_R_NOSPACE -- Not enough room in the buffer to write
+ * all records requested.
+ *\li #DNS_R_MOREDATA -- All requested records written, and there
+ * are records remaining for this section.
+ */
+
+void
+dns_message_renderheader(dns_message_t *msg, isc_buffer_t *target);
+/*%<
+ * Render the message header. This is implicitly called by
+ * dns_message_renderend().
+ *
+ * Requires:
+ *
+ *\li 'msg' be a valid message.
+ *
+ *\li dns_message_renderbegin() was called.
+ *
+ *\li 'target' is a valid buffer with enough space to hold a message header
+ */
+
+isc_result_t
+dns_message_renderend(dns_message_t *msg);
+/*%<
+ * Finish rendering to the buffer. Note that more data can be in the
+ * 'msg' structure. Destroying the structure will free this, or in a multi-
+ * part EDNS1 message this data can be rendered to another buffer later.
+ *
+ * Requires:
+ *
+ *\li 'msg' be a valid message.
+ *
+ *\li dns_message_renderbegin() was called.
+ *
+ * Returns:
+ *\li #ISC_R_SUCCESS -- all is well.
+ */
+
+void
+dns_message_renderreset(dns_message_t *msg);
+/*%<
+ * Reset the message so that it may be rendered again.
+ *
+ * Notes:
+ *
+ *\li If dns_message_renderbegin() has been called, dns_message_renderend()
+ * must be called before calling this function.
+ *
+ * Requires:
+ *
+ *\li 'msg' be a valid message with rendering intent.
+ */
+
+isc_result_t
+dns_message_firstname(dns_message_t *msg, dns_section_t section);
+/*%<
+ * Set internal per-section name pointer to the beginning of the section.
+ *
+ * The functions dns_message_firstname() and dns_message_nextname() may
+ * be used for iterating over the owner names in a section.
+ *
+ * Requires:
+ *
+ *\li 'msg' be valid.
+ *
+ *\li 'section' be a valid section.
+ *
+ * Returns:
+ *\li #ISC_R_SUCCESS -- All is well.
+ *\li #ISC_R_NOMORE -- No names on given section.
+ */
+
+isc_result_t
+dns_message_nextname(dns_message_t *msg, dns_section_t section);
+/*%<
+ * Sets the internal per-section name pointer to point to the next name
+ * in that section.
+ *
+ * Requires:
+ *
+ * \li 'msg' be valid.
+ *
+ *\li 'section' be a valid section.
+ *
+ *\li dns_message_firstname() must have been called on this section,
+ * and the result was ISC_R_SUCCESS.
+ *
+ * Returns:
+ *\li #ISC_R_SUCCESS -- All is well.
+ *\li #ISC_R_NOMORE -- No more names in given section.
+ */
+
+void
+dns_message_currentname(dns_message_t *msg, dns_section_t section,
+ dns_name_t **name);
+/*%<
+ * Sets 'name' to point to the name where the per-section internal name
+ * pointer is currently set.
+ *
+ * This function returns the name in the database, so any data associated
+ * with it (via the name's "list" member) contains the actual rdatasets.
+ *
+ * Requires:
+ *
+ *\li 'msg' be valid.
+ *
+ *\li 'name' be non-NULL, and *name be NULL.
+ *
+ *\li 'section' be a valid section.
+ *
+ *\li dns_message_firstname() must have been called on this section,
+ * and the result of it and any dns_message_nextname() calls was
+ * #ISC_R_SUCCESS.
+ */
+
+isc_result_t
+dns_message_findname(dns_message_t *msg, dns_section_t section,
+ const dns_name_t *target, dns_rdatatype_t type,
+ dns_rdatatype_t covers, dns_name_t **foundname,
+ dns_rdataset_t **rdataset);
+/*%<
+ * Search for a name in the specified section. If it is found, *name is
+ * set to point to the name, and *rdataset is set to point to the found
+ * rdataset (if type is specified as other than dns_rdatatype_any).
+ *
+ * Requires:
+ *\li 'msg' be valid.
+ *
+ *\li 'section' be a valid section.
+ *
+ *\li If a pointer to the name is desired, 'foundname' should be non-NULL.
+ * If it is non-NULL, '*foundname' MUST be NULL.
+ *
+ *\li If a type other than dns_datatype_any is searched for, 'rdataset'
+ * may be non-NULL, '*rdataset' be NULL, and will point at the found
+ * rdataset. If the type is dns_datatype_any, 'rdataset' must be NULL.
+ *
+ *\li 'target' be a valid name.
+ *
+ *\li 'type' be a valid type.
+ *
+ *\li If 'type' is dns_rdatatype_rrsig, 'covers' must be a valid type.
+ * Otherwise it should be 0.
+ *
+ * Returns:
+ *\li #ISC_R_SUCCESS -- all is well.
+ *\li #DNS_R_NXDOMAIN -- name does not exist in that section.
+ *\li #DNS_R_NXRRSET -- The name does exist, but the desired
+ * type does not.
+ */
+
+isc_result_t
+dns_message_findtype(const dns_name_t *name, dns_rdatatype_t type,
+ dns_rdatatype_t covers, dns_rdataset_t **rdataset);
+/*%<
+ * Search the name for the specified type. If it is found, *rdataset is
+ * filled in with a pointer to that rdataset.
+ *
+ * Requires:
+ *\li if '**rdataset' is non-NULL, *rdataset needs to be NULL.
+ *
+ *\li 'type' be a valid type, and NOT dns_rdatatype_any.
+ *
+ *\li If 'type' is dns_rdatatype_rrsig, 'covers' must be a valid type.
+ * Otherwise it should be 0.
+ *
+ * Returns:
+ *\li #ISC_R_SUCCESS -- all is well.
+ *\li #ISC_R_NOTFOUND -- the desired type does not exist.
+ */
+
+isc_result_t
+dns_message_find(const dns_name_t *name, dns_rdataclass_t rdclass,
+ dns_rdatatype_t type, dns_rdatatype_t covers,
+ dns_rdataset_t **rdataset);
+/*%<
+ * Search the name for the specified rdclass and type. If it is found,
+ * *rdataset is filled in with a pointer to that rdataset.
+ *
+ * Requires:
+ *\li if '**rdataset' is non-NULL, *rdataset needs to be NULL.
+ *
+ *\li 'type' be a valid type, and NOT dns_rdatatype_any.
+ *
+ *\li If 'type' is dns_rdatatype_rrsig, 'covers' must be a valid type.
+ * Otherwise it should be 0.
+ *
+ * Returns:
+ *\li #ISC_R_SUCCESS -- all is well.
+ *\li #ISC_R_NOTFOUND -- the desired type does not exist.
+ */
+
+void
+dns_message_movename(dns_message_t *msg, dns_name_t *name,
+ dns_section_t fromsection, dns_section_t tosection);
+/*%<
+ * Move a name from one section to another.
+ *
+ * Requires:
+ *
+ *\li 'msg' be valid.
+ *
+ *\li 'name' must be a name already in 'fromsection'.
+ *
+ *\li 'fromsection' must be a valid section.
+ *
+ *\li 'tosection' must be a valid section.
+ */
+
+void
+dns_message_addname(dns_message_t *msg, dns_name_t *name,
+ dns_section_t section);
+/*%<
+ * Adds the name to the given section.
+ *
+ * It is the caller's responsibility to enforce any unique name requirements
+ * in a section.
+ *
+ * Requires:
+ *
+ *\li 'msg' be valid, and be a renderable message.
+ *
+ *\li 'name' be a valid absolute name.
+ *
+ *\li 'section' be a named section.
+ */
+
+void
+dns_message_removename(dns_message_t *msg, dns_name_t *name,
+ dns_section_t section);
+/*%<
+ * Remove a existing name from a given section.
+ *
+ * It is the caller's responsibility to ensure the name is part of the
+ * given section.
+ *
+ * Requires:
+ *
+ *\li 'msg' be valid, and be a renderable message.
+ *
+ *\li 'name' be a valid absolute name.
+ *
+ *\li 'section' be a named section.
+ */
+
+/*
+ * LOANOUT FUNCTIONS
+ *
+ * Each of these functions loan a particular type of data to the caller.
+ * The storage for these will vanish when the message is destroyed or
+ * reset, and must NOT be used after these operations.
+ */
+
+isc_result_t
+dns_message_gettempname(dns_message_t *msg, dns_name_t **item);
+/*%<
+ * Return a name that can be used for any temporary purpose, including
+ * inserting into the message's linked lists. The name must be returned
+ * to the message code using dns_message_puttempname() or inserted into
+ * one of the message's sections before the message is destroyed.
+ *
+ * The name will be associated with a dns_fixedname object, and will
+ * be initialized.
+ *
+ * Requires:
+ *\li msg be a valid message
+ *
+ *\li item != NULL && *item == NULL
+ *
+ * Returns:
+ *\li #ISC_R_SUCCESS -- All is well.
+ */
+
+isc_result_t
+dns_message_gettemprdata(dns_message_t *msg, dns_rdata_t **item);
+/*%<
+ * Return a rdata that can be used for any temporary purpose, including
+ * inserting into the message's linked lists. The rdata will be freed
+ * when the message is destroyed or reset.
+ *
+ * Requires:
+ *\li msg be a valid message
+ *
+ *\li item != NULL && *item == NULL
+ *
+ * Returns:
+ *\li #ISC_R_SUCCESS -- All is well.
+ */
+
+isc_result_t
+dns_message_gettemprdataset(dns_message_t *msg, dns_rdataset_t **item);
+/*%<
+ * Return a rdataset that can be used for any temporary purpose, including
+ * inserting into the message's linked lists. The name must be returned
+ * to the message code using dns_message_puttempname() or inserted into
+ * one of the message's sections before the message is destroyed.
+ *
+ * Requires:
+ *\li msg be a valid message
+ *
+ *\li item != NULL && *item == NULL
+ *
+ * Returns:
+ *\li #ISC_R_SUCCESS -- All is well.
+ */
+
+isc_result_t
+dns_message_gettemprdatalist(dns_message_t *msg, dns_rdatalist_t **item);
+/*%<
+ * Return a rdatalist that can be used for any temporary purpose, including
+ * inserting into the message's linked lists. The rdatalist will be
+ * destroyed when the message is destroyed or reset.
+ *
+ * Requires:
+ *\li msg be a valid message
+ *
+ *\li item != NULL && *item == NULL
+ *
+ * Returns:
+ *\li #ISC_R_SUCCESS -- All is well.
+ */
+
+void
+dns_message_puttempname(dns_message_t *msg, dns_name_t **item);
+/*%<
+ * Return a borrowed name to the message's name free list.
+ *
+ * Requires:
+ *\li msg be a valid message
+ *
+ *\li item != NULL && *item point to a name returned by
+ * dns_message_gettempname()
+ *
+ * Ensures:
+ *\li *item == NULL
+ */
+
+void
+dns_message_puttemprdata(dns_message_t *msg, dns_rdata_t **item);
+/*%<
+ * Return a borrowed rdata to the message's rdata free list.
+ *
+ * Requires:
+ *\li msg be a valid message
+ *
+ *\li item != NULL && *item point to a rdata returned by
+ * dns_message_gettemprdata()
+ *
+ * Ensures:
+ *\li *item == NULL
+ */
+
+void
+dns_message_puttemprdataset(dns_message_t *msg, dns_rdataset_t **item);
+/*%<
+ * Return a borrowed rdataset to the message's rdataset free list.
+ *
+ * Requires:
+ *\li msg be a valid message
+ *
+ *\li item != NULL && *item point to a rdataset returned by
+ * dns_message_gettemprdataset()
+ *
+ * Ensures:
+ *\li *item == NULL
+ */
+
+void
+dns_message_puttemprdatalist(dns_message_t *msg, dns_rdatalist_t **item);
+/*%<
+ * Return a borrowed rdatalist to the message's rdatalist free list.
+ *
+ * Requires:
+ *\li msg be a valid message
+ *
+ *\li item != NULL && *item point to a rdatalist returned by
+ * dns_message_gettemprdatalist()
+ *
+ * Ensures:
+ *\li *item == NULL
+ */
+
+isc_result_t
+dns_message_peekheader(isc_buffer_t *source, dns_messageid_t *idp,
+ unsigned int *flagsp);
+/*%<
+ * Assume the remaining region of "source" is a DNS message. Peek into
+ * it and fill in "*idp" with the message id, and "*flagsp" with the flags.
+ *
+ * Requires:
+ *
+ *\li source != NULL
+ *
+ * Ensures:
+ *
+ *\li if (idp != NULL) *idp == message id.
+ *
+ *\li if (flagsp != NULL) *flagsp == message flags.
+ *
+ * Returns:
+ *
+ *\li #ISC_R_SUCCESS -- all is well.
+ *
+ *\li #ISC_R_UNEXPECTEDEND -- buffer doesn't contain enough for a header.
+ */
+
+isc_result_t
+dns_message_reply(dns_message_t *msg, bool want_question_section);
+/*%<
+ * Start formatting a reply to the query in 'msg'.
+ *
+ * Requires:
+ *
+ *\li 'msg' is a valid message with parsing intent, and contains a query.
+ *
+ * Ensures:
+ *
+ *\li The message will have a rendering intent. If 'want_question_section'
+ * is true, the message opcode is query or notify, and the question
+ * section is present and properly formatted, then the question section
+ * will be included in the reply. All other sections will be cleared.
+ * The QR flag will be set, the RD flag will be preserved, and all other
+ * flags will be cleared.
+ *
+ * Returns:
+ *
+ *\li #ISC_R_SUCCESS -- all is well.
+ *
+ *\li #DNS_R_FORMERR -- the header or question section of the
+ * message is invalid, replying is impossible.
+ * If DNS_R_FORMERR is returned when
+ * want_question_section is false, then
+ * it's the header section that's bad;
+ * otherwise either of the header or question
+ * sections may be bad.
+ */
+
+dns_rdataset_t *
+dns_message_getopt(dns_message_t *msg);
+/*%<
+ * Get the OPT record for 'msg'.
+ *
+ * Requires:
+ *
+ *\li 'msg' is a valid message.
+ *
+ * Returns:
+ *
+ *\li The OPT rdataset of 'msg', or NULL if there isn't one.
+ */
+
+isc_result_t
+dns_message_setopt(dns_message_t *msg, dns_rdataset_t *opt);
+/*%<
+ * Set the OPT record for 'msg'.
+ *
+ * Requires:
+ *
+ *\li 'msg' is a valid message with rendering intent
+ * and no sections have been rendered.
+ *
+ *\li 'opt' is a valid OPT record.
+ *
+ * Ensures:
+ *
+ *\li The OPT record has either been freed or ownership of it has
+ * been transferred to the message.
+ *
+ *\li If ISC_R_SUCCESS was returned, the OPT record will be rendered
+ * when dns_message_renderend() is called.
+ *
+ * Returns:
+ *
+ *\li #ISC_R_SUCCESS -- all is well.
+ *
+ *\li #ISC_R_NOSPACE -- there is no space for the OPT record.
+ */
+
+dns_rdataset_t *
+dns_message_gettsig(dns_message_t *msg, const dns_name_t **owner);
+/*%<
+ * Get the TSIG record and owner for 'msg'.
+ *
+ * Requires:
+ *
+ *\li 'msg' is a valid message.
+ *\li 'owner' is NULL or *owner is NULL.
+ *
+ * Returns:
+ *
+ *\li The TSIG rdataset of 'msg', or NULL if there isn't one.
+ *
+ * Ensures:
+ *
+ * \li If 'owner' is not NULL, it will point to the owner name.
+ */
+
+isc_result_t
+dns_message_settsigkey(dns_message_t *msg, dns_tsigkey_t *key);
+/*%<
+ * Set the tsig key for 'msg'. This is only necessary for when rendering a
+ * query or parsing a response. The key (if non-NULL) is attached to, and
+ * will be detached when the message is destroyed.
+ *
+ * Requires:
+ *
+ *\li 'msg' is a valid message with rendering intent,
+ * dns_message_renderbegin() has been called, and no sections have been
+ * rendered.
+ *\li 'key' is a valid tsig key or NULL.
+ *
+ * Returns:
+ *
+ *\li #ISC_R_SUCCESS -- all is well.
+ *
+ *\li #ISC_R_NOSPACE -- there is no space for the TSIG record.
+ */
+
+dns_tsigkey_t *
+dns_message_gettsigkey(dns_message_t *msg);
+/*%<
+ * Gets the tsig key for 'msg'.
+ *
+ * Requires:
+ *
+ *\li 'msg' is a valid message
+ */
+
+isc_result_t
+dns_message_setquerytsig(dns_message_t *msg, isc_buffer_t *querytsig);
+/*%<
+ * Indicates that 'querytsig' is the TSIG from the signed query for which
+ * 'msg' is the response. This is also used for chained TSIGs in TCP
+ * responses.
+ *
+ * Requires:
+ *
+ *\li 'querytsig' is a valid buffer as returned by dns_message_getquerytsig()
+ * or NULL
+ *
+ *\li 'msg' is a valid message
+ *
+ * Returns:
+ *
+ *\li #ISC_R_SUCCESS
+ *\li #ISC_R_NOMEMORY
+ */
+
+isc_result_t
+dns_message_getquerytsig(dns_message_t *msg, isc_mem_t *mctx,
+ isc_buffer_t **querytsig);
+/*%<
+ * Gets the tsig from the TSIG from the signed query 'msg'. This is also used
+ * for chained TSIGs in TCP responses. Unlike dns_message_gettsig, this makes
+ * a copy of the data, so can be used if the message is destroyed.
+ *
+ * Requires:
+ *
+ *\li 'msg' is a valid signed message
+ *\li 'mctx' is a valid memory context
+ *\li querytsig != NULL && *querytsig == NULL
+ *
+ * Returns:
+ *
+ *\li #ISC_R_SUCCESS
+ *\li #ISC_R_NOMEMORY
+ *
+ * Ensures:
+ *\li 'tsig' points to NULL or an allocated buffer which must be freed
+ * by the caller.
+ */
+
+dns_rdataset_t *
+dns_message_getsig0(dns_message_t *msg, const dns_name_t **owner);
+/*%<
+ * Get the SIG(0) record and owner for 'msg'.
+ *
+ * Requires:
+ *
+ *\li 'msg' is a valid message.
+ *\li 'owner' is NULL or *owner is NULL.
+ *
+ * Returns:
+ *
+ *\li The SIG(0) rdataset of 'msg', or NULL if there isn't one.
+ *
+ * Ensures:
+ *
+ * \li If 'owner' is not NULL, it will point to the owner name.
+ */
+
+isc_result_t
+dns_message_setsig0key(dns_message_t *msg, dst_key_t *key);
+/*%<
+ * Set the SIG(0) key for 'msg'.
+ *
+ * Requires:
+ *
+ *\li 'msg' is a valid message with rendering intent,
+ * dns_message_renderbegin() has been called, and no sections have been
+ * rendered.
+ *\li 'key' is a valid sig key or NULL.
+ *
+ * Returns:
+ *
+ *\li #ISC_R_SUCCESS -- all is well.
+ *
+ *\li #ISC_R_NOSPACE -- there is no space for the SIG(0) record.
+ */
+
+dst_key_t *
+dns_message_getsig0key(dns_message_t *msg);
+/*%<
+ * Gets the SIG(0) key for 'msg'.
+ *
+ * Requires:
+ *
+ *\li 'msg' is a valid message
+ */
+
+void
+dns_message_takebuffer(dns_message_t *msg, isc_buffer_t **buffer);
+/*%<
+ * Give the *buffer to the message code to clean up when it is no
+ * longer needed. This is usually when the message is reset or
+ * destroyed.
+ *
+ * Requires:
+ *
+ *\li msg be a valid message.
+ *
+ *\li buffer != NULL && *buffer is a valid isc_buffer_t, which was
+ * dynamically allocated via isc_buffer_allocate().
+ */
+
+isc_result_t
+dns_message_signer(dns_message_t *msg, dns_name_t *signer);
+/*%<
+ * If this message was signed, return the identity of the signer.
+ * Unless ISC_R_NOTFOUND is returned, signer will reflect the name of the
+ * key that signed the message.
+ *
+ * Requires:
+ *
+ *\li msg is a valid parsed message.
+ *\li signer is a valid name
+ *
+ * Returns:
+ *
+ *\li #ISC_R_SUCCESS - the message was signed, and *signer
+ * contains the signing identity
+ *
+ *\li #ISC_R_NOTFOUND - no TSIG or SIG(0) record is present in the
+ * message
+ *
+ *\li #DNS_R_TSIGVERIFYFAILURE - the message was signed by a TSIG, but
+ * the signature failed to verify
+ *
+ *\li #DNS_R_TSIGERRORSET - the message was signed by a TSIG and
+ * verified, but the query was rejected by
+ * the server
+ *
+ *\li #DNS_R_NOIDENTITY - the message was signed by a TSIG and
+ * verified, but the key has no identity since
+ * it was generated by an unsigned TKEY process
+ *
+ *\li #DNS_R_SIGINVALID - the message was signed by a SIG(0), but
+ * the signature failed to verify
+ *
+ *\li #DNS_R_NOTVERIFIEDYET - the message was signed by a TSIG or SIG(0),
+ * but the signature has not been verified yet
+ */
+
+isc_result_t
+dns_message_checksig(dns_message_t *msg, dns_view_t *view);
+/*%<
+ * If this message was signed, verify the signature.
+ *
+ * Requires:
+ *
+ *\li msg is a valid parsed message.
+ *\li view is a valid view or NULL
+ *
+ * Returns:
+ *
+ *\li #ISC_R_SUCCESS - the message was unsigned, or the message
+ * was signed correctly.
+ *
+ *\li #DNS_R_EXPECTEDTSIG - A TSIG was expected, but not seen
+ *\li #DNS_R_UNEXPECTEDTSIG - A TSIG was seen but not expected
+ *\li #DNS_R_TSIGVERIFYFAILURE - The TSIG failed to verify
+ */
+
+isc_result_t
+dns_message_rechecksig(dns_message_t *msg, dns_view_t *view);
+/*%<
+ * Reset the signature state and then if the message was signed,
+ * verify the message.
+ *
+ * Requires:
+ *
+ *\li msg is a valid parsed message.
+ *\li view is a valid view or NULL
+ *
+ * Returns:
+ *
+ *\li #ISC_R_SUCCESS - the message was unsigned, or the message
+ * was signed correctly.
+ *
+ *\li #DNS_R_EXPECTEDTSIG - A TSIG was expected, but not seen
+ *\li #DNS_R_UNEXPECTEDTSIG - A TSIG was seen but not expected
+ *\li #DNS_R_TSIGVERIFYFAILURE - The TSIG failed to verify
+ */
+
+void
+dns_message_resetsig(dns_message_t *msg);
+/*%<
+ * Reset the signature state.
+ *
+ * Requires:
+ *\li 'msg' is a valid parsed message.
+ */
+
+isc_region_t *
+dns_message_getrawmessage(dns_message_t *msg);
+/*%<
+ * Retrieve the raw message in compressed wire format. The message must
+ * have been successfully parsed for it to have been saved.
+ *
+ * Requires:
+ *\li msg is a valid parsed message.
+ *
+ * Returns:
+ *\li NULL if there is no saved message.
+ * a pointer to a region which refers the dns message.
+ */
+
+void
+dns_message_setsortorder(dns_message_t *msg, dns_rdatasetorderfunc_t order,
+ dns_aclenv_t *env, dns_acl_t *acl,
+ const dns_aclelement_t *element);
+/*%<
+ * Define the order in which RR sets get rendered by
+ * dns_message_rendersection() to be the ascending order
+ * defined by the integer value returned by 'order' when
+ * given each RR and a ns_sortlist_arg_t constructed from 'env',
+ * 'acl', and 'element' as arguments.
+ *
+ * If 'order' is NULL, a default order is used.
+ *
+ * Requires:
+ *\li msg be a valid message.
+ *\li If 'env' is NULL, 'order' must be NULL.
+ *\li If 'env' is not NULL, 'order' must not be NULL and at least one of
+ * 'acl' and 'element' must also not be NULL.
+ */
+
+void
+dns_message_settimeadjust(dns_message_t *msg, int timeadjust);
+/*%<
+ * Adjust the time used to sign/verify a message by timeadjust.
+ * Currently only TSIG.
+ *
+ * Requires:
+ *\li msg be a valid message.
+ */
+
+int
+dns_message_gettimeadjust(dns_message_t *msg);
+/*%<
+ * Return the current time adjustment.
+ *
+ * Requires:
+ *\li msg be a valid message.
+ */
+
+void
+dns_message_logpacket(dns_message_t *message, const char *description,
+ const isc_sockaddr_t *address,
+ isc_logcategory_t *category, isc_logmodule_t *module,
+ int level, isc_mem_t *mctx);
+
+void
+dns_message_logfmtpacket(dns_message_t *message, const char *description,
+ const isc_sockaddr_t *address,
+ isc_logcategory_t *category, isc_logmodule_t *module,
+ const dns_master_style_t *style, int level,
+ isc_mem_t *mctx);
+/*%<
+ * Log 'message' at the specified logging parameters.
+ *
+ * For dns_message_logpacket and dns_message_logfmtpacket expect the
+ * 'description' to end in a newline.
+ *
+ * For dns_message_logpacket2 and dns_message_logfmtpacket2
+ * 'description' will be emitted at the start of the message followed
+ * by the formatted address and a newline.
+ *
+ * Requires:
+ * \li message be a valid.
+ * \li description to be non NULL.
+ * \li address to be non NULL.
+ * \li category to be valid.
+ * \li module to be valid.
+ * \li style to be valid.
+ * \li mctx to be a valid.
+ */
+
+isc_result_t
+dns_message_buildopt(dns_message_t *msg, dns_rdataset_t **opt,
+ unsigned int version, uint16_t udpsize, unsigned int flags,
+ dns_ednsopt_t *ednsopts, size_t count);
+/*%<
+ * Built a opt record.
+ *
+ * Requires:
+ * \li msg be a valid message.
+ * \li opt to be a non NULL and *opt to be NULL.
+ *
+ * Returns:
+ * \li ISC_R_SUCCESS on success.
+ * \li ISC_R_NOMEMORY
+ * \li ISC_R_NOSPACE
+ * \li other.
+ */
+
+void
+dns_message_setclass(dns_message_t *msg, dns_rdataclass_t rdclass);
+/*%<
+ * Set the expected class of records in the response.
+ *
+ * Requires:
+ * \li msg be a valid message with parsing intent.
+ */
+
+void
+dns_message_setpadding(dns_message_t *msg, uint16_t padding);
+/*%<
+ * Set the padding block size in the response.
+ * 0 means no padding (default).
+ *
+ * Requires:
+ * \li msg be a valid message.
+ */
+
+void
+dns_message_clonebuffer(dns_message_t *msg);
+/*%<
+ * Clone the query or saved buffers if they where not cloned
+ * when parsing.
+ *
+ * Requires:
+ * \li msg be a valid message.
+ */
+
+isc_result_t
+dns_message_minttl(dns_message_t *msg, const dns_section_t sectionid,
+ dns_ttl_t *pttl);
+/*%<
+ * Get the smallest TTL from the 'sectionid' section of a rendered
+ * message.
+ *
+ * Requires:
+ * \li msg be a valid rendered message;
+ * \li 'pttl != NULL'.
+ */
+
+isc_result_t
+dns_message_response_minttl(dns_message_t *msg, dns_ttl_t *pttl);
+/*%<
+ * Get the smalled TTL from the Answer section of 'msg', or if empty, try
+ * the MIN(SOA TTL, SOA MINIMUM) value from an SOA record in the Authority
+ * section. If neither of these are set, return ISC_R_NOTFOUND.
+ *
+ * Requires:
+ * \li msg be a valid rendered message;
+ * \li 'pttl != NULL'.
+ */
+
+ISC_LANG_ENDDECLS
diff --git a/lib/dns/include/dns/name.h b/lib/dns/include/dns/name.h
new file mode 100644
index 0000000..a758c4d
--- /dev/null
+++ b/lib/dns/include/dns/name.h
@@ -0,0 +1,1381 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#pragma once
+
+/*****
+***** Module Info
+*****/
+
+/*! \file dns/name.h
+ * \brief
+ * Provides facilities for manipulating DNS names and labels, including
+ * conversions to and from wire format and text format.
+ *
+ * Given the large number of names possible in a nameserver, and because
+ * names occur in rdata, it was important to come up with a very efficient
+ * way of storing name data, but at the same time allow names to be
+ * manipulated. The decision was to store names in uncompressed wire format,
+ * and not to make them fully abstracted objects; i.e. certain parts of the
+ * server know names are stored that way. This saves a lot of memory, and
+ * makes adding names to messages easy. Having much of the server know
+ * the representation would be perilous, and we certainly don't want each
+ * user of names to be manipulating such a low-level structure. This is
+ * where the Names and Labels module comes in. The module allows name or
+ * label handles to be created and attached to uncompressed wire format
+ * regions. All name operations and conversions are done through these
+ * handles.
+ *
+ * MP:
+ *\li Clients of this module must impose any required synchronization.
+ *
+ * Reliability:
+ *\li This module deals with low-level byte streams. Errors in any of
+ * the functions are likely to crash the server or corrupt memory.
+ *
+ * Resources:
+ *\li None.
+ *
+ * Security:
+ *
+ *\li *** WARNING ***
+ *
+ *\li dns_name_fromwire() deals with raw network data. An error in
+ * this routine could result in the failure or hijacking of the server.
+ *
+ * Standards:
+ *\li RFC1035
+ *\li Draft EDNS0 (0)
+ *\li Draft Binary Labels (2)
+ *
+ */
+
+/***
+ *** Imports
+ ***/
+
+#include <inttypes.h>
+#include <stdbool.h>
+#include <stdio.h>
+
+#include <isc/lang.h>
+#include <isc/magic.h>
+#include <isc/region.h> /* Required for storage size of dns_label_t. */
+
+#include <dns/types.h>
+
+ISC_LANG_BEGINDECLS
+
+/*****
+***** Labels
+*****
+***** A 'label' is basically a region. It contains one DNS wire format
+***** label of type 00 (ordinary).
+*****/
+
+/*****
+***** Names
+*****
+***** A 'name' is a handle to a binary region. It contains a sequence of one
+***** or more DNS wire format labels of type 00 (ordinary).
+***** Note that all names are not required to end with the root label,
+***** as they are in the actual DNS wire protocol.
+*****/
+
+/***
+ *** Types
+ ***/
+
+/*%
+ * Clients are strongly discouraged from using this type directly, with
+ * the exception of the 'link' and 'list' fields which may be used directly
+ * for whatever purpose the client desires.
+ */
+struct dns_name {
+ unsigned int magic;
+ unsigned char *ndata;
+ unsigned int length;
+ unsigned int labels;
+ unsigned int attributes;
+ unsigned char *offsets;
+ isc_buffer_t *buffer;
+ ISC_LINK(dns_name_t) link;
+ ISC_LIST(dns_rdataset_t) list;
+};
+
+#define DNS_NAME_MAGIC ISC_MAGIC('D', 'N', 'S', 'n')
+
+#define DNS_NAMEATTR_ABSOLUTE 0x00000001
+#define DNS_NAMEATTR_READONLY 0x00000002
+#define DNS_NAMEATTR_DYNAMIC 0x00000004
+#define DNS_NAMEATTR_DYNOFFSETS 0x00000008
+#define DNS_NAMEATTR_NOCOMPRESS 0x00000010
+/*
+ * Attributes below 0x0100 reserved for name.c usage.
+ */
+#define DNS_NAMEATTR_CACHE 0x00000100 /*%< Used by resolver. */
+#define DNS_NAMEATTR_ANSWER 0x00000200 /*%< Used by resolver. */
+#define DNS_NAMEATTR_NCACHE 0x00000400 /*%< Used by resolver. */
+#define DNS_NAMEATTR_CHAINING 0x00000800 /*%< Used by resolver. */
+#define DNS_NAMEATTR_CHASE 0x00001000 /*%< Used by resolver. */
+#define DNS_NAMEATTR_WILDCARD 0x00002000 /*%< Used by server. */
+#define DNS_NAMEATTR_PREREQUISITE 0x00004000 /*%< Used by client. */
+#define DNS_NAMEATTR_UPDATE 0x00008000 /*%< Used by client. */
+#define DNS_NAMEATTR_HASUPDATEREC 0x00010000 /*%< Used by client. */
+
+/*
+ * Various flags.
+ */
+#define DNS_NAME_DOWNCASE 0x0001
+#define DNS_NAME_CHECKNAMES 0x0002 /*%< Used by rdata. */
+#define DNS_NAME_CHECKNAMESFAIL 0x0004 /*%< Used by rdata. */
+#define DNS_NAME_CHECKREVERSE 0x0008 /*%< Used by rdata. */
+#define DNS_NAME_CHECKMX 0x0010 /*%< Used by rdata. */
+#define DNS_NAME_CHECKMXFAIL 0x0020 /*%< Used by rdata. */
+
+extern const dns_name_t *dns_rootname;
+extern const dns_name_t *dns_wildcardname;
+
+/*%<
+ * DNS_NAME_INITNONABSOLUTE and DNS_NAME_INITABSOLUTE are macros for
+ * initializing dns_name_t structures.
+ *
+ * Note[1]: 'length' is set to (sizeof(A) - 1) in DNS_NAME_INITNONABSOLUTE
+ * and sizeof(A) in DNS_NAME_INITABSOLUTE to allow C strings to be used
+ * to initialize 'ndata'.
+ *
+ * Note[2]: The final value of offsets for DNS_NAME_INITABSOLUTE should
+ * match (sizeof(A) - 1) which is the offset of the root label.
+ *
+ * Typical usage:
+ * unsigned char data[] = "\005value";
+ * unsigned char offsets[] = { 0 };
+ * dns_name_t value = DNS_NAME_INITNONABSOLUTE(data, offsets);
+ *
+ * unsigned char data[] = "\005value";
+ * unsigned char offsets[] = { 0, 6 };
+ * dns_name_t value = DNS_NAME_INITABSOLUTE(data, offsets);
+ */
+#define DNS_NAME_INITNONABSOLUTE(A, B) \
+ { \
+ DNS_NAME_MAGIC, A, (sizeof(A) - 1), sizeof(B), \
+ DNS_NAMEATTR_READONLY, B, NULL, \
+ { (void *)-1, (void *)-1 }, { \
+ NULL, NULL \
+ } \
+ }
+
+#define DNS_NAME_INITABSOLUTE(A, B) \
+ { \
+ DNS_NAME_MAGIC, A, sizeof(A), sizeof(B), \
+ DNS_NAMEATTR_READONLY | DNS_NAMEATTR_ABSOLUTE, B, \
+ NULL, { (void *)-1, (void *)-1 }, { \
+ NULL, NULL \
+ } \
+ }
+
+#define DNS_NAME_INITEMPTY \
+ { \
+ DNS_NAME_MAGIC, NULL, 0, 0, 0, NULL, NULL, \
+ { (void *)-1, (void *)-1 }, { \
+ NULL, NULL \
+ } \
+ }
+
+/*%
+ * Standard size of a wire format name
+ */
+#define DNS_NAME_MAXWIRE 255
+
+/*
+ * Text output filter procedure.
+ * 'target' is the buffer to be converted. The region to be converted
+ * is from 'buffer'->base + 'used_org' to the end of the used region.
+ */
+typedef isc_result_t(dns_name_totextfilter_t)(isc_buffer_t *target,
+ unsigned int used_org);
+
+/***
+ *** Initialization
+ ***/
+
+void
+dns_name_init(dns_name_t *name, unsigned char *offsets);
+/*%<
+ * Initialize 'name'.
+ *
+ * Notes:
+ * \li 'offsets' is never required to be non-NULL, but specifying a
+ * dns_offsets_t for 'offsets' will improve the performance of most
+ * name operations if the name is used more than once.
+ *
+ * Requires:
+ * \li 'name' is not NULL and points to a struct dns_name.
+ *
+ * \li offsets == NULL or offsets is a dns_offsets_t.
+ *
+ * Ensures:
+ * \li 'name' is a valid name.
+ * \li dns_name_countlabels(name) == 0
+ * \li dns_name_isabsolute(name) == false
+ */
+
+void
+dns_name_reset(dns_name_t *name);
+/*%<
+ * Reinitialize 'name'.
+ *
+ * Notes:
+ * \li This function distinguishes itself from dns_name_init() in two
+ * key ways:
+ *
+ * \li + If any buffer is associated with 'name' (via dns_name_setbuffer()
+ * or by being part of a dns_fixedname_t) the link to the buffer
+ * is retained but the buffer itself is cleared.
+ *
+ * \li + Of the attributes associated with 'name', all are retained except
+ * DNS_NAMEATTR_ABSOLUTE.
+ *
+ * Requires:
+ * \li 'name' is a valid name.
+ *
+ * Ensures:
+ * \li 'name' is a valid name.
+ * \li dns_name_countlabels(name) == 0
+ * \li dns_name_isabsolute(name) == false
+ */
+
+void
+dns_name_invalidate(dns_name_t *name);
+/*%<
+ * Make 'name' invalid.
+ *
+ * Requires:
+ * \li 'name' is a valid name.
+ *
+ * Ensures:
+ * \li If assertion checking is enabled, future attempts to use 'name'
+ * without initializing it will cause an assertion failure.
+ *
+ * \li If the name had a dedicated buffer, that association is ended.
+ */
+
+bool
+dns_name_isvalid(const dns_name_t *name);
+/*%<
+ * Check whether 'name' points to a valid dns_name
+ */
+
+/***
+ *** Dedicated Buffers
+ ***/
+
+void
+dns_name_setbuffer(dns_name_t *name, isc_buffer_t *buffer);
+/*%<
+ * Dedicate a buffer for use with 'name'.
+ *
+ * Notes:
+ * \li Specification of a target buffer in dns_name_fromwire(),
+ * dns_name_fromtext(), and dns_name_concatenate() is optional if
+ * 'name' has a dedicated buffer.
+ *
+ * \li The caller must not write to buffer until the name has been
+ * invalidated or is otherwise known not to be in use.
+ *
+ * \li If buffer is NULL and the name previously had a dedicated buffer,
+ * than that buffer is no longer dedicated to use with this name.
+ * The caller is responsible for ensuring that the storage used by
+ * the name remains valid.
+ *
+ * Requires:
+ * \li 'name' is a valid name.
+ *
+ * \li 'buffer' is a valid binary buffer and 'name' doesn't have a
+ * dedicated buffer already, or 'buffer' is NULL.
+ */
+
+bool
+dns_name_hasbuffer(const dns_name_t *name);
+/*%<
+ * Does 'name' have a dedicated buffer?
+ *
+ * Requires:
+ * \li 'name' is a valid name.
+ *
+ * Returns:
+ * \li true 'name' has a dedicated buffer.
+ * \li false 'name' does not have a dedicated buffer.
+ */
+
+/***
+ *** Properties
+ ***/
+
+bool
+dns_name_isabsolute(const dns_name_t *name);
+/*%<
+ * Does 'name' end in the root label?
+ *
+ * Requires:
+ * \li 'name' is a valid name
+ *
+ * Returns:
+ * \li TRUE The last label in 'name' is the root label.
+ * \li FALSE The last label in 'name' is not the root label.
+ */
+
+bool
+dns_name_iswildcard(const dns_name_t *name);
+/*%<
+ * Is 'name' a wildcard name?
+ *
+ * Requires:
+ * \li 'name' is a valid name
+ *
+ * \li dns_name_countlabels(name) > 0
+ *
+ * Returns:
+ * \li TRUE The least significant label of 'name' is '*'.
+ * \li FALSE The least significant label of 'name' is not '*'.
+ */
+
+unsigned int
+dns_name_hash(const dns_name_t *name, bool case_sensitive);
+/*%<
+ * Provide a hash value for 'name'.
+ *
+ * Note: if 'case_sensitive' is false, then names which differ only in
+ * case will have the same hash value.
+ *
+ * Requires:
+ * \li 'name' is a valid name
+ *
+ * Returns:
+ * \li A hash value
+ */
+
+unsigned int
+dns_name_fullhash(const dns_name_t *name, bool case_sensitive);
+/*%<
+ * Provide a hash value for 'name'. Unlike dns_name_hash(), this function
+ * always takes into account of the entire name to calculate the hash value.
+ *
+ * Note: if 'case_sensitive' is false, then names which differ only in
+ * case will have the same hash value.
+ *
+ * Requires:
+ *\li 'name' is a valid name
+ *
+ * Returns:
+ *\li A hash value
+ */
+
+/*
+ *** Comparisons
+ ***/
+
+dns_namereln_t
+dns_name_fullcompare(const dns_name_t *name1, const dns_name_t *name2,
+ int *orderp, unsigned int *nlabelsp);
+/*%<
+ * Determine the relative ordering under the DNSSEC order relation of
+ * 'name1' and 'name2', and also determine the hierarchical
+ * relationship of the names.
+ *
+ * Note: It makes no sense for one of the names to be relative and the
+ * other absolute. If both names are relative, then to be meaningfully
+ * compared the caller must ensure that they are both relative to the
+ * same domain.
+ *
+ * Requires:
+ *\li 'name1' is a valid name
+ *
+ *\li dns_name_countlabels(name1) > 0
+ *
+ *\li 'name2' is a valid name
+ *
+ *\li dns_name_countlabels(name2) > 0
+ *
+ *\li orderp and nlabelsp are valid pointers.
+ *
+ *\li Either name1 is absolute and name2 is absolute, or neither is.
+ *
+ * Ensures:
+ *
+ *\li *orderp is < 0 if name1 < name2, 0 if name1 = name2, > 0 if
+ * name1 > name2.
+ *
+ *\li *nlabelsp is the number of common significant labels.
+ *
+ * Returns:
+ *\li dns_namereln_none There's no hierarchical relationship
+ * between name1 and name2.
+ *\li dns_namereln_contains name1 properly contains name2; i.e.
+ * name2 is a proper subdomain of name1.
+ *\li dns_namereln_subdomain name1 is a proper subdomain of name2.
+ *\li dns_namereln_equal name1 and name2 are equal.
+ *\li dns_namereln_commonancestor name1 and name2 share a common
+ * ancestor.
+ */
+
+int
+dns_name_compare(const dns_name_t *name1, const dns_name_t *name2);
+/*%<
+ * Determine the relative ordering under the DNSSEC order relation of
+ * 'name1' and 'name2'.
+ *
+ * Note: It makes no sense for one of the names to be relative and the
+ * other absolute. If both names are relative, then to be meaningfully
+ * compared the caller must ensure that they are both relative to the
+ * same domain.
+ *
+ * Requires:
+ * \li 'name1' is a valid name
+ *
+ * \li 'name2' is a valid name
+ *
+ * \li Either name1 is absolute and name2 is absolute, or neither is.
+ *
+ * Returns:
+ * \li < 0 'name1' is less than 'name2'
+ * \li 0 'name1' is equal to 'name2'
+ * \li > 0 'name1' is greater than 'name2'
+ */
+
+bool
+dns_name_equal(const dns_name_t *name1, const dns_name_t *name2);
+/*%<
+ * Are 'name1' and 'name2' equal?
+ *
+ * Notes:
+ * \li Because it only needs to test for equality, dns_name_equal() can be
+ * significantly faster than dns_name_fullcompare() or dns_name_compare().
+ *
+ * \li Offsets tables are not used in the comparison.
+ *
+ * \li It makes no sense for one of the names to be relative and the
+ * other absolute. If both names are relative, then to be meaningfully
+ * compared the caller must ensure that they are both relative to the
+ * same domain.
+ *
+ * Requires:
+ * \li 'name1' is a valid name
+ *
+ * \li 'name2' is a valid name
+ *
+ * \li Either name1 is absolute and name2 is absolute, or neither is.
+ *
+ * Returns:
+ * \li true 'name1' and 'name2' are equal
+ * \li false 'name1' and 'name2' are not equal
+ */
+
+bool
+dns_name_caseequal(const dns_name_t *name1, const dns_name_t *name2);
+/*%<
+ * Case sensitive version of dns_name_equal().
+ */
+
+int
+dns_name_rdatacompare(const dns_name_t *name1, const dns_name_t *name2);
+/*%<
+ * Compare two names as if they are part of rdata in DNSSEC canonical
+ * form.
+ *
+ * Requires:
+ * \li 'name1' is a valid absolute name
+ *
+ * \li dns_name_countlabels(name1) > 0
+ *
+ * \li 'name2' is a valid absolute name
+ *
+ * \li dns_name_countlabels(name2) > 0
+ *
+ * Returns:
+ * \li < 0 'name1' is less than 'name2'
+ * \li 0 'name1' is equal to 'name2'
+ * \li > 0 'name1' is greater than 'name2'
+ */
+
+bool
+dns_name_issubdomain(const dns_name_t *name1, const dns_name_t *name2);
+/*%<
+ * Is 'name1' a subdomain of 'name2'?
+ *
+ * Notes:
+ * \li name1 is a subdomain of name2 if name1 is contained in name2, or
+ * name1 equals name2.
+ *
+ * \li It makes no sense for one of the names to be relative and the
+ * other absolute. If both names are relative, then to be meaningfully
+ * compared the caller must ensure that they are both relative to the
+ * same domain.
+ *
+ * Requires:
+ * \li 'name1' is a valid name
+ *
+ * \li 'name2' is a valid name
+ *
+ * \li Either name1 is absolute and name2 is absolute, or neither is.
+ *
+ * Returns:
+ * \li TRUE 'name1' is a subdomain of 'name2'
+ * \li FALSE 'name1' is not a subdomain of 'name2'
+ */
+
+bool
+dns_name_matcheswildcard(const dns_name_t *name, const dns_name_t *wname);
+/*%<
+ * Does 'name' match the wildcard specified in 'wname'?
+ *
+ * Notes:
+ * \li name matches the wildcard specified in wname if all labels
+ * following the wildcard in wname are identical to the same number
+ * of labels at the end of name.
+ *
+ * \li It makes no sense for one of the names to be relative and the
+ * other absolute. If both names are relative, then to be meaningfully
+ * compared the caller must ensure that they are both relative to the
+ * same domain.
+ *
+ * Requires:
+ * \li 'name' is a valid name
+ *
+ * \li dns_name_countlabels(name) > 0
+ *
+ * \li 'wname' is a valid name
+ *
+ * \li dns_name_countlabels(wname) > 0
+ *
+ * \li dns_name_iswildcard(wname) is true
+ *
+ * \li Either name is absolute and wname is absolute, or neither is.
+ *
+ * Returns:
+ * \li TRUE 'name' matches the wildcard specified in 'wname'
+ * \li FALSE 'name' does not match the wildcard specified in 'wname'
+ */
+
+/***
+ *** Labels
+ ***/
+
+unsigned int
+dns_name_countlabels(const dns_name_t *name);
+/*%<
+ * How many labels does 'name' have?
+ *
+ * Notes:
+ * \li In this case, as in other places, a 'label' is an ordinary label.
+ *
+ * Requires:
+ * \li 'name' is a valid name
+ *
+ * Ensures:
+ * \li The result is <= 128.
+ *
+ * Returns:
+ * \li The number of labels in 'name'.
+ */
+
+void
+dns_name_getlabel(const dns_name_t *name, unsigned int n, dns_label_t *label);
+/*%<
+ * Make 'label' refer to the 'n'th least significant label of 'name'.
+ *
+ * Notes:
+ * \li Numbering starts at 0.
+ *
+ * \li Given "rc.vix.com.", the label 0 is "rc", and label 3 is the
+ * root label.
+ *
+ * \li 'label' refers to the same memory as 'name', so 'name' must not
+ * be changed while 'label' is still in use.
+ *
+ * Requires:
+ * \li n < dns_name_countlabels(name)
+ */
+
+void
+dns_name_getlabelsequence(const dns_name_t *source, unsigned int first,
+ unsigned int n, dns_name_t *target);
+/*%<
+ * Make 'target' refer to the 'n' labels including and following 'first'
+ * in 'source'.
+ *
+ * Notes:
+ * \li Numbering starts at 0.
+ *
+ * \li Given "rc.vix.com.", the label 0 is "rc", and label 3 is the
+ * root label.
+ *
+ * \li 'target' refers to the same memory as 'source', so 'source'
+ * must not be changed while 'target' is still in use.
+ *
+ * Requires:
+ * \li 'source' and 'target' are valid names.
+ *
+ * \li first < dns_name_countlabels(name)
+ *
+ * \li first + n <= dns_name_countlabels(name)
+ */
+
+void
+dns_name_clone(const dns_name_t *source, dns_name_t *target);
+/*%<
+ * Make 'target' refer to the same name as 'source'.
+ *
+ * Notes:
+ *
+ * \li 'target' refers to the same memory as 'source', so 'source'
+ * must not be changed or freed while 'target' is still in use.
+ *
+ * \li This call is functionally equivalent to:
+ *
+ * \code
+ * dns_name_getlabelsequence(source, 0,
+ * dns_name_countlabels(source),
+ * target);
+ * \endcode
+ *
+ * but is more efficient. Also, dns_name_clone() works even if 'source'
+ * is empty.
+ *
+ * Requires:
+ *
+ * \li 'source' is a valid name.
+ *
+ * \li 'target' is a valid name that is not read-only.
+ */
+
+/***
+ *** Conversions
+ ***/
+
+void
+dns_name_fromregion(dns_name_t *name, const isc_region_t *r);
+/*%<
+ * Make 'name' refer to region 'r'.
+ *
+ * Note:
+ * \li If the conversion encounters a root label before the end of the
+ * region the conversion stops and the length is set to the length
+ * so far converted. A maximum of 255 bytes is converted.
+ *
+ * Requires:
+ * \li The data in 'r' is a sequence of one or more type 00 or type 01000001
+ * labels.
+ */
+
+void
+dns_name_toregion(const dns_name_t *name, isc_region_t *r);
+/*%<
+ * Make 'r' refer to 'name'.
+ *
+ * Requires:
+ *
+ * \li 'name' is a valid name.
+ *
+ * \li 'r' is a valid region.
+ */
+
+isc_result_t
+dns_name_fromwire(dns_name_t *name, isc_buffer_t *source,
+ dns_decompress_t *dctx, unsigned int options,
+ isc_buffer_t *target);
+/*%<
+ * Copy the possibly-compressed name at source (active region) into target,
+ * decompressing it.
+ *
+ * Notes:
+ * \li Decompression policy is controlled by 'dctx'.
+ *
+ * Security:
+ *
+ * \li *** WARNING ***
+ *
+ * \li This routine will often be used when 'source' contains raw network
+ * data. A programming error in this routine could result in a denial
+ * of service, or in the hijacking of the server.
+ *
+ * Requires:
+ *
+ * \li 'name' is a valid name.
+ *
+ * \li 'source' is a valid buffer and the first byte of the active
+ * region should be the first byte of a DNS wire format domain name.
+ *
+ * \li 'target' is a valid buffer or 'target' is NULL and 'name' has
+ * a dedicated buffer.
+ *
+ * \li 'dctx' is a valid decompression context.
+ *
+ * \li DNS_NAME_DOWNCASE is not set.
+ *
+ * Ensures:
+ *
+ * If result is success:
+ * \li If 'target' is not NULL, 'name' is attached to it.
+ *
+ * \li The current location in source is advanced, and the used space
+ * in target is updated.
+ *
+ * Result:
+ * \li Success
+ * \li Bad Form: Label Length
+ * \li Bad Form: Unknown Label Type
+ * \li Bad Form: Name Length
+ * \li Bad Form: Compression type not allowed
+ * \li Bad Form: Bad compression pointer
+ * \li Bad Form: Input too short
+ * \li Resource Limit: Not enough space in buffer
+ */
+
+isc_result_t
+dns_name_towire(const dns_name_t *name, dns_compress_t *cctx,
+ isc_buffer_t *target);
+isc_result_t
+dns_name_towire2(const dns_name_t *name, dns_compress_t *cctx,
+ isc_buffer_t *target, uint16_t *comp_offsetp);
+/*%<
+ * Convert 'name' into wire format, compressing it as specified by the
+ * compression context 'cctx', and storing the result in 'target'.
+ *
+ * Notes:
+ * \li If the compression context allows global compression, then the
+ * global compression table may be updated.
+ *
+ * Requires:
+ * \li 'name' is a valid name
+ *
+ * \li dns_name_countlabels(name) > 0
+ *
+ * \li dns_name_isabsolute(name) == TRUE
+ *
+ * \li target is a valid buffer.
+ *
+ * \li Any offsets specified in a global compression table are valid
+ * for buffer.
+ *
+ * Ensures:
+ *
+ * If the result is success:
+ *
+ * \li The used space in target is updated.
+ *
+ * Returns:
+ * \li Success
+ * \li Resource Limit: Not enough space in buffer
+ */
+
+isc_result_t
+dns_name_fromtext(dns_name_t *name, isc_buffer_t *source,
+ const dns_name_t *origin, unsigned int options,
+ isc_buffer_t *target);
+/*%<
+ * Convert the textual representation of a DNS name at source
+ * into uncompressed wire form stored in target.
+ *
+ * Notes:
+ * \li Relative domain names will have 'origin' appended to them
+ * unless 'origin' is NULL, in which case relative domain names
+ * will remain relative.
+ *
+ * \li If DNS_NAME_DOWNCASE is set in 'options', any uppercase letters
+ * in 'source' will be downcased when they are copied into 'target'.
+ *
+ * Requires:
+ *
+ * \li 'name' is a valid name.
+ *
+ * \li 'source' is a valid buffer.
+ *
+ * \li 'target' is a valid buffer or 'target' is NULL and 'name' has
+ * a dedicated buffer.
+ *
+ * Ensures:
+ *
+ * If result is success:
+ * \li If 'target' is not NULL, 'name' is attached to it.
+ *
+ * \li Uppercase letters are downcased in the copy iff
+ * DNS_NAME_DOWNCASE is set in 'options'.
+ *
+ * \li The current location in source is advanced, and the used space
+ * in target is updated.
+ *
+ * Result:
+ *\li #ISC_R_SUCCESS
+ *\li #DNS_R_EMPTYLABEL
+ *\li #DNS_R_LABELTOOLONG
+ *\li #DNS_R_BADESCAPE
+ *\li #DNS_R_BADDOTTEDQUAD
+ *\li #ISC_R_NOSPACE
+ *\li #ISC_R_UNEXPECTEDEND
+ */
+
+#define DNS_NAME_OMITFINALDOT 0x01U
+#define DNS_NAME_MASTERFILE 0x02U /* escape $ and @ */
+
+isc_result_t
+dns_name_toprincipal(const dns_name_t *name, isc_buffer_t *target);
+
+isc_result_t
+dns_name_totext(const dns_name_t *name, bool omit_final_dot,
+ isc_buffer_t *target);
+
+isc_result_t
+dns_name_totext2(const dns_name_t *name, unsigned int options,
+ isc_buffer_t *target);
+/*%<
+ * Convert 'name' into text format, storing the result in 'target'.
+ *
+ * Notes:
+ *\li If 'omit_final_dot' is true, then the final '.' in absolute
+ * names other than the root name will be omitted.
+ *
+ *\li If DNS_NAME_OMITFINALDOT is set in options, then the final '.'
+ * in absolute names other than the root name will be omitted.
+ *
+ *\li If DNS_NAME_MASTERFILE is set in options, '$' and '@' will also
+ * be escaped.
+ *
+ *\li If dns_name_countlabels == 0, the name will be "@", representing the
+ * current origin as described by RFC1035.
+ *
+ *\li The name is not NUL terminated.
+ *
+ * Requires:
+ *
+ *\li 'name' is a valid name
+ *
+ *\li 'target' is a valid buffer.
+ *
+ *\li if dns_name_isabsolute == FALSE, then omit_final_dot == FALSE
+ *
+ * Ensures:
+ *
+ *\li If the result is success:
+ * the used space in target is updated.
+ *
+ * Returns:
+ *\li #ISC_R_SUCCESS
+ *\li #ISC_R_NOSPACE
+ */
+
+#define DNS_NAME_MAXTEXT 1023
+/*%<
+ * The maximum length of the text representation of a domain
+ * name as generated by dns_name_totext(). This does not
+ * include space for a terminating NULL.
+ *
+ * This definition is conservative - the actual maximum
+ * is 1004, derived as follows:
+ *
+ * A backslash-decimal escaped character takes 4 bytes.
+ * A wire-encoded name can be up to 255 bytes and each
+ * label is one length byte + at most 63 bytes of data.
+ * Maximizing the label lengths gives us a name of
+ * three 63-octet labels, one 61-octet label, and the
+ * root label:
+ *
+ * 1 + 63 + 1 + 63 + 1 + 63 + 1 + 61 + 1 = 255
+ *
+ * When printed, this is (3 * 63 + 61) * 4
+ * bytes for the escaped label data + 4 bytes for the
+ * dot terminating each label = 1004 bytes total.
+ */
+
+isc_result_t
+dns_name_tofilenametext(const dns_name_t *name, bool omit_final_dot,
+ isc_buffer_t *target);
+/*%<
+ * Convert 'name' into an alternate text format appropriate for filenames,
+ * storing the result in 'target'. The name data is downcased, guaranteeing
+ * that the filename does not depend on the case of the converted name.
+ *
+ * Notes:
+ *\li If 'omit_final_dot' is true, then the final '.' in absolute
+ * names other than the root name will be omitted.
+ *
+ *\li The name is not NUL terminated.
+ *
+ * Requires:
+ *
+ *\li 'name' is a valid absolute name
+ *
+ *\li 'target' is a valid buffer.
+ *
+ * Ensures:
+ *
+ *\li If the result is success:
+ * the used space in target is updated.
+ *
+ * Returns:
+ *\li #ISC_R_SUCCESS
+ *\li #ISC_R_NOSPACE
+ */
+
+isc_result_t
+dns_name_downcase(const dns_name_t *source, dns_name_t *name,
+ isc_buffer_t *target);
+/*%<
+ * Downcase 'source'.
+ *
+ * Requires:
+ *
+ *\li 'source' and 'name' are valid names.
+ *
+ *\li If source == name, then
+ * 'source' must not be read-only
+ *
+ *\li Otherwise,
+ * 'target' is a valid buffer or 'target' is NULL and
+ * 'name' has a dedicated buffer.
+ *
+ * Returns:
+ *\li #ISC_R_SUCCESS
+ *\li #ISC_R_NOSPACE
+ *
+ * Note: if source == name, then the result will always be ISC_R_SUCCESS.
+ */
+
+isc_result_t
+dns_name_concatenate(const dns_name_t *prefix, const dns_name_t *suffix,
+ dns_name_t *name, isc_buffer_t *target);
+/*%<
+ * Concatenate 'prefix' and 'suffix'.
+ *
+ * Requires:
+ *
+ *\li 'prefix' is a valid name or NULL.
+ *
+ *\li 'suffix' is a valid name or NULL.
+ *
+ *\li 'name' is a valid name or NULL.
+ *
+ *\li 'target' is a valid buffer or 'target' is NULL and 'name' has
+ * a dedicated buffer.
+ *
+ *\li If 'prefix' is absolute, 'suffix' must be NULL or the empty name.
+ *
+ * Ensures:
+ *
+ *\li On success,
+ * If 'target' is not NULL and 'name' is not NULL, then 'name'
+ * is attached to it.
+ * The used space in target is updated.
+ *
+ * Returns:
+ *\li #ISC_R_SUCCESS
+ *\li #ISC_R_NOSPACE
+ *\li #DNS_R_NAMETOOLONG
+ */
+
+void
+dns_name_split(const dns_name_t *name, unsigned int suffixlabels,
+ dns_name_t *prefix, dns_name_t *suffix);
+/*%<
+ *
+ * Split 'name' into two pieces on a label boundary.
+ *
+ * Notes:
+ * \li 'name' is split such that 'suffix' holds the most significant
+ * 'suffixlabels' labels. All other labels are stored in 'prefix'.
+ *
+ *\li Copying name data is avoided as much as possible, so 'prefix'
+ * and 'suffix' will end up pointing at the data for 'name'.
+ *
+ *\li It is legitimate to pass a 'prefix' or 'suffix' that has
+ * its name data stored someplace other than the dedicated buffer.
+ * This is useful to avoid name copying in the calling function.
+ *
+ *\li It is also legitimate to pass a 'prefix' or 'suffix' that is
+ * the same dns_name_t as 'name'.
+ *
+ * Requires:
+ *\li 'name' is a valid name.
+ *
+ *\li 'suffixlabels' cannot exceed the number of labels in 'name'.
+ *
+ * \li 'prefix' is a valid name or NULL, and cannot be read-only.
+ *
+ *\li 'suffix' is a valid name or NULL, and cannot be read-only.
+ *
+ * Ensures:
+ *
+ *\li On success:
+ * If 'prefix' is not NULL it will contain the least significant
+ * labels.
+ * If 'suffix' is not NULL it will contain the most significant
+ * labels. dns_name_countlabels(suffix) will be equal to
+ * suffixlabels.
+ *
+ *\li On failure:
+ * Either 'prefix' or 'suffix' is invalidated (depending
+ * on which one the problem was encountered with).
+ *
+ * Returns:
+ *\li #ISC_R_SUCCESS No worries. (This function should always success).
+ */
+
+void
+dns_name_dup(const dns_name_t *source, isc_mem_t *mctx, dns_name_t *target);
+/*%<
+ * Make 'target' a dynamically allocated copy of 'source'.
+ *
+ * Requires:
+ *
+ *\li 'source' is a valid non-empty name.
+ *
+ *\li 'target' is a valid name that is not read-only.
+ *
+ *\li 'mctx' is a valid memory context.
+ */
+
+isc_result_t
+dns_name_dupwithoffsets(const dns_name_t *source, isc_mem_t *mctx,
+ dns_name_t *target);
+/*%<
+ * Make 'target' a read-only dynamically allocated copy of 'source'.
+ * 'target' will also have a dynamically allocated offsets table.
+ *
+ * Requires:
+ *
+ *\li 'source' is a valid non-empty name.
+ *
+ *\li 'target' is a valid name that is not read-only.
+ *
+ *\li 'target' has no offsets table.
+ *
+ *\li 'mctx' is a valid memory context.
+ */
+
+void
+dns_name_free(dns_name_t *name, isc_mem_t *mctx);
+/*%<
+ * Free 'name'.
+ *
+ * Requires:
+ *
+ *\li 'name' is a valid name created previously in 'mctx' by dns_name_dup().
+ *
+ *\li 'mctx' is a valid memory context.
+ *
+ * Ensures:
+ *
+ *\li All dynamic resources used by 'name' are freed and the name is
+ * invalidated.
+ */
+
+isc_result_t
+dns_name_digest(const dns_name_t *name, dns_digestfunc_t digest, void *arg);
+/*%<
+ * Send 'name' in DNSSEC canonical form to 'digest'.
+ *
+ * Requires:
+ *
+ *\li 'name' is a valid name.
+ *
+ *\li 'digest' is a valid dns_digestfunc_t.
+ *
+ * Ensures:
+ *
+ *\li If successful, the DNSSEC canonical form of 'name' will have been
+ * sent to 'digest'.
+ *
+ *\li If digest() returns something other than ISC_R_SUCCESS, that result
+ * will be returned as the result of dns_name_digest().
+ *
+ * Returns:
+ *
+ *\li #ISC_R_SUCCESS
+ *
+ *\li Many other results are possible if not successful.
+ *
+ */
+
+bool
+dns_name_dynamic(const dns_name_t *name);
+/*%<
+ * Returns whether there is dynamic memory associated with this name.
+ *
+ * Requires:
+ *
+ *\li 'name' is a valid name.
+ *
+ * Returns:
+ *
+ *\li 'true' if the name is dynamic otherwise 'false'.
+ */
+
+isc_result_t
+dns_name_print(const dns_name_t *name, FILE *stream);
+/*%<
+ * Print 'name' on 'stream'.
+ *
+ * Requires:
+ *
+ *\li 'name' is a valid name.
+ *
+ *\li 'stream' is a valid stream.
+ *
+ * Returns:
+ *
+ *\li #ISC_R_SUCCESS
+ *
+ *\li Any error that dns_name_totext() can return.
+ */
+
+void
+dns_name_format(const dns_name_t *name, char *cp, unsigned int size);
+/*%<
+ * Format 'name' as text appropriate for use in log messages.
+ *
+ * Store the formatted name at 'cp', writing no more than
+ * 'size' bytes. The resulting string is guaranteed to be
+ * null terminated.
+ *
+ * The formatted name will have a terminating dot only if it is
+ * the root.
+ *
+ * This function cannot fail, instead any errors are indicated
+ * in the returned text.
+ *
+ * Requires:
+ *
+ *\li 'name' is a valid name.
+ *
+ *\li 'cp' points a valid character array of size 'size'.
+ *
+ *\li 'size' > 0.
+ *
+ */
+
+isc_result_t
+dns_name_tostring(const dns_name_t *source, char **target, isc_mem_t *mctx);
+/*%<
+ * Convert 'name' to string format, allocating sufficient memory to
+ * hold it (free with isc_mem_free()).
+ *
+ * Differs from dns_name_format in that it allocates its own memory.
+ *
+ * Requires:
+ *
+ *\li 'name' is a valid name.
+ *\li 'target' is not NULL.
+ *\li '*target' is NULL.
+ *
+ * Returns:
+ *
+ *\li ISC_R_SUCCESS
+ *\li ISC_R_NOMEMORY
+ *
+ *\li Any error that dns_name_totext() can return.
+ */
+
+isc_result_t
+dns_name_fromstring(dns_name_t *target, const char *src, unsigned int options,
+ isc_mem_t *mctx);
+isc_result_t
+dns_name_fromstring2(dns_name_t *target, const char *src,
+ const dns_name_t *origin, unsigned int options,
+ isc_mem_t *mctx);
+/*%<
+ * Convert a string to a name and place it in target, allocating memory
+ * as necessary. 'options' has the same semantics as that of
+ * dns_name_fromtext().
+ *
+ * If 'target' has a buffer then the name will be copied into it rather than
+ * memory being allocated.
+ *
+ * Requires:
+ *
+ * \li 'target' is a valid name that is not read-only.
+ * \li 'src' is not NULL.
+ *
+ * Returns:
+ *
+ *\li #ISC_R_SUCCESS
+ *
+ *\li Any error that dns_name_fromtext() can return.
+ *
+ *\li Any error that dns_name_dup() can return.
+ */
+
+isc_result_t
+dns_name_settotextfilter(dns_name_totextfilter_t *proc);
+/*%<
+ * Set / clear a thread specific function 'proc' to be called at the
+ * end of dns_name_totext().
+ *
+ * Note: Under Windows you need to call "dns_name_settotextfilter(NULL);"
+ * prior to exiting the thread otherwise memory will be leaked.
+ * For other platforms, which are pthreads based, this is still a good
+ * idea but not required.
+ *
+ * Returns
+ *\li #ISC_R_SUCCESS
+ *\li #ISC_R_UNEXPECTED
+ */
+
+#define DNS_NAME_FORMATSIZE (DNS_NAME_MAXTEXT + 1)
+/*%<
+ * Suggested size of buffer passed to dns_name_format().
+ * Includes space for the terminating NULL.
+ */
+
+void
+dns_name_copy(const dns_name_t *source, dns_name_t *dest);
+/*%<
+ * Copies the name in 'source' into 'dest'. The name data is copied to
+ * the dedicated buffer for 'dest'. (If copying to a name that doesn't
+ * have a dedicated buffer, use dns_name_setbuffer() first.)
+ *
+ * Requires:
+ * \li 'source' is a valid name.
+ *
+ * \li 'dest' is an initialized name with a dedicated buffer.
+ */
+
+bool
+dns_name_ishostname(const dns_name_t *name, bool wildcard);
+/*%<
+ * Return if 'name' is a valid hostname. RFC 952 / RFC 1123.
+ * If 'wildcard' is true then allow the first label of name to
+ * be a wildcard.
+ * The root is also accepted.
+ *
+ * Requires:
+ * 'name' to be valid.
+ */
+
+bool
+dns_name_ismailbox(const dns_name_t *name);
+/*%<
+ * Return if 'name' is a valid mailbox. RFC 821.
+ *
+ * Requires:
+ * \li 'name' to be valid.
+ */
+
+bool
+dns_name_internalwildcard(const dns_name_t *name);
+/*%<
+ * Return if 'name' contains a internal wildcard name.
+ *
+ * Requires:
+ * \li 'name' to be valid.
+ */
+
+bool
+dns_name_isdnssd(const dns_name_t *owner);
+/*%<
+ * Determine if the 'owner' is a DNS-SD prefix.
+ */
+
+bool
+dns_name_isrfc1918(const dns_name_t *owner);
+/*%<
+ * Determine if the 'name' is in the RFC 1918 reverse namespace.
+ */
+
+bool
+dns_name_isula(const dns_name_t *owner);
+/*%<
+ * Determine if the 'name' is in the ULA reverse namespace.
+ */
+
+bool
+dns_name_istat(const dns_name_t *name);
+/*
+ * Determine if 'name' is a potential 'trust-anchor-telemetry' name.
+ */
+
+ISC_LANG_ENDDECLS
+
+/*
+ *** High Performance Macros
+ ***/
+
+/*
+ * WARNING: Use of these macros by applications may require recompilation
+ * of the application in some situations where calling the function
+ * would not.
+ *
+ * WARNING: No assertion checking is done for these macros.
+ */
+
+#define DNS_NAME_INIT(n, o) \
+ do { \
+ dns_name_t *_n = (n); \
+ /* memset(_n, 0, sizeof(*_n)); */ \
+ _n->magic = DNS_NAME_MAGIC; \
+ _n->ndata = NULL; \
+ _n->length = 0; \
+ _n->labels = 0; \
+ _n->attributes = 0; \
+ _n->offsets = (o); \
+ _n->buffer = NULL; \
+ ISC_LINK_INIT(_n, link); \
+ ISC_LIST_INIT(_n->list); \
+ } while (0)
+
+#define DNS_NAME_RESET(n) \
+ do { \
+ (n)->ndata = NULL; \
+ (n)->length = 0; \
+ (n)->labels = 0; \
+ (n)->attributes &= ~DNS_NAMEATTR_ABSOLUTE; \
+ if ((n)->buffer != NULL) \
+ isc_buffer_clear((n)->buffer); \
+ } while (0)
+
+#define DNS_NAME_SETBUFFER(n, b) (n)->buffer = (b)
+
+#define DNS_NAME_ISABSOLUTE(n) \
+ (((n)->attributes & DNS_NAMEATTR_ABSOLUTE) != 0 ? true : false)
+
+#define DNS_NAME_COUNTLABELS(n) ((n)->labels)
+
+#define DNS_NAME_TOREGION(n, r) \
+ do { \
+ (r)->base = (n)->ndata; \
+ (r)->length = (n)->length; \
+ } while (0)
+
+#define DNS_NAME_SPLIT(n, l, p, s) \
+ do { \
+ dns_name_t *_n = (n); \
+ dns_name_t *_p = (p); \
+ dns_name_t *_s = (s); \
+ unsigned int _l = (l); \
+ if (_p != NULL) \
+ dns_name_getlabelsequence(_n, 0, _n->labels - _l, _p); \
+ if (_s != NULL) \
+ dns_name_getlabelsequence(_n, _n->labels - _l, _l, \
+ _s); \
+ } while (0)
+
+#ifdef DNS_NAME_USEINLINE
+
+#define dns_name_init(n, o) DNS_NAME_INIT(n, o)
+#define dns_name_reset(n) DNS_NAME_RESET(n)
+#define dns_name_setbuffer(n, b) DNS_NAME_SETBUFFER(n, b)
+#define dns_name_countlabels(n) DNS_NAME_COUNTLABELS(n)
+#define dns_name_isabsolute(n) DNS_NAME_ISABSOLUTE(n)
+#define dns_name_toregion(n, r) DNS_NAME_TOREGION(n, r)
+#define dns_name_split(n, l, p, s) DNS_NAME_SPLIT(n, l, p, s)
+
+#endif /* DNS_NAME_USEINLINE */
diff --git a/lib/dns/include/dns/ncache.h b/lib/dns/include/dns/ncache.h
new file mode 100644
index 0000000..ce62136
--- /dev/null
+++ b/lib/dns/include/dns/ncache.h
@@ -0,0 +1,184 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#pragma once
+
+/*****
+***** Module Info
+*****/
+
+/*! \file dns/ncache.h
+ *\brief
+ * DNS Ncache
+ *
+ * XXX TBS XXX
+ *
+ * MP:
+ *\li The caller must ensure any required synchronization.
+ *
+ * Reliability:
+ *\li No anticipated impact.
+ *
+ * Resources:
+ *\li TBS
+ *
+ * Security:
+ *\li No anticipated impact.
+ *
+ * Standards:
+ *\li RFC2308
+ */
+
+#include <stdbool.h>
+
+#include <isc/lang.h>
+#include <isc/stdtime.h>
+
+#include <dns/types.h>
+
+ISC_LANG_BEGINDECLS
+
+/*%
+ * _OMITDNSSEC:
+ * Omit DNSSEC records when rendering.
+ */
+#define DNS_NCACHETOWIRE_OMITDNSSEC 0x0001
+
+isc_result_t
+dns_ncache_add(dns_message_t *message, dns_db_t *cache, dns_dbnode_t *node,
+ dns_rdatatype_t covers, isc_stdtime_t now, dns_ttl_t minttl,
+ dns_ttl_t maxttl, dns_rdataset_t *addedrdataset);
+isc_result_t
+dns_ncache_addoptout(dns_message_t *message, dns_db_t *cache,
+ dns_dbnode_t *node, dns_rdatatype_t covers,
+ isc_stdtime_t now, dns_ttl_t minttl, dns_ttl_t maxttl,
+ bool optout, dns_rdataset_t *addedrdataset);
+/*%<
+ * Convert the authority data from 'message' into a negative cache
+ * rdataset, and store it in 'cache' at 'node' with a TTL limited to
+ * 'maxttl'.
+ *
+ * \li dns_ncache_add produces a negative cache entry with a trust of no
+ * more than answer
+ * \li dns_ncache_addoptout produces a negative cache entry which will have
+ * a trust of secure if all the records that make up the entry are secure.
+ *
+ * The 'covers' argument is the RR type whose nonexistence we are caching,
+ * or dns_rdatatype_any when caching a NXDOMAIN response.
+ *
+ * 'optout' indicates a DNS_RDATASETATTR_OPTOUT should be set.
+ *
+ * Note:
+ *\li If 'addedrdataset' is not NULL, then it will be attached to the added
+ * rdataset. See dns_db_addrdataset() for more details.
+ *
+ * Requires:
+ *\li 'message' is a valid message with a properly formatting negative cache
+ * authority section.
+ *
+ *\li The requirements of dns_db_addrdataset() apply to 'cache', 'node',
+ * 'now', and 'addedrdataset'.
+ *
+ * Returns:
+ *\li #ISC_R_SUCCESS
+ *\li #ISC_R_NOSPACE
+ *
+ *\li Any result code of dns_db_addrdataset() is a possible result code
+ * of dns_ncache_add().
+ */
+
+isc_result_t
+dns_ncache_towire(dns_rdataset_t *rdataset, dns_compress_t *cctx,
+ isc_buffer_t *target, unsigned int options,
+ unsigned int *countp);
+/*%<
+ * Convert the negative caching rdataset 'rdataset' to wire format,
+ * compressing names as specified in 'cctx', and storing the result in
+ * 'target'. If 'omit_dnssec' is set, DNSSEC records will not
+ * be added to 'target'.
+ *
+ * Notes:
+ *\li The number of RRs added to target will be added to *countp.
+ *
+ * Requires:
+ *\li 'rdataset' is a valid negative caching rdataset.
+ *
+ *\li 'rdataset' is not empty.
+ *
+ *\li 'countp' is a valid pointer.
+ *
+ * Ensures:
+ *\li On a return of ISC_R_SUCCESS, 'target' contains a wire format
+ * for the data contained in 'rdataset'. Any error return leaves
+ * the buffer unchanged.
+ *
+ *\li *countp has been incremented by the number of RRs added to
+ * target.
+ *
+ * Returns:
+ *\li #ISC_R_SUCCESS - all ok
+ *\li #ISC_R_NOSPACE - 'target' doesn't have enough room
+ *
+ *\li Any error returned by dns_rdata_towire(), dns_rdataset_next(),
+ * dns_name_towire().
+ */
+
+isc_result_t
+dns_ncache_getrdataset(dns_rdataset_t *ncacherdataset, dns_name_t *name,
+ dns_rdatatype_t type, dns_rdataset_t *rdataset);
+/*%<
+ * Search the negative caching rdataset for an rdataset with the
+ * specified name and type.
+ *
+ * Requires:
+ *\li 'ncacherdataset' is a valid negative caching rdataset.
+ *
+ *\li 'ncacherdataset' is not empty.
+ *
+ *\li 'name' is a valid name.
+ *
+ *\li 'type' is not SIG, or a meta-RR type.
+ *
+ *\li 'rdataset' is a valid disassociated rdataset.
+ *
+ * Ensures:
+ *\li On a return of ISC_R_SUCCESS, 'rdataset' is bound to the found
+ * rdataset.
+ *
+ * Returns:
+ *\li #ISC_R_SUCCESS - the rdataset was found.
+ *\li #ISC_R_NOTFOUND - the rdataset was not found.
+ *
+ */
+
+isc_result_t
+dns_ncache_getsigrdataset(dns_rdataset_t *ncacherdataset, dns_name_t *name,
+ dns_rdatatype_t covers, dns_rdataset_t *rdataset);
+/*%<
+ * Similar to dns_ncache_getrdataset() but get the rrsig that matches.
+ */
+
+void
+dns_ncache_current(dns_rdataset_t *ncacherdataset, dns_name_t *found,
+ dns_rdataset_t *rdataset);
+
+/*%<
+ * Extract the current rdataset and name from a ncache entry.
+ *
+ * Requires:
+ * \li 'ncacherdataset' to be valid and to be a negative cache entry
+ * \li 'found' to be valid.
+ * \li 'rdataset' to be unassociated.
+ */
+
+ISC_LANG_ENDDECLS
diff --git a/lib/dns/include/dns/nsec.h b/lib/dns/include/dns/nsec.h
new file mode 100644
index 0000000..e68ea35
--- /dev/null
+++ b/lib/dns/include/dns/nsec.h
@@ -0,0 +1,126 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#pragma once
+
+/*! \file dns/nsec.h */
+
+#include <stdbool.h>
+
+#include <isc/lang.h>
+
+#include <dns/diff.h>
+#include <dns/name.h>
+#include <dns/types.h>
+
+#define DNS_NSEC_BUFFERSIZE (DNS_NAME_MAXWIRE + 8192 + 512)
+
+ISC_LANG_BEGINDECLS
+
+isc_result_t
+dns_nsec_buildrdata(dns_db_t *db, dns_dbversion_t *version, dns_dbnode_t *node,
+ const dns_name_t *target, unsigned char *buffer,
+ dns_rdata_t *rdata);
+/*%<
+ * Build the rdata of a NSEC record.
+ *
+ * Requires:
+ *\li buffer Points to a temporary buffer of at least
+ * DNS_NSEC_BUFFERSIZE bytes.
+ *\li rdata Points to an initialized dns_rdata_t.
+ *
+ * Ensures:
+ * \li *rdata Contains a valid NSEC rdata. The 'data' member refers
+ * to 'buffer'.
+ */
+
+isc_result_t
+dns_nsec_build(dns_db_t *db, dns_dbversion_t *version, dns_dbnode_t *node,
+ const dns_name_t *target, dns_ttl_t ttl);
+/*%<
+ * Build a NSEC record and add it to a database.
+ */
+
+bool
+dns_nsec_typepresent(dns_rdata_t *nsec, dns_rdatatype_t type);
+/*%<
+ * Determine if a type is marked as present in an NSEC record.
+ *
+ * Requires:
+ *\li 'nsec' points to a valid rdataset of type NSEC
+ */
+
+isc_result_t
+dns_nsec_nseconly(dns_db_t *db, dns_dbversion_t *version, dns_diff_t *diff,
+ bool *answer);
+/*
+ * Report whether the DNSKEY RRset has a NSEC only algorithm. Unknown
+ * algorithms are assumed to support NSEC3. If DNSKEY is not found,
+ * *answer is set to false, and ISC_R_NOTFOUND is returned.
+ * If 'diff' is provided, check if the NSEC only DNSKEY will be deleted.
+ * If so, and there are no other NSEC only DNSKEYs that will stay in 'db',
+ * consider the DNSKEY RRset to have no NSEC only DNSKEYs.
+ *
+ * Requires:
+ * 'answer' to be non NULL.
+ */
+
+unsigned int
+dns_nsec_compressbitmap(unsigned char *map, const unsigned char *raw,
+ unsigned int max_type);
+/*%<
+ * Convert a raw bitmap into a compressed windowed bit map. 'map' and 'raw'
+ * may overlap.
+ *
+ * Returns the length of the compressed windowed bit map.
+ */
+
+void
+dns_nsec_setbit(unsigned char *array, unsigned int type, unsigned int bit);
+/*%<
+ * Set type bit in raw 'array' to 'bit'.
+ */
+
+bool
+dns_nsec_isset(const unsigned char *array, unsigned int type);
+/*%<
+ * Test if the corresponding 'type' bit is set in 'array'.
+ */
+
+isc_result_t
+dns_nsec_noexistnodata(dns_rdatatype_t type, const dns_name_t *name,
+ const dns_name_t *nsecname, dns_rdataset_t *nsecset,
+ bool *exists, bool *data, dns_name_t *wild,
+ dns_nseclog_t log, void *arg);
+/*%
+ * Return ISC_R_SUCCESS if we can determine that the name doesn't exist
+ * or we can determine whether there is data or not at the name.
+ * If the name does not exist return the wildcard name.
+ *
+ * Return DNS_R_DNAME when the NSEC indicates that name is covered by
+ * a DNAME. 'wild' is not set in this case.
+ *
+ * Return ISC_R_IGNORE when the NSEC is not the appropriate one.
+ */
+
+bool
+dns_nsec_requiredtypespresent(dns_rdataset_t *rdataset);
+/*
+ * Return true if all the NSEC records in rdataset have both
+ * NSEC and RRSIG present.
+ *
+ * Requires:
+ * \li rdataset to be a NSEC rdataset.
+ */
+
+ISC_LANG_ENDDECLS
diff --git a/lib/dns/include/dns/nsec3.h b/lib/dns/include/dns/nsec3.h
new file mode 100644
index 0000000..e4da790
--- /dev/null
+++ b/lib/dns/include/dns/nsec3.h
@@ -0,0 +1,269 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#pragma once
+
+#include <stdbool.h>
+
+#include <isc/iterated_hash.h>
+#include <isc/lang.h>
+#include <isc/log.h>
+
+#include <dns/db.h>
+#include <dns/diff.h>
+#include <dns/name.h>
+#include <dns/rdatastruct.h>
+#include <dns/types.h>
+
+#define DNS_NSEC3_SALTSIZE 255
+#define DNS_NSEC3_MAXITERATIONS 150U
+
+/*
+ * hash = 1, flags =1, iterations = 2, salt length = 1, salt = 255 (max)
+ * hash length = 1, hash = 255 (max), bitmap = 8192 + 512 (max)
+ */
+#define DNS_NSEC3_BUFFERSIZE (6 + 255 + 255 + 8192 + 512)
+/*
+ * hash = 1, flags = 1, iterations = 2, salt length = 1, salt = 255 (max)
+ */
+#define DNS_NSEC3PARAM_BUFFERSIZE (5 + 255)
+
+/*
+ * Test "unknown" algorithm. Is mapped to dns_hash_sha1.
+ */
+#define DNS_NSEC3_UNKNOWNALG ((dns_hash_t)245U)
+
+ISC_LANG_BEGINDECLS
+
+isc_result_t
+dns_nsec3_buildrdata(dns_db_t *db, dns_dbversion_t *version, dns_dbnode_t *node,
+ unsigned int hashalg, unsigned int optin,
+ unsigned int iterations, const unsigned char *salt,
+ size_t salt_length, const unsigned char *nexthash,
+ size_t hash_length, unsigned char *buffer,
+ dns_rdata_t *rdata);
+/*%<
+ * Build the rdata of a NSEC3 record for the data at 'node'.
+ * Note: 'node' is not the node where the NSEC3 record will be stored.
+ *
+ * Requires:
+ * buffer Points to a temporary buffer of at least
+ * DNS_NSEC_BUFFERSIZE bytes.
+ * rdata Points to an initialized dns_rdata_t.
+ *
+ * Ensures:
+ * *rdata Contains a valid NSEC3 rdata. The 'data' member refers
+ * to 'buffer'.
+ */
+
+bool
+dns_nsec3_typepresent(dns_rdata_t *nsec, dns_rdatatype_t type);
+/*%<
+ * Determine if a type is marked as present in an NSEC3 record.
+ *
+ * Requires:
+ * 'nsec' points to a valid rdataset of type NSEC3
+ */
+
+isc_result_t
+dns_nsec3_generate_salt(unsigned char *salt, size_t saltlen);
+/*%<
+ * Generate a salt with the given salt length.
+ */
+
+isc_result_t
+dns_nsec3_hashname(dns_fixedname_t *result,
+ unsigned char rethash[NSEC3_MAX_HASH_LENGTH],
+ size_t *hash_length, const dns_name_t *name,
+ const dns_name_t *origin, dns_hash_t hashalg,
+ unsigned int iterations, const unsigned char *salt,
+ size_t saltlength);
+/*%<
+ * Make a hashed domain name from an unhashed one. If rethash is not NULL
+ * the raw hash is stored there.
+ */
+
+unsigned int
+dns_nsec3_hashlength(dns_hash_t hash);
+/*%<
+ * Return the length of the hash produced by the specified algorithm
+ * or zero when unknown.
+ */
+
+bool
+dns_nsec3_supportedhash(dns_hash_t hash);
+/*%<
+ * Return whether we support this hash algorithm or not.
+ */
+
+isc_result_t
+dns_nsec3_addnsec3(dns_db_t *db, dns_dbversion_t *version,
+ const dns_name_t *name,
+ const dns_rdata_nsec3param_t *nsec3param, dns_ttl_t nsecttl,
+ bool unsecure, dns_diff_t *diff);
+
+isc_result_t
+dns_nsec3_addnsec3s(dns_db_t *db, dns_dbversion_t *version,
+ const dns_name_t *name, dns_ttl_t nsecttl, bool unsecure,
+ dns_diff_t *diff);
+
+isc_result_t
+dns_nsec3_addnsec3sx(dns_db_t *db, dns_dbversion_t *version,
+ const dns_name_t *name, dns_ttl_t nsecttl, bool unsecure,
+ dns_rdatatype_t private, dns_diff_t *diff);
+/*%<
+ * Add NSEC3 records for 'name', recording the change in 'diff'.
+ * Adjust previous NSEC3 records, if any, to reflect the addition.
+ * The existing NSEC3 records are removed.
+ *
+ * dns_nsec3_addnsec3() will only add records to the chain identified by
+ * 'nsec3param'.
+ *
+ * 'unsecure' should be set to reflect if this is a potentially
+ * unsecure delegation (no DS record).
+ *
+ * dns_nsec3_addnsec3s() will examine the NSEC3PARAM RRset to determine which
+ * chains to be updated. NSEC3PARAM records with the DNS_NSEC3FLAG_CREATE
+ * will be preferentially chosen over NSEC3PARAM records without
+ * DNS_NSEC3FLAG_CREATE set. NSEC3PARAM records with DNS_NSEC3FLAG_REMOVE
+ * set will be ignored by dns_nsec3_addnsec3s(). If DNS_NSEC3FLAG_CREATE
+ * is set then the new NSEC3 will have OPTOUT set to match the that in the
+ * NSEC3PARAM record otherwise OPTOUT will be inherited from the previous
+ * record in the chain.
+ *
+ * dns_nsec3_addnsec3sx() is similar to dns_nsec3_addnsec3s() but 'private'
+ * specifies the type of the private rdataset to be checked in addition to
+ * the nsec3param rdataset at the zone apex.
+ *
+ * Requires:
+ * 'db' to be valid.
+ * 'version' to be valid or NULL.
+ * 'name' to be valid.
+ * 'nsec3param' to be valid.
+ * 'diff' to be valid.
+ */
+
+isc_result_t
+dns_nsec3_delnsec3(dns_db_t *db, dns_dbversion_t *version,
+ const dns_name_t *name,
+ const dns_rdata_nsec3param_t *nsec3param, dns_diff_t *diff);
+
+isc_result_t
+dns_nsec3_delnsec3s(dns_db_t *db, dns_dbversion_t *version,
+ const dns_name_t *name, dns_diff_t *diff);
+
+isc_result_t
+dns_nsec3_delnsec3sx(dns_db_t *db, dns_dbversion_t *version,
+ const dns_name_t *name, dns_rdatatype_t private,
+ dns_diff_t *diff);
+/*%<
+ * Remove NSEC3 records for 'name', recording the change in 'diff'.
+ * Adjust previous NSEC3 records, if any, to reflect the removal.
+ *
+ * dns_nsec3_delnsec3() performs the above for the chain identified by
+ * 'nsec3param'.
+ *
+ * dns_nsec3_delnsec3s() examines the NSEC3PARAM RRset in a similar manner
+ * to dns_nsec3_addnsec3s(). Unlike dns_nsec3_addnsec3s() updated NSEC3
+ * records have the OPTOUT flag preserved.
+ *
+ * dns_nsec3_delnsec3sx() is similar to dns_nsec3_delnsec3s() but 'private'
+ * specifies the type of the private rdataset to be checked in addition to
+ * the nsec3param rdataset at the zone apex.
+ *
+ * Requires:
+ * 'db' to be valid.
+ * 'version' to be valid or NULL.
+ * 'name' to be valid.
+ * 'nsec3param' to be valid.
+ * 'diff' to be valid.
+ */
+
+isc_result_t
+dns_nsec3_active(dns_db_t *db, dns_dbversion_t *version, bool complete,
+ bool *answer);
+
+isc_result_t
+dns_nsec3_activex(dns_db_t *db, dns_dbversion_t *version, bool complete,
+ dns_rdatatype_t private, bool *answer);
+/*%<
+ * Check if there are any complete/to be built NSEC3 chains.
+ * If 'complete' is true only complete chains will be recognized.
+ *
+ * dns_nsec3_activex() is similar to dns_nsec3_active() but 'private'
+ * specifies the type of the private rdataset to be checked in addition to
+ * the nsec3param rdataset at the zone apex.
+ *
+ * Requires:
+ * 'db' to be valid.
+ * 'version' to be valid or NULL.
+ * 'answer' to be non NULL.
+ */
+
+unsigned int
+dns_nsec3_maxiterations(void);
+/*%<
+ * Return the maximum permissible number of NSEC3 iterations.
+ */
+
+bool
+dns_nsec3param_fromprivate(dns_rdata_t *src, dns_rdata_t *target,
+ unsigned char *buf, size_t buflen);
+/*%<
+ * Convert a private rdata to a nsec3param rdata.
+ *
+ * Return true if 'src' could be successfully converted.
+ *
+ * 'buf' should be at least DNS_NSEC3PARAM_BUFFERSIZE in size.
+ */
+
+void
+dns_nsec3param_toprivate(dns_rdata_t *src, dns_rdata_t *target,
+ dns_rdatatype_t privatetype, unsigned char *buf,
+ size_t buflen);
+/*%<
+ * Convert a nsec3param rdata to a private rdata.
+ *
+ * 'buf' should be at least src->length + 1 in size.
+ */
+
+isc_result_t
+dns_nsec3param_salttotext(dns_rdata_nsec3param_t *nsec3param, char *dst,
+ size_t dstlen);
+/*%<
+ * Convert the salt of given NSEC3PARAM RDATA into hex-encoded, NULL-terminated
+ * text stored at "dst".
+ *
+ * Requires:
+ *
+ *\li "dst" to have enough space (as indicated by "dstlen") to hold the
+ * resulting text and its NULL-terminating byte.
+ */
+
+isc_result_t
+dns_nsec3param_deletechains(dns_db_t *db, dns_dbversion_t *ver,
+ dns_zone_t *zone, bool nonsec, dns_diff_t *diff);
+
+/*%<
+ * Mark NSEC3PARAM for deletion.
+ */
+
+isc_result_t
+dns_nsec3_noexistnodata(dns_rdatatype_t type, const dns_name_t *name,
+ const dns_name_t *nsec3name, dns_rdataset_t *nsec3set,
+ dns_name_t *zonename, bool *exists, bool *data,
+ bool *optout, bool *unknown, bool *setclosest,
+ bool *setnearest, dns_name_t *closest,
+ dns_name_t *nearest, dns_nseclog_t logit, void *arg);
+
+ISC_LANG_ENDDECLS
diff --git a/lib/dns/include/dns/nta.h b/lib/dns/include/dns/nta.h
new file mode 100644
index 0000000..eb3f0dc
--- /dev/null
+++ b/lib/dns/include/dns/nta.h
@@ -0,0 +1,209 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#pragma once
+
+/*****
+***** Module Info
+*****/
+
+/*! \file
+ * \brief
+ * The NTA module provides services for storing and retrieving negative
+ * trust anchors, and determine whether a given domain is subject to
+ * DNSSEC validation.
+ */
+
+#include <inttypes.h>
+#include <stdbool.h>
+
+#include <isc/buffer.h>
+#include <isc/lang.h>
+#include <isc/magic.h>
+#include <isc/refcount.h>
+#include <isc/rwlock.h>
+#include <isc/stdtime.h>
+#include <isc/task.h>
+#include <isc/timer.h>
+
+#include <dns/rdataset.h>
+#include <dns/resolver.h>
+#include <dns/types.h>
+#include <dns/view.h>
+
+ISC_LANG_BEGINDECLS
+
+struct dns_ntatable {
+ /* Unlocked. */
+ unsigned int magic;
+ dns_view_t *view;
+ isc_rwlock_t rwlock;
+ isc_taskmgr_t *taskmgr;
+ isc_timermgr_t *timermgr;
+ isc_task_t *task;
+ /* Protected by atomics */
+ isc_refcount_t references;
+ /* Locked by rwlock. */
+ dns_rbt_t *table;
+ bool shuttingdown;
+};
+
+#define NTATABLE_MAGIC ISC_MAGIC('N', 'T', 'A', 't')
+#define VALID_NTATABLE(nt) ISC_MAGIC_VALID(nt, NTATABLE_MAGIC)
+
+isc_result_t
+dns_ntatable_create(dns_view_t *view, isc_taskmgr_t *taskmgr,
+ isc_timermgr_t *timermgr, dns_ntatable_t **ntatablep);
+/*%<
+ * Create an NTA table in view 'view'.
+ *
+ * Requires:
+ *
+ *\li 'view' is a valid view.
+ *
+ *\li 'tmgr' is a valid timer manager.
+ *
+ *\li ntatablep != NULL && *ntatablep == NULL
+ *
+ * Ensures:
+ *
+ *\li On success, *ntatablep is a valid, empty NTA table.
+ *
+ * Returns:
+ *
+ *\li ISC_R_SUCCESS
+ *\li Any other result indicates failure.
+ */
+
+void
+dns_ntatable_attach(dns_ntatable_t *source, dns_ntatable_t **targetp);
+/*%<
+ * Attach *targetp to source.
+ *
+ * Requires:
+ *
+ *\li 'source' is a valid ntatable.
+ *
+ *\li 'targetp' points to a NULL dns_ntatable_t *.
+ *
+ * Ensures:
+ *
+ *\li *targetp is attached to source.
+ */
+
+void
+dns_ntatable_detach(dns_ntatable_t **ntatablep);
+/*%<
+ * Detach *ntatablep from its ntatable.
+ *
+ * Requires:
+ *
+ *\li 'ntatablep' points to a valid ntatable.
+ *
+ * Ensures:
+ *
+ *\li *ntatablep is NULL.
+ *
+ *\li If '*ntatablep' is the last reference to the ntatable,
+ * all resources used by the ntatable will be freed
+ */
+
+isc_result_t
+dns_ntatable_add(dns_ntatable_t *ntatable, const dns_name_t *name, bool force,
+ isc_stdtime_t now, uint32_t lifetime);
+/*%<
+ * Add a negative trust anchor to 'ntatable' for name 'name',
+ * which will expire at time 'now' + 'lifetime'. If 'force' is true,
+ * then the NTA will persist for the entire specified lifetime.
+ * If it is false, then the name will be queried periodically and
+ * validation will be attempted to see whether it's still bogus;
+ * if validation is successful, the NTA will be allowed to expire
+ * early and validation below the NTA will resume.
+ *
+ * Notes:
+ *
+ *\li If an NTA already exists in the table, its expiry time
+ * is updated.
+ *
+ * Requires:
+ *
+ *\li 'ntatable' points to a valid ntatable.
+ *
+ *\li 'name' points to a valid name.
+ *
+ * Returns:
+ *
+ *\li ISC_R_SUCCESS
+ *
+ *\li Any other result indicates failure.
+ */
+
+isc_result_t
+dns_ntatable_delete(dns_ntatable_t *ntatable, const dns_name_t *keyname);
+/*%<
+ * Delete node(s) from 'ntatable' matching name 'keyname'
+ *
+ * Requires:
+ *
+ *\li 'ntatable' points to a valid ntatable.
+ *
+ *\li 'name' is not NULL
+ *
+ * Returns:
+ *
+ *\li ISC_R_SUCCESS
+ *
+ *\li Any other result indicates failure.
+ */
+
+bool
+dns_ntatable_covered(dns_ntatable_t *ntatable, isc_stdtime_t now,
+ const dns_name_t *name, const dns_name_t *anchor);
+/*%<
+ * Return true if 'name' is below a non-expired negative trust
+ * anchor which in turn is at or below 'anchor'.
+ *
+ * If 'ntatable' has not been initialized, return false.
+ *
+ * Requires:
+ *
+ *\li 'ntatable' is NULL or is a valid ntatable.
+ *
+ *\li 'name' is a valid absolute name.
+ */
+
+isc_result_t
+dns_ntatable_totext(dns_ntatable_t *ntatable, const char *view,
+ isc_buffer_t **buf);
+/*%<
+ * Dump the NTA table to buffer at 'buf', with view names
+ *
+ * Requires:
+ * \li "ntatable" is a valid table.
+ *
+ * \li "*buf" is a valid buffer.
+ */
+
+isc_result_t
+dns_ntatable_save(dns_ntatable_t *ntatable, FILE *fp);
+/*%<
+ * Save the NTA table to the file opened as 'fp', for later loading.
+ */
+
+void
+dns_ntatable_shutdown(dns_ntatable_t *ntatable);
+/*%<
+ * Cancel future checks to see if NTAs can be removed.
+ */
+
+ISC_LANG_ENDDECLS
diff --git a/lib/dns/include/dns/opcode.h b/lib/dns/include/dns/opcode.h
new file mode 100644
index 0000000..6b8f6f8
--- /dev/null
+++ b/lib/dns/include/dns/opcode.h
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#pragma once
+
+/*! \file dns/opcode.h */
+
+#include <isc/lang.h>
+
+#include <dns/types.h>
+
+ISC_LANG_BEGINDECLS
+
+isc_result_t
+dns_opcode_totext(dns_opcode_t opcode, isc_buffer_t *target);
+/*%<
+ * Put a textual representation of error 'opcode' into 'target'.
+ *
+ * Requires:
+ *\li 'opcode' is a valid opcode.
+ *
+ *\li 'target' is a valid text buffer.
+ *
+ * Ensures:
+ *\li If the result is success:
+ * The used space in 'target' is updated.
+ *
+ * Returns:
+ *\li #ISC_R_SUCCESS on success
+ *\li #ISC_R_NOSPACE target buffer is too small
+ */
+
+ISC_LANG_ENDDECLS
diff --git a/lib/dns/include/dns/order.h b/lib/dns/include/dns/order.h
new file mode 100644
index 0000000..4b0a25f
--- /dev/null
+++ b/lib/dns/include/dns/order.h
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#pragma once
+
+/*! \file dns/order.h */
+
+#include <isc/lang.h>
+#include <isc/types.h>
+
+#include <dns/types.h>
+
+ISC_LANG_BEGINDECLS
+
+isc_result_t
+dns_order_create(isc_mem_t *mctx, dns_order_t **orderp);
+/*%<
+ * Create a order object.
+ *
+ * Requires:
+ * \li 'orderp' to be non NULL and '*orderp == NULL'.
+ *\li 'mctx' to be valid.
+ *
+ * Returns:
+ *\li ISC_R_SUCCESS
+ *\li ISC_R_NOMEMORY
+ */
+
+isc_result_t
+dns_order_add(dns_order_t *order, const dns_name_t *name,
+ dns_rdatatype_t rdtype, dns_rdataclass_t rdclass,
+ unsigned int mode);
+/*%<
+ * Add a entry to the end of the order list.
+ *
+ * Requires:
+ * \li 'order' to be valid.
+ *\li 'name' to be valid.
+ *\li 'mode' to be one of #DNS_RDATASETATTR_RANDOMIZE,
+ * #DNS_RDATASETATTR_FIXEDORDER or zero (#DNS_RDATASETATTR_CYCLIC).
+ *
+ * Returns:
+ *\li #ISC_R_SUCCESS
+ *\li #ISC_R_NOMEMORY
+ */
+
+unsigned int
+dns_order_find(dns_order_t *order, const dns_name_t *name,
+ dns_rdatatype_t rdtype, dns_rdataclass_t rdclass);
+/*%<
+ * Find the first matching entry on the list.
+ *
+ * Requires:
+ *\li 'order' to be valid.
+ *\li 'name' to be valid.
+ *
+ * Returns the mode set by dns_order_add() or zero.
+ */
+
+void
+dns_order_attach(dns_order_t *source, dns_order_t **target);
+/*%<
+ * Attach to the 'source' object.
+ *
+ * Requires:
+ * \li 'source' to be valid.
+ *\li 'target' to be non NULL and '*target == NULL'.
+ */
+
+void
+dns_order_detach(dns_order_t **orderp);
+/*%<
+ * Detach from the object. Clean up if last this was the last
+ * reference.
+ *
+ * Requires:
+ *\li '*orderp' to be valid.
+ */
+
+ISC_LANG_ENDDECLS
diff --git a/lib/dns/include/dns/peer.h b/lib/dns/include/dns/peer.h
new file mode 100644
index 0000000..101e6f2
--- /dev/null
+++ b/lib/dns/include/dns/peer.h
@@ -0,0 +1,208 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#pragma once
+
+/*****
+***** Module Info
+*****/
+
+/*! \file dns/peer.h
+ * \brief
+ * Data structures for peers (e.g. a 'server' config file statement)
+ */
+
+/***
+ *** Imports
+ ***/
+
+#include <inttypes.h>
+#include <stdbool.h>
+
+#include <isc/lang.h>
+#include <isc/magic.h>
+#include <isc/netaddr.h>
+#include <isc/refcount.h>
+
+#include <dns/types.h>
+
+#define DNS_PEERLIST_MAGIC ISC_MAGIC('s', 'e', 'R', 'L')
+#define DNS_PEER_MAGIC ISC_MAGIC('S', 'E', 'r', 'v')
+
+#define DNS_PEERLIST_VALID(ptr) ISC_MAGIC_VALID(ptr, DNS_PEERLIST_MAGIC)
+#define DNS_PEER_VALID(ptr) ISC_MAGIC_VALID(ptr, DNS_PEER_MAGIC)
+
+/***
+ *** Functions
+ ***/
+
+ISC_LANG_BEGINDECLS
+
+isc_result_t
+dns_peerlist_new(isc_mem_t *mem, dns_peerlist_t **list);
+
+void
+dns_peerlist_attach(dns_peerlist_t *source, dns_peerlist_t **target);
+
+void
+dns_peerlist_detach(dns_peerlist_t **list);
+
+/*
+ * After return caller still holds a reference to peer.
+ */
+void
+dns_peerlist_addpeer(dns_peerlist_t *peers, dns_peer_t *peer);
+
+/*
+ * Ditto. */
+isc_result_t
+dns_peerlist_peerbyaddr(dns_peerlist_t *peers, const isc_netaddr_t *addr,
+ dns_peer_t **retval);
+
+/*
+ * What he said.
+ */
+isc_result_t
+dns_peerlist_currpeer(dns_peerlist_t *peers, dns_peer_t **retval);
+
+isc_result_t
+dns_peer_new(isc_mem_t *mem, const isc_netaddr_t *ipaddr, dns_peer_t **peer);
+
+isc_result_t
+dns_peer_newprefix(isc_mem_t *mem, const isc_netaddr_t *ipaddr,
+ unsigned int prefixlen, dns_peer_t **peer);
+
+void
+dns_peer_attach(dns_peer_t *source, dns_peer_t **target);
+
+void
+dns_peer_detach(dns_peer_t **list);
+
+isc_result_t
+dns_peer_setbogus(dns_peer_t *peer, bool newval);
+
+isc_result_t
+dns_peer_getbogus(dns_peer_t *peer, bool *retval);
+
+isc_result_t
+dns_peer_setrequestixfr(dns_peer_t *peer, bool newval);
+
+isc_result_t
+dns_peer_getrequestixfr(dns_peer_t *peer, bool *retval);
+
+isc_result_t
+dns_peer_setprovideixfr(dns_peer_t *peer, bool newval);
+
+isc_result_t
+dns_peer_getprovideixfr(dns_peer_t *peer, bool *retval);
+
+isc_result_t
+dns_peer_setrequestnsid(dns_peer_t *peer, bool newval);
+
+isc_result_t
+dns_peer_getrequestnsid(dns_peer_t *peer, bool *retval);
+
+isc_result_t
+dns_peer_setsendcookie(dns_peer_t *peer, bool newval);
+
+isc_result_t
+dns_peer_getsendcookie(dns_peer_t *peer, bool *retval);
+
+isc_result_t
+dns_peer_setrequestexpire(dns_peer_t *peer, bool newval);
+
+isc_result_t
+dns_peer_getrequestexpire(dns_peer_t *peer, bool *retval);
+
+isc_result_t
+dns_peer_setsupportedns(dns_peer_t *peer, bool newval);
+
+isc_result_t
+dns_peer_getforcetcp(dns_peer_t *peer, bool *retval);
+
+isc_result_t
+dns_peer_setforcetcp(dns_peer_t *peer, bool newval);
+
+isc_result_t
+dns_peer_gettcpkeepalive(dns_peer_t *peer, bool *retval);
+
+isc_result_t
+dns_peer_settcpkeepalive(dns_peer_t *peer, bool newval);
+
+isc_result_t
+dns_peer_getsupportedns(dns_peer_t *peer, bool *retval);
+
+isc_result_t
+dns_peer_settransfers(dns_peer_t *peer, uint32_t newval);
+
+isc_result_t
+dns_peer_gettransfers(dns_peer_t *peer, uint32_t *retval);
+
+isc_result_t
+dns_peer_settransferformat(dns_peer_t *peer, dns_transfer_format_t newval);
+
+isc_result_t
+dns_peer_gettransferformat(dns_peer_t *peer, dns_transfer_format_t *retval);
+
+isc_result_t
+dns_peer_setkeybycharp(dns_peer_t *peer, const char *keyval);
+
+isc_result_t
+dns_peer_getkey(dns_peer_t *peer, dns_name_t **retval);
+
+isc_result_t
+dns_peer_setkey(dns_peer_t *peer, dns_name_t **keyval);
+
+isc_result_t
+dns_peer_settransfersource(dns_peer_t *peer,
+ const isc_sockaddr_t *transfer_source);
+
+isc_result_t
+dns_peer_gettransfersource(dns_peer_t *peer, isc_sockaddr_t *transfer_source);
+
+isc_result_t
+dns_peer_setudpsize(dns_peer_t *peer, uint16_t udpsize);
+
+isc_result_t
+dns_peer_getudpsize(dns_peer_t *peer, uint16_t *udpsize);
+
+isc_result_t
+dns_peer_setmaxudp(dns_peer_t *peer, uint16_t maxudp);
+
+isc_result_t
+dns_peer_getmaxudp(dns_peer_t *peer, uint16_t *maxudp);
+
+isc_result_t
+dns_peer_setpadding(dns_peer_t *peer, uint16_t padding);
+
+isc_result_t
+dns_peer_getpadding(dns_peer_t *peer, uint16_t *padding);
+
+isc_result_t
+dns_peer_setnotifysource(dns_peer_t *peer, const isc_sockaddr_t *notify_source);
+
+isc_result_t
+dns_peer_getnotifysource(dns_peer_t *peer, isc_sockaddr_t *notify_source);
+
+isc_result_t
+dns_peer_setquerysource(dns_peer_t *peer, const isc_sockaddr_t *query_source);
+
+isc_result_t
+dns_peer_getquerysource(dns_peer_t *peer, isc_sockaddr_t *query_source);
+
+isc_result_t
+dns_peer_setednsversion(dns_peer_t *peer, uint8_t ednsversion);
+
+isc_result_t
+dns_peer_getednsversion(dns_peer_t *peer, uint8_t *ednsversion);
+ISC_LANG_ENDDECLS
diff --git a/lib/dns/include/dns/private.h b/lib/dns/include/dns/private.h
new file mode 100644
index 0000000..675510c
--- /dev/null
+++ b/lib/dns/include/dns/private.h
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#include <stdbool.h>
+
+#include <isc/lang.h>
+#include <isc/types.h>
+
+#include <dns/db.h>
+#include <dns/types.h>
+
+#pragma once
+
+ISC_LANG_BEGINDECLS
+
+isc_result_t
+dns_private_chains(dns_db_t *db, dns_dbversion_t *ver,
+ dns_rdatatype_t privatetype, bool *build_nsec,
+ bool *build_nsec3);
+/*%<
+ * Examine the NSEC, NSEC3PARAM and privatetype RRsets at the apex of the
+ * database to determine which of NSEC or NSEC3 chains we are currently
+ * maintaining. In normal operations only one of NSEC or NSEC3 is being
+ * maintained but when we are transitiong between NSEC and NSEC3 we need
+ * to update both sets of chains. If 'privatetype' is zero then the
+ * privatetype RRset will not be examined.
+ *
+ * Requires:
+ * \li 'db' is valid.
+ * \li 'version' is valid or NULL.
+ * \li 'build_nsec' is a pointer to a bool or NULL.
+ * \li 'build_nsec3' is a pointer to a bool or NULL.
+ *
+ * Returns:
+ * \li ISC_R_SUCCESS, 'build_nsec' and 'build_nsec3' will be valid.
+ * \li other on error
+ */
+
+isc_result_t
+dns_private_totext(dns_rdata_t *privaterdata, isc_buffer_t *buffer);
+/*%<
+ * Convert a private-type RR 'privaterdata' to human-readable form,
+ * and place the result in 'buffer'. The text should indicate
+ * which action the private-type record specifies and whether the
+ * action has been completed.
+ *
+ * Requires:
+ * \li 'privaterdata' is a valid rdata containing at least five bytes
+ * \li 'buffer' is a valid buffer
+ *
+ * Returns:
+ * \li ISC_R_SUCCESS
+ * \li other on error
+ */
+
+ISC_LANG_ENDDECLS
diff --git a/lib/dns/include/dns/rbt.h b/lib/dns/include/dns/rbt.h
new file mode 100644
index 0000000..3b62e12
--- /dev/null
+++ b/lib/dns/include/dns/rbt.h
@@ -0,0 +1,995 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#pragma once
+
+/*! \file dns/rbt.h */
+
+#include <inttypes.h>
+#include <stdbool.h>
+
+#include <isc/assertions.h>
+#include <isc/crc64.h>
+#include <isc/lang.h>
+#include <isc/magic.h>
+#include <isc/refcount.h>
+
+#include <dns/types.h>
+
+ISC_LANG_BEGINDECLS
+
+/*@{*/
+/*%
+ * Option values for dns_rbt_findnode() and dns_rbt_findname().
+ * These are used to form a bitmask.
+ */
+#define DNS_RBTFIND_NOOPTIONS 0x00
+#define DNS_RBTFIND_EMPTYDATA 0x01
+#define DNS_RBTFIND_NOEXACT 0x02
+#define DNS_RBTFIND_NOPREDECESSOR 0x04
+/*@}*/
+
+#define DNS_RBT_USEMAGIC 1
+
+#define DNS_RBT_LOCKLENGTH (sizeof(((dns_rbtnode_t *)0)->locknum) * 8)
+
+#define DNS_RBTNODE_MAGIC ISC_MAGIC('R', 'B', 'N', 'O')
+#if DNS_RBT_USEMAGIC
+#define DNS_RBTNODE_VALID(n) ISC_MAGIC_VALID(n, DNS_RBTNODE_MAGIC)
+#else /* if DNS_RBT_USEMAGIC */
+#define DNS_RBTNODE_VALID(n) true
+#endif /* if DNS_RBT_USEMAGIC */
+
+/*%
+ * This is the structure that is used for each node in the red/black
+ * tree of trees. NOTE WELL: the implementation manages this as a variable
+ * length structure, with the actual wire-format name and other data
+ * appended to this structure. Allocating a contiguous block of memory for
+ * multiple dns_rbtnode structures will not work.
+ */
+typedef struct dns_rbtnode dns_rbtnode_t;
+enum {
+ DNS_RBT_NSEC_NORMAL = 0, /* in main tree */
+ DNS_RBT_NSEC_HAS_NSEC = 1, /* also has node in nsec tree */
+ DNS_RBT_NSEC_NSEC = 2, /* in nsec tree */
+ DNS_RBT_NSEC_NSEC3 = 3 /* in nsec3 tree */
+};
+struct dns_rbtnode {
+#if DNS_RBT_USEMAGIC
+ unsigned int magic;
+#endif /* if DNS_RBT_USEMAGIC */
+ /*@{*/
+ /*!
+ * The following bitfields add up to a total bitwidth of 32.
+ * The range of values necessary for each item is indicated,
+ * but in the case of "attributes" the field is wider to accommodate
+ * possible future expansion.
+ *
+ * In each case below the "range" indicated is what's _necessary_ for
+ * the bitfield to hold, not what it actually _can_ hold.
+ *
+ * Note: Tree lock must be held before modifying these
+ * bit-fields.
+ *
+ * Note: The two "unsigned int :0;" unnamed bitfields on either
+ * side of the bitfields below are scaffolding that border the
+ * set of bitfields which are accessed after acquiring the tree
+ * lock. Please don't insert any other bitfield members between
+ * the unnamed bitfields unless they should also be accessed
+ * after acquiring the tree lock.
+ */
+ unsigned int : 0; /* start of bitfields c/o tree lock */
+ unsigned int is_root : 1; /*%< range is 0..1 */
+ unsigned int color : 1; /*%< range is 0..1 */
+ unsigned int find_callback : 1; /*%< range is 0..1 */
+ unsigned int attributes : 3; /*%< range is 0..2 */
+ unsigned int nsec : 2; /*%< range is 0..3 */
+ unsigned int namelen : 8; /*%< range is 1..255 */
+ unsigned int offsetlen : 8; /*%< range is 1..128 */
+ unsigned int oldnamelen : 8; /*%< range is 1..255 */
+ /*@}*/
+
+ /* node needs to be cleaned from rpz */
+ unsigned int rpz : 1;
+ unsigned int : 0; /* end of bitfields c/o tree lock */
+
+ /*%
+ * These are needed for hashing. The 'uppernode' points to the
+ * node's superdomain node in the parent subtree, so that it can
+ * be reached from a child that was found by a hash lookup.
+ */
+ unsigned int hashval;
+ dns_rbtnode_t *uppernode;
+ dns_rbtnode_t *hashnext;
+
+ dns_rbtnode_t *parent;
+ dns_rbtnode_t *left;
+ dns_rbtnode_t *right;
+ dns_rbtnode_t *down;
+
+ /*%
+ * Used for LRU cache. This linked list is used to mark nodes which
+ * have no data any longer, but we cannot unlink at that exact moment
+ * because we did not or could not obtain a write lock on the tree.
+ */
+ ISC_LINK(dns_rbtnode_t) deadlink;
+
+ /*@{*/
+ /*!
+ * These values are used in the RBT DB implementation. The appropriate
+ * node lock must be held before accessing them.
+ *
+ * Note: The two "unsigned int :0;" unnamed bitfields on either
+ * side of the bitfields below are scaffolding that border the
+ * set of bitfields which are accessed after acquiring the node
+ * lock. Please don't insert any other bitfield members between
+ * the unnamed bitfields unless they should also be accessed
+ * after acquiring the node lock.
+ *
+ * NOTE: Do not merge these fields into bitfields above, as
+ * they'll all be put in the same qword that could be accessed
+ * without the node lock as it shares the qword with other
+ * members. Leave these members here so that they occupy a
+ * separate region of memory.
+ */
+ void *data;
+ uint8_t : 0; /* start of bitfields c/o node lock */
+ uint8_t dirty : 1;
+ uint8_t wild : 1;
+ uint8_t : 0; /* end of bitfields c/o node lock */
+ uint16_t locknum; /* note that this is not in the bitfield */
+ isc_refcount_t references;
+ /*@}*/
+};
+
+typedef isc_result_t (*dns_rbtfindcallback_t)(dns_rbtnode_t *node,
+ dns_name_t *name,
+ void *callback_arg);
+
+typedef isc_result_t (*dns_rbtdatawriter_t)(FILE *file, unsigned char *data,
+ void *arg, uint64_t *crc);
+
+typedef isc_result_t (*dns_rbtdatafixer_t)(dns_rbtnode_t *rbtnode, void *base,
+ size_t offset, void *arg,
+ uint64_t *crc);
+
+typedef void (*dns_rbtdeleter_t)(void *, void *);
+
+/*****
+***** Chain Info
+*****/
+
+/*!
+ * A chain is used to keep track of the sequence of nodes to reach any given
+ * node from the root of the tree. Originally nodes did not have parent
+ * pointers in them (for memory usage reasons) so there was no way to find
+ * the path back to the root from any given node. Now that nodes have parent
+ * pointers, chains might be going away in a future release, though the
+ * movement functionality would remain.
+ *
+ * Chains may be used to iterate over a tree of trees. After setting up the
+ * chain's structure using dns_rbtnodechain_init(), it needs to be initialized
+ * to point to the lexically first or lexically last node in the tree of trees
+ * using dns_rbtnodechain_first() or dns_rbtnodechain_last(), respectively.
+ * Calling dns_rbtnodechain_next() or dns_rbtnodechain_prev() then moves the
+ * chain over to the next or previous node, respectively.
+ *
+ * In any event, parent information, whether via parent pointers or chains, is
+ * necessary information for iterating through the tree or for basic internal
+ * tree maintenance issues (ie, the rotations that are done to rebalance the
+ * tree when a node is added). The obvious implication of this is that for a
+ * chain to remain valid, the tree has to be locked down against writes for the
+ * duration of the useful life of the chain, because additions or removals can
+ * change the path from the root to the node the chain has targeted.
+ *
+ * The dns_rbtnodechain_ functions _first, _last, _prev and _next all take
+ * dns_name_t parameters for the name and the origin, which can be NULL. If
+ * non-NULL, 'name' will end up pointing to the name data and offsets that are
+ * stored at the node (and thus it will be read-only), so it should be a
+ * regular dns_name_t that has been initialized with dns_name_init. When
+ * 'origin' is non-NULL, it will get the name of the origin stored in it, so it
+ * needs to have its own buffer space and offsets, which is most easily
+ * accomplished with a dns_fixedname_t. It is _not_ necessary to reinitialize
+ * either 'name' or 'origin' between calls to the chain functions.
+ *
+ * NOTE WELL: even though the name data at the root of the tree of trees will
+ * be absolute (typically just "."), it will will be made into a relative name
+ * with an origin of "." -- an empty name when the node is ".". This is
+ * because a common on operation on 'name' and 'origin' is to use
+ * dns_name_concatenate() on them to generate the complete name. An empty name
+ * can be detected when dns_name_countlabels == 0, and is printed by
+ * dns_name_totext()/dns_name_format() as "@", consistent with RFC1035's
+ * definition of "@" as the current origin.
+ *
+ * dns_rbtnodechain_current is similar to the _first, _last, _prev and _next
+ * functions but additionally can provide the node to which the chain points.
+ */
+
+/*%
+ * The number of level blocks to allocate at a time. Currently the maximum
+ * number of levels is allocated directly in the structure, but future
+ * revisions of this code might have a static initial block with dynamic
+ * growth. Allocating space for 256 levels when the tree is almost never that
+ * deep is wasteful, but it's not clear that it matters, since the waste is
+ * only 2MB for 1000 concurrently active chains on a system with 64-bit
+ * pointers.
+ */
+#define DNS_RBT_LEVELBLOCK 254
+
+typedef struct dns_rbtnodechain {
+ unsigned int magic;
+ /*%
+ * The terminal node of the chain. It is not in levels[].
+ * This is ostensibly private ... but in a pinch it could be
+ * used tell that the chain points nowhere without needing to
+ * call dns_rbtnodechain_current().
+ */
+ dns_rbtnode_t *end;
+ /*%
+ * The maximum number of labels in a name is 128; bitstrings mean
+ * a conceptually very large number (which I have not bothered to
+ * compute) of logical levels because splitting can potentially occur
+ * at each bit. However, DNSSEC restricts the number of "logical"
+ * labels in a name to 255, meaning only 254 pointers are needed
+ * in the worst case.
+ */
+ dns_rbtnode_t *levels[DNS_RBT_LEVELBLOCK];
+ /*%
+ * level_count indicates how deep the chain points into the
+ * tree of trees, and is the index into the levels[] array.
+ * Thus, levels[level_count - 1] is the last level node stored.
+ * A chain that points to the top level of the tree of trees has
+ * a level_count of 0, the first level has a level_count of 1, and
+ * so on.
+ */
+ unsigned int level_count;
+ /*%
+ * level_matches tells how many levels matched above the node
+ * returned by dns_rbt_findnode(). A match (partial or exact) found
+ * in the first level thus results in level_matches being set to 1.
+ * This is used by the rbtdb to set the start point for a recursive
+ * search of superdomains until the RR it is looking for is found.
+ */
+ unsigned int level_matches;
+} dns_rbtnodechain_t;
+
+/*****
+***** Public interfaces.
+*****/
+isc_result_t
+dns_rbt_create(isc_mem_t *mctx, dns_rbtdeleter_t deleter, void *deleter_arg,
+ dns_rbt_t **rbtp);
+/*%<
+ * Initialize a red-black tree of trees.
+ *
+ * Notes:
+ *\li The deleter argument, if non-null, points to a function that is
+ * responsible for cleaning up any memory associated with the data
+ * pointer of a node when the node is deleted. It is passed the
+ * deleted node's data pointer as its first argument and deleter_arg
+ * as its second argument.
+ *
+ * Requires:
+ * \li mctx is a pointer to a valid memory context.
+ *\li rbtp != NULL && *rbtp == NULL
+ *\li arg == NULL iff deleter == NULL
+ *
+ * Ensures:
+ *\li If result is ISC_R_SUCCESS:
+ * *rbtp points to a valid red-black tree manager
+ *
+ *\li If result is failure:
+ * *rbtp does not point to a valid red-black tree manager.
+ *
+ * Returns:
+ *\li #ISC_R_SUCCESS Success
+ *\li #ISC_R_NOMEMORY Resource limit: Out of Memory
+ */
+
+isc_result_t
+dns_rbt_addname(dns_rbt_t *rbt, const dns_name_t *name, void *data);
+/*%<
+ * Add 'name' to the tree of trees, associated with 'data'.
+ *
+ * Notes:
+ *\li 'data' is never required to be non-NULL, but specifying it
+ * when the name is added is faster than searching for 'name'
+ * again and then setting the data pointer. The lack of a data pointer
+ * for a node also has other ramifications regarding whether
+ * dns_rbt_findname considers a node to exist, or dns_rbt_deletename
+ * joins nodes.
+ *
+ * Requires:
+ *\li rbt is a valid rbt manager.
+ *\li dns_name_isabsolute(name) == TRUE
+ *
+ * Ensures:
+ *\li 'name' is not altered in any way.
+ *
+ *\li Any external references to nodes in the tree are unaffected by
+ * node splits that are necessary to insert the new name.
+ *
+ *\li If result is #ISC_R_SUCCESS:
+ * 'name' is findable in the red/black tree of trees in O(log N).
+ * The data pointer of the node for 'name' is set to 'data'.
+ *
+ *\li If result is #ISC_R_EXISTS or #ISC_R_NOSPACE:
+ * The tree of trees is unaltered.
+ *
+ *\li If result is #ISC_R_NOMEMORY:
+ * No guarantees.
+ *
+ * Returns:
+ *\li #ISC_R_SUCCESS Success
+ *\li #ISC_R_EXISTS The name already exists with associated data.
+ *\li #ISC_R_NOSPACE The name had more logical labels than are allowed.
+ *\li #ISC_R_NOMEMORY Resource Limit: Out of Memory
+ */
+
+isc_result_t
+dns_rbt_addnode(dns_rbt_t *rbt, const dns_name_t *name, dns_rbtnode_t **nodep);
+
+/*%<
+ * Just like dns_rbt_addname, but returns the address of the node.
+ *
+ * Requires:
+ *\li rbt is a valid rbt structure.
+ *\li dns_name_isabsolute(name) == TRUE
+ *\li nodep != NULL && *nodep == NULL
+ *
+ * Ensures:
+ *\li 'name' is not altered in any way.
+ *
+ *\li Any external references to nodes in the tree are unaffected by
+ * node splits that are necessary to insert the new name.
+ *
+ *\li If result is ISC_R_SUCCESS:
+ * 'name' is findable in the red/black tree of trees in O(log N).
+ * *nodep is the node that was added for 'name'.
+ *
+ *\li If result is ISC_R_EXISTS:
+ * The tree of trees is unaltered.
+ * *nodep is the existing node for 'name'.
+ *
+ *\li If result is ISC_R_NOMEMORY:
+ * No guarantees.
+ *
+ * Returns:
+ *\li #ISC_R_SUCCESS Success
+ *\li #ISC_R_EXISTS The name already exists, possibly without data.
+ *\li #ISC_R_NOMEMORY Resource Limit: Out of Memory
+ */
+
+isc_result_t
+dns_rbt_findname(dns_rbt_t *rbt, const dns_name_t *name, unsigned int options,
+ dns_name_t *foundname, void **data);
+/*%<
+ * Get the data pointer associated with 'name'.
+ *
+ * Notes:
+ *\li When #DNS_RBTFIND_NOEXACT is set, the closest matching superdomain is
+ * returned (also subject to #DNS_RBTFIND_EMPTYDATA), even when there is
+ * an exact match in the tree.
+ *
+ *\li A node that has no data is considered not to exist for this function,
+ * unless the #DNS_RBTFIND_EMPTYDATA option is set.
+ *
+ * Requires:
+ *\li rbt is a valid rbt manager.
+ *\li dns_name_isabsolute(name) == TRUE
+ *\li data != NULL && *data == NULL
+ *
+ * Ensures:
+ *\li 'name' and the tree are not altered in any way.
+ *
+ *\li If result is ISC_R_SUCCESS:
+ * *data is the data associated with 'name'.
+ *
+ *\li If result is DNS_R_PARTIALMATCH:
+ * *data is the data associated with the deepest superdomain
+ * of 'name' which has data.
+ *
+ *\li If result is ISC_R_NOTFOUND:
+ * Neither the name nor a superdomain was found with data.
+ *
+ * Returns:
+ *\li #ISC_R_SUCCESS Success
+ *\li #DNS_R_PARTIALMATCH Superdomain found with data
+ *\li #ISC_R_NOTFOUND No match
+ *\li #ISC_R_NOSPACE Concatenating nodes to form foundname failed
+ */
+
+isc_result_t
+dns_rbt_findnode(dns_rbt_t *rbt, const dns_name_t *name, dns_name_t *foundname,
+ dns_rbtnode_t **node, dns_rbtnodechain_t *chain,
+ unsigned int options, dns_rbtfindcallback_t callback,
+ void *callback_arg);
+/*%<
+ * Find the node for 'name'.
+ *
+ * Notes:
+ *\li A node that has no data is considered not to exist for this function,
+ * unless the DNS_RBTFIND_EMPTYDATA option is set. This applies to both
+ * exact matches and partial matches.
+ *
+ *\li If the chain parameter is non-NULL, then the path through the tree
+ * to the DNSSEC predecessor of the searched for name is maintained,
+ * unless the DNS_RBTFIND_NOPREDECESSOR or DNS_RBTFIND_NOEXACT option
+ * is used. (For more details on those options, see below.)
+ *
+ *\li If there is no predecessor, then the chain will point to nowhere, as
+ * indicated by chain->end being NULL or dns_rbtnodechain_current
+ * returning ISC_R_NOTFOUND. Note that in a normal Internet DNS RBT
+ * there will always be a predecessor for all names except the root
+ * name, because '.' will exist and '.' is the predecessor of
+ * everything. But you can certainly construct a trivial tree and a
+ * search for it that has no predecessor.
+ *
+ *\li Within the chain structure, the 'levels' member of the structure holds
+ * the root node of each level except the first.
+ *
+ *\li The 'level_count' of the chain indicates how deep the chain to the
+ * predecessor name is, as an index into the 'levels[]' array. It does
+ * not count name elements, per se, but only levels of the tree of trees,
+ * the distinction arising because multiple labels from a name can be
+ * stored on only one level. It is also does not include the level
+ * that has the node, since that level is not stored in levels[].
+ *
+ *\li The chain's 'level_matches' is not directly related to the predecessor.
+ * It is the number of levels above the level of the found 'node',
+ * regardless of whether it was a partial match or exact match. When
+ * the node is found in the top level tree, or no node is found at all,
+ * level_matches is 0.
+ *
+ *\li When DNS_RBTFIND_NOEXACT is set, the closest matching superdomain is
+ * returned (also subject to DNS_RBTFIND_EMPTYDATA), even when
+ * there is an exact match in the tree. In this case, the chain
+ * will not point to the DNSSEC predecessor, but will instead point
+ * to the exact match, if there was any. Thus the preceding paragraphs
+ * should have "exact match" substituted for "predecessor" to describe
+ * how the various elements of the chain are set. This was done to
+ * ensure that the chain's state was sane, and to prevent problems that
+ * occurred when running the predecessor location code under conditions
+ * it was not designed for. It is not clear *where* the chain should
+ * point when DNS_RBTFIND_NOEXACT is set, so if you end up using a chain
+ * with this option because you want a particular node, let us know
+ * where you want the chain pointed, so this can be made more firm.
+ *
+ * Requires:
+ *\li rbt is a valid rbt manager.
+ *\li dns_name_isabsolute(name) == TRUE.
+ *\li node != NULL && *node == NULL.
+ *\li #DNS_RBTFIND_NOEXACT and DNS_RBTFIND_NOPREDECESSOR are mutually
+ * exclusive.
+ *
+ * Ensures:
+ *\li 'name' and the tree are not altered in any way.
+ *
+ *\li If result is ISC_R_SUCCESS:
+ *\verbatim
+ * *node is the terminal node for 'name'.
+ *
+ * 'foundname' and 'name' represent the same name (though not
+ * the same memory).
+ *
+ * 'chain' points to the DNSSEC predecessor, if any, of 'name'.
+ *
+ * chain->level_matches and chain->level_count are equal.
+ *\endverbatim
+ *
+ * If result is DNS_R_PARTIALMATCH:
+ *\verbatim
+ * *node is the data associated with the deepest superdomain
+ * of 'name' which has data.
+ *
+ * 'foundname' is the name of deepest superdomain (which has
+ * data, unless the DNS_RBTFIND_EMPTYDATA option is set).
+ *
+ * 'chain' points to the DNSSEC predecessor, if any, of 'name'.
+ *\endverbatim
+ *
+ *\li If result is ISC_R_NOTFOUND:
+ *\verbatim
+ * Neither the name nor a superdomain was found. *node is NULL.
+ *
+ * 'chain' points to the DNSSEC predecessor, if any, of 'name'.
+ *
+ * chain->level_matches is 0.
+ *\endverbatim
+ *
+ * Returns:
+ *\li #ISC_R_SUCCESS Success
+ *\li #DNS_R_PARTIALMATCH Superdomain found with data
+ *\li #ISC_R_NOTFOUND No match, or superdomain with no data
+ *\li #ISC_R_NOSPACE Concatenating nodes to form foundname failed
+ */
+
+isc_result_t
+dns_rbt_deletename(dns_rbt_t *rbt, const dns_name_t *name, bool recurse);
+/*%<
+ * Delete 'name' from the tree of trees.
+ *
+ * Notes:
+ *\li When 'name' is removed, if recurse is true then all of its
+ * subnames are removed too.
+ *
+ * Requires:
+ *\li rbt is a valid rbt manager.
+ *\li dns_name_isabsolute(name) == TRUE
+ *
+ * Ensures:
+ *\li 'name' is not altered in any way.
+ *
+ *\li Does NOT ensure that any external references to nodes in the tree
+ * are unaffected by node joins.
+ *
+ *\li If result is ISC_R_SUCCESS:
+ * 'name' does not appear in the tree with data; however,
+ * the node for the name might still exist which can be
+ * found with dns_rbt_findnode (but not dns_rbt_findname).
+ *
+ *\li If result is ISC_R_NOTFOUND:
+ * 'name' does not appear in the tree with data, because
+ * it did not appear in the tree before the function was called.
+ *
+ *\li If result is something else:
+ * See result codes for dns_rbt_findnode (if it fails, the
+ * node is not deleted) or dns_rbt_deletenode (if it fails,
+ * the node is deleted, but the tree is not optimized when
+ * it could have been).
+ *
+ * Returns:
+ *\li #ISC_R_SUCCESS Success
+ *\li #ISC_R_NOTFOUND No match
+ *\li something_else Any return code from dns_rbt_findnode except
+ * DNS_R_PARTIALMATCH (which causes ISC_R_NOTFOUND
+ * to be returned instead), and any code from
+ * dns_rbt_deletenode.
+ */
+
+isc_result_t
+dns_rbt_deletenode(dns_rbt_t *rbt, dns_rbtnode_t *node, bool recurse);
+/*%<
+ * Delete 'node' from the tree of trees.
+ *
+ * Notes:
+ *\li When 'node' is removed, if recurse is true then all nodes
+ * in levels down from it are removed too.
+ *
+ * Requires:
+ *\li rbt is a valid rbt manager.
+ *\li node != NULL.
+ *
+ * Ensures:
+ *\li Does NOT ensure that any external references to nodes in the tree
+ * are unaffected by node joins.
+ *
+ *\li If result is ISC_R_SUCCESS:
+ * 'node' does not appear in the tree with data; however,
+ * the node might still exist if it serves as a pointer to
+ * a lower tree level as long as 'recurse' was false, hence
+ * the node could can be found with dns_rbt_findnode when
+ * that function's empty_data_ok parameter is true.
+ *
+ *\li If result is ISC_R_NOMEMORY or ISC_R_NOSPACE:
+ * The node was deleted, but the tree structure was not
+ * optimized.
+ *
+ * Returns:
+ *\li #ISC_R_SUCCESS Success
+ *\li #ISC_R_NOMEMORY Resource Limit: Out of Memory when joining nodes.
+ *\li #ISC_R_NOSPACE dns_name_concatenate failed when joining nodes.
+ */
+
+void
+dns_rbt_namefromnode(dns_rbtnode_t *node, dns_name_t *name);
+/*%<
+ * Convert the sequence of labels stored at 'node' into a 'name'.
+ *
+ * Notes:
+ *\li This function does not return the full name, from the root, but
+ * just the labels at the indicated node.
+ *
+ *\li The name data pointed to by 'name' is the information stored
+ * in the node, not a copy. Altering the data at this pointer
+ * will likely cause grief.
+ *
+ * Requires:
+ * \li name->offsets == NULL
+ *
+ * Ensures:
+ * \li 'name' is DNS_NAMEATTR_READONLY.
+ *
+ * \li 'name' will point directly to the labels stored after the
+ * dns_rbtnode_t struct.
+ *
+ * \li 'name' will have offsets that also point to the information stored
+ * as part of the node.
+ */
+
+isc_result_t
+dns_rbt_fullnamefromnode(dns_rbtnode_t *node, dns_name_t *name);
+/*%<
+ * Like dns_rbt_namefromnode, but returns the full name from the root.
+ *
+ * Notes:
+ * \li Unlike dns_rbt_namefromnode, the name will not point directly
+ * to node data. Rather, dns_name_concatenate will be used to copy
+ * the name data from each node into the 'name' argument.
+ *
+ * Requires:
+ * \li name != NULL
+ * \li name has a dedicated buffer.
+ *
+ * Returns:
+ * \li ISC_R_SUCCESS
+ * \li ISC_R_NOSPACE (possible via dns_name_concatenate)
+ * \li DNS_R_NAMETOOLONG (possible via dns_name_concatenate)
+ */
+
+char *
+dns_rbt_formatnodename(dns_rbtnode_t *node, char *printname, unsigned int size);
+/*%<
+ * Format the full name of a node for printing, using dns_name_format().
+ *
+ * Notes:
+ * \li 'size' is the length of the printname buffer. This should be
+ * DNS_NAME_FORMATSIZE or larger.
+ *
+ * Requires:
+ * \li node and printname are not NULL.
+ *
+ * Returns:
+ * \li The 'printname' pointer.
+ */
+
+unsigned int
+dns_rbt_nodecount(dns_rbt_t *rbt);
+/*%<
+ * Obtain the number of nodes in the tree of trees.
+ *
+ * Requires:
+ * \li rbt is a valid rbt manager.
+ */
+
+size_t
+dns_rbt_hashsize(dns_rbt_t *rbt);
+/*%<
+ * Obtain the current number of buckets in the 'rbt' hash table.
+ *
+ * Requires:
+ * \li rbt is a valid rbt manager.
+ */
+
+void
+dns_rbt_destroy(dns_rbt_t **rbtp);
+isc_result_t
+dns_rbt_destroy2(dns_rbt_t **rbtp, unsigned int quantum);
+/*%<
+ * Stop working with a red-black tree of trees.
+ * If 'quantum' is zero then the entire tree will be destroyed.
+ * If 'quantum' is non zero then up to 'quantum' nodes will be destroyed
+ * allowing the rbt to be incrementally destroyed by repeated calls to
+ * dns_rbt_destroy2(). Once dns_rbt_destroy2() has been called no other
+ * operations than dns_rbt_destroy()/dns_rbt_destroy2() should be
+ * performed on the tree of trees.
+ *
+ * Requires:
+ * \li *rbt is a valid rbt manager.
+ *
+ * Ensures on ISC_R_SUCCESS:
+ * \li All space allocated by the RBT library has been returned.
+ *
+ * \li *rbt is invalidated as an rbt manager.
+ *
+ * Returns:
+ * \li ISC_R_SUCCESS
+ * \li ISC_R_QUOTA if 'quantum' nodes have been destroyed.
+ */
+
+void
+dns_rbt_printtext(dns_rbt_t *rbt, void (*data_printer)(FILE *, void *),
+ FILE *f);
+/*%<
+ * Print an ASCII representation of the internal structure of the red-black
+ * tree of trees to the passed stream.
+ *
+ * data_printer is a callback function that is called to print the data
+ * in a node. It should print it to the passed FILE stream.
+ *
+ * Notes:
+ * \li The name stored at each node, along with the node's color, is printed.
+ * Then the down pointer, left and right pointers are displayed
+ * recursively in turn. NULL down pointers are silently omitted;
+ * NULL left and right pointers are printed.
+ */
+
+void
+dns_rbt_printdot(dns_rbt_t *rbt, bool show_pointers, FILE *f);
+/*%<
+ * Print a GraphViz dot representation of the internal structure of the
+ * red-black tree of trees to the passed stream.
+ *
+ * If show_pointers is TRUE, pointers are also included in the generated
+ * graph.
+ *
+ * Notes:
+ * \li The name stored at each node, along with the node's color is displayed.
+ * Then the down pointer, left and right pointers are displayed
+ * recursively in turn. NULL left, right and down pointers are
+ * silently omitted.
+ */
+
+void
+dns_rbt_printnodeinfo(dns_rbtnode_t *n, FILE *f);
+/*%<
+ * Print out various information about a node
+ *
+ * Requires:
+ *\li 'n' is a valid pointer.
+ *
+ *\li 'f' points to a valid open FILE structure that allows writing.
+ */
+
+size_t
+dns__rbt_getheight(dns_rbt_t *rbt);
+/*%<
+ * Return the maximum height of sub-root nodes found in the red-black
+ * forest.
+ *
+ * The height of a node is defined as the number of nodes in the longest
+ * path from the node to a leaf. For each subtree in the forest, this
+ * function determines the height of its root node. Then it returns the
+ * maximum such height in the forest.
+ *
+ * Note: This function exists for testing purposes. Non-test code must
+ * not use it.
+ *
+ * Requires:
+ * \li rbt is a valid rbt manager.
+ */
+
+bool
+dns__rbt_checkproperties(dns_rbt_t *rbt);
+/*%<
+ * Check red-black properties of the forest.
+ *
+ * Note: This function exists for testing purposes. Non-test code must
+ * not use it.
+ *
+ * Requires:
+ * \li rbt is a valid rbt manager.
+ */
+
+size_t
+dns__rbtnode_getdistance(dns_rbtnode_t *node);
+/*%<
+ * Return the distance (in nodes) from the node to its upper node of its
+ * subtree. The root node has a distance of 1. A child of the root node
+ * has a distance of 2.
+ */
+
+/*****
+***** Chain Functions
+*****/
+
+void
+dns_rbtnodechain_init(dns_rbtnodechain_t *chain);
+/*%<
+ * Initialize 'chain'.
+ *
+ * Requires:
+ *\li 'chain' is a valid pointer.
+ *
+ * Ensures:
+ *\li 'chain' is suitable for use.
+ */
+
+void
+dns_rbtnodechain_reset(dns_rbtnodechain_t *chain);
+/*%<
+ * Free any dynamic storage associated with 'chain', and then reinitialize
+ * 'chain'.
+ *
+ * Requires:
+ *\li 'chain' is a valid pointer.
+ *
+ * Ensures:
+ *\li 'chain' is suitable for use, and uses no dynamic storage.
+ */
+
+void
+dns_rbtnodechain_invalidate(dns_rbtnodechain_t *chain);
+/*%<
+ * Free any dynamic storage associated with 'chain', and then invalidates it.
+ *
+ * Notes:
+ *\li Future calls to any dns_rbtnodechain_ function will need to call
+ * dns_rbtnodechain_init on the chain first (except, of course,
+ * dns_rbtnodechain_init itself).
+ *
+ * Requires:
+ *\li 'chain' is a valid chain.
+ *
+ * Ensures:
+ *\li 'chain' is no longer suitable for use, and uses no dynamic storage.
+ */
+
+isc_result_t
+dns_rbtnodechain_current(dns_rbtnodechain_t *chain, dns_name_t *name,
+ dns_name_t *origin, dns_rbtnode_t **node);
+/*%<
+ * Provide the name, origin and node to which the chain is currently pointed.
+ *
+ * Notes:
+ *\li The tree need not have be locked against additions for the chain
+ * to remain valid, however there are no guarantees if any deletion
+ * has been made since the chain was established.
+ *
+ * Requires:
+ *\li 'chain' is a valid chain.
+ *
+ * Ensures:
+ *\li 'node', if non-NULL, is the node to which the chain was pointed
+ * by dns_rbt_findnode, dns_rbtnodechain_first or dns_rbtnodechain_last.
+ * If none were called for the chain since it was initialized or reset,
+ * or if the was no predecessor to the name searched for with
+ * dns_rbt_findnode, then '*node' is NULL and ISC_R_NOTFOUND is returned.
+ *
+ *\li 'name', if non-NULL, is the name stored at the terminal level of
+ * the chain. This is typically a single label, like the "www" of
+ * "www.isc.org", but need not be so. At the root of the tree of trees,
+ * if the node is "." then 'name' is ".", otherwise it is relative to ".".
+ * (Minimalist and atypical case: if the tree has just the name
+ * "isc.org." then the root node's stored name is "isc.org." but 'name'
+ * will be "isc.org".)
+ *
+ *\li 'origin', if non-NULL, is the sequence of labels in the levels
+ * above the terminal level, such as "isc.org." in the above example.
+ * 'origin' is always "." for the root node.
+ *
+ *
+ * Returns:
+ *\li #ISC_R_SUCCESS name, origin & node were successfully set.
+ *\li #ISC_R_NOTFOUND The chain does not point to any node.
+ *\li &lt;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 &lt;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 &lt;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 &lt;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 &lt;something_else> Any error result from dns_name_concatenate.
+ */
+
+isc_result_t
+dns_rbtnodechain_down(dns_rbtnodechain_t *chain, dns_name_t *name,
+ dns_name_t *origin);
+/*%<
+ * Descend down if possible.
+ */
+
+isc_result_t
+dns_rbtnodechain_nextflat(dns_rbtnodechain_t *chain, dns_name_t *name);
+/*%<
+ * Find the next node at the current depth in DNSSEC order.
+ */
+
+unsigned int
+dns__rbtnode_namelen(dns_rbtnode_t *node);
+/*%<
+ * Returns the length of the full name of the node. Used only internally
+ * and in unit tests.
+ */
+ISC_LANG_ENDDECLS
diff --git a/lib/dns/include/dns/rcode.h b/lib/dns/include/dns/rcode.h
new file mode 100644
index 0000000..2c496ee
--- /dev/null
+++ b/lib/dns/include/dns/rcode.h
@@ -0,0 +1,107 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#pragma once
+
+/*! \file dns/rcode.h */
+
+#include <isc/lang.h>
+
+#include <dns/types.h>
+
+ISC_LANG_BEGINDECLS
+
+isc_result_t
+dns_rcode_fromtext(dns_rcode_t *rcodep, isc_textregion_t *source);
+/*%<
+ * Convert the text 'source' refers to into a DNS error value.
+ *
+ * Requires:
+ *\li 'rcodep' is a valid pointer.
+ *
+ *\li 'source' is a valid text region.
+ *
+ * Returns:
+ *\li #ISC_R_SUCCESS on success
+ *\li #DNS_R_UNKNOWN type is unknown
+ */
+
+isc_result_t
+dns_rcode_totext(dns_rcode_t rcode, isc_buffer_t *target);
+/*%<
+ * Put a textual representation of error 'rcode' into 'target'.
+ *
+ * Requires:
+ *\li 'rcode' is a valid rcode.
+ *
+ *\li 'target' is a valid text buffer.
+ *
+ * Ensures:
+ *\li If the result is success:
+ * The used space in 'target' is updated.
+ *
+ * Returns:
+ *\li #ISC_R_SUCCESS on success
+ *\li #ISC_R_NOSPACE target buffer is too small
+ */
+
+isc_result_t
+dns_tsigrcode_fromtext(dns_rcode_t *rcodep, isc_textregion_t *source);
+/*%<
+ * Convert the text 'source' refers to into a TSIG/TKEY error value.
+ *
+ * Requires:
+ *\li 'rcodep' is a valid pointer.
+ *
+ *\li 'source' is a valid text region.
+ *
+ * Returns:
+ *\li #ISC_R_SUCCESS on success
+ *\li #DNS_R_UNKNOWN type is unknown
+ */
+
+isc_result_t
+dns_tsigrcode_totext(dns_rcode_t rcode, isc_buffer_t *target);
+/*%<
+ * Put a textual representation of TSIG/TKEY error 'rcode' into 'target'.
+ *
+ * Requires:
+ *\li 'rcode' is a valid TSIG/TKEY error code.
+ *
+ *\li 'target' is a valid text buffer.
+ *
+ * Ensures:
+ *\li If the result is success:
+ * The used space in 'target' is updated.
+ *
+ * Returns:
+ *\li #ISC_R_SUCCESS on success
+ *\li #ISC_R_NOSPACE target buffer is too small
+ */
+
+isc_result_t
+dns_hashalg_fromtext(unsigned char *hashalg, isc_textregion_t *source);
+/*%<
+ * Convert the text 'source' refers to into a has algorithm value.
+ *
+ * Requires:
+ *\li 'hashalg' is a valid pointer.
+ *
+ *\li 'source' is a valid text region.
+ *
+ * Returns:
+ *\li #ISC_R_SUCCESS on success
+ *\li #DNS_R_UNKNOWN type is unknown
+ */
+
+ISC_LANG_ENDDECLS
diff --git a/lib/dns/include/dns/rdata.h b/lib/dns/include/dns/rdata.h
new file mode 100644
index 0000000..257a22c
--- /dev/null
+++ b/lib/dns/include/dns/rdata.h
@@ -0,0 +1,809 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#pragma once
+
+/*****
+***** Module Info
+*****/
+
+/*! \file dns/rdata.h
+ * \brief
+ * Provides facilities for manipulating DNS rdata, including conversions to
+ * and from wire format and text format.
+ *
+ * Given the large amount of rdata possible in a nameserver, it was important
+ * to come up with a very efficient way of storing rdata, but at the same
+ * time allow it to be manipulated.
+ *
+ * The decision was to store rdata in uncompressed wire format,
+ * and not to make it a fully abstracted object; i.e. certain parts of the
+ * server know rdata is stored that way. This saves a lot of memory, and
+ * makes adding rdata to messages easy. Having much of the server know
+ * the representation would be perilous, and we certainly don't want each
+ * user of rdata to be manipulating such a low-level structure. This is
+ * where the rdata module comes in. The module allows rdata handles to be
+ * created and attached to uncompressed wire format regions. All rdata
+ * operations and conversions are done through these handles.
+ *
+ * Implementation Notes:
+ *
+ *\li The routines in this module are expected to be synthesized by the
+ * build process from a set of source files, one per rdata type. For
+ * portability, it's probably best that the building be done by a C
+ * program. Adding a new rdata type will be a simple matter of adding
+ * a file to a directory and rebuilding the server. *All* knowledge of
+ * the format of a particular rdata type is in this file.
+ *
+ * MP:
+ *\li Clients of this module must impose any required synchronization.
+ *
+ * Reliability:
+ *\li This module deals with low-level byte streams. Errors in any of
+ * the functions are likely to crash the server or corrupt memory.
+ *
+ *\li Rdata is typed, and the caller must know what type of rdata it has.
+ * A caller that gets this wrong could crash the server.
+ *
+ *\li The fromstruct() and tostruct() routines use a void * pointer to
+ * represent the structure. The caller must ensure that it passes a
+ * pointer to the appropriate type, or the server could crash or memory
+ * could be corrupted.
+ *
+ * Resources:
+ *\li None.
+ *
+ * Security:
+ *
+ *\li *** WARNING ***
+ * dns_rdata_fromwire() deals with raw network data. An error in
+ * this routine could result in the failure or hijacking of the server.
+ *
+ * Standards:
+ *\li RFC1035
+ *\li Draft EDNS0 (0)
+ *\li Draft EDNS1 (0)
+ *\li Draft Binary Labels (2)
+ *\li Draft Local Compression (1)
+ *\li Various RFCs for particular types; these will be documented in the
+ * sources files of the types.
+ *
+ */
+
+/***
+ *** Imports
+ ***/
+
+#include <stdbool.h>
+
+#include <isc/lang.h>
+
+#include <dns/message.h>
+#include <dns/name.h>
+#include <dns/types.h>
+
+ISC_LANG_BEGINDECLS
+
+/***
+ *** Types
+ ***/
+
+/*%
+ ***** An 'rdata' is a handle to a binary region. The handle has an RR
+ ***** class and type, and the data in the binary region is in the format
+ ***** of the given class and type.
+ *****/
+/*%
+ * Clients are strongly discouraged from using this type directly, with
+ * the exception of the 'link' field which may be used directly for whatever
+ * purpose the client desires.
+ */
+struct dns_rdata {
+ unsigned char *data;
+ unsigned int length;
+ dns_rdataclass_t rdclass;
+ dns_rdatatype_t type;
+ unsigned int flags;
+ ISC_LINK(dns_rdata_t) link;
+};
+
+#define DNS_RDATA_INIT \
+ { \
+ NULL, 0, 0, 0, 0, { (void *)(-1), (void *)(-1) } \
+ }
+
+#define DNS_RDATA_CHECKINITIALIZED
+#ifdef DNS_RDATA_CHECKINITIALIZED
+#define DNS_RDATA_INITIALIZED(rdata) \
+ ((rdata)->data == NULL && (rdata)->length == 0 && \
+ (rdata)->rdclass == 0 && (rdata)->type == 0 && (rdata)->flags == 0 && \
+ !ISC_LINK_LINKED((rdata), link))
+#else /* ifdef DNS_RDATA_CHECKINITIALIZED */
+#ifdef ISC_LIST_CHECKINIT
+#define DNS_RDATA_INITIALIZED(rdata) (!ISC_LINK_LINKED((rdata), link))
+#else /* ifdef ISC_LIST_CHECKINIT */
+#define DNS_RDATA_INITIALIZED(rdata) true
+#endif /* ifdef ISC_LIST_CHECKINIT */
+#endif /* ifdef DNS_RDATA_CHECKINITIALIZED */
+
+#define DNS_RDATA_UPDATE 0x0001 /*%< update pseudo record. */
+#define DNS_RDATA_OFFLINE 0x0002 /*%< RRSIG has a offline key. */
+
+#define DNS_RDATA_VALIDFLAGS(rdata) \
+ (((rdata)->flags & ~(DNS_RDATA_UPDATE | DNS_RDATA_OFFLINE)) == 0)
+
+/*
+ * The maximum length of a RDATA that can be sent on the wire.
+ * Max packet size (65535) less header (12), less name (1), type (2),
+ * class (2), ttl(4), length (2).
+ *
+ * None of the defined types that support name compression can exceed
+ * this and all new types are to be sent uncompressed.
+ */
+
+#define DNS_RDATA_MAXLENGTH 65512U
+
+/*
+ * Flags affecting rdata formatting style. Flags 0xFFFF0000
+ * are used by masterfile-level formatting and defined elsewhere.
+ * See additional comments at dns_rdata_tofmttext().
+ */
+
+/*% Split the rdata into multiple lines to try to keep it
+ * within the "width". */
+#define DNS_STYLEFLAG_MULTILINE 0x00000001ULL
+
+/*% Output explanatory comments. */
+#define DNS_STYLEFLAG_COMMENT 0x00000002ULL
+#define DNS_STYLEFLAG_RRCOMMENT 0x00000004ULL
+
+/*% Output KEYDATA in human readable format. */
+#define DNS_STYLEFLAG_KEYDATA 0x00000008ULL
+
+/*% Output textual RR type and RDATA in RFC 3597 unknown format */
+#define DNS_STYLEFLAG_UNKNOWNFORMAT 0x00000010ULL
+
+/*% Print AAAA record fully expanded */
+#define DNS_STYLEFLAG_EXPANDAAAA 0x00000020ULL
+
+#define DNS_RDATA_DOWNCASE DNS_NAME_DOWNCASE
+#define DNS_RDATA_CHECKNAMES DNS_NAME_CHECKNAMES
+#define DNS_RDATA_CHECKNAMESFAIL DNS_NAME_CHECKNAMESFAIL
+#define DNS_RDATA_CHECKREVERSE DNS_NAME_CHECKREVERSE
+#define DNS_RDATA_CHECKMX DNS_NAME_CHECKMX
+#define DNS_RDATA_CHECKMXFAIL DNS_NAME_CHECKMXFAIL
+#define DNS_RDATA_UNKNOWNESCAPE 0x80000000
+
+/***
+ *** Initialization
+ ***/
+
+void
+dns_rdata_init(dns_rdata_t *rdata);
+/*%<
+ * Make 'rdata' empty.
+ *
+ * Requires:
+ * 'rdata' is a valid rdata (i.e. not NULL, points to a struct dns_rdata)
+ */
+
+void
+dns_rdata_reset(dns_rdata_t *rdata);
+/*%<
+ * Make 'rdata' empty.
+ *
+ * Requires:
+ *\li 'rdata' is a previously initialized rdata and is not linked.
+ */
+
+void
+dns_rdata_clone(const dns_rdata_t *src, dns_rdata_t *target);
+/*%<
+ * Clone 'target' from 'src'.
+ *
+ * Requires:
+ *\li 'src' to be initialized.
+ *\li 'target' to be initialized.
+ */
+
+/***
+ *** Comparisons
+ ***/
+
+int
+dns_rdata_compare(const dns_rdata_t *rdata1, const dns_rdata_t *rdata2);
+/*%<
+ * Determine the relative ordering under the DNSSEC order relation of
+ * 'rdata1' and 'rdata2'.
+ *
+ * Requires:
+ *
+ *\li 'rdata1' is a valid, non-empty rdata
+ *
+ *\li 'rdata2' is a valid, non-empty rdata
+ *
+ * Returns:
+ *\li < 0 'rdata1' is less than 'rdata2'
+ *\li 0 'rdata1' is equal to 'rdata2'
+ *\li > 0 'rdata1' is greater than 'rdata2'
+ */
+
+int
+dns_rdata_casecompare(const dns_rdata_t *rdata1, const dns_rdata_t *rdata2);
+/*%<
+ * dns_rdata_casecompare() is similar to dns_rdata_compare() but also
+ * compares domain names case insensitively in known rdata types that
+ * are treated as opaque data by dns_rdata_compare().
+ *
+ * Requires:
+ *
+ *\li 'rdata1' is a valid, non-empty rdata
+ *
+ *\li 'rdata2' is a valid, non-empty rdata
+ *
+ * Returns:
+ *\li < 0 'rdata1' is less than 'rdata2'
+ *\li 0 'rdata1' is equal to 'rdata2'
+ *\li > 0 'rdata1' is greater than 'rdata2'
+ */
+
+/***
+ *** Conversions
+ ***/
+
+void
+dns_rdata_fromregion(dns_rdata_t *rdata, dns_rdataclass_t rdclass,
+ dns_rdatatype_t type, isc_region_t *r);
+/*%<
+ * Make 'rdata' refer to region 'r'.
+ *
+ * Requires:
+ *
+ *\li The data in 'r' is properly formatted for whatever type it is.
+ */
+
+void
+dns_rdata_toregion(const dns_rdata_t *rdata, isc_region_t *r);
+/*%<
+ * Make 'r' refer to 'rdata'.
+ */
+
+isc_result_t
+dns_rdata_fromwire(dns_rdata_t *rdata, dns_rdataclass_t rdclass,
+ dns_rdatatype_t type, isc_buffer_t *source,
+ dns_decompress_t *dctx, unsigned int options,
+ isc_buffer_t *target);
+/*%<
+ * Copy the possibly-compressed rdata at source into the target region.
+ *
+ * Notes:
+ *\li Name decompression policy is controlled by 'dctx'.
+ *
+ * 'options'
+ *\li DNS_RDATA_DOWNCASE downcase domain names when they are copied
+ * into target.
+ *
+ * Requires:
+ *
+ *\li 'rdclass' and 'type' are valid.
+ *
+ *\li 'source' is a valid buffer, and the active region of 'source'
+ * references the rdata to be processed.
+ *
+ *\li 'target' is a valid buffer.
+ *
+ *\li 'dctx' is a valid decompression context.
+ *
+ * Ensures,
+ * if result is success:
+ * \li If 'rdata' is not NULL, it is attached to the target.
+ * \li The conditions dns_name_fromwire() ensures for names hold
+ * for all names in the rdata.
+ * \li The current location in source is advanced, and the used space
+ * in target is updated.
+ *
+ * Result:
+ *\li Success
+ *\li Any non-success status from dns_name_fromwire()
+ *\li Various 'Bad Form' class failures depending on class and type
+ *\li Bad Form: Input too short
+ *\li Resource Limit: Not enough space
+ */
+
+isc_result_t
+dns_rdata_towire(dns_rdata_t *rdata, dns_compress_t *cctx,
+ isc_buffer_t *target);
+/*%<
+ * Convert 'rdata' into wire format, compressing it as specified by the
+ * compression context 'cctx', and storing the result in 'target'.
+ *
+ * Notes:
+ *\li If the compression context allows global compression, then the
+ * global compression table may be updated.
+ *
+ * Requires:
+ *\li 'rdata' is a valid, non-empty rdata
+ *
+ *\li target is a valid buffer
+ *
+ *\li Any offsets specified in a global compression table are valid
+ * for target.
+ *
+ * Ensures,
+ * if the result is success:
+ * \li The used space in target is updated.
+ *
+ * Returns:
+ *\li Success
+ *\li Any non-success status from dns_name_towire()
+ *\li Resource Limit: Not enough space
+ */
+
+isc_result_t
+dns_rdata_fromtext(dns_rdata_t *rdata, dns_rdataclass_t rdclass,
+ dns_rdatatype_t type, isc_lex_t *lexer,
+ const dns_name_t *origin, unsigned int options,
+ isc_mem_t *mctx, isc_buffer_t *target,
+ dns_rdatacallbacks_t *callbacks);
+/*%<
+ * Convert the textual representation of a DNS rdata into uncompressed wire
+ * form stored in the target region. Tokens constituting the text of the rdata
+ * are taken from 'lexer'.
+ *
+ * Notes:
+ *\li Relative domain names in the rdata will have 'origin' appended to them.
+ * A NULL origin implies "origin == dns_rootname".
+ *
+ *
+ * 'options'
+ *\li DNS_RDATA_DOWNCASE downcase domain names when they are copied
+ * into target.
+ *\li DNS_RDATA_CHECKNAMES perform checknames checks.
+ *\li DNS_RDATA_CHECKNAMESFAIL fail if the checknames check fail. If
+ * not set a warning will be issued.
+ *\li DNS_RDATA_CHECKREVERSE this should set if the owner name ends
+ * in IP6.ARPA, IP6.INT or IN-ADDR.ARPA.
+ *
+ * Requires:
+ *
+ *\li 'rdclass' and 'type' are valid.
+ *
+ *\li 'lexer' is a valid isc_lex_t.
+ *
+ *\li 'mctx' is a valid isc_mem_t.
+ *
+ *\li 'target' is a valid region.
+ *
+ *\li 'origin' if non NULL it must be absolute.
+ *
+ *\li 'callbacks' to be NULL or callbacks->warn and callbacks->error be
+ * initialized.
+ *
+ * Ensures,
+ * if result is success:
+ *\li If 'rdata' is not NULL, it is attached to the target.
+ *
+ *\li The conditions dns_name_fromtext() ensures for names hold
+ * for all names in the rdata.
+ *
+ *\li The used space in target is updated.
+ *
+ * Result:
+ *\li Success
+ *\li Translated result codes from isc_lex_gettoken
+ *\li Various 'Bad Form' class failures depending on class and type
+ *\li Bad Form: Input too short
+ *\li Resource Limit: Not enough space
+ *\li Resource Limit: Not enough memory
+ */
+
+isc_result_t
+dns_rdata_totext(dns_rdata_t *rdata, const dns_name_t *origin,
+ isc_buffer_t *target);
+/*%<
+ * Convert 'rdata' into text format, storing the result in 'target'.
+ * The text will consist of a single line, with fields separated by
+ * single spaces.
+ *
+ * Notes:
+ *\li If 'origin' is not NULL, then any names in the rdata that are
+ * subdomains of 'origin' will be made relative it.
+ *
+ *\li XXX Do we *really* want to support 'origin'? I'm inclined towards "no"
+ * at the moment.
+ *
+ * Requires:
+ *
+ *\li 'rdata' is a valid, non-empty rdata
+ *
+ *\li 'origin' is NULL, or is a valid name
+ *
+ *\li 'target' is a valid text buffer
+ *
+ * Ensures,
+ * if the result is success:
+ *
+ * \li The used space in target is updated.
+ *
+ * Returns:
+ *\li Success
+ *\li Any non-success status from dns_name_totext()
+ *\li Resource Limit: Not enough space
+ */
+
+isc_result_t
+dns_rdata_tofmttext(dns_rdata_t *rdata, const dns_name_t *origin,
+ dns_masterstyle_flags_t flags, unsigned int width,
+ unsigned int split_width, const char *linebreak,
+ isc_buffer_t *target);
+/*%<
+ * Like dns_rdata_totext, but do formatted output suitable for
+ * database dumps. This is intended for use by dns_db_dump();
+ * library users are discouraged from calling it directly.
+ *
+ * If (flags & #DNS_STYLEFLAG_MULTILINE) != 0, attempt to stay
+ * within 'width' by breaking the text into multiple lines.
+ * The string 'linebreak' is inserted between lines, and parentheses
+ * are added when necessary. Because RRs contain unbreakable elements
+ * such as domain names whose length is variable, unpredictable, and
+ * potentially large, there is no guarantee that the lines will
+ * not exceed 'width' anyway.
+ *
+ * If (flags & #DNS_STYLEFLAG_MULTILINE) == 0, the rdata is always
+ * printed as a single line, and no parentheses are used.
+ * The 'width' and 'linebreak' arguments are ignored.
+ *
+ * If (flags & #DNS_STYLEFLAG_COMMENT) != 0, output explanatory
+ * comments next to things like the SOA timer fields. Some
+ * comments (e.g., the SOA ones) are only printed when multiline
+ * output is selected.
+ *
+ * base64 rdata text (e.g., DNSKEY records) will be split into chunks
+ * of 'split_width' characters. If split_width == 0, the text will
+ * not be split at all. If split_width == UINT_MAX (0xffffffff), then
+ * it is undefined and falls back to the default value of 'width'
+ */
+
+isc_result_t
+dns_rdata_fromstruct(dns_rdata_t *rdata, dns_rdataclass_t rdclass,
+ dns_rdatatype_t type, void *source, isc_buffer_t *target);
+/*%<
+ * Convert the C structure representation of an rdata into uncompressed wire
+ * format in 'target'.
+ *
+ * XXX Should we have a 'size' parameter as a sanity check on target?
+ *
+ * Requires:
+ *
+ *\li 'rdclass' and 'type' are valid.
+ *
+ *\li 'source' points to a valid C struct for the class and type.
+ *
+ *\li 'target' is a valid buffer.
+ *
+ *\li All structure pointers to memory blocks should be NULL if their
+ * corresponding length values are zero.
+ *
+ * Ensures,
+ * if result is success:
+ * \li If 'rdata' is not NULL, it is attached to the target.
+ *
+ * \li The used space in 'target' is updated.
+ *
+ * Result:
+ *\li Success
+ *\li Various 'Bad Form' class failures depending on class and type
+ *\li Resource Limit: Not enough space
+ */
+
+isc_result_t
+dns_rdata_tostruct(const dns_rdata_t *rdata, void *target, isc_mem_t *mctx);
+/*%<
+ * Convert an rdata into its C structure representation.
+ *
+ * If 'mctx' is NULL then 'rdata' must persist while 'target' is being used.
+ *
+ * If 'mctx' is non NULL then memory will be allocated if required.
+ *
+ * Requires:
+ *
+ *\li 'rdata' is a valid, non-empty, non-pseudo rdata.
+ *
+ *\li 'target' to point to a valid pointer for the type and class.
+ *
+ * Result:
+ *\li Success
+ *\li Resource Limit: Not enough memory
+ */
+
+void
+dns_rdata_freestruct(void *source);
+/*%<
+ * Free dynamic memory attached to 'source' (if any).
+ *
+ * Requires:
+ *
+ *\li 'source' to point to the structure previously filled in by
+ * dns_rdata_tostruct().
+ */
+
+bool
+dns_rdatatype_ismeta(dns_rdatatype_t type);
+/*%<
+ * Return true iff the rdata type 'type' is a meta-type
+ * like ANY or AXFR.
+ */
+
+bool
+dns_rdatatype_issingleton(dns_rdatatype_t type);
+/*%<
+ * Return true iff the rdata type 'type' is a singleton type,
+ * like CNAME or SOA.
+ *
+ * Requires:
+ * \li 'type' is a valid rdata type.
+ *
+ */
+
+bool
+dns_rdataclass_ismeta(dns_rdataclass_t rdclass);
+/*%<
+ * Return true iff the rdata class 'rdclass' is a meta-class
+ * like ANY or NONE.
+ */
+
+bool
+dns_rdatatype_isdnssec(dns_rdatatype_t type);
+/*%<
+ * Return true iff 'type' is one of the DNSSEC
+ * rdata types that may exist alongside a CNAME record.
+ *
+ * Requires:
+ * \li 'type' is a valid rdata type.
+ */
+
+bool
+dns_rdatatype_iszonecutauth(dns_rdatatype_t type);
+/*%<
+ * Return true iff rdata of type 'type' is considered authoritative
+ * data (not glue) in the NSEC chain when it occurs in the parent zone
+ * at a zone cut.
+ *
+ * Requires:
+ * \li 'type' is a valid rdata type.
+ *
+ */
+
+bool
+dns_rdatatype_isknown(dns_rdatatype_t type);
+/*%<
+ * Return true iff the rdata type 'type' is known.
+ *
+ * Requires:
+ * \li 'type' is a valid rdata type.
+ *
+ */
+
+isc_result_t
+dns_rdata_additionaldata(dns_rdata_t *rdata, const dns_name_t *owner,
+ dns_additionaldatafunc_t add, void *arg);
+/*%<
+ * Call 'add' for each name and type from 'rdata' which is subject to
+ * additional section processing.
+ *
+ * Requires:
+ *
+ *\li 'rdata' is a valid, non-empty rdata.
+ *
+ *\li 'add' is a valid dns_additionalfunc_t.
+ *
+ * Ensures:
+ *
+ *\li If successful, then add() will have been called for each name
+ * and type subject to additional section processing.
+ *
+ *\li If add() returns something other than #ISC_R_SUCCESS, that result
+ * will be returned as the result of dns_rdata_additionaldata().
+ *
+ * Returns:
+ *
+ *\li ISC_R_SUCCESS
+ *
+ *\li Many other results are possible if not successful.
+ */
+
+isc_result_t
+dns_rdata_digest(dns_rdata_t *rdata, dns_digestfunc_t digest, void *arg);
+/*%<
+ * Send 'rdata' in DNSSEC canonical form to 'digest'.
+ *
+ * Note:
+ *\li 'digest' may be called more than once by dns_rdata_digest(). The
+ * concatenation of all the regions, in the order they were given
+ * to 'digest', will be the DNSSEC canonical form of 'rdata'.
+ *
+ * Requires:
+ *
+ *\li 'rdata' is a valid, non-empty rdata.
+ *
+ *\li 'digest' is a valid dns_digestfunc_t.
+ *
+ * Ensures:
+ *
+ *\li If successful, then all of the rdata's data has been sent, in
+ * DNSSEC canonical form, to 'digest'.
+ *
+ *\li If digest() returns something other than ISC_R_SUCCESS, that result
+ * will be returned as the result of dns_rdata_digest().
+ *
+ * Returns:
+ *
+ *\li ISC_R_SUCCESS
+ *
+ *\li Many other results are possible if not successful.
+ */
+
+bool
+dns_rdatatype_questiononly(dns_rdatatype_t type);
+/*%<
+ * Return true iff rdata of type 'type' can only appear in the question
+ * section of a properly formatted message.
+ *
+ * Requires:
+ * \li 'type' is a valid rdata type.
+ *
+ */
+
+bool
+dns_rdatatype_notquestion(dns_rdatatype_t type);
+/*%<
+ * Return true iff rdata of type 'type' can not appear in the question
+ * section of a properly formatted message.
+ *
+ * Requires:
+ * \li 'type' is a valid rdata type.
+ *
+ */
+
+bool
+dns_rdatatype_atparent(dns_rdatatype_t type);
+/*%<
+ * Return true iff rdata of type 'type' should appear at the parent of
+ * a zone cut.
+ *
+ * Requires:
+ * \li 'type' is a valid rdata type.
+ *
+ */
+
+bool
+dns_rdatatype_atcname(dns_rdatatype_t type);
+/*%<
+ * Return true iff rdata of type 'type' can appear beside a cname.
+ *
+ * Requires:
+ * \li 'type' is a valid rdata type.
+ *
+ */
+
+bool
+dns_rdatatype_followadditional(dns_rdatatype_t type);
+/*%<
+ * Return true if adding a record of type 'type' to the ADDITIONAL section
+ * of a message can itself trigger the addition of still more data to the
+ * additional section.
+ *
+ * (For example: adding SRV to the ADDITIONAL section may trigger
+ * the addition of address records associated with that SRV.)
+ *
+ * Requires:
+ * \li 'type' is a valid rdata type.
+ *
+ */
+
+unsigned int
+dns_rdatatype_attributes(dns_rdatatype_t rdtype);
+/*%<
+ * Return attributes for the given type.
+ *
+ * Requires:
+ *\li 'rdtype' are known.
+ *
+ * Returns:
+ *\li a bitmask consisting of the following flags.
+ */
+
+/*% only one may exist for a name */
+#define DNS_RDATATYPEATTR_SINGLETON 0x00000001U
+/*% requires no other data be present */
+#define DNS_RDATATYPEATTR_EXCLUSIVE 0x00000002U
+/*% Is a meta type */
+#define DNS_RDATATYPEATTR_META 0x00000004U
+/*% Is a DNSSEC type, like RRSIG or NSEC */
+#define DNS_RDATATYPEATTR_DNSSEC 0x00000008U
+/*% Is a zone cut authority type */
+#define DNS_RDATATYPEATTR_ZONECUTAUTH 0x00000010U
+/*% Is reserved (unusable) */
+#define DNS_RDATATYPEATTR_RESERVED 0x00000020U
+/*% Is an unknown type */
+#define DNS_RDATATYPEATTR_UNKNOWN 0x00000040U
+/*% Is META, and can only be in a question section */
+#define DNS_RDATATYPEATTR_QUESTIONONLY 0x00000080U
+/*% Is META, and can NOT be in a question section */
+#define DNS_RDATATYPEATTR_NOTQUESTION 0x00000100U
+/*% Is present at zone cuts in the parent, not the child */
+#define DNS_RDATATYPEATTR_ATPARENT 0x00000200U
+/*% Can exist along side a CNAME */
+#define DNS_RDATATYPEATTR_ATCNAME 0x00000400U
+/*% Follow additional */
+#define DNS_RDATATYPEATTR_FOLLOWADDITIONAL 0x00000800U
+
+dns_rdatatype_t
+dns_rdata_covers(dns_rdata_t *rdata);
+/*%<
+ * Return the rdatatype that this type covers.
+ *
+ * Requires:
+ *\li 'rdata' is a valid, non-empty rdata.
+ *
+ *\li 'rdata' is a type that covers other rdata types.
+ *
+ * Returns:
+ *\li The type covered.
+ */
+
+bool
+dns_rdata_checkowner(const dns_name_t *name, dns_rdataclass_t rdclass,
+ dns_rdatatype_t type, bool wildcard);
+/*
+ * Returns whether this is a valid ownername for this <type,class>.
+ * If wildcard is true allow the first label to be a wildcard if
+ * appropriate.
+ *
+ * Requires:
+ * 'name' is a valid name.
+ */
+
+bool
+dns_rdata_checknames(dns_rdata_t *rdata, const dns_name_t *owner,
+ dns_name_t *bad);
+/*
+ * Returns whether 'rdata' contains valid domain names. The checks are
+ * sensitive to the owner name.
+ *
+ * If 'bad' is non-NULL and a domain name fails the check the
+ * the offending name will be return in 'bad' by cloning from
+ * the 'rdata' contents.
+ *
+ * Requires:
+ * 'rdata' to be valid.
+ * 'owner' to be valid.
+ * 'bad' to be NULL or valid.
+ */
+
+void
+dns_rdata_exists(dns_rdata_t *rdata, dns_rdatatype_t type);
+
+void
+dns_rdata_notexist(dns_rdata_t *rdata, dns_rdatatype_t type);
+
+void
+dns_rdata_deleterrset(dns_rdata_t *rdata, dns_rdatatype_t type);
+
+void
+dns_rdata_makedelete(dns_rdata_t *rdata);
+
+const char *
+dns_rdata_updateop(dns_rdata_t *rdata, dns_section_t section);
+
+ISC_LANG_ENDDECLS
diff --git a/lib/dns/include/dns/rdataclass.h b/lib/dns/include/dns/rdataclass.h
new file mode 100644
index 0000000..466c4a3
--- /dev/null
+++ b/lib/dns/include/dns/rdataclass.h
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#pragma once
+
+/*! \file dns/rdataclass.h */
+
+#include <isc/lang.h>
+
+#include <dns/types.h>
+
+ISC_LANG_BEGINDECLS
+
+isc_result_t
+dns_rdataclass_fromtext(dns_rdataclass_t *classp, isc_textregion_t *source);
+/*%<
+ * Convert the text 'source' refers to into a DNS class.
+ *
+ * Requires:
+ *\li 'classp' is a valid pointer.
+ *
+ *\li 'source' is a valid text region.
+ *
+ * Returns:
+ *\li #ISC_R_SUCCESS on success
+ *\li #DNS_R_UNKNOWN class is unknown
+ */
+
+isc_result_t
+dns_rdataclass_totext(dns_rdataclass_t rdclass, isc_buffer_t *target);
+/*%<
+ * Put a textual representation of class 'rdclass' into 'target'.
+ *
+ * Requires:
+ *\li 'rdclass' is a valid class.
+ *
+ *\li 'target' is a valid text buffer.
+ *
+ * Ensures,
+ * if the result is success:
+ *\li The used space in 'target' is updated.
+ *
+ * Returns:
+ *\li #ISC_R_SUCCESS on success
+ *\li #ISC_R_NOSPACE target buffer is too small
+ */
+
+isc_result_t
+dns_rdataclass_tounknowntext(dns_rdataclass_t rdclass, isc_buffer_t *target);
+/*%<
+ * Put textual RFC3597 CLASSXXXX representation of class 'rdclass' into
+ * 'target'.
+ *
+ * Requires:
+ *\li 'rdclass' is a valid class.
+ *
+ *\li 'target' is a valid text buffer.
+ *
+ * Ensures,
+ * if the result is success:
+ *\li The used space in 'target' is updated.
+ *
+ * Returns:
+ *\li #ISC_R_SUCCESS on success
+ *\li #ISC_R_NOSPACE target buffer is too small
+ */
+
+void
+dns_rdataclass_format(dns_rdataclass_t rdclass, char *array, unsigned int size);
+/*%<
+ * Format a human-readable representation of the class 'rdclass'
+ * into the character array 'array', which is of size 'size'.
+ * The resulting string is guaranteed to be null-terminated.
+ */
+
+#define DNS_RDATACLASS_FORMATSIZE sizeof("CLASS65535")
+/*%<
+ * Minimum size of array to pass to dns_rdataclass_format().
+ */
+
+ISC_LANG_ENDDECLS
diff --git a/lib/dns/include/dns/rdatalist.h b/lib/dns/include/dns/rdatalist.h
new file mode 100644
index 0000000..3608cfe
--- /dev/null
+++ b/lib/dns/include/dns/rdatalist.h
@@ -0,0 +1,120 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#pragma once
+
+/*****
+***** Module Info
+*****/
+
+/*! \file dns/rdatalist.h
+ * \brief
+ * A DNS rdatalist is a list of rdata of a common type and class.
+ *
+ * MP:
+ *\li Clients of this module must impose any required synchronization.
+ *
+ * Reliability:
+ *\li No anticipated impact.
+ *
+ * Resources:
+ *\li TBS
+ *
+ * Security:
+ *\li No anticipated impact.
+ *
+ * Standards:
+ *\li None.
+ */
+
+#include <isc/lang.h>
+
+#include <dns/types.h>
+
+/*%
+ * Clients may use this type directly.
+ */
+struct dns_rdatalist {
+ dns_rdataclass_t rdclass;
+ dns_rdatatype_t type;
+ dns_rdatatype_t covers;
+ dns_ttl_t ttl;
+ ISC_LIST(dns_rdata_t) rdata;
+ ISC_LINK(dns_rdatalist_t) link;
+ /*%<
+ * Case vector. If the bit is set then the corresponding
+ * character in the owner name needs to be AND'd with 0x20,
+ * rendering that character upper case.
+ */
+ unsigned char upper[32];
+};
+
+ISC_LANG_BEGINDECLS
+
+void
+dns_rdatalist_init(dns_rdatalist_t *rdatalist);
+/*%<
+ * Initialize rdatalist.
+ *
+ * Ensures:
+ *\li All fields of rdatalist have been initialized to their default
+ * values.
+ */
+
+isc_result_t
+dns_rdatalist_tordataset(dns_rdatalist_t *rdatalist, dns_rdataset_t *rdataset);
+/*%<
+ * Make 'rdataset' refer to the rdata in 'rdatalist'.
+ *
+ * Note:
+ *\li The caller must ensure that 'rdatalist' remains valid and unchanged
+ * while 'rdataset' is associated with it.
+ *
+ * Requires:
+ *
+ *\li 'rdatalist' is a valid rdatalist.
+ *
+ *\li 'rdataset' is a valid rdataset that is not currently associated with
+ * any rdata.
+ *
+ * Ensures,
+ * on success,
+ *
+ *\li 'rdataset' is associated with the rdata in rdatalist.
+ *
+ * Returns:
+ *\li #ISC_R_SUCCESS
+ */
+
+isc_result_t
+dns_rdatalist_fromrdataset(dns_rdataset_t *rdataset,
+ dns_rdatalist_t **rdatalist);
+/*%<
+ * Point 'rdatalist' to the rdatalist in 'rdataset'.
+ *
+ * Requires:
+ *
+ *\li 'rdatalist' is a pointer to a NULL dns_rdatalist_t pointer.
+ *
+ *\li 'rdataset' is a valid rdataset associated with an rdatalist.
+ *
+ * Ensures,
+ * on success,
+ *
+ *\li 'rdatalist' is pointed to the rdatalist in rdataset.
+ *
+ * Returns:
+ *\li #ISC_R_SUCCESS
+ */
+
+ISC_LANG_ENDDECLS
diff --git a/lib/dns/include/dns/rdataset.h b/lib/dns/include/dns/rdataset.h
new file mode 100644
index 0000000..566ea44
--- /dev/null
+++ b/lib/dns/include/dns/rdataset.h
@@ -0,0 +1,621 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#pragma once
+
+/*****
+***** Module Info
+*****/
+
+/*! \file dns/rdataset.h
+ * \brief
+ * A DNS rdataset is a handle that can be associated with a collection of
+ * rdata all having a common owner name, class, and type.
+ *
+ * The dns_rdataset_t type is like a "virtual class". To actually use
+ * rdatasets, an implementation of the method suite (e.g. "slabbed rdata") is
+ * required.
+ *
+ * XXX &lt;more&gt; 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 <inttypes.h>
+#include <stdbool.h>
+
+#include <isc/lang.h>
+#include <isc/magic.h>
+#include <isc/stdtime.h>
+
+#include <dns/rdatastruct.h>
+#include <dns/types.h>
+
+ISC_LANG_BEGINDECLS
+
+typedef enum {
+ dns_rdatasetadditional_fromauth,
+ dns_rdatasetadditional_fromcache,
+ dns_rdatasetadditional_fromglue
+} dns_rdatasetadditional_t;
+
+typedef struct dns_rdatasetmethods {
+ void (*disassociate)(dns_rdataset_t *rdataset);
+ isc_result_t (*first)(dns_rdataset_t *rdataset);
+ isc_result_t (*next)(dns_rdataset_t *rdataset);
+ void (*current)(dns_rdataset_t *rdataset, dns_rdata_t *rdata);
+ void (*clone)(dns_rdataset_t *source, dns_rdataset_t *target);
+ unsigned int (*count)(dns_rdataset_t *rdataset);
+ isc_result_t (*addnoqname)(dns_rdataset_t *rdataset,
+ const dns_name_t *name);
+ isc_result_t (*getnoqname)(dns_rdataset_t *rdataset, dns_name_t *name,
+ dns_rdataset_t *neg, dns_rdataset_t *negsig);
+ isc_result_t (*addclosest)(dns_rdataset_t *rdataset,
+ const dns_name_t *name);
+ isc_result_t (*getclosest)(dns_rdataset_t *rdataset, dns_name_t *name,
+ dns_rdataset_t *neg, dns_rdataset_t *negsig);
+ void (*settrust)(dns_rdataset_t *rdataset, dns_trust_t trust);
+ void (*expire)(dns_rdataset_t *rdataset);
+ void (*clearprefetch)(dns_rdataset_t *rdataset);
+ void (*setownercase)(dns_rdataset_t *rdataset, const dns_name_t *name);
+ void (*getownercase)(const dns_rdataset_t *rdataset, dns_name_t *name);
+ isc_result_t (*addglue)(dns_rdataset_t *rdataset,
+ dns_dbversion_t *version, dns_message_t *msg);
+} dns_rdatasetmethods_t;
+
+#define DNS_RDATASET_MAGIC ISC_MAGIC('D', 'N', 'S', 'R')
+#define DNS_RDATASET_VALID(set) ISC_MAGIC_VALID(set, DNS_RDATASET_MAGIC)
+
+/*%
+ * Direct use of this structure by clients is strongly discouraged, except
+ * for the 'link' field which may be used however the client wishes. The
+ * 'private', 'current', and 'index' fields MUST NOT be changed by clients.
+ * rdataset implementations may change any of the fields.
+ */
+struct dns_rdataset {
+ unsigned int magic;
+ dns_rdatasetmethods_t *methods;
+ ISC_LINK(dns_rdataset_t) link;
+
+ /*
+ * XXX do we need these, or should they be retrieved by methods?
+ * Leaning towards the latter, since they are not frequently required
+ * once you have the rdataset.
+ */
+ dns_rdataclass_t rdclass;
+ dns_rdatatype_t type;
+ dns_ttl_t ttl;
+
+ dns_trust_t trust;
+ dns_rdatatype_t covers;
+
+ /*
+ * attributes
+ */
+ unsigned int attributes;
+
+ /*%
+ * the counter provides the starting point in the "cyclic" order.
+ * The value UINT32_MAX has a special meaning of "picking up a
+ * random value." in order to take care of databases that do not
+ * increment the counter.
+ */
+ uint32_t count;
+
+ /*
+ * This RRSIG RRset should be re-generated around this time.
+ * Only valid if DNS_RDATASETATTR_RESIGN is set in attributes.
+ */
+ isc_stdtime_t resign;
+
+ /*@{*/
+ /*%
+ * These are for use by the rdataset implementation, and MUST NOT
+ * be changed by clients.
+ */
+ void *private1;
+ void *private2;
+ void *private3;
+ unsigned int privateuint4;
+ void *private5;
+ const void *private6;
+ const void *private7;
+ /*@}*/
+};
+
+#define DNS_RDATASET_COUNT_UNDEFINED UINT32_MAX
+
+#define DNS_RDATASET_INIT \
+ { \
+ .magic = DNS_RDATASET_MAGIC, .link = ISC_LINK_INITIALIZER, \
+ .count = DNS_RDATASET_COUNT_UNDEFINED \
+ }
+
+/*!
+ * \def DNS_RDATASETATTR_RENDERED
+ * Used by message.c to indicate that the rdataset was rendered.
+ *
+ * \def DNS_RDATASETATTR_TTLADJUSTED
+ * Used by message.c to indicate that the rdataset's rdata had differing
+ * TTL values, and the rdataset->ttl holds the smallest.
+ *
+ * \def DNS_RDATASETATTR_LOADORDER
+ * Output the RRset in load order.
+ *
+ * \def DNS_RDATASETATTR_STALE_ADDED
+ * Set on rdatasets that were added during a stale-answer-client-timeout
+ * lookup. In other words, the RRset was added during a lookup of stale
+ * data and does not necessarily mean that the rdataset itself is stale.
+ */
+
+#define DNS_RDATASETATTR_NONE 0x00000000 /*%< No ordering. */
+#define DNS_RDATASETATTR_QUESTION 0x00000001
+#define DNS_RDATASETATTR_RENDERED 0x00000002 /*%< Used by message.c */
+#define DNS_RDATASETATTR_ANSWERED 0x00000004 /*%< Used by server. */
+#define DNS_RDATASETATTR_CACHE 0x00000008 /*%< Used by resolver. */
+#define DNS_RDATASETATTR_ANSWER 0x00000010 /*%< Used by resolver. */
+#define DNS_RDATASETATTR_ANSWERSIG 0x00000020 /*%< Used by resolver. */
+#define DNS_RDATASETATTR_EXTERNAL 0x00000040 /*%< Used by resolver. */
+#define DNS_RDATASETATTR_NCACHE 0x00000080 /*%< Used by resolver. */
+#define DNS_RDATASETATTR_CHAINING 0x00000100 /*%< Used by resolver. */
+#define DNS_RDATASETATTR_TTLADJUSTED 0x00000200 /*%< Used by message.c */
+#define DNS_RDATASETATTR_FIXEDORDER 0x00000400 /*%< Fixed ordering. */
+#define DNS_RDATASETATTR_RANDOMIZE 0x00000800 /*%< Random ordering. */
+#define DNS_RDATASETATTR_CHASE 0x00001000 /*%< Used by resolver. */
+#define DNS_RDATASETATTR_NXDOMAIN 0x00002000
+#define DNS_RDATASETATTR_NOQNAME 0x00004000
+#define DNS_RDATASETATTR_CHECKNAMES 0x00008000 /*%< Used by resolver. */
+#define DNS_RDATASETATTR_REQUIRED 0x00010000
+#define DNS_RDATASETATTR_REQUIREDGLUE DNS_RDATASETATTR_REQUIRED
+#define DNS_RDATASETATTR_LOADORDER 0x00020000
+#define DNS_RDATASETATTR_RESIGN 0x00040000
+#define DNS_RDATASETATTR_CLOSEST 0x00080000
+#define DNS_RDATASETATTR_OPTOUT 0x00100000 /*%< OPTOUT proof */
+#define DNS_RDATASETATTR_NEGATIVE 0x00200000
+#define DNS_RDATASETATTR_PREFETCH 0x00400000
+#define DNS_RDATASETATTR_CYCLIC 0x00800000 /*%< Cyclic ordering. */
+#define DNS_RDATASETATTR_STALE 0x01000000
+#define DNS_RDATASETATTR_ANCIENT 0x02000000
+#define DNS_RDATASETATTR_STALE_WINDOW 0x04000000
+#define DNS_RDATASETATTR_STALE_ADDED 0x08000000
+
+/*%
+ * _OMITDNSSEC:
+ * Omit DNSSEC records when rendering ncache records.
+ */
+#define DNS_RDATASETTOWIRE_OMITDNSSEC 0x0001
+
+void
+dns_rdataset_init(dns_rdataset_t *rdataset);
+/*%<
+ * Make 'rdataset' a valid, disassociated rdataset.
+ *
+ * Requires:
+ *\li 'rdataset' is not NULL.
+ *
+ * Ensures:
+ *\li 'rdataset' is a valid, disassociated rdataset.
+ */
+
+void
+dns_rdataset_invalidate(dns_rdataset_t *rdataset);
+/*%<
+ * Invalidate 'rdataset'.
+ *
+ * Requires:
+ *\li 'rdataset' is a valid, disassociated rdataset.
+ *
+ * Ensures:
+ *\li If assertion checking is enabled, future attempts to use 'rdataset'
+ * without initializing it will cause an assertion failure.
+ */
+
+void
+dns_rdataset_disassociate(dns_rdataset_t *rdataset);
+/*%<
+ * Disassociate 'rdataset' from its rdata, allowing it to be reused.
+ *
+ * Notes:
+ *\li The client must ensure it has no references to rdata in the rdataset
+ * before disassociating.
+ *
+ * Requires:
+ *\li 'rdataset' is a valid, associated rdataset.
+ *
+ * Ensures:
+ *\li 'rdataset' is a valid, disassociated rdataset.
+ */
+
+bool
+dns_rdataset_isassociated(dns_rdataset_t *rdataset);
+/*%<
+ * Is 'rdataset' associated?
+ *
+ * Requires:
+ *\li 'rdataset' is a valid rdataset.
+ *
+ * Returns:
+ *\li #true 'rdataset' is associated.
+ *\li #false 'rdataset' is not associated.
+ */
+
+void
+dns_rdataset_makequestion(dns_rdataset_t *rdataset, dns_rdataclass_t rdclass,
+ dns_rdatatype_t type);
+/*%<
+ * Make 'rdataset' a valid, associated, question rdataset, with a
+ * question class of 'rdclass' and type 'type'.
+ *
+ * Notes:
+ *\li Question rdatasets have a class and type, but no rdata.
+ *
+ * Requires:
+ *\li 'rdataset' is a valid, disassociated rdataset.
+ *
+ * Ensures:
+ *\li 'rdataset' is a valid, associated, question rdataset.
+ */
+
+void
+dns_rdataset_clone(dns_rdataset_t *source, dns_rdataset_t *target);
+/*%<
+ * Make 'target' refer to the same rdataset as 'source'.
+ *
+ * Requires:
+ *\li 'source' is a valid, associated rdataset.
+ *
+ *\li 'target' is a valid, dissociated rdataset.
+ *
+ * Ensures:
+ *\li 'target' references the same rdataset as 'source'.
+ */
+
+unsigned int
+dns_rdataset_count(dns_rdataset_t *rdataset);
+/*%<
+ * Return the number of records in 'rdataset'.
+ *
+ * Requires:
+ *\li 'rdataset' is a valid, associated rdataset.
+ *
+ * Returns:
+ *\li The number of records in 'rdataset'.
+ */
+
+isc_result_t
+dns_rdataset_first(dns_rdataset_t *rdataset);
+/*%<
+ * Move the rdata cursor to the first rdata in the rdataset (if any).
+ *
+ * Requires:
+ *\li 'rdataset' is a valid, associated rdataset.
+ *
+ * Returns:
+ *\li #ISC_R_SUCCESS
+ *\li #ISC_R_NOMORE There are no rdata in the set.
+ */
+
+isc_result_t
+dns_rdataset_next(dns_rdataset_t *rdataset);
+/*%<
+ * Move the rdata cursor to the next rdata in the rdataset (if any).
+ *
+ * Requires:
+ *\li 'rdataset' is a valid, associated rdataset.
+ *
+ * Returns:
+ *\li #ISC_R_SUCCESS
+ *\li #ISC_R_NOMORE There are no more rdata in the set.
+ */
+
+void
+dns_rdataset_current(dns_rdataset_t *rdataset, dns_rdata_t *rdata);
+/*%<
+ * Make 'rdata' refer to the current rdata.
+ *
+ * Notes:
+ *
+ *\li The data returned in 'rdata' is valid for the life of the
+ * rdataset; in particular, subsequent changes in the cursor position
+ * do not invalidate 'rdata'.
+ *
+ * Requires:
+ *\li 'rdataset' is a valid, associated rdataset.
+ *
+ *\li The rdata cursor of 'rdataset' is at a valid location (i.e. the
+ * result of last call to a cursor movement command was ISC_R_SUCCESS).
+ *
+ * Ensures:
+ *\li 'rdata' refers to the rdata at the rdata cursor location of
+ *\li 'rdataset'.
+ */
+
+isc_result_t
+dns_rdataset_totext(dns_rdataset_t *rdataset, const dns_name_t *owner_name,
+ bool omit_final_dot, bool question, isc_buffer_t *target);
+/*%<
+ * Convert 'rdataset' to text format, storing the result in 'target'.
+ *
+ * Notes:
+ *\li The rdata cursor position will be changed.
+ *
+ *\li The 'question' flag should normally be #false. If it is
+ * #true, the TTL and rdata fields are not printed. This is
+ * for use when printing an rdata representing a question section.
+ *
+ *\li This interface is deprecated; use dns_master_rdatasettottext()
+ * and/or dns_master_questiontotext() instead.
+ *
+ * Requires:
+ *\li 'rdataset' is a valid rdataset.
+ *
+ *\li 'rdataset' is not empty.
+ */
+
+isc_result_t
+dns_rdataset_towire(dns_rdataset_t *rdataset, const dns_name_t *owner_name,
+ dns_compress_t *cctx, isc_buffer_t *target,
+ unsigned int options, unsigned int *countp);
+/*%<
+ * Convert 'rdataset' to wire format, compressing names as specified
+ * in 'cctx', and storing the result in 'target'.
+ *
+ * Notes:
+ *\li The rdata cursor position will be changed.
+ *
+ *\li The number of RRs added to target will be added to *countp.
+ *
+ * Requires:
+ *\li 'rdataset' is a valid rdataset.
+ *
+ *\li 'rdataset' is not empty.
+ *
+ *\li 'countp' is a valid pointer.
+ *
+ * Ensures:
+ *\li On a return of ISC_R_SUCCESS, 'target' contains a wire format
+ * for the data contained in 'rdataset'. Any error return leaves
+ * the buffer unchanged.
+ *
+ *\li *countp has been incremented by the number of RRs added to
+ * target.
+ *
+ * Returns:
+ *\li #ISC_R_SUCCESS - all ok
+ *\li #ISC_R_NOSPACE - 'target' doesn't have enough room
+ *
+ *\li Any error returned by dns_rdata_towire(), dns_rdataset_next(),
+ * dns_name_towire().
+ */
+
+isc_result_t
+dns_rdataset_towiresorted(dns_rdataset_t *rdataset,
+ const dns_name_t *owner_name, dns_compress_t *cctx,
+ isc_buffer_t *target, dns_rdatasetorderfunc_t order,
+ const void *order_arg, unsigned int options,
+ unsigned int *countp);
+/*%<
+ * Like dns_rdataset_towire(), but sorting the rdatasets according to
+ * the integer value returned by 'order' when called with the rdataset
+ * and 'order_arg' as arguments.
+ *
+ * Requires:
+ *\li All the requirements of dns_rdataset_towire(), and
+ * that order_arg is NULL if and only if order is NULL.
+ */
+
+isc_result_t
+dns_rdataset_towirepartial(dns_rdataset_t *rdataset,
+ const dns_name_t *owner_name, dns_compress_t *cctx,
+ isc_buffer_t *target, dns_rdatasetorderfunc_t order,
+ const void *order_arg, unsigned int options,
+ unsigned int *countp, void **state);
+/*%<
+ * Like dns_rdataset_towiresorted() except that a partial rdataset
+ * may be written.
+ *
+ * Requires:
+ *\li All the requirements of dns_rdataset_towiresorted().
+ * If 'state' is non NULL then the current position in the
+ * rdataset will be remembered if the rdataset in not
+ * completely written and should be passed on on subsequent
+ * calls (NOT CURRENTLY IMPLEMENTED).
+ *
+ * Returns:
+ *\li #ISC_R_SUCCESS if all of the records were written.
+ *\li #ISC_R_NOSPACE if unable to fit in all of the records. *countp
+ * will be updated to reflect the number of records
+ * written.
+ */
+
+isc_result_t
+dns_rdataset_additionaldata(dns_rdataset_t *rdataset,
+ const dns_name_t *owner_name,
+ dns_additionaldatafunc_t add, void *arg);
+/*%<
+ * For each rdata in rdataset, call 'add' for each name and type in the
+ * rdata which is subject to additional section processing.
+ *
+ * Requires:
+ *
+ *\li 'rdataset' is a valid, non-question rdataset.
+ *
+ *\li 'add' is a valid dns_additionaldatafunc_t
+ *
+ * Ensures:
+ *
+ *\li If successful, dns_rdata_additionaldata() will have been called for
+ * each rdata in 'rdataset'.
+ *
+ *\li If a call to dns_rdata_additionaldata() is not successful, the
+ * result returned will be the result of dns_rdataset_additionaldata().
+ *
+ * Returns:
+ *
+ *\li #ISC_R_SUCCESS
+ *
+ *\li Any error that dns_rdata_additionaldata() can return.
+ */
+
+isc_result_t
+dns_rdataset_getnoqname(dns_rdataset_t *rdataset, dns_name_t *name,
+ dns_rdataset_t *neg, dns_rdataset_t *negsig);
+/*%<
+ * Return the noqname proof for this record.
+ *
+ * Requires:
+ *\li 'rdataset' to be valid and #DNS_RDATASETATTR_NOQNAME to be set.
+ *\li 'name' to be valid.
+ *\li 'neg' and 'negsig' to be valid and not associated.
+ */
+
+isc_result_t
+dns_rdataset_addnoqname(dns_rdataset_t *rdataset, dns_name_t *name);
+/*%<
+ * Associate a noqname proof with this record.
+ * Sets #DNS_RDATASETATTR_NOQNAME if successful.
+ * Adjusts the 'rdataset->ttl' to minimum of the 'rdataset->ttl' and
+ * the 'nsec'/'nsec3' and 'rrsig(nsec)'/'rrsig(nsec3)' ttl.
+ *
+ * Requires:
+ *\li 'rdataset' to be valid and #DNS_RDATASETATTR_NOQNAME to be set.
+ *\li 'name' to be valid and have NSEC or NSEC3 and associated RRSIG
+ * rdatasets.
+ */
+
+isc_result_t
+dns_rdataset_getclosest(dns_rdataset_t *rdataset, dns_name_t *name,
+ dns_rdataset_t *nsec, dns_rdataset_t *nsecsig);
+/*%<
+ * Return the closest encloser for this record.
+ *
+ * Requires:
+ *\li 'rdataset' to be valid and #DNS_RDATASETATTR_CLOSEST to be set.
+ *\li 'name' to be valid.
+ *\li 'nsec' and 'nsecsig' to be valid and not associated.
+ */
+
+isc_result_t
+dns_rdataset_addclosest(dns_rdataset_t *rdataset, const dns_name_t *name);
+/*%<
+ * Associate a closest encloset proof with this record.
+ * Sets #DNS_RDATASETATTR_CLOSEST if successful.
+ * Adjusts the 'rdataset->ttl' to minimum of the 'rdataset->ttl' and
+ * the 'nsec' and 'rrsig(nsec)' ttl.
+ *
+ * Requires:
+ *\li 'rdataset' to be valid and #DNS_RDATASETATTR_CLOSEST to be set.
+ *\li 'name' to be valid and have NSEC3 and RRSIG(NSEC3) rdatasets.
+ */
+
+void
+dns_rdataset_settrust(dns_rdataset_t *rdataset, dns_trust_t trust);
+/*%<
+ * Set the trust of the 'rdataset' to trust in any in the backing database.
+ * The local trust level of 'rdataset' is also set.
+ */
+
+void
+dns_rdataset_expire(dns_rdataset_t *rdataset);
+/*%<
+ * Mark the rdataset to be expired in the backing database.
+ */
+
+void
+dns_rdataset_clearprefetch(dns_rdataset_t *rdataset);
+/*%<
+ * Clear the PREFETCH attribute for the given rdataset in the
+ * underlying database.
+ *
+ * In the cache database, this signals that the rdataset is not
+ * eligible to be prefetched when the TTL is close to expiring.
+ * It has no function in other databases.
+ */
+
+void
+dns_rdataset_setownercase(dns_rdataset_t *rdataset, const dns_name_t *name);
+/*%<
+ * Store the casing of 'name', the owner name of 'rdataset', into
+ * a bitfield so that the name can be capitalized the same when when
+ * the rdataset is used later. This sets the CASESET attribute.
+ */
+
+void
+dns_rdataset_getownercase(const dns_rdataset_t *rdataset, dns_name_t *name);
+/*%<
+ * If the CASESET attribute is set, retrieve the case bitfield that was
+ * previously stored by dns_rdataset_getownername(), and capitalize 'name'
+ * according to it. If CASESET is not set, do nothing.
+ */
+
+isc_result_t
+dns_rdataset_addglue(dns_rdataset_t *rdataset, dns_dbversion_t *version,
+ dns_message_t *msg);
+/*%<
+ * Add glue records for rdataset to the additional section of message in
+ * 'msg'. 'rdataset' must be of type NS.
+ *
+ * In case a successful result is not returned, the caller should try to
+ * add glue directly to the message by iterating for additional data.
+ *
+ * Requires:
+ * \li 'rdataset' is a valid NS rdataset.
+ * \li 'version' is the DB version.
+ * \li 'msg' is the DNS message to which the glue should be added.
+ *
+ * Returns:
+ *\li #ISC_R_SUCCESS
+ *\li #ISC_R_NOTIMPLEMENTED
+ *\li #ISC_R_FAILURE
+ *\li Any error that dns_rdata_additionaldata() can return.
+ */
+
+void
+dns_rdataset_trimttl(dns_rdataset_t *rdataset, dns_rdataset_t *sigrdataset,
+ dns_rdata_rrsig_t *rrsig, isc_stdtime_t now,
+ bool acceptexpired);
+/*%<
+ * Trim the ttl of 'rdataset' and 'sigrdataset' so that they will expire
+ * at or before 'rrsig->expiretime'. If 'acceptexpired' is true and the
+ * signature has expired or will expire in the next 120 seconds, limit
+ * the ttl to be no more than 120 seconds.
+ *
+ * The ttl is further limited by the original ttl as stored in 'rrsig'
+ * and the original ttl values of 'rdataset' and 'sigrdataset'.
+ *
+ * Requires:
+ * \li 'rdataset' is a valid rdataset.
+ * \li 'sigrdataset' is a valid rdataset.
+ * \li 'rrsig' is non NULL.
+ */
+
+const char *
+dns_trust_totext(dns_trust_t trust);
+/*%<
+ * Display trust in textual form.
+ */
+
+ISC_LANG_ENDDECLS
diff --git a/lib/dns/include/dns/rdatasetiter.h b/lib/dns/include/dns/rdatasetiter.h
new file mode 100644
index 0000000..8611d99
--- /dev/null
+++ b/lib/dns/include/dns/rdatasetiter.h
@@ -0,0 +1,161 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#pragma once
+
+/*****
+***** Module Info
+*****/
+
+/*! \file dns/rdatasetiter.h
+ * \brief
+ * The DNS Rdataset Iterator interface allows iteration of all of the
+ * rdatasets at a node.
+ *
+ * The dns_rdatasetiter_t type is like a "virtual class". To actually use
+ * it, an implementation of the class is required. This implementation is
+ * supplied by the database.
+ *
+ * It is the client's responsibility to call dns_rdataset_disassociate()
+ * on all rdatasets returned.
+ *
+ * XXX more XXX
+ *
+ * MP:
+ *\li The iterator itself is not locked. The caller must ensure
+ * synchronization.
+ *
+ *\li The iterator methods ensure appropriate database locking.
+ *
+ * Reliability:
+ *\li No anticipated impact.
+ *
+ * Resources:
+ *\li TBS
+ *
+ * Security:
+ *\li No anticipated impact.
+ *
+ * Standards:
+ *\li None.
+ */
+
+/*****
+***** Imports
+*****/
+
+#include <isc/lang.h>
+#include <isc/magic.h>
+#include <isc/stdtime.h>
+
+#include <dns/types.h>
+
+ISC_LANG_BEGINDECLS
+
+/*****
+***** Types
+*****/
+
+typedef struct dns_rdatasetitermethods {
+ void (*destroy)(dns_rdatasetiter_t **iteratorp);
+ isc_result_t (*first)(dns_rdatasetiter_t *iterator);
+ isc_result_t (*next)(dns_rdatasetiter_t *iterator);
+ void (*current)(dns_rdatasetiter_t *iterator, dns_rdataset_t *rdataset);
+} dns_rdatasetitermethods_t;
+
+#define DNS_RDATASETITER_MAGIC ISC_MAGIC('D', 'N', 'S', 'i')
+#define DNS_RDATASETITER_VALID(i) ISC_MAGIC_VALID(i, DNS_RDATASETITER_MAGIC)
+
+/*%
+ * This structure is actually just the common prefix of a DNS db
+ * implementation's version of a dns_rdatasetiter_t.
+ * \brief
+ * Direct use of this structure by clients is forbidden. DB implementations
+ * may change the structure. 'magic' must be #DNS_RDATASETITER_MAGIC for
+ * any of the dns_rdatasetiter routines to work. DB implementations must
+ * maintain all DB rdataset iterator invariants.
+ */
+struct dns_rdatasetiter {
+ /* Unlocked. */
+ unsigned int magic;
+ dns_rdatasetitermethods_t *methods;
+ dns_db_t *db;
+ dns_dbnode_t *node;
+ dns_dbversion_t *version;
+ isc_stdtime_t now;
+ unsigned int options;
+};
+
+void
+dns_rdatasetiter_destroy(dns_rdatasetiter_t **iteratorp);
+/*%<
+ * Destroy '*iteratorp'.
+ *
+ * Requires:
+ *
+ *\li '*iteratorp' is a valid iterator.
+ *
+ * Ensures:
+ *
+ *\li All resources used by the iterator are freed.
+ *
+ *\li *iteratorp == NULL.
+ */
+
+isc_result_t
+dns_rdatasetiter_first(dns_rdatasetiter_t *iterator);
+/*%<
+ * Move the rdataset cursor to the first rdataset at the node (if any).
+ *
+ * Requires:
+ *\li 'iterator' is a valid iterator.
+ *
+ * Returns:
+ *\li ISC_R_SUCCESS
+ *\li ISC_R_NOMORE There are no rdatasets at the node.
+ *
+ *\li Other results are possible, depending on the DB implementation.
+ */
+
+isc_result_t
+dns_rdatasetiter_next(dns_rdatasetiter_t *iterator);
+/*%<
+ * Move the rdataset cursor to the next rdataset at the node (if any).
+ *
+ * Requires:
+ *\li 'iterator' is a valid iterator.
+ *
+ * Returns:
+ *\li ISC_R_SUCCESS
+ *\li ISC_R_NOMORE There are no more rdatasets at the
+ * node.
+ *
+ *\li Other results are possible, depending on the DB implementation.
+ */
+
+void
+dns_rdatasetiter_current(dns_rdatasetiter_t *iterator,
+ dns_rdataset_t *rdataset);
+/*%<
+ * Return the current rdataset.
+ *
+ * Requires:
+ *\li 'iterator' is a valid iterator.
+ *
+ *\li 'rdataset' is a valid, disassociated rdataset.
+ *
+ *\li The rdataset cursor of 'iterator' is at a valid location (i.e. the
+ * result of last call to a cursor movement command was #ISC_R_SUCCESS).
+ */
+
+ISC_LANG_ENDDECLS
diff --git a/lib/dns/include/dns/rdataslab.h b/lib/dns/include/dns/rdataslab.h
new file mode 100644
index 0000000..7364b8d
--- /dev/null
+++ b/lib/dns/include/dns/rdataslab.h
@@ -0,0 +1,170 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#pragma once
+
+/*! \file dns/rdataslab.h
+ * \brief
+ * Implements storage of rdatasets into slabs of memory.
+ *
+ * MP:
+ *\li Clients of this module must impose any required synchronization.
+ *
+ * Reliability:
+ *\li This module deals with low-level byte streams. Errors in any of
+ * the functions are likely to crash the server or corrupt memory.
+ *
+ *\li If the caller passes invalid memory references, these functions are
+ * likely to crash the server or corrupt memory.
+ *
+ * Resources:
+ *\li None.
+ *
+ * Security:
+ *\li None.
+ *
+ * Standards:
+ *\li None.
+ */
+
+/***
+ *** Imports
+ ***/
+
+#include <stdbool.h>
+
+#include <isc/lang.h>
+
+#include <dns/types.h>
+
+ISC_LANG_BEGINDECLS
+
+#define DNS_RDATASLAB_FORCE 0x1
+#define DNS_RDATASLAB_EXACT 0x2
+
+#define DNS_RDATASLAB_OFFLINE 0x01 /* RRSIG is for offline DNSKEY */
+#define DNS_RDATASLAB_WARNMASK \
+ 0x0E /*%< RRSIG(DNSKEY) expired \
+ * warnings number mask. */
+#define DNS_RDATASLAB_WARNSHIFT \
+ 1 /*%< How many bits to shift to find \
+ * remaining expired warning number. */
+
+/***
+ *** Functions
+ ***/
+
+isc_result_t
+dns_rdataslab_fromrdataset(dns_rdataset_t *rdataset, isc_mem_t *mctx,
+ isc_region_t *region, unsigned int reservelen);
+/*%<
+ * Slabify a rdataset. The slab area will be allocated and returned
+ * in 'region'.
+ *
+ * Requires:
+ *\li 'rdataset' is valid.
+ *
+ * Ensures:
+ *\li 'region' will have base pointing to the start of allocated memory,
+ * with the slabified region beginning at region->base + reservelen.
+ * region->length contains the total length allocated.
+ *
+ * Returns:
+ *\li ISC_R_SUCCESS - successful completion
+ *\li ISC_R_NOMEMORY - no memory.
+ *\li XXX others
+ */
+
+unsigned int
+dns_rdataslab_size(unsigned char *slab, unsigned int reservelen);
+/*%<
+ * Return the total size of an rdataslab.
+ *
+ * Requires:
+ *\li 'slab' points to a slab.
+ *
+ * Returns:
+ *\li The number of bytes in the slab, including the reservelen.
+ */
+
+unsigned int
+dns_rdataslab_rdatasize(unsigned char *slab, unsigned int reservelen);
+/*%<
+ * Return the size of the rdata in an rdataslab.
+ *
+ * Requires:
+ *\li 'slab' points to a slab.
+ */
+
+unsigned int
+dns_rdataslab_count(unsigned char *slab, unsigned int reservelen);
+/*%<
+ * Return the number of records in the rdataslab
+ *
+ * Requires:
+ *\li 'slab' points to a slab.
+ *
+ * Returns:
+ *\li The number of records in the slab.
+ */
+
+isc_result_t
+dns_rdataslab_merge(unsigned char *oslab, unsigned char *nslab,
+ unsigned int reservelen, isc_mem_t *mctx,
+ dns_rdataclass_t rdclass, dns_rdatatype_t type,
+ unsigned int flags, unsigned char **tslabp);
+/*%<
+ * Merge 'oslab' and 'nslab'.
+ */
+
+isc_result_t
+dns_rdataslab_subtract(unsigned char *mslab, unsigned char *sslab,
+ unsigned int reservelen, isc_mem_t *mctx,
+ dns_rdataclass_t rdclass, dns_rdatatype_t type,
+ unsigned int flags, unsigned char **tslabp);
+/*%<
+ * Subtract 'sslab' from 'mslab'. If 'exact' is true then all elements
+ * of 'sslab' must exist in 'mslab'.
+ *
+ * XXX
+ * valid flags are DNS_RDATASLAB_EXACT
+ */
+
+bool
+dns_rdataslab_equal(unsigned char *slab1, unsigned char *slab2,
+ unsigned int reservelen);
+/*%<
+ * Compare two rdataslabs for equality. This does _not_ do a full
+ * DNSSEC comparison.
+ *
+ * Requires:
+ *\li 'slab1' and 'slab2' point to slabs.
+ *
+ * Returns:
+ *\li true if the slabs are equal, false otherwise.
+ */
+bool
+dns_rdataslab_equalx(unsigned char *slab1, unsigned char *slab2,
+ unsigned int reservelen, dns_rdataclass_t rdclass,
+ dns_rdatatype_t type);
+/*%<
+ * Compare two rdataslabs for DNSSEC equality.
+ *
+ * Requires:
+ *\li 'slab1' and 'slab2' point to slabs.
+ *
+ * Returns:
+ *\li true if the slabs are equal, #false otherwise.
+ */
+
+ISC_LANG_ENDDECLS
diff --git a/lib/dns/include/dns/rdatatype.h b/lib/dns/include/dns/rdatatype.h
new file mode 100644
index 0000000..9590752
--- /dev/null
+++ b/lib/dns/include/dns/rdatatype.h
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#pragma once
+
+/*! \file dns/rdatatype.h */
+
+#include <isc/lang.h>
+
+#include <dns/types.h>
+
+ISC_LANG_BEGINDECLS
+
+isc_result_t
+dns_rdatatype_fromtext(dns_rdatatype_t *typep, isc_textregion_t *source);
+/*%<
+ * Convert the text 'source' refers to into a DNS rdata type.
+ *
+ * Requires:
+ *\li 'typep' is a valid pointer.
+ *
+ *\li 'source' is a valid text region.
+ *
+ * Returns:
+ *\li ISC_R_SUCCESS on success
+ *\li DNS_R_UNKNOWN type is unknown
+ */
+
+isc_result_t
+dns_rdatatype_totext(dns_rdatatype_t type, isc_buffer_t *target);
+/*%<
+ * Put a textual representation of type 'type' into 'target'.
+ *
+ * Requires:
+ *\li 'type' is a valid type.
+ *
+ *\li 'target' is a valid text buffer.
+ *
+ * Ensures,
+ * if the result is success:
+ *\li The used space in 'target' is updated.
+ *
+ * Returns:
+ *\li #ISC_R_SUCCESS on success
+ *\li #ISC_R_NOSPACE target buffer is too small
+ */
+
+isc_result_t
+dns_rdatatype_tounknowntext(dns_rdatatype_t type, isc_buffer_t *target);
+/*%<
+ * Put textual RFC3597 TYPEXXXX representation of type 'type' into
+ * 'target'.
+ *
+ * Requires:
+ *\li 'type' is a valid type.
+ *
+ *\li 'target' is a valid text buffer.
+ *
+ * Ensures,
+ * if the result is success:
+ *\li The used space in 'target' is updated.
+ *
+ * Returns:
+ *\li #ISC_R_SUCCESS on success
+ *\li #ISC_R_NOSPACE target buffer is too small
+ */
+
+void
+dns_rdatatype_format(dns_rdatatype_t rdtype, char *array, unsigned int size);
+/*%<
+ * Format a human-readable representation of the type 'rdtype'
+ * into the character array 'array', which is of size 'size'.
+ * The resulting string is guaranteed to be null-terminated.
+ */
+
+#define DNS_RDATATYPE_FORMATSIZE sizeof("NSEC3PARAM")
+
+/*%<
+ * Minimum size of array to pass to dns_rdatatype_format().
+ * May need to be adjusted if a new RR type with a very long
+ * name is defined.
+ */
+
+ISC_LANG_ENDDECLS
diff --git a/lib/dns/include/dns/request.h b/lib/dns/include/dns/request.h
new file mode 100644
index 0000000..d00574f
--- /dev/null
+++ b/lib/dns/include/dns/request.h
@@ -0,0 +1,321 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#pragma once
+
+/*****
+***** Module Info
+*****/
+
+/*! \file dns/request.h
+ *
+ * \brief
+ * The request module provides simple request/response services useful for
+ * sending SOA queries, DNS Notify messages, and dynamic update requests.
+ *
+ * MP:
+ *\li The module ensures appropriate synchronization of data structures it
+ * creates and manipulates.
+ *
+ * Resources:
+ *\li TBS
+ *
+ * Security:
+ *\li No anticipated impact.
+ */
+
+#include <stdbool.h>
+
+#include <isc/event.h>
+#include <isc/lang.h>
+
+#include <dns/types.h>
+
+#define DNS_REQUESTOPT_TCP 0x00000001U
+#define DNS_REQUESTOPT_CASE 0x00000002U
+#define DNS_REQUESTOPT_FIXEDID 0x00000004U
+
+typedef struct dns_requestevent {
+ ISC_EVENT_COMMON(struct dns_requestevent);
+ isc_result_t result;
+ dns_request_t *request;
+} dns_requestevent_t;
+
+ISC_LANG_BEGINDECLS
+
+isc_result_t
+dns_requestmgr_create(isc_mem_t *mctx, isc_taskmgr_t *taskmgr,
+ dns_dispatchmgr_t *dispatchmgr,
+ dns_dispatch_t *dispatchv4, dns_dispatch_t *dispatchv6,
+ dns_requestmgr_t **requestmgrp);
+/*%<
+ * Create a request manager.
+ *
+ * Requires:
+ *
+ *\li 'mctx' is a valid memory context.
+ *
+ *\li 'socketmgr' is a valid socket manager.
+ *
+ *\li 'taskmgr' is a valid task manager.
+ *
+ *\li 'dispatchv4' is a valid dispatcher with an IPv4 UDP socket, or is NULL.
+ *
+ *\li 'dispatchv6' is a valid dispatcher with an IPv6 UDP socket, or is NULL.
+ *
+ *\li requestmgrp != NULL && *requestmgrp == NULL
+ *
+ * Ensures:
+ *
+ *\li On success, *requestmgrp is a valid request manager.
+ *
+ * Returns:
+ *
+ *\li ISC_R_SUCCESS
+ *
+ *\li Any other result indicates failure.
+ */
+
+void
+dns_requestmgr_whenshutdown(dns_requestmgr_t *requestmgr, isc_task_t *task,
+ isc_event_t **eventp);
+/*%<
+ * Send '*eventp' to 'task' when 'requestmgr' has completed shutdown.
+ *
+ * Notes:
+ *
+ *\li It is not safe to detach the last reference to 'requestmgr' until
+ * shutdown is complete.
+ *
+ * Requires:
+ *
+ *\li 'requestmgr' is a valid request manager.
+ *
+ *\li 'task' is a valid task.
+ *
+ *\li *eventp is a valid event.
+ *
+ * Ensures:
+ *
+ *\li *eventp == NULL.
+ */
+
+void
+dns_requestmgr_shutdown(dns_requestmgr_t *requestmgr);
+/*%<
+ * Start the shutdown process for 'requestmgr'.
+ *
+ * Notes:
+ *
+ *\li This call has no effect if the request manager is already shutting
+ * down.
+ *
+ * Requires:
+ *
+ *\li 'requestmgr' is a valid requestmgr.
+ */
+
+void
+dns_requestmgr_attach(dns_requestmgr_t *source, dns_requestmgr_t **targetp);
+/*%<
+ * Attach to the request manager. dns_requestmgr_shutdown() must not
+ * have been called on 'source' prior to calling dns_requestmgr_attach().
+ *
+ * Requires:
+ *
+ *\li 'source' is a valid requestmgr.
+ *
+ *\li 'targetp' to be non NULL and '*targetp' to be NULL.
+ */
+
+void
+dns_requestmgr_detach(dns_requestmgr_t **requestmgrp);
+/*%<
+ * Detach from the given requestmgr. If this is the final detach
+ * requestmgr will be destroyed. dns_requestmgr_shutdown() must
+ * be called before the final detach.
+ *
+ * Requires:
+ *
+ *\li '*requestmgrp' is a valid requestmgr.
+ *
+ * Ensures:
+ *\li '*requestmgrp' is NULL.
+ */
+
+isc_result_t
+dns_request_create(dns_requestmgr_t *requestmgr, dns_message_t *message,
+ const isc_sockaddr_t *srcaddr,
+ const isc_sockaddr_t *destaddr, unsigned int options,
+ dns_tsigkey_t *key, unsigned int timeout,
+ unsigned int udptimeout, unsigned int udpretries,
+ isc_task_t *task, isc_taskaction_t action, void *arg,
+ dns_request_t **requestp);
+/*%<
+ * Create and send a request.
+ *
+ * Notes:
+ *
+ *\li 'message' will be rendered and sent to 'address'. If the
+ * #DNS_REQUESTOPT_TCP option is set, TCP will be used,
+ * #DNS_REQUESTOPT_SHARE option is set too, connecting TCP
+ * (vs. connected) will be shared too. The request
+ * will timeout after 'timeout' seconds. UDP requests will be resent
+ * at 'udptimeout' intervals if non-zero or 'udpretries' is non-zero.
+ *
+ *\li If the #DNS_REQUESTOPT_CASE option is set, use case sensitive
+ * compression.
+ *
+ *\li When the request completes, successfully, due to a timeout, or
+ * because it was canceled, a completion event will be sent to 'task'.
+ *
+ * Requires:
+ *
+ *\li 'message' is a valid DNS message.
+ *
+ *\li 'dstaddr' is a valid sockaddr.
+ *
+ *\li 'srcaddr' is a valid sockaddr or NULL.
+ *
+ *\li 'srcaddr' and 'dstaddr' are the same protocol family.
+ *
+ *\li 'timeout' > 0
+ *
+ *\li 'task' is a valid task.
+ *
+ *\li requestp != NULL && *requestp == NULL
+ */
+
+isc_result_t
+dns_request_createraw(dns_requestmgr_t *requestmgr, isc_buffer_t *msgbuf,
+ const isc_sockaddr_t *srcaddr,
+ const isc_sockaddr_t *destaddr, unsigned int options,
+ unsigned int timeout, unsigned int udptimeout,
+ unsigned int udpretries, isc_task_t *task,
+ isc_taskaction_t action, void *arg,
+ dns_request_t **requestp);
+/*!<
+ * \brief Create and send a request.
+ *
+ * Notes:
+ *
+ *\li 'msgbuf' will be sent to 'destaddr' after setting the id. If the
+ * #DNS_REQUESTOPT_TCP option is set, TCP will be used,
+ * #DNS_REQUESTOPT_SHARE option is set too, connecting TCP
+ * (vs. connected) will be shared too. The request
+ * will timeout after 'timeout' seconds. UDP requests will be resent
+ * at 'udptimeout' intervals if non-zero or if 'udpretries' is not zero.
+ *
+ *\li When the request completes, successfully, due to a timeout, or
+ * because it was canceled, a completion event will be sent to 'task'.
+ *
+ * Requires:
+ *
+ *\li 'msgbuf' is a valid DNS message in compressed wire format.
+ *
+ *\li 'destaddr' is a valid sockaddr.
+ *
+ *\li 'srcaddr' is a valid sockaddr or NULL.
+ *
+ *\li 'srcaddr' and 'dstaddr' are the same protocol family.
+ *
+ *\li 'timeout' > 0
+ *
+ *\li 'task' is a valid task.
+ *
+ *\li requestp != NULL && *requestp == NULL
+ */
+
+void
+dns_request_cancel(dns_request_t *request);
+/*%<
+ * Cancel 'request'.
+ *
+ * Requires:
+ *
+ *\li 'request' is a valid request.
+ *
+ * Ensures:
+ *
+ *\li If the completion event for 'request' has not yet been sent, it
+ * will be sent, and the result code will be ISC_R_CANCELED.
+ */
+
+isc_result_t
+dns_request_getresponse(dns_request_t *request, dns_message_t *message,
+ unsigned int options);
+/*%<
+ * Get the response to 'request' by filling in 'message'.
+ *
+ * 'options' is passed to dns_message_parse(). See dns_message_parse()
+ * for more details.
+ *
+ * Requires:
+ *
+ *\li 'request' is a valid request for which the caller has received the
+ * completion event.
+ *
+ *\li The result code of the completion event was #ISC_R_SUCCESS.
+ *
+ * Returns:
+ *
+ *\li ISC_R_SUCCESS
+ *
+ *\li Any result that dns_message_parse() can return.
+ */
+isc_buffer_t *
+dns_request_getanswer(dns_request_t *request);
+/*
+ * Get the response to 'request' as a buffer.
+ *
+ * Requires:
+ *
+ *\li 'request' is a valid request for which the caller has received the
+ * completion event.
+ *
+ * Returns:
+ *
+ *\li a pointer to the answer buffer.
+ */
+
+bool
+dns_request_usedtcp(dns_request_t *request);
+/*%<
+ * Return whether this query used TCP or not. Setting #DNS_REQUESTOPT_TCP
+ * in the call to dns_request_create() will cause the function to return
+ * #true, otherwise the result is based on the query message size.
+ *
+ * Requires:
+ *\li 'request' is a valid request.
+ *
+ * Returns:
+ *\li true if TCP was used.
+ *\li false if UDP was used.
+ */
+
+void
+dns_request_destroy(dns_request_t **requestp);
+/*%<
+ * Destroy 'request'.
+ *
+ * Requires:
+ *
+ *\li 'request' is a valid request for which the caller has received the
+ * completion event.
+ *
+ * Ensures:
+ *
+ *\li *requestp == NULL
+ */
+
+ISC_LANG_ENDDECLS
diff --git a/lib/dns/include/dns/resolver.h b/lib/dns/include/dns/resolver.h
new file mode 100644
index 0000000..4de314c
--- /dev/null
+++ b/lib/dns/include/dns/resolver.h
@@ -0,0 +1,709 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#pragma once
+
+/*****
+***** Module Info
+*****/
+
+/*! \file dns/resolver.h
+ *
+ * \brief
+ * This is the BIND 9 resolver, the module responsible for resolving DNS
+ * requests by iteratively querying authoritative servers and following
+ * referrals. This is a "full resolver", not to be confused with
+ * the stub resolvers most people associate with the word "resolver".
+ * The full resolver is part of the caching name server or resolver
+ * daemon the stub resolver talks to.
+ *
+ * MP:
+ *\li The module ensures appropriate synchronization of data structures it
+ * creates and manipulates.
+ *
+ * Reliability:
+ *\li No anticipated impact.
+ *
+ * Resources:
+ *\li TBS
+ *
+ * Security:
+ *\li No anticipated impact.
+ *
+ * Standards:
+ *\li RFCs: 1034, 1035, 2181, TBS
+ *\li Drafts: TBS
+ */
+
+#include <inttypes.h>
+#include <stdbool.h>
+
+#include <isc/event.h>
+#include <isc/lang.h>
+#include <isc/stats.h>
+
+#include <dns/fixedname.h>
+#include <dns/types.h>
+
+ISC_LANG_BEGINDECLS
+
+/*%
+ * A dns_fetchevent_t is sent when a 'fetch' completes. Any of 'db',
+ * 'node', 'rdataset', and 'sigrdataset' may be bound. It is the
+ * receiver's responsibility to detach before freeing the event.
+ * \brief
+ * 'rdataset', 'sigrdataset', 'client' and 'id' are the values that were
+ * supplied when dns_resolver_createfetch() was called. They are returned
+ * to the caller so that they may be freed.
+ */
+typedef struct dns_fetchevent {
+ ISC_EVENT_COMMON(struct dns_fetchevent);
+ dns_fetch_t *fetch;
+ isc_result_t result;
+ dns_rdatatype_t qtype;
+ dns_db_t *db;
+ dns_dbnode_t *node;
+ dns_rdataset_t *rdataset;
+ dns_rdataset_t *sigrdataset;
+ dns_fixedname_t fname;
+ dns_name_t *foundname;
+ const isc_sockaddr_t *client;
+ dns_messageid_t id;
+ isc_result_t vresult;
+} dns_fetchevent_t;
+
+/*%
+ * The two quota types (fetches-per-zone and fetches-per-server)
+ */
+typedef enum { dns_quotatype_zone = 0, dns_quotatype_server } dns_quotatype_t;
+
+/*
+ * Options that modify how a 'fetch' is done.
+ */
+enum {
+ DNS_FETCHOPT_TCP = 1 << 0, /*%< Use TCP. */
+ DNS_FETCHOPT_UNSHARED = 1 << 1, /*%< See below. */
+ DNS_FETCHOPT_RECURSIVE = 1 << 2, /*%< Set RD? */
+ DNS_FETCHOPT_NOEDNS0 = 1 << 3, /*%< Do not use EDNS. */
+ DNS_FETCHOPT_FORWARDONLY = 1 << 4, /*%< Only use forwarders. */
+ DNS_FETCHOPT_NOVALIDATE = 1 << 5, /*%< Disable validation. */
+ DNS_FETCHOPT_WANTNSID = 1 << 6, /*%< Request NSID */
+ DNS_FETCHOPT_PREFETCH = 1 << 7, /*%< Do prefetch */
+ DNS_FETCHOPT_NOCDFLAG = 1 << 8, /*%< Don't set CD flag. */
+ DNS_FETCHOPT_NONTA = 1 << 9, /*%< Ignore NTA table. */
+ DNS_FETCHOPT_NOCACHED = 1 << 10, /*%< Force cache update. */
+ DNS_FETCHOPT_QMINIMIZE = 1 << 11, /*%< Use qname minimization. */
+ DNS_FETCHOPT_NOFOLLOW = 1 << 12, /*%< Don't retrieve the NS RRset
+ * from the child zone when a
+ * delegation is returned in
+ * response to a NS query. */
+ DNS_FETCHOPT_QMIN_STRICT = 1 << 13, /*%< Do not work around servers
+ * that return errors on
+ * non-empty terminals. */
+ DNS_FETCHOPT_QMIN_SKIP_IP6A = 1 << 14, /*%< Skip some labels when
+ * doing qname minimization
+ * on ip6.arpa. */
+ DNS_FETCHOPT_NOFORWARD = 1 << 15, /*%< Do not use forwarders if
+ * possible. */
+ DNS_FETCHOPT_TRYSTALE_ONTIMEOUT = 1 << 16,
+
+ /*% EDNS version bits: */
+ DNS_FETCHOPT_EDNSVERSIONSET = 1 << 23,
+ DNS_FETCHOPT_EDNSVERSIONSHIFT = 24,
+ DNS_FETCHOPT_EDNSVERSIONMASK = 0xff000000,
+};
+
+/*
+ * Upper bounds of class of query RTT (ms). Corresponds to
+ * dns_resstatscounter_queryrttX statistics counters.
+ */
+#define DNS_RESOLVER_QRYRTTCLASS0 10
+#define DNS_RESOLVER_QRYRTTCLASS0STR "10"
+#define DNS_RESOLVER_QRYRTTCLASS1 100
+#define DNS_RESOLVER_QRYRTTCLASS1STR "100"
+#define DNS_RESOLVER_QRYRTTCLASS2 500
+#define DNS_RESOLVER_QRYRTTCLASS2STR "500"
+#define DNS_RESOLVER_QRYRTTCLASS3 800
+#define DNS_RESOLVER_QRYRTTCLASS3STR "800"
+#define DNS_RESOLVER_QRYRTTCLASS4 1600
+#define DNS_RESOLVER_QRYRTTCLASS4STR "1600"
+
+/*
+ * XXXRTH Should this API be made semi-private? (I.e.
+ * _dns_resolver_create()).
+ */
+
+#define DNS_RESOLVER_CHECKNAMES 0x01
+#define DNS_RESOLVER_CHECKNAMESFAIL 0x02
+
+#define DNS_QMIN_MAXLABELS 7
+#define DNS_QMIN_MAX_NO_DELEGATION 3
+#define DNS_MAX_LABELS 127
+
+isc_result_t
+dns_resolver_create(dns_view_t *view, isc_taskmgr_t *taskmgr,
+ unsigned int ntasks, unsigned int ndisp, isc_nm_t *nm,
+ isc_timermgr_t *timermgr, unsigned int options,
+ dns_dispatchmgr_t *dispatchmgr, dns_dispatch_t *dispatchv4,
+ dns_dispatch_t *dispatchv6, dns_resolver_t **resp);
+
+/*%<
+ * Create a resolver.
+ *
+ * Notes:
+ *
+ *\li Generally, applications should not create a resolver directly, but
+ * should instead call dns_view_createresolver().
+ *
+ * Requires:
+ *
+ *\li 'view' is a valid view.
+ *
+ *\li 'taskmgr' is a valid task manager.
+ *
+ *\li 'ntasks' > 0.
+ *
+ *\li 'nm' is a valid network manager.
+ *
+ *\li 'timermgr' is a valid timer manager.
+ *
+ *\li 'dispatchv4' is a dispatch with an IPv4 UDP socket, or is NULL.
+ * If not NULL, 'ndisp' clones of it will be created by the resolver.
+ *
+ *\li 'dispatchv6' is a dispatch with an IPv6 UDP socket, or is NULL.
+ * If not NULL, 'ndisp' clones of it will be created by the resolver.
+ *
+ *\li resp != NULL && *resp == NULL.
+ *
+ * Returns:
+ *
+ *\li #ISC_R_SUCCESS On success.
+ *
+ *\li Anything else Failure.
+ */
+
+void
+dns_resolver_freeze(dns_resolver_t *res);
+/*%<
+ * Freeze resolver.
+ *
+ * Notes:
+ *
+ *\li Certain configuration changes cannot be made after the resolver
+ * is frozen. Fetches cannot be created until the resolver is frozen.
+ *
+ * Requires:
+ *
+ *\li 'res' is a valid resolver.
+ *
+ * Ensures:
+ *
+ *\li 'res' is frozen.
+ */
+
+void
+dns_resolver_prime(dns_resolver_t *res);
+/*%<
+ * Prime resolver.
+ *
+ * Notes:
+ *
+ *\li Resolvers which have a forwarding policy other than dns_fwdpolicy_only
+ * need to be primed with the root nameservers, otherwise the root
+ * nameserver hints data may be used indefinitely. This function requests
+ * that the resolver start a priming fetch, if it isn't already priming.
+ *
+ * Requires:
+ *
+ *\li 'res' is a valid, frozen resolver.
+ */
+
+void
+dns_resolver_whenshutdown(dns_resolver_t *res, isc_task_t *task,
+ isc_event_t **eventp);
+/*%<
+ * Send '*eventp' to 'task' when 'res' has completed shutdown.
+ *
+ * Notes:
+ *
+ *\li It is not safe to detach the last reference to 'res' until
+ * shutdown is complete.
+ *
+ * Requires:
+ *
+ *\li 'res' is a valid resolver.
+ *
+ *\li 'task' is a valid task.
+ *
+ *\li *eventp is a valid event.
+ *
+ * Ensures:
+ *
+ *\li *eventp == NULL.
+ */
+
+void
+dns_resolver_shutdown(dns_resolver_t *res);
+/*%<
+ * Start the shutdown process for 'res'.
+ *
+ * Notes:
+ *
+ *\li This call has no effect if the resolver is already shutting down.
+ *
+ * Requires:
+ *
+ *\li 'res' is a valid resolver.
+ */
+
+void
+dns_resolver_attach(dns_resolver_t *source, dns_resolver_t **targetp);
+
+void
+dns_resolver_detach(dns_resolver_t **resp);
+
+isc_result_t
+dns_resolver_createfetch(dns_resolver_t *res, const dns_name_t *name,
+ dns_rdatatype_t type, const dns_name_t *domain,
+ dns_rdataset_t *nameservers,
+ dns_forwarders_t *forwarders,
+ const isc_sockaddr_t *client, dns_messageid_t id,
+ unsigned int options, unsigned int depth,
+ isc_counter_t *qc, isc_task_t *task,
+ isc_taskaction_t action, void *arg,
+ dns_rdataset_t *rdataset, dns_rdataset_t *sigrdataset,
+ dns_fetch_t **fetchp);
+/*%<
+ * Recurse to answer a question.
+ *
+ * Notes:
+ *
+ *\li This call starts a query for 'name', type 'type'.
+ *
+ *\li The 'domain' is a parent domain of 'name' for which
+ * a set of name servers 'nameservers' is known. If no
+ * such name server information is available, set
+ * 'domain' and 'nameservers' to NULL.
+ *
+ *\li 'forwarders' is unimplemented, and subject to change when
+ * we figure out how selective forwarding will work.
+ *
+ *\li When the fetch completes (successfully or otherwise), a
+ * #DNS_EVENT_FETCHDONE event with action 'action' and arg 'arg' will be
+ * posted to 'task'.
+ *
+ *\li The values of 'rdataset' and 'sigrdataset' will be returned in
+ * the FETCHDONE event.
+ *
+ *\li 'client' and 'id' are used for duplicate query detection. '*client'
+ * must remain stable until after 'action' has been called or
+ * dns_resolver_cancelfetch() is called.
+ *
+ * Requires:
+ *
+ *\li 'res' is a valid resolver that has been frozen.
+ *
+ *\li 'name' is a valid name.
+ *
+ *\li 'type' is not a meta type other than ANY.
+ *
+ *\li 'domain' is a valid name or NULL.
+ *
+ *\li 'nameservers' is a valid NS rdataset (whose owner name is 'domain')
+ * iff. 'domain' is not NULL.
+ *
+ *\li 'forwarders' is NULL.
+ *
+ *\li 'client' is a valid sockaddr or NULL.
+ *
+ *\li 'options' contains valid options.
+ *
+ *\li 'rdataset' is a valid, disassociated rdataset.
+ *
+ *\li 'sigrdataset' is NULL, or is a valid, disassociated rdataset.
+ *
+ *\li fetchp != NULL && *fetchp == NULL.
+ *
+ * Returns:
+ *
+ *\li #ISC_R_SUCCESS Success
+ *\li #DNS_R_DUPLICATE
+ *\li #DNS_R_DROP
+ *
+ *\li Many other values are possible, all of which indicate failure.
+ */
+
+void
+dns_resolver_cancelfetch(dns_fetch_t *fetch);
+/*%<
+ * Cancel 'fetch'.
+ *
+ * Notes:
+ *
+ *\li If 'fetch' has not completed, post its FETCHDONE event with a
+ * result code of #ISC_R_CANCELED.
+ *
+ * Requires:
+ *
+ *\li 'fetch' is a valid fetch.
+ */
+
+void
+dns_resolver_destroyfetch(dns_fetch_t **fetchp);
+/*%<
+ * Destroy 'fetch'.
+ *
+ * Requires:
+ *
+ *\li '*fetchp' is a valid fetch.
+ *
+ *\li The caller has received the FETCHDONE event (either because the
+ * fetch completed or because dns_resolver_cancelfetch() was called).
+ *
+ * Ensures:
+ *
+ *\li *fetchp == NULL.
+ */
+
+void
+dns_resolver_logfetch(dns_fetch_t *fetch, isc_log_t *lctx,
+ isc_logcategory_t *category, isc_logmodule_t *module,
+ int level, bool duplicateok);
+/*%<
+ * Dump a log message on internal state at the completion of given 'fetch'.
+ * 'lctx', 'category', 'module', and 'level' are used to write the log message.
+ * By default, only one log message is written even if the corresponding fetch
+ * context serves multiple clients; if 'duplicateok' is true the suppression
+ * is disabled and the message can be written every time this function is
+ * called.
+ *
+ * Requires:
+ *
+ *\li 'fetch' is a valid fetch, and has completed.
+ */
+
+dns_dispatchmgr_t *
+dns_resolver_dispatchmgr(dns_resolver_t *resolver);
+
+dns_dispatch_t *
+dns_resolver_dispatchv4(dns_resolver_t *resolver);
+
+dns_dispatch_t *
+dns_resolver_dispatchv6(dns_resolver_t *resolver);
+
+isc_taskmgr_t *
+dns_resolver_taskmgr(dns_resolver_t *resolver);
+
+uint32_t
+dns_resolver_getlamettl(dns_resolver_t *resolver);
+/*%<
+ * Get the resolver's lame-ttl. zero => no lame processing.
+ *
+ * Requires:
+ *\li 'resolver' to be valid.
+ */
+
+void
+dns_resolver_setlamettl(dns_resolver_t *resolver, uint32_t lame_ttl);
+/*%<
+ * Set the resolver's lame-ttl. zero => no lame processing.
+ *
+ * Requires:
+ *\li 'resolver' to be valid.
+ */
+
+void
+dns_resolver_addalternate(dns_resolver_t *resolver, const isc_sockaddr_t *alt,
+ const dns_name_t *name, in_port_t port);
+/*%<
+ * Add alternate addresses to be tried in the event that the nameservers
+ * for a zone are not available in the address families supported by the
+ * operating system.
+ *
+ * Require:
+ * \li only one of 'name' or 'alt' to be valid.
+ */
+
+void
+dns_resolver_setudpsize(dns_resolver_t *resolver, uint16_t udpsize);
+/*%<
+ * Set the EDNS UDP buffer size advertised by the server.
+ */
+
+uint16_t
+dns_resolver_getudpsize(dns_resolver_t *resolver);
+/*%<
+ * Get the current EDNS UDP buffer size.
+ */
+
+void
+dns_resolver_reset_algorithms(dns_resolver_t *resolver);
+/*%<
+ * Clear the disabled DNSSEC algorithms.
+ */
+
+void
+dns_resolver_reset_ds_digests(dns_resolver_t *resolver);
+/*%<
+ * Clear the disabled DS digest types.
+ */
+
+isc_result_t
+dns_resolver_disable_algorithm(dns_resolver_t *resolver, const dns_name_t *name,
+ unsigned int alg);
+/*%<
+ * Mark the given DNSSEC algorithm as disabled and below 'name'.
+ * Valid algorithms are less than 256.
+ *
+ * Returns:
+ *\li #ISC_R_SUCCESS
+ *\li #ISC_R_RANGE
+ *\li #ISC_R_NOMEMORY
+ */
+
+isc_result_t
+dns_resolver_disable_ds_digest(dns_resolver_t *resolver, const dns_name_t *name,
+ unsigned int digest_type);
+/*%<
+ * Mark the given DS digest type as disabled and below 'name'.
+ * Valid types are less than 256.
+ *
+ * Returns:
+ *\li #ISC_R_SUCCESS
+ *\li #ISC_R_RANGE
+ *\li #ISC_R_NOMEMORY
+ */
+
+bool
+dns_resolver_algorithm_supported(dns_resolver_t *resolver,
+ const dns_name_t *name, unsigned int alg);
+/*%<
+ * Check if the given algorithm is supported by this resolver.
+ * This checks whether the algorithm has been disabled via
+ * dns_resolver_disable_algorithm(), then checks the underlying
+ * crypto libraries if it was not specifically disabled.
+ */
+
+bool
+dns_resolver_ds_digest_supported(dns_resolver_t *resolver,
+ const dns_name_t *name,
+ unsigned int digest_type);
+/*%<
+ * Check if the given digest type is supported by this resolver.
+ * This checks whether the digest type has been disabled via
+ * dns_resolver_disable_ds_digest(), then checks the underlying
+ * crypto libraries if it was not specifically disabled.
+ */
+
+void
+dns_resolver_resetmustbesecure(dns_resolver_t *resolver);
+
+isc_result_t
+dns_resolver_setmustbesecure(dns_resolver_t *resolver, const dns_name_t *name,
+ bool value);
+
+bool
+dns_resolver_getmustbesecure(dns_resolver_t *resolver, const dns_name_t *name);
+
+void
+dns_resolver_settimeout(dns_resolver_t *resolver, unsigned int timeout);
+/*%<
+ * Set the length of time the resolver will work on a query, in milliseconds.
+ *
+ * 'timeout' was originally defined in seconds, and later redefined to be in
+ * milliseconds. Values less than or equal to 300 are treated as seconds.
+ *
+ * If timeout is 0, the default timeout will be applied.
+ *
+ * Requires:
+ * \li resolver to be valid.
+ */
+
+unsigned int
+dns_resolver_gettimeout(dns_resolver_t *resolver);
+/*%<
+ * Get the current length of time the resolver will work on a query,
+ * in milliseconds.
+ *
+ * Requires:
+ * \li resolver to be valid.
+ */
+
+void
+dns_resolver_setclientsperquery(dns_resolver_t *resolver, uint32_t min,
+ uint32_t max);
+void
+dns_resolver_setfetchesperzone(dns_resolver_t *resolver, uint32_t clients);
+
+void
+dns_resolver_getclientsperquery(dns_resolver_t *resolver, uint32_t *cur,
+ uint32_t *min, uint32_t *max);
+
+bool
+dns_resolver_getzeronosoattl(dns_resolver_t *resolver);
+
+void
+dns_resolver_setzeronosoattl(dns_resolver_t *resolver, bool state);
+
+unsigned int
+dns_resolver_getretryinterval(dns_resolver_t *resolver);
+
+void
+dns_resolver_setretryinterval(dns_resolver_t *resolver, unsigned int interval);
+/*%<
+ * Sets the amount of time, in milliseconds, that is waited for a reply
+ * to a server before another server is tried. Interacts with the
+ * value of dns_resolver_getnonbackofftries() by trying that number of times
+ * at this interval, before doing exponential backoff and doubling the interval
+ * on each subsequent try, to a maximum of 10 seconds. Defaults to 800 ms;
+ * silently capped at 2000 ms.
+ *
+ * Requires:
+ * \li resolver to be valid.
+ * \li interval > 0.
+ */
+
+unsigned int
+dns_resolver_getnonbackofftries(dns_resolver_t *resolver);
+
+void
+dns_resolver_setnonbackofftries(dns_resolver_t *resolver, unsigned int tries);
+/*%<
+ * Sets the number of failures of getting a reply from remote servers for
+ * a query before backing off by doubling the retry interval for each
+ * subsequent request sent. Defaults to 3.
+ *
+ * Requires:
+ * \li resolver to be valid.
+ * \li tries > 0.
+ */
+
+unsigned int
+dns_resolver_getoptions(dns_resolver_t *resolver);
+/*%<
+ * Get the resolver options.
+ *
+ * Requires:
+ * \li resolver to be valid.
+ */
+
+void
+dns_resolver_addbadcache(dns_resolver_t *resolver, const dns_name_t *name,
+ dns_rdatatype_t type, isc_time_t *expire);
+/*%<
+ * Add a entry to the bad cache for <name,type> that will expire at 'expire'.
+ *
+ * Requires:
+ * \li resolver to be valid.
+ * \li name to be valid.
+ */
+
+bool
+dns_resolver_getbadcache(dns_resolver_t *resolver, const dns_name_t *name,
+ dns_rdatatype_t type, isc_time_t *now);
+/*%<
+ * Check to see if there is a unexpired entry in the bad cache for
+ * <name,type>.
+ *
+ * Requires:
+ * \li resolver to be valid.
+ * \li name to be valid.
+ */
+
+void
+dns_resolver_flushbadcache(dns_resolver_t *resolver, const dns_name_t *name);
+/*%<
+ * Flush the bad cache of all entries at 'name' if 'name' is non NULL.
+ * Flush the entire bad cache if 'name' is NULL.
+ *
+ * Requires:
+ * \li resolver to be valid.
+ */
+
+void
+dns_resolver_flushbadnames(dns_resolver_t *resolver, const dns_name_t *name);
+/*%<
+ * Flush the bad cache of all entries at or below 'name'.
+ *
+ * Requires:
+ * \li resolver to be valid.
+ * \li name != NULL
+ */
+
+void
+dns_resolver_printbadcache(dns_resolver_t *resolver, FILE *fp);
+/*%
+ * Print out the contents of the bad cache to 'fp'.
+ *
+ * Requires:
+ * \li resolver to be valid.
+ */
+
+void
+dns_resolver_setmaxdepth(dns_resolver_t *resolver, unsigned int maxdepth);
+unsigned int
+dns_resolver_getmaxdepth(dns_resolver_t *resolver);
+/*%
+ * Get and set how many NS indirections will be followed when looking for
+ * nameserver addresses.
+ *
+ * Requires:
+ * \li resolver to be valid.
+ */
+
+void
+dns_resolver_setmaxqueries(dns_resolver_t *resolver, unsigned int queries);
+unsigned int
+dns_resolver_getmaxqueries(dns_resolver_t *resolver);
+/*%
+ * Get and set how many iterative queries will be allowed before
+ * terminating a recursive query.
+ *
+ * Requires:
+ * \li resolver to be valid.
+ */
+
+void
+dns_resolver_setquotaresponse(dns_resolver_t *resolver, dns_quotatype_t which,
+ isc_result_t resp);
+isc_result_t
+dns_resolver_getquotaresponse(dns_resolver_t *resolver, dns_quotatype_t which);
+/*%
+ * Get and set the result code that will be used when quotas
+ * are exceeded. If 'which' is set to quotatype "zone", then the
+ * result specified in 'resp' will be used when the fetches-per-zone
+ * quota is exceeded by a fetch. If 'which' is set to quotatype "server",
+ * then the result specified in 'resp' will be used when the
+ * fetches-per-server quota has been exceeded for all the
+ * authoritative servers for a zone. Valid choices are
+ * DNS_R_DROP or DNS_R_SERVFAIL.
+ *
+ * Requires:
+ * \li 'resolver' to be valid.
+ * \li 'which' to be dns_quotatype_zone or dns_quotatype_server
+ * \li 'resp' to be DNS_R_DROP or DNS_R_SERVFAIL.
+ */
+
+void
+dns_resolver_dumpfetches(dns_resolver_t *resolver, isc_statsformat_t format,
+ FILE *fp);
+
+#ifdef ENABLE_AFL
+/*%
+ * Enable fuzzing of resolver, changes behaviour and eliminates retries
+ */
+void
+dns_resolver_setfuzzing(void);
+#endif /* ifdef ENABLE_AFL */
+
+ISC_LANG_ENDDECLS
diff --git a/lib/dns/include/dns/result.h b/lib/dns/include/dns/result.h
new file mode 100644
index 0000000..6ab7e51
--- /dev/null
+++ b/lib/dns/include/dns/result.h
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#pragma once
+
+/*! \file dns/result.h */
+
+#include <isc/lang.h>
+#include <isc/result.h>
+
+#include <dns/types.h>
+
+ISC_LANG_BEGINDECLS
+
+dns_rcode_t
+dns_result_torcode(isc_result_t result);
+
+isc_result_t
+dns_result_fromrcode(dns_rcode_t rcode);
+
+ISC_LANG_ENDDECLS
diff --git a/lib/dns/include/dns/rootns.h b/lib/dns/include/dns/rootns.h
new file mode 100644
index 0000000..d7740f7
--- /dev/null
+++ b/lib/dns/include/dns/rootns.h
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#pragma once
+
+/*! \file dns/rootns.h */
+
+#include <isc/lang.h>
+
+#include <dns/types.h>
+
+ISC_LANG_BEGINDECLS
+
+isc_result_t
+dns_rootns_create(isc_mem_t *mctx, dns_rdataclass_t rdclass,
+ const char *filename, dns_db_t **target);
+
+void
+dns_root_checkhints(dns_view_t *view, dns_db_t *hints, dns_db_t *db);
+/*
+ * Reports differences between hints and the real roots.
+ *
+ * Requires view, hints and (cache) db to be valid.
+ */
+
+ISC_LANG_ENDDECLS
diff --git a/lib/dns/include/dns/rpz.h b/lib/dns/include/dns/rpz.h
new file mode 100644
index 0000000..364ad92
--- /dev/null
+++ b/lib/dns/include/dns/rpz.h
@@ -0,0 +1,441 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#pragma once
+
+/*
+ * Define this for reference count tracing in the unit
+ */
+#undef DNS_RPZ_TRACE
+
+#include <inttypes.h>
+#include <stdbool.h>
+
+#include <isc/deprecated.h>
+#include <isc/event.h>
+#include <isc/ht.h>
+#include <isc/lang.h>
+#include <isc/refcount.h>
+#include <isc/rwlock.h>
+#include <isc/time.h>
+#include <isc/timer.h>
+
+#include <dns/fixedname.h>
+#include <dns/rdata.h>
+#include <dns/types.h>
+
+ISC_LANG_BEGINDECLS
+
+#define DNS_RPZ_PREFIX "rpz-"
+/*
+ * Sub-zones of various trigger types.
+ */
+#define DNS_RPZ_CLIENT_IP_ZONE DNS_RPZ_PREFIX "client-ip"
+#define DNS_RPZ_IP_ZONE DNS_RPZ_PREFIX "ip"
+#define DNS_RPZ_NSIP_ZONE DNS_RPZ_PREFIX "nsip"
+#define DNS_RPZ_NSDNAME_ZONE DNS_RPZ_PREFIX "nsdname"
+/*
+ * Special policies.
+ */
+#define DNS_RPZ_PASSTHRU_NAME DNS_RPZ_PREFIX "passthru"
+#define DNS_RPZ_DROP_NAME DNS_RPZ_PREFIX "drop"
+#define DNS_RPZ_TCP_ONLY_NAME DNS_RPZ_PREFIX "tcp-only"
+
+typedef uint8_t dns_rpz_prefix_t;
+
+typedef enum {
+ DNS_RPZ_TYPE_BAD,
+ DNS_RPZ_TYPE_CLIENT_IP,
+ DNS_RPZ_TYPE_QNAME,
+ DNS_RPZ_TYPE_IP,
+ DNS_RPZ_TYPE_NSDNAME,
+ DNS_RPZ_TYPE_NSIP
+} dns_rpz_type_t;
+
+/*
+ * Require DNS_RPZ_POLICY_PASSTHRU < DNS_RPZ_POLICY_DROP
+ * < DNS_RPZ_POLICY_TCP_ONLY DNS_RPZ_POLICY_NXDOMAIN < DNS_RPZ_POLICY_NODATA
+ * < DNS_RPZ_POLICY_CNAME to choose among competing policies.
+ */
+typedef enum {
+ DNS_RPZ_POLICY_GIVEN = 0, /* 'given': what policy record says */
+ DNS_RPZ_POLICY_DISABLED = 1, /* log what would have happened */
+ DNS_RPZ_POLICY_PASSTHRU = 2, /* 'passthru': do not rewrite */
+ DNS_RPZ_POLICY_DROP = 3, /* 'drop': do not respond */
+ DNS_RPZ_POLICY_TCP_ONLY = 4, /* 'tcp-only': answer UDP with TC=1 */
+ DNS_RPZ_POLICY_NXDOMAIN = 5, /* 'nxdomain': answer with NXDOMAIN */
+ DNS_RPZ_POLICY_NODATA = 6, /* 'nodata': answer with ANCOUNT=0 */
+ DNS_RPZ_POLICY_CNAME = 7, /* 'cname x': answer with x's rrsets */
+ DNS_RPZ_POLICY_DNS64, /* Apply DN64 to the A rewrite */
+ DNS_RPZ_POLICY_RECORD,
+ DNS_RPZ_POLICY_WILDCNAME,
+ DNS_RPZ_POLICY_MISS,
+ DNS_RPZ_POLICY_ERROR
+} dns_rpz_policy_t;
+
+typedef uint8_t dns_rpz_num_t;
+
+#define DNS_RPZ_MAX_ZONES 64
+/*
+ * Type dns_rpz_zbits_t must be an unsigned int wide enough to contain
+ * at least DNS_RPZ_MAX_ZONES bits.
+ */
+typedef uint64_t dns_rpz_zbits_t;
+
+#define DNS_RPZ_ALL_ZBITS ((dns_rpz_zbits_t)-1)
+
+#define DNS_RPZ_INVALID_NUM DNS_RPZ_MAX_ZONES
+
+#define DNS_RPZ_ZBIT(n) (((dns_rpz_zbits_t)1) << (dns_rpz_num_t)(n))
+
+/*
+ * Mask of the specified and higher numbered policy zones
+ * Avoid hassles with (1<<33) or (1<<65)
+ */
+#define DNS_RPZ_ZMASK(n) \
+ ((dns_rpz_zbits_t)((((n) >= DNS_RPZ_MAX_ZONES - 1) \
+ ? 0 \
+ : (1ULL << ((n) + 1))) - \
+ 1))
+
+/*
+ * The trigger counter type.
+ */
+typedef size_t dns_rpz_trigger_counter_t;
+
+/*
+ * The number of triggers of each type in a response policy zone.
+ */
+typedef struct dns_rpz_triggers dns_rpz_triggers_t;
+struct dns_rpz_triggers {
+ dns_rpz_trigger_counter_t client_ipv4;
+ dns_rpz_trigger_counter_t client_ipv6;
+ dns_rpz_trigger_counter_t qname;
+ dns_rpz_trigger_counter_t ipv4;
+ dns_rpz_trigger_counter_t ipv6;
+ dns_rpz_trigger_counter_t nsdname;
+ dns_rpz_trigger_counter_t nsipv4;
+ dns_rpz_trigger_counter_t nsipv6;
+};
+
+/*
+ * A single response policy zone.
+ */
+typedef struct dns_rpz_zone dns_rpz_zone_t;
+typedef struct dns_rpz_zones dns_rpz_zones_t;
+
+struct dns_rpz_zone {
+ unsigned int magic;
+
+ dns_rpz_num_t num; /* ordinal in list of policy zones */
+ dns_name_t origin; /* Policy zone name */
+ dns_name_t client_ip; /* DNS_RPZ_CLIENT_IP_ZONE.origin. */
+ dns_name_t ip; /* DNS_RPZ_IP_ZONE.origin. */
+ dns_name_t nsdname; /* DNS_RPZ_NSDNAME_ZONE.origin */
+ dns_name_t nsip; /* DNS_RPZ_NSIP_ZONE.origin. */
+ dns_name_t passthru; /* DNS_RPZ_PASSTHRU_NAME. */
+ dns_name_t drop; /* DNS_RPZ_DROP_NAME. */
+ dns_name_t tcp_only; /* DNS_RPZ_TCP_ONLY_NAME. */
+ dns_name_t cname; /* override value for ..._CNAME */
+ dns_ttl_t max_policy_ttl;
+ dns_rpz_policy_t policy; /* DNS_RPZ_POLICY_GIVEN or override */
+
+ uint32_t min_update_interval; /* minimal interval between updates */
+ isc_ht_t *nodes; /* entries in zone */
+ dns_rpz_zones_t *rpzs; /* owner */
+ isc_time_t lastupdated; /* last time the zone was processed */
+ bool updatepending; /* there is an update pending */
+ bool updaterunning; /* there is an update running */
+ isc_result_t updateresult; /* result from the offloaded work */
+ dns_db_t *db; /* zones database */
+ dns_dbversion_t *dbversion; /* version we will be updating to */
+ dns_db_t *updb; /* zones database we're working on */
+ dns_dbversion_t *updbversion; /* version we're working on */
+ bool addsoa; /* add soa to the additional section */
+ isc_timer_t *updatetimer;
+ isc_event_t updateevent;
+};
+
+/*
+ * Radix tree node for response policy IP addresses
+ */
+typedef struct dns_rpz_cidr_node dns_rpz_cidr_node_t;
+
+/*
+ * Bitfields indicating which policy zones have policies of
+ * which type.
+ */
+typedef struct dns_rpz_have dns_rpz_have_t;
+struct dns_rpz_have {
+ dns_rpz_zbits_t client_ipv4;
+ dns_rpz_zbits_t client_ipv6;
+ dns_rpz_zbits_t client_ip;
+ dns_rpz_zbits_t qname;
+ dns_rpz_zbits_t ipv4;
+ dns_rpz_zbits_t ipv6;
+ dns_rpz_zbits_t ip;
+ dns_rpz_zbits_t nsdname;
+ dns_rpz_zbits_t nsipv4;
+ dns_rpz_zbits_t nsipv6;
+ dns_rpz_zbits_t nsip;
+ dns_rpz_zbits_t qname_skip_recurse;
+};
+
+/*
+ * Policy options
+ */
+typedef struct dns_rpz_popt dns_rpz_popt_t;
+struct dns_rpz_popt {
+ dns_rpz_zbits_t no_rd_ok;
+ dns_rpz_zbits_t no_log;
+ dns_rpz_zbits_t nsip_on;
+ dns_rpz_zbits_t nsdname_on;
+ bool dnsrps_enabled;
+ bool break_dnssec;
+ bool qname_wait_recurse;
+ bool nsip_wait_recurse;
+ bool nsdname_wait_recurse;
+ unsigned int min_ns_labels;
+ dns_rpz_num_t num_zones;
+};
+
+/*
+ * Response policy zones known to a view.
+ */
+struct dns_rpz_zones {
+ unsigned int magic;
+ isc_refcount_t references;
+ isc_mem_t *mctx;
+ isc_taskmgr_t *taskmgr;
+ isc_timermgr_t *timermgr;
+ isc_task_t *updater;
+
+ dns_rpz_popt_t p;
+ dns_rpz_zone_t *zones[DNS_RPZ_MAX_ZONES];
+ dns_rpz_triggers_t triggers[DNS_RPZ_MAX_ZONES];
+
+ /*
+ * RPZ policy version number.
+ * It is initially 0 and it increases whenever the server is
+ * reconfigured with new zones or policy.
+ */
+ int rpz_ver;
+
+ dns_rpz_zbits_t defined;
+
+ /*
+ * The set of records for a policy zone are in one of these states:
+ * never loaded load_begun=0 have=0
+ * during initial loading load_begun=1 have=0
+ * and rbtdb->rpzsp == rbtdb->load_rpzsp
+ * after good load load_begun=1 have!=0
+ * after failed initial load load_begun=1 have=0
+ * and rbtdb->load_rpzsp == NULL
+ * reloading after failure load_begun=1 have=0
+ * reloading after success
+ * main rpzs load_begun=1 have!=0
+ * load rpzs load_begun=1 have=0
+ */
+ dns_rpz_zbits_t load_begun;
+ dns_rpz_have_t have;
+
+ /*
+ * total_triggers maintains the total number of triggers in all
+ * policy zones in the view. It is only used to print summary
+ * statistics after a zone load of how the trigger counts
+ * changed.
+ */
+ dns_rpz_triggers_t total_triggers;
+
+ /*
+ * One lock for short term read-only search that guarantees the
+ * consistency of the pointers.
+ * A second lock for maintenance that guarantees no other thread
+ * is adding or deleting nodes.
+ */
+ isc_rwlock_t search_lock;
+ isc_mutex_t maint_lock;
+
+ bool shuttingdown;
+
+ dns_rpz_cidr_node_t *cidr;
+ dns_rbt_t *rbt;
+
+ /*
+ * DNSRPZ librpz configuration string and handle on librpz connection
+ */
+ char *rps_cstr;
+ size_t rps_cstr_size;
+ struct librpz_client *rps_client;
+};
+
+/*
+ * context for finding the best policy
+ */
+typedef struct {
+ unsigned int state;
+#define DNS_RPZ_REWRITTEN 0x0001
+#define DNS_RPZ_DONE_CLIENT_IP 0x0002 /* client IP address checked */
+#define DNS_RPZ_DONE_QNAME 0x0004 /* qname checked */
+#define DNS_RPZ_DONE_QNAME_IP 0x0008 /* IP addresses of qname checked */
+#define DNS_RPZ_DONE_NSDNAME 0x0010 /* NS name missed; checking addresses */
+#define DNS_RPZ_DONE_IPv4 0x0020
+#define DNS_RPZ_RECURSING 0x0040
+#define DNS_RPZ_ACTIVE 0x0080
+ /*
+ * Best match so far.
+ */
+ struct {
+ dns_rpz_type_t type;
+ dns_rpz_zone_t *rpz;
+ dns_rpz_prefix_t prefix;
+ dns_rpz_policy_t policy;
+ dns_ttl_t ttl;
+ isc_result_t result;
+ dns_zone_t *zone;
+ dns_db_t *db;
+ dns_dbversion_t *version;
+ dns_dbnode_t *node;
+ dns_rdataset_t *rdataset;
+ } m;
+ /*
+ * State for chasing IP addresses and NS names including recursion.
+ */
+ struct {
+ unsigned int label;
+ dns_db_t *db;
+ dns_rdataset_t *ns_rdataset;
+ dns_rdatatype_t r_type;
+ isc_result_t r_result;
+ dns_rdataset_t *r_rdataset;
+ } r;
+
+ /*
+ * State of real query while recursing for NSIP or NSDNAME.
+ */
+ struct {
+ isc_result_t result;
+ bool is_zone;
+ bool authoritative;
+ dns_zone_t *zone;
+ dns_db_t *db;
+ dns_dbnode_t *node;
+ dns_rdataset_t *rdataset;
+ dns_rdataset_t *sigrdataset;
+ dns_rdatatype_t qtype;
+ } q;
+
+ /*
+ * A copy of the 'have' and 'p' structures and the RPZ
+ * policy version as of the beginning of RPZ processing,
+ * used to avoid problems when policy is updated while
+ * RPZ recursion is ongoing.
+ */
+ dns_rpz_have_t have;
+ dns_rpz_popt_t popt;
+ int rpz_ver;
+
+ /*
+ * Shim db between BIND and DNRPS librpz.
+ */
+ dns_db_t *rpsdb;
+
+ /*
+ * p_name: current policy owner name
+ * r_name: recursing for this name to possible policy triggers
+ * f_name: saved found name from before recursion
+ */
+ dns_name_t *p_name;
+ dns_name_t *r_name;
+ dns_name_t *fname;
+ dns_fixedname_t _p_namef;
+ dns_fixedname_t _r_namef;
+ dns_fixedname_t _fnamef;
+} dns_rpz_st_t;
+
+#define DNS_RPZ_TTL_DEFAULT 5
+#define DNS_RPZ_MAX_TTL_DEFAULT DNS_RPZ_TTL_DEFAULT
+#define DNS_RPZ_MINUPDATEINTERVAL_DEFAULT 60
+
+/*
+ * So various response policy zone messages can be turned up or down.
+ */
+#define DNS_RPZ_ERROR_LEVEL ISC_LOG_WARNING
+#define DNS_RPZ_INFO_LEVEL ISC_LOG_INFO
+#define DNS_RPZ_DEBUG_LEVEL1 ISC_LOG_DEBUG(1)
+#define DNS_RPZ_DEBUG_LEVEL2 ISC_LOG_DEBUG(2)
+#define DNS_RPZ_DEBUG_LEVEL3 ISC_LOG_DEBUG(3)
+#define DNS_RPZ_DEBUG_QUIET (DNS_RPZ_DEBUG_LEVEL3 + 1)
+
+const char *
+dns_rpz_type2str(dns_rpz_type_t type);
+
+dns_rpz_policy_t
+dns_rpz_str2policy(const char *str);
+
+const char *
+dns_rpz_policy2str(dns_rpz_policy_t policy);
+
+dns_rpz_policy_t
+dns_rpz_decode_cname(dns_rpz_zone_t *rpz, dns_rdataset_t *rdataset,
+ dns_name_t *selfname);
+
+isc_result_t
+dns_rpz_new_zones(isc_mem_t *mctx, isc_taskmgr_t *taskmgr,
+ isc_timermgr_t *timermgr, char *rps_cstr,
+ size_t rps_cstr_size, dns_rpz_zones_t **rpzsp);
+
+isc_result_t
+dns_rpz_new_zone(dns_rpz_zones_t *rpzs, dns_rpz_zone_t **rpzp);
+
+isc_result_t
+dns_rpz_dbupdate_callback(dns_db_t *db, void *fn_arg);
+
+void
+dns_rpz_zones_shutdown(dns_rpz_zones_t *rpzs);
+
+#ifdef DNS_RPZ_TRACE
+/* Compatibility macros */
+#define dns_rpz_detach_rpzs(rpzsp) \
+ dns_rpz_zones__detach(rpzsp, __func__, __FILE__, __LINE__)
+#define dns_rpz_attach_rpzs(rpzs, rpzsp) \
+ dns_rpz_zones__attach(rpzs, rpzsp, __func__, __FILE__, __LINE__)
+#define dns_rpz_ref_rpzs(ptr) \
+ dns_rpz_zones__ref(ptr, __func__, __FILE__, __LINE__)
+#define dns_rpz_unref_rpzs(ptr) \
+ dns_rpz_zones__unref(ptr, __func__, __FILE__, __LINE__)
+#define dns_rpz_shutdown_rpzs(rpzs) \
+ dns_rpz_zones_shutdown(rpzs, __func__, __FILE__, __LINE__)
+
+ISC_REFCOUNT_TRACE_DECL(dns_rpz_zones);
+#else
+/* Compatibility macros */
+#define dns_rpz_detach_rpzs(rpzsp) dns_rpz_zones_detach(rpzsp)
+#define dns_rpz_attach_rpzs(rpzs, rpzsp) dns_rpz_zones_attach(rpzs, rpzsp)
+#define dns_rpz_shutdown_rpzs(rpzsp) dns_rpz_zones_shutdown(rpzsp)
+#define dns_rpz_ref_rpzs(ptr) dns_rpz_zones_ref(ptr)
+#define dns_rpz_unref_rpzs(ptr) dns_rpz_zones_unref(ptr)
+
+ISC_REFCOUNT_DECL(dns_rpz_zones);
+#endif
+
+dns_rpz_num_t
+dns_rpz_find_ip(dns_rpz_zones_t *rpzs, dns_rpz_type_t rpz_type,
+ dns_rpz_zbits_t zbits, const isc_netaddr_t *netaddr,
+ dns_name_t *ip_name, dns_rpz_prefix_t *prefixp);
+
+dns_rpz_zbits_t
+dns_rpz_find_name(dns_rpz_zones_t *rpzs, dns_rpz_type_t rpz_type,
+ dns_rpz_zbits_t zbits, dns_name_t *trig_name);
+
+ISC_LANG_ENDDECLS
diff --git a/lib/dns/include/dns/rriterator.h b/lib/dns/include/dns/rriterator.h
new file mode 100644
index 0000000..a1cd9b9
--- /dev/null
+++ b/lib/dns/include/dns/rriterator.h
@@ -0,0 +1,179 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#pragma once
+
+/*****
+***** Module Info
+*****/
+
+/*! \file dns/rriterator.h
+ * \brief
+ * Functions for "walking" a zone database, visiting each RR or RRset in turn.
+ */
+
+/*****
+***** Imports
+*****/
+
+#include <inttypes.h>
+
+#include <isc/lang.h>
+#include <isc/magic.h>
+#include <isc/stdtime.h>
+
+#include <dns/db.h>
+#include <dns/dbiterator.h>
+#include <dns/fixedname.h>
+#include <dns/name.h>
+#include <dns/rdata.h>
+#include <dns/rdataset.h>
+#include <dns/rdatasetiter.h>
+#include <dns/types.h>
+
+ISC_LANG_BEGINDECLS
+
+/*****
+***** Types
+*****/
+
+/*%
+ * A dns_rriterator_t is an iterator that iterates over an entire database,
+ * returning one RR at a time, in some arbitrary order.
+ */
+
+typedef struct dns_rriterator {
+ unsigned int magic;
+ isc_result_t result;
+ dns_db_t *db;
+ dns_dbiterator_t *dbit;
+ dns_dbversion_t *ver;
+ isc_stdtime_t now;
+ dns_dbnode_t *node;
+ dns_fixedname_t fixedname;
+ dns_rdatasetiter_t *rdatasetit;
+ dns_rdataset_t rdataset;
+ dns_rdata_t rdata;
+} dns_rriterator_t;
+
+#define RRITERATOR_MAGIC ISC_MAGIC('R', 'R', 'I', 't')
+#define VALID_RRITERATOR(m) ISC_MAGIC_VALID(m, RRITERATOR_MAGIC)
+
+isc_result_t
+dns_rriterator_init(dns_rriterator_t *it, dns_db_t *db, dns_dbversion_t *ver,
+ isc_stdtime_t now);
+/*%
+ * Initialize an rriterator; sets the cursor to the origin node
+ * of the database.
+ *
+ * Requires:
+ *
+ * \li 'db' is a valid database.
+ *
+ * Returns:
+ *
+ * \li #ISC_R_SUCCESS
+ * \li #ISC_R_NOMEMORY
+ */
+
+isc_result_t
+dns_rriterator_first(dns_rriterator_t *it);
+/*%<
+ * Move the rriterator cursor to the first rdata in the database.
+ *
+ * Requires:
+ *\li 'it' is a valid, initialized rriterator
+ *
+ * Returns:
+ *\li #ISC_R_SUCCESS
+ *\li #ISC_R_NOMORE There are no rdata in the set.
+ */
+
+isc_result_t
+dns_rriterator_nextrrset(dns_rriterator_t *it);
+/*%<
+ * Move the rriterator cursor to the next rrset in the database,
+ * skipping over any remaining records that have the same rdatatype
+ * as the current one.
+ *
+ * Requires:
+ *\li 'it' is a valid, initialized rriterator
+ *
+ * Returns:
+ *\li #ISC_R_SUCCESS
+ *\li #ISC_R_NOMORE No more rrsets in the database
+ */
+
+isc_result_t
+dns_rriterator_next(dns_rriterator_t *it);
+/*%<
+ * Move the rriterator cursor to the next rrset in the database,
+ * skipping over any remaining records that have the same rdatatype
+ * as the current one.
+ *
+ * Requires:
+ *\li 'it' is a valid, initialized rriterator
+ *
+ * Returns:
+ *\li #ISC_R_SUCCESS
+ *\li #ISC_R_NOMORE No more records in the database
+ */
+
+void
+dns_rriterator_current(dns_rriterator_t *it, dns_name_t **name, uint32_t *ttl,
+ dns_rdataset_t **rdataset, dns_rdata_t **rdata);
+/*%<
+ * Make '*name' refer to the current name. If 'rdataset' is not NULL,
+ * make '*rdataset' refer to the current * rdataset. If '*rdata' is not
+ * NULL, make '*rdata' refer to the current record.
+ *
+ * Requires:
+ *\li '*name' is a valid name object
+ *\li 'rdataset' is NULL or '*rdataset' is NULL
+ *\li 'rdata' is NULL or '*rdata' is NULL
+ *
+ * Ensures:
+ *\li 'rdata' refers to the rdata at the rdata cursor location of
+ *\li 'rdataset'.
+ */
+
+void
+dns_rriterator_pause(dns_rriterator_t *it);
+/*%<
+ * Pause rriterator. Frees any locks held by the database iterator.
+ * Callers should use this routine any time they are not going to
+ * execute another rriterator method in the immediate future.
+ *
+ * Requires:
+ *\li 'it' is a valid iterator.
+ *
+ * Ensures:
+ *\li Any database locks being held for efficiency of iterator access are
+ * released.
+ */
+
+void
+dns_rriterator_destroy(dns_rriterator_t *it);
+/*%<
+ * Shut down and free resources in rriterator 'it'.
+ *
+ * Requires:
+ *
+ *\li 'it' is a valid iterator.
+ *
+ * Ensures:
+ *
+ *\li All resources used by the rriterator are freed.
+ */
+
+ISC_LANG_ENDDECLS
diff --git a/lib/dns/include/dns/rrl.h b/lib/dns/include/dns/rrl.h
new file mode 100644
index 0000000..bdffed9
--- /dev/null
+++ b/lib/dns/include/dns/rrl.h
@@ -0,0 +1,269 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#pragma once
+
+/*
+ * Rate limit DNS responses.
+ */
+
+#include <inttypes.h>
+#include <stdbool.h>
+
+#include <isc/lang.h>
+
+#include <dns/fixedname.h>
+#include <dns/rdata.h>
+#include <dns/types.h>
+
+ISC_LANG_BEGINDECLS
+
+/*
+ * Memory allocation or other failures.
+ */
+#define DNS_RRL_LOG_FAIL ISC_LOG_WARNING
+/*
+ * dropped or slipped responses.
+ */
+#define DNS_RRL_LOG_DROP ISC_LOG_INFO
+/*
+ * Major events in dropping or slipping.
+ */
+#define DNS_RRL_LOG_DEBUG1 ISC_LOG_DEBUG(3)
+/*
+ * Limit computations.
+ */
+#define DNS_RRL_LOG_DEBUG2 ISC_LOG_DEBUG(4)
+/*
+ * Even less interesting.
+ */
+#define DNS_RRL_LOG_DEBUG3 ISC_LOG_DEBUG(9)
+
+#define DNS_RRL_LOG_ERR_LEN 64
+#define DNS_RRL_LOG_BUF_LEN \
+ (sizeof("would continue limiting") + DNS_RRL_LOG_ERR_LEN + \
+ sizeof(" responses to ") + ISC_NETADDR_FORMATSIZE + \
+ sizeof("/128 for IN ") + DNS_RDATATYPE_FORMATSIZE + \
+ DNS_NAME_FORMATSIZE)
+
+typedef struct dns_rrl_hash dns_rrl_hash_t;
+
+/*
+ * Response types.
+ */
+typedef enum {
+ DNS_RRL_RTYPE_FREE = 0,
+ DNS_RRL_RTYPE_QUERY,
+ DNS_RRL_RTYPE_REFERRAL,
+ DNS_RRL_RTYPE_NODATA,
+ DNS_RRL_RTYPE_NXDOMAIN,
+ DNS_RRL_RTYPE_ERROR,
+ DNS_RRL_RTYPE_ALL,
+ DNS_RRL_RTYPE_TCP,
+} dns_rrl_rtype_t;
+
+/*
+ * A rate limit bucket key.
+ * This should be small to limit the total size of the database.
+ * The hash of the qname should be wide enough to make the probability
+ * of collisions among requests from a single IP address block less than 50%.
+ * We need a 32-bit hash value for 10000 qps (e.g. random qnames forged
+ * by attacker) to collide with legitimate qnames from the target with
+ * probability at most 1%.
+ */
+#define DNS_RRL_MAX_PREFIX 64
+typedef union dns_rrl_key dns_rrl_key_t;
+struct dns__rrl_key {
+ uint32_t ip[DNS_RRL_MAX_PREFIX / 32];
+ uint32_t qname_hash;
+ dns_rdatatype_t qtype;
+ uint8_t qclass;
+ unsigned int rtype : 4; /* dns_rrl_rtype_t */
+ unsigned int ipv6 : 1;
+};
+union dns_rrl_key {
+ struct dns__rrl_key s;
+ uint16_t w[sizeof(struct dns__rrl_key) / sizeof(uint16_t)];
+};
+
+/*
+ * A rate-limit entry.
+ * This should be small to limit the total size of the table of entries.
+ */
+typedef struct dns_rrl_entry dns_rrl_entry_t;
+typedef ISC_LIST(dns_rrl_entry_t) dns_rrl_bin_t;
+struct dns_rrl_entry {
+ ISC_LINK(dns_rrl_entry_t) lru;
+ ISC_LINK(dns_rrl_entry_t) hlink;
+ dns_rrl_key_t key;
+#define DNS_RRL_RESPONSE_BITS 24
+ signed int responses : DNS_RRL_RESPONSE_BITS;
+#define DNS_RRL_QNAMES_BITS 8
+ unsigned int log_qname : DNS_RRL_QNAMES_BITS;
+
+#define DNS_RRL_TS_GEN_BITS 2
+ unsigned int ts_gen : DNS_RRL_TS_GEN_BITS;
+ unsigned int ts_valid : 1;
+#define DNS_RRL_HASH_GEN_BITS 1
+ unsigned int hash_gen : DNS_RRL_HASH_GEN_BITS;
+ unsigned int logged : 1;
+#define DNS_RRL_LOG_BITS 11
+ unsigned int log_secs : DNS_RRL_LOG_BITS;
+
+#define DNS_RRL_TS_BITS 12
+ unsigned int ts : DNS_RRL_TS_BITS;
+
+#define DNS_RRL_MAX_SLIP 10
+ unsigned int slip_cnt : 4;
+};
+
+#define DNS_RRL_MAX_TIME_TRAVEL 5
+#define DNS_RRL_FOREVER (1 << DNS_RRL_TS_BITS)
+#define DNS_RRL_MAX_TS (DNS_RRL_FOREVER - 1)
+
+#define DNS_RRL_MAX_RESPONSES ((1 << (DNS_RRL_RESPONSE_BITS - 1)) - 1)
+#define DNS_RRL_MAX_WINDOW 3600
+#if DNS_RRL_MAX_WINDOW >= DNS_RRL_MAX_TS
+#error "DNS_RRL_MAX_WINDOW is too large"
+#endif /* if DNS_RRL_MAX_WINDOW >= DNS_RRL_MAX_TS */
+#define DNS_RRL_MAX_RATE 1000
+#if DNS_RRL_MAX_RATE >= (DNS_RRL_MAX_RESPONSES / DNS_RRL_MAX_WINDOW)
+#error "DNS_RRL_MAX_rate is too large"
+#endif /* if DNS_RRL_MAX_RATE >= (DNS_RRL_MAX_RESPONSES / DNS_RRL_MAX_WINDOW) \
+ */
+
+#if (1 << DNS_RRL_LOG_BITS) >= DNS_RRL_FOREVER
+#error DNS_RRL_LOG_BITS is too big
+#endif /* if (1 << DNS_RRL_LOG_BITS) >= DNS_RRL_FOREVER */
+#define DNS_RRL_MAX_LOG_SECS 1800
+#if DNS_RRL_MAX_LOG_SECS >= (1 << DNS_RRL_LOG_BITS)
+#error "DNS_RRL_MAX_LOG_SECS is too large"
+#endif /* if DNS_RRL_MAX_LOG_SECS >= (1 << DNS_RRL_LOG_BITS) */
+#define DNS_RRL_STOP_LOG_SECS 60
+#if DNS_RRL_STOP_LOG_SECS >= (1 << DNS_RRL_LOG_BITS)
+#error "DNS_RRL_STOP_LOG_SECS is too large"
+#endif /* if DNS_RRL_STOP_LOG_SECS >= (1 << DNS_RRL_LOG_BITS) */
+
+/*
+ * A hash table of rate-limit entries.
+ */
+struct dns_rrl_hash {
+ isc_stdtime_t check_time;
+ unsigned int gen : DNS_RRL_HASH_GEN_BITS;
+ int length;
+ dns_rrl_bin_t bins[1];
+};
+
+/*
+ * A block of rate-limit entries.
+ */
+typedef struct dns_rrl_block dns_rrl_block_t;
+struct dns_rrl_block {
+ ISC_LINK(dns_rrl_block_t) link;
+ int size;
+ dns_rrl_entry_t entries[1];
+};
+
+/*
+ * A rate limited qname buffer.
+ */
+typedef struct dns_rrl_qname_buf dns_rrl_qname_buf_t;
+struct dns_rrl_qname_buf {
+ ISC_LINK(dns_rrl_qname_buf_t) link;
+ const dns_rrl_entry_t *e;
+ unsigned int index;
+ dns_fixedname_t qname;
+};
+
+typedef struct dns_rrl_rate dns_rrl_rate_t;
+struct dns_rrl_rate {
+ int r;
+ int scaled;
+ const char *str;
+};
+
+/*
+ * Per-view query rate limit parameters and a pointer to database.
+ */
+typedef struct dns_rrl dns_rrl_t;
+struct dns_rrl {
+ isc_mutex_t lock;
+ isc_mem_t *mctx;
+
+ bool log_only;
+ dns_rrl_rate_t responses_per_second;
+ dns_rrl_rate_t referrals_per_second;
+ dns_rrl_rate_t nodata_per_second;
+ dns_rrl_rate_t nxdomains_per_second;
+ dns_rrl_rate_t errors_per_second;
+ dns_rrl_rate_t all_per_second;
+ dns_rrl_rate_t slip;
+ int window;
+ double qps_scale;
+ int max_entries;
+
+ dns_acl_t *exempt;
+
+ int num_entries;
+
+ int qps_responses;
+ isc_stdtime_t qps_time;
+ double qps;
+
+ unsigned int probes;
+ unsigned int searches;
+
+ ISC_LIST(dns_rrl_block_t) blocks;
+ ISC_LIST(dns_rrl_entry_t) lru;
+
+ dns_rrl_hash_t *hash;
+ dns_rrl_hash_t *old_hash;
+ unsigned int hash_gen;
+
+ unsigned int ts_gen;
+#define DNS_RRL_TS_BASES (1 << DNS_RRL_TS_GEN_BITS)
+ isc_stdtime_t ts_bases[DNS_RRL_TS_BASES];
+
+ int ipv4_prefixlen;
+ uint32_t ipv4_mask;
+ int ipv6_prefixlen;
+ uint32_t ipv6_mask[4];
+
+ isc_stdtime_t log_stops_time;
+ dns_rrl_entry_t *last_logged;
+ int num_logged;
+ int num_qnames;
+ ISC_LIST(dns_rrl_qname_buf_t) qname_free;
+#define DNS_RRL_QNAMES (1 << DNS_RRL_QNAMES_BITS)
+ dns_rrl_qname_buf_t *qnames[DNS_RRL_QNAMES];
+};
+
+typedef enum {
+ DNS_RRL_RESULT_OK,
+ DNS_RRL_RESULT_DROP,
+ DNS_RRL_RESULT_SLIP,
+} dns_rrl_result_t;
+
+dns_rrl_result_t
+dns_rrl(dns_view_t *view, dns_zone_t *zone, const isc_sockaddr_t *client_addr,
+ bool is_tcp, dns_rdataclass_t rdclass, dns_rdatatype_t qtype,
+ const dns_name_t *qname, isc_result_t resp_result, isc_stdtime_t now,
+ bool wouldlog, char *log_buf, unsigned int log_buf_len);
+
+void
+dns_rrl_view_destroy(dns_view_t *view);
+
+isc_result_t
+dns_rrl_init(dns_rrl_t **rrlp, dns_view_t *view, int min_entries);
+
+ISC_LANG_ENDDECLS
diff --git a/lib/dns/include/dns/sdb.h b/lib/dns/include/dns/sdb.h
new file mode 100644
index 0000000..54b2223
--- /dev/null
+++ b/lib/dns/include/dns/sdb.h
@@ -0,0 +1,211 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#pragma once
+
+/*****
+***** Module Info
+*****/
+
+/*! \file dns/sdb.h
+ * \brief
+ * Simple database API.
+ */
+
+/***
+ *** Imports
+ ***/
+
+#include <inttypes.h>
+
+#include <isc/lang.h>
+
+#include <dns/clientinfo.h>
+#include <dns/types.h>
+
+/***
+ *** Types
+ ***/
+
+/*%
+ * A simple database. This is an opaque type.
+ */
+typedef struct dns_sdb dns_sdb_t;
+
+/*%
+ * A simple database lookup in progress. This is an opaque type.
+ */
+typedef struct dns_sdblookup dns_sdblookup_t;
+
+/*%
+ * A simple database traversal in progress. This is an opaque type.
+ */
+typedef struct dns_sdballnodes dns_sdballnodes_t;
+
+typedef isc_result_t (*dns_sdblookupfunc_t)(const char *zone, const char *name,
+ void *dbdata,
+ dns_sdblookup_t *lookup,
+ dns_clientinfomethods_t *methods,
+ dns_clientinfo_t *clientinfo);
+typedef isc_result_t (*dns_sdblookup2func_t)(const dns_name_t *zone,
+ const dns_name_t *name,
+ void *dbdata,
+ dns_sdblookup_t *lookup,
+ dns_clientinfomethods_t *methods,
+ dns_clientinfo_t *clientinfo);
+
+typedef isc_result_t (*dns_sdbauthorityfunc_t)(const char *zone, void *dbdata,
+ dns_sdblookup_t *);
+
+typedef isc_result_t (*dns_sdballnodesfunc_t)(const char *zone, void *dbdata,
+ dns_sdballnodes_t *allnodes);
+
+typedef isc_result_t (*dns_sdbcreatefunc_t)(const char *zone, int argc,
+ char **argv, void *driverdata,
+ void **dbdata);
+
+typedef void (*dns_sdbdestroyfunc_t)(const char *zone, void *driverdata,
+ void **dbdata);
+
+typedef struct dns_sdbmethods {
+ dns_sdblookupfunc_t lookup;
+ dns_sdbauthorityfunc_t authority;
+ dns_sdballnodesfunc_t allnodes;
+ dns_sdbcreatefunc_t create;
+ dns_sdbdestroyfunc_t destroy;
+ dns_sdblookup2func_t lookup2;
+} dns_sdbmethods_t;
+
+/***
+ *** Functions
+ ***/
+
+ISC_LANG_BEGINDECLS
+
+#define DNS_SDBFLAG_RELATIVEOWNER 0x00000001U
+#define DNS_SDBFLAG_RELATIVERDATA 0x00000002U
+#define DNS_SDBFLAG_THREADSAFE 0x00000004U
+#define DNS_SDBFLAG_DNS64 0x00000008U
+
+isc_result_t
+dns_sdb_register(const char *drivername, const dns_sdbmethods_t *methods,
+ void *driverdata, unsigned int flags, isc_mem_t *mctx,
+ dns_sdbimplementation_t **sdbimp);
+/*%<
+ * Register a simple database driver for the database type 'drivername',
+ * implemented by the functions in '*methods'.
+ *
+ * sdbimp must point to a NULL dns_sdbimplementation_t pointer. That is,
+ * sdbimp != NULL && *sdbimp == NULL. It will be assigned a value that
+ * will later be used to identify the driver when deregistering it.
+ *
+ * The name server will perform lookups in the database by calling the
+ * function 'lookup', passing it a printable zone name 'zone', a printable
+ * domain name 'name', and a copy of the argument 'dbdata' that
+ * was potentially returned by the create function. The 'dns_sdblookup_t'
+ * argument to 'lookup' and 'authority' is an opaque pointer to be passed to
+ * ns_sdb_putrr().
+ *
+ * The lookup function returns the lookup results to the name server
+ * by calling ns_sdb_putrr() once for each record found. On success,
+ * the return value of the lookup function should be ISC_R_SUCCESS.
+ * If the domain name 'name' does not exist, the lookup function should
+ * ISC_R_NOTFOUND. Any other return value is treated as an error.
+ *
+ * Lookups at the zone apex will cause the server to also call the
+ * function 'authority' (if non-NULL), which must provide an SOA record
+ * and NS records for the zone by calling ns_sdb_putrr() once for each of
+ * these records. The 'authority' function may be NULL if invoking
+ * the 'lookup' function on the zone apex will return SOA and NS records.
+ *
+ * The allnodes function, if non-NULL, fills in an opaque structure to be
+ * used by a database iterator. This allows the zone to be transferred.
+ * This may use a considerable amount of memory for large zones, and the
+ * zone transfer may not be fully RFC1035 compliant if the zone is
+ * frequently changed.
+ *
+ * The create function will be called for each zone configured
+ * into the name server using this database type. It can be used
+ * to create a "database object" containing zone specific data,
+ * which can make use of the database arguments specified in the
+ * name server configuration.
+ *
+ * The destroy function will be called to free the database object
+ * when its zone is destroyed.
+ *
+ * The create and destroy functions may be NULL.
+ *
+ * If flags includes DNS_SDBFLAG_RELATIVEOWNER, the lookup and authority
+ * functions will be called with relative names rather than absolute names.
+ * The string "@" represents the zone apex in this case.
+ *
+ * If flags includes DNS_SDBFLAG_RELATIVERDATA, the rdata strings may
+ * include relative names. Otherwise, all names in the rdata string must
+ * be absolute. Be aware that if relative names are allowed, any
+ * absolute names must contain a trailing dot.
+ *
+ * If flags includes DNS_SDBFLAG_THREADSAFE, the driver must be able to
+ * handle multiple lookups in parallel. Otherwise, calls into the driver
+ * are serialized.
+ */
+
+void
+dns_sdb_unregister(dns_sdbimplementation_t **sdbimp);
+/*%<
+ * Removes the simple database driver from the list of registered database
+ * types. There must be no active databases of this type when this function
+ * is called.
+ */
+
+/*% See dns_sdb_putradata() */
+isc_result_t
+dns_sdb_putrr(dns_sdblookup_t *lookup, const char *type, dns_ttl_t ttl,
+ const char *data);
+isc_result_t
+dns_sdb_putrdata(dns_sdblookup_t *lookup, dns_rdatatype_t type, dns_ttl_t ttl,
+ const unsigned char *rdata, unsigned int rdlen);
+/*%<
+ * Add a single resource record to the lookup structure to be
+ * returned in the query response. dns_sdb_putrr() takes the
+ * resource record in master file text format as a null-terminated
+ * string, and dns_sdb_putrdata() takes the raw RDATA in
+ * uncompressed wire format.
+ */
+
+/*% See dns_sdb_putnamerdata() */
+isc_result_t
+dns_sdb_putnamedrr(dns_sdballnodes_t *allnodes, const char *name,
+ const char *type, dns_ttl_t ttl, const char *data);
+isc_result_t
+dns_sdb_putnamedrdata(dns_sdballnodes_t *allnodes, const char *name,
+ dns_rdatatype_t type, dns_ttl_t ttl, const void *rdata,
+ unsigned int rdlen);
+/*%<
+ * Add a single resource record to the allnodes structure to be
+ * included in a zone transfer response, in text or wire
+ * format as above.
+ */
+
+isc_result_t
+dns_sdb_putsoa(dns_sdblookup_t *lookup, const char *mname, const char *rname,
+ uint32_t serial);
+/*%<
+ * This function may optionally be called from the 'authority' callback
+ * to simplify construction of the SOA record for 'zone'. It will
+ * provide a SOA listing 'mname' as as the primary server and 'rname' as
+ * the responsible person mailbox. It is the responsibility of the
+ * driver to increment the serial number between responses if necessary.
+ * All other SOA fields will have reasonable default values.
+ */
+
+ISC_LANG_ENDDECLS
diff --git a/lib/dns/include/dns/sdlz.h b/lib/dns/include/dns/sdlz.h
new file mode 100644
index 0000000..b0388b9
--- /dev/null
+++ b/lib/dns/include/dns/sdlz.h
@@ -0,0 +1,356 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0 AND ISC
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/*
+ * Copyright (C) 2002 Stichting NLnet, Netherlands, stichting@nlnet.nl.
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the
+ * above copyright notice and this permission notice appear in all
+ * copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND STICHTING NLNET
+ * DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
+ * STICHTING NLNET BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR
+ * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
+ * OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+ * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE
+ * USE OR PERFORMANCE OF THIS SOFTWARE.
+ *
+ * The development of Dynamically Loadable Zones (DLZ) for Bind 9 was
+ * conceived and contributed by Rob Butler.
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the
+ * above copyright notice and this permission notice appear in all
+ * copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND ROB BUTLER
+ * DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
+ * ROB BUTLER BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR
+ * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
+ * OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+ * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE
+ * USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+/*! \file dns/sdlz.h */
+
+#pragma once
+
+#include <inttypes.h>
+#include <stdbool.h>
+
+#include <dns/clientinfo.h>
+#include <dns/dlz.h>
+
+ISC_LANG_BEGINDECLS
+
+#define DNS_SDLZFLAG_THREADSAFE 0x00000001U
+#define DNS_SDLZFLAG_RELATIVEOWNER 0x00000002U
+#define DNS_SDLZFLAG_RELATIVERDATA 0x00000004U
+
+/* A simple DLZ database. */
+typedef struct dns_sdlz_db dns_sdlz_db_t;
+
+/* A simple DLZ database lookup in progress. */
+typedef struct dns_sdlzlookup dns_sdlzlookup_t;
+
+/* A simple DLZ database traversal in progress. */
+typedef struct dns_sdlzallnodes dns_sdlzallnodes_t;
+
+typedef isc_result_t (*dns_sdlzallnodesfunc_t)(const char *zone,
+ void *driverarg, void *dbdata,
+ dns_sdlzallnodes_t *allnodes);
+/*%<
+ * Method prototype. Drivers implementing the SDLZ interface may
+ * supply an all nodes method. This method is called when the DNS
+ * server is performing a zone transfer query, after the allow zone
+ * transfer method has been called. This method is only called if the
+ * allow zone transfer method returned ISC_R_SUCCESS. This method and
+ * the allow zone transfer method are both required for zone transfers
+ * to be supported. If the driver generates data dynamically (instead
+ * of searching in a database for it) it should not implement this
+ * function as a zone transfer would be meaningless. A SDLZ driver
+ * does not have to implement an all nodes method.
+ */
+
+typedef isc_result_t (*dns_sdlzallowzonexfr_t)(void *driverarg, void *dbdata,
+ const char *name,
+ const char *client);
+
+/*%<
+ * Method prototype. Drivers implementing the SDLZ interface may
+ * supply an allow zone transfer method. This method is called when
+ * the DNS server is performing a zone transfer query, before the all
+ * nodes method can be called. This method and the all node method
+ * are both required for zone transfers to be supported. If the
+ * driver generates data dynamically (instead of searching in a
+ * database for it) it should not implement this function as a zone
+ * transfer would be meaningless. A SDLZ driver does not have to
+ * implement an allow zone transfer method.
+ *
+ * This method should return ISC_R_SUCCESS if the zone is supported by
+ * the database and a zone transfer is allowed for the specified
+ * client. If the zone is supported by the database, but zone
+ * transfers are not allowed for the specified client this method
+ * should return ISC_R_NOPERM.. Lastly the method should return
+ * ISC_R_NOTFOUND if the zone is not supported by the database. If an
+ * error occurs it should return a result code indicating the type of
+ * error.
+ */
+
+typedef isc_result_t (*dns_sdlzauthorityfunc_t)(const char *zone,
+ void *driverarg, void *dbdata,
+ dns_sdlzlookup_t *lookup);
+
+/*%<
+ * Method prototype. Drivers implementing the SDLZ interface may
+ * supply an authority method. This method is called when the DNS
+ * server is performing a query, after both the find zone and lookup
+ * methods have been called. This method is required if the lookup
+ * function does not supply authority information for the dns
+ * record. A SDLZ driver does not have to implement an authority
+ * method.
+ */
+
+typedef isc_result_t (*dns_sdlzcreate_t)(const char *dlzname, unsigned int argc,
+ char *argv[], void *driverarg,
+ void **dbdata);
+
+/*%<
+ * Method prototype. Drivers implementing the SDLZ interface may
+ * supply a create method. This method is called when the DNS server
+ * is starting up and creating drivers for use later. A SDLZ driver
+ * does not have to implement a create method.
+ */
+
+typedef void (*dns_sdlzdestroy_t)(void *driverarg, void *dbdata);
+
+/*%<
+ * Method prototype. Drivers implementing the SDLZ interface may
+ * supply a destroy method. This method is called when the DNS server
+ * is shutting down and no longer needs the driver. A SDLZ driver does
+ * not have to implement a destroy method.
+ */
+
+typedef isc_result_t (*dns_sdlzfindzone_t)(void *driverarg, void *dbdata,
+ const char *name,
+ dns_clientinfomethods_t *methods,
+ dns_clientinfo_t *clientinfo);
+/*%<
+ * Method prototype. Drivers implementing the SDLZ interface MUST
+ * supply a find zone method. This method is called when the DNS
+ * server is performing a query to to determine if 'name' is a
+ * supported dns zone. The find zone method will be called with the
+ * longest possible name first, and continue to be called with
+ * successively shorter domain names, until any of the following
+ * occur:
+ *
+ * \li 1) the function returns (ISC_R_SUCCESS) indicating a zone name
+ * match.
+ *
+ * \li 2) a problem occurs, and the functions returns anything other than
+ * (ISC_R_NOTFOUND)
+ *
+ * \li 3) we run out of domain name labels. I.E. we have tried the
+ * shortest domain name
+ *
+ * \li 4) the number of labels in the domain name is less than min_labels
+ * for dns_dlzfindzone
+ *
+ * The driver's find zone method should return ISC_R_SUCCESS if the
+ * zone is supported by the database. Otherwise it should return
+ * ISC_R_NOTFOUND, if the zone is not supported. If an error occurs
+ * it should return a result code indicating the type of error.
+ */
+
+typedef isc_result_t (*dns_sdlzlookupfunc_t)(const char *zone, const char *name,
+ void *driverarg, void *dbdata,
+ dns_sdlzlookup_t *lookup,
+ dns_clientinfomethods_t *methods,
+ dns_clientinfo_t *clientinfo);
+
+/*%<
+ * Method prototype. Drivers implementing the SDLZ interface MUST
+ * supply a lookup method. This method is called when the
+ * DNS server is performing a query, after the find zone and before any
+ * other methods have been called. This function returns DNS record
+ * information using the dns_sdlz_putrr and dns_sdlz_putsoa functions.
+ * If this function supplies authority information for the DNS record
+ * the authority method is not required. If it does not, the
+ * authority function is required.
+ *
+ * The 'methods' and 'clientinfo' args allow an SDLZ driver to retrieve
+ * information about the querying client (such as source IP address)
+ * from the caller.
+ */
+
+typedef isc_result_t (*dns_sdlznewversion_t)(const char *zone, void *driverarg,
+ void *dbdata, void **versionp);
+/*%<
+ * Method prototype. Drivers implementing the SDLZ interface may
+ * supply a newversion method. This method is called to start a
+ * write transaction on a zone and should only be implemented by
+ * writeable backends.
+ * When implemented, the driver should create a new transaction, and
+ * fill *versionp with a pointer to the transaction state. The
+ * closeversion function will be called to close the transaction.
+ */
+
+typedef void (*dns_sdlzcloseversion_t)(const char *zone, bool commit,
+ void *driverarg, void *dbdata,
+ void **versionp);
+/*%<
+ * Method prototype. Drivers implementing the SDLZ interface must
+ * supply a closeversion method if they supply a newversion method.
+ * When implemented, the driver should close the given transaction,
+ * committing changes if 'commit' is true. If 'commit' is not true
+ * then all changes should be discarded and the database rolled back.
+ * If the call is successful then *versionp should be set to NULL
+ */
+
+typedef isc_result_t (*dns_sdlzconfigure_t)(dns_view_t *view,
+ dns_dlzdb_t *dlzdb, void *driverarg,
+ void *dbdata);
+/*%<
+ * Method prototype. Drivers implementing the SDLZ interface may
+ * supply a configure method. When supplied, it will be called
+ * immediately after the create method to give the driver a chance
+ * to configure writeable zones
+ */
+
+typedef bool (*dns_sdlzssumatch_t)(const char *signer, const char *name,
+ const char *tcpaddr, const char *type,
+ const char *key, uint32_t keydatalen,
+ unsigned char *keydata, void *driverarg,
+ void *dbdata);
+
+/*%<
+ * Method prototype. Drivers implementing the SDLZ interface may
+ * supply a ssumatch method. If supplied, then ssumatch will be
+ * called to authorize any zone updates. The driver should return
+ * true to allow the update, and false to deny it. For a DLZ
+ * controlled zone, this is the only access control on updates.
+ */
+
+typedef isc_result_t (*dns_sdlzmodrdataset_t)(const char *name,
+ const char *rdatastr,
+ void *driverarg, void *dbdata,
+ void *version);
+/*%<
+ * Method prototype. Drivers implementing the SDLZ interface may
+ * supply addrdataset and subtractrdataset methods. If supplied, then these
+ * will be called when rdatasets are added/subtracted during
+ * updates. The version parameter comes from a call to the sdlz
+ * newversion() method from the driver. The rdataset parameter is a
+ * linearise string representation of the rdataset change. The format
+ * is the same as used by dig when displaying records. The fields are
+ * tab delimited.
+ */
+
+typedef isc_result_t (*dns_sdlzdelrdataset_t)(const char *name,
+ const char *type, void *driverarg,
+ void *dbdata, void *version);
+/*%<
+ * Method prototype. Drivers implementing the SDLZ interface may
+ * supply a delrdataset method. If supplied, then this
+ * function will be called when rdatasets are deleted during
+ * updates. The call should remove all rdatasets of the given type for
+ * the specified name.
+ */
+
+typedef struct dns_sdlzmethods {
+ dns_sdlzcreate_t create;
+ dns_sdlzdestroy_t destroy;
+ dns_sdlzfindzone_t findzone;
+ dns_sdlzlookupfunc_t lookup;
+ dns_sdlzauthorityfunc_t authority;
+ dns_sdlzallnodesfunc_t allnodes;
+ dns_sdlzallowzonexfr_t allowzonexfr;
+ dns_sdlznewversion_t newversion;
+ dns_sdlzcloseversion_t closeversion;
+ dns_sdlzconfigure_t configure;
+ dns_sdlzssumatch_t ssumatch;
+ dns_sdlzmodrdataset_t addrdataset;
+ dns_sdlzmodrdataset_t subtractrdataset;
+ dns_sdlzdelrdataset_t delrdataset;
+} dns_sdlzmethods_t;
+
+isc_result_t
+dns_sdlzregister(const char *drivername, const dns_sdlzmethods_t *methods,
+ void *driverarg, unsigned int flags, isc_mem_t *mctx,
+ dns_sdlzimplementation_t **sdlzimp);
+/*%<
+ * Register a dynamically loadable zones (dlz) driver for the database
+ * type 'drivername', implemented by the functions in '*methods'.
+ *
+ * sdlzimp must point to a NULL dns_sdlzimplementation_t pointer.
+ * That is, sdlzimp != NULL && *sdlzimp == NULL. It will be assigned
+ * a value that will later be used to identify the driver when
+ * deregistering it.
+ */
+
+void
+dns_sdlzunregister(dns_sdlzimplementation_t **sdlzimp);
+
+/*%<
+ * Removes the sdlz driver from the list of registered sdlz drivers.
+ * There must be no active sdlz drivers of this type when this
+ * function is called.
+ */
+
+typedef isc_result_t
+dns_sdlz_putnamedrr_t(dns_sdlzallnodes_t *allnodes, const char *name,
+ const char *type, dns_ttl_t ttl, const char *data);
+dns_sdlz_putnamedrr_t dns_sdlz_putnamedrr;
+
+/*%<
+ * Add a single resource record to the allnodes structure to be later
+ * parsed into a zone transfer response.
+ */
+
+typedef isc_result_t
+dns_sdlz_putrr_t(dns_sdlzlookup_t *lookup, const char *type, dns_ttl_t ttl,
+ const char *data);
+dns_sdlz_putrr_t dns_sdlz_putrr;
+/*%<
+ * Add a single resource record to the lookup structure to be later
+ * parsed into a query response.
+ */
+
+typedef isc_result_t
+ dns_sdlz_putsoa_t(dns_sdlzlookup_t *lookup, const char *mname,
+ const char *rname, uint32_t serial);
+dns_sdlz_putsoa_t dns_sdlz_putsoa;
+/*%<
+ * This function may optionally be called from the 'authority'
+ * callback to simplify construction of the SOA record for 'zone'. It
+ * will provide a SOA listing 'mname' as as the primary server and
+ * 'rname' as the responsible person mailbox. It is the
+ * responsibility of the driver to increment the serial number between
+ * responses if necessary. All other SOA fields will have reasonable
+ * default values.
+ */
+
+typedef isc_result_t
+dns_sdlz_setdb_t(dns_dlzdb_t *dlzdatabase, dns_rdataclass_t rdclass,
+ const dns_name_t *name, dns_db_t **dbp);
+dns_sdlz_setdb_t dns_sdlz_setdb;
+/*%<
+ * Create the database pointers for a writeable SDLZ zone
+ */
+
+ISC_LANG_ENDDECLS
diff --git a/lib/dns/include/dns/secalg.h b/lib/dns/include/dns/secalg.h
new file mode 100644
index 0000000..09b3d2b
--- /dev/null
+++ b/lib/dns/include/dns/secalg.h
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#pragma once
+
+/*! \file dns/secalg.h */
+
+#include <isc/lang.h>
+
+#include <dns/types.h>
+
+ISC_LANG_BEGINDECLS
+
+isc_result_t
+dns_secalg_fromtext(dns_secalg_t *secalgp, isc_textregion_t *source);
+/*%<
+ * Convert the text 'source' refers to into a DNSSEC security algorithm value.
+ * The text may contain either a mnemonic algorithm name or a decimal algorithm
+ * number.
+ *
+ * Requires:
+ *\li 'secalgp' is a valid pointer.
+ *
+ *\li 'source' is a valid text region.
+ *
+ * Returns:
+ *\li ISC_R_SUCCESS on success
+ *\li ISC_R_RANGE numeric type is out of range
+ *\li DNS_R_UNKNOWN mnemonic type is unknown
+ */
+
+isc_result_t
+dns_secalg_totext(dns_secalg_t secalg, isc_buffer_t *target);
+/*%<
+ * Put a textual representation of the DNSSEC security algorithm 'secalg'
+ * into 'target'.
+ *
+ * Requires:
+ *\li 'secalg' is a valid secalg.
+ *
+ *\li 'target' is a valid text buffer.
+ *
+ * Ensures,
+ * if the result is success:
+ *\li The used space in 'target' is updated.
+ *
+ * Returns:
+ *\li ISC_R_SUCCESS on success
+ *\li ISC_R_NOSPACE target buffer is too small
+ */
+
+#define DNS_SECALG_FORMATSIZE 20
+void
+dns_secalg_format(dns_secalg_t alg, char *cp, unsigned int size);
+/*%<
+ * Wrapper for dns_secalg_totext(), writing text into 'cp'
+ */
+
+ISC_LANG_ENDDECLS
diff --git a/lib/dns/include/dns/secproto.h b/lib/dns/include/dns/secproto.h
new file mode 100644
index 0000000..0dc80a0
--- /dev/null
+++ b/lib/dns/include/dns/secproto.h
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#pragma once
+
+/*! \file dns/secproto.h */
+
+#include <isc/lang.h>
+
+#include <dns/types.h>
+
+ISC_LANG_BEGINDECLS
+
+isc_result_t
+dns_secproto_fromtext(dns_secproto_t *secprotop, isc_textregion_t *source);
+/*%<
+ * Convert the text 'source' refers to into a DNSSEC security protocol value.
+ * The text may contain either a mnemonic protocol name or a decimal protocol
+ * number.
+ *
+ * Requires:
+ *\li 'secprotop' is a valid pointer.
+ *
+ *\li 'source' is a valid text region.
+ *
+ * Returns:
+ *\li ISC_R_SUCCESS on success
+ *\li ISC_R_RANGE numeric type is out of range
+ *\li DNS_R_UNKNOWN mnemonic type is unknown
+ */
+
+isc_result_t
+dns_secproto_totext(dns_secproto_t secproto, isc_buffer_t *target);
+/*%<
+ * Put a textual representation of the DNSSEC security protocol 'secproto'
+ * into 'target'.
+ *
+ * Requires:
+ *\li 'secproto' is a valid secproto.
+ *
+ *\li 'target' is a valid text buffer.
+ *
+ * Ensures,
+ * if the result is success:
+ * \li The used space in 'target' is updated.
+ *
+ * Returns:
+ *\li ISC_R_SUCCESS on success
+ *\li ISC_R_NOSPACE target buffer is too small
+ */
+
+ISC_LANG_ENDDECLS
diff --git a/lib/dns/include/dns/soa.h b/lib/dns/include/dns/soa.h
new file mode 100644
index 0000000..4f8aa07
--- /dev/null
+++ b/lib/dns/include/dns/soa.h
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#pragma once
+
+/*****
+***** Module Info
+*****/
+
+/*! \file dns/soa.h
+ * \brief
+ * SOA utilities.
+ */
+
+/***
+ *** Imports
+ ***/
+
+#include <inttypes.h>
+
+#include <isc/lang.h>
+#include <isc/types.h>
+
+#include <dns/types.h>
+
+ISC_LANG_BEGINDECLS
+
+#define DNS_SOA_BUFFERSIZE ((2 * DNS_NAME_MAXWIRE) + (4 * 5))
+
+isc_result_t
+dns_soa_buildrdata(const dns_name_t *origin, const dns_name_t *contact,
+ dns_rdataclass_t rdclass, uint32_t serial, uint32_t refresh,
+ uint32_t retry, uint32_t expire, uint32_t minimum,
+ unsigned char *buffer, dns_rdata_t *rdata);
+/*%<
+ * Build the rdata of an SOA record.
+ *
+ * Requires:
+ *\li buffer Points to a temporary buffer of at least
+ * DNS_SOA_BUFFERSIZE bytes.
+ *\li rdata Points to an initialized dns_rdata_t.
+ *
+ * Ensures:
+ * \li *rdata Contains a valid SOA rdata. The 'data' member
+ * refers to 'buffer'.
+ */
+
+uint32_t
+dns_soa_getserial(dns_rdata_t *rdata);
+uint32_t
+dns_soa_getrefresh(dns_rdata_t *rdata);
+uint32_t
+dns_soa_getretry(dns_rdata_t *rdata);
+uint32_t
+dns_soa_getexpire(dns_rdata_t *rdata);
+uint32_t
+dns_soa_getminimum(dns_rdata_t *rdata);
+/*
+ * Extract an integer field from the rdata of a SOA record.
+ *
+ * Requires:
+ * rdata refers to the rdata of a well-formed SOA record.
+ */
+
+void
+dns_soa_setserial(uint32_t val, dns_rdata_t *rdata);
+void
+dns_soa_setrefresh(uint32_t val, dns_rdata_t *rdata);
+void
+dns_soa_setretry(uint32_t val, dns_rdata_t *rdata);
+void
+dns_soa_setexpire(uint32_t val, dns_rdata_t *rdata);
+void
+dns_soa_setminimum(uint32_t val, dns_rdata_t *rdata);
+/*
+ * Change an integer field of a SOA record by modifying the
+ * rdata in-place.
+ *
+ * Requires:
+ * rdata refers to the rdata of a well-formed SOA record.
+ */
+
+ISC_LANG_ENDDECLS
diff --git a/lib/dns/include/dns/ssu.h b/lib/dns/include/dns/ssu.h
new file mode 100644
index 0000000..ead0097
--- /dev/null
+++ b/lib/dns/include/dns/ssu.h
@@ -0,0 +1,261 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#pragma once
+
+/*! \file dns/ssu.h */
+
+#include <stdbool.h>
+
+#include <isc/lang.h>
+
+#include <dns/acl.h>
+#include <dns/types.h>
+
+#include <dst/dst.h>
+
+ISC_LANG_BEGINDECLS
+
+typedef enum {
+ dns_ssumatchtype_name = 0,
+ dns_ssumatchtype_subdomain = 1,
+ dns_ssumatchtype_wildcard = 2,
+ dns_ssumatchtype_self = 3,
+ dns_ssumatchtype_selfsub = 4,
+ dns_ssumatchtype_selfwild = 5,
+ dns_ssumatchtype_selfkrb5 = 6,
+ dns_ssumatchtype_selfms = 7,
+ dns_ssumatchtype_subdomainms = 8,
+ dns_ssumatchtype_subdomainkrb5 = 9,
+ dns_ssumatchtype_tcpself = 10,
+ dns_ssumatchtype_6to4self = 11,
+ dns_ssumatchtype_external = 12,
+ dns_ssumatchtype_local = 13,
+ dns_ssumatchtype_selfsubms = 14,
+ dns_ssumatchtype_selfsubkrb5 = 15,
+ dns_ssumatchtype_subdomainselfkrb5rhs = 16,
+ dns_ssumatchtype_subdomainselfmsrhs = 17,
+ dns_ssumatchtype_max = 17, /* max value */
+
+ dns_ssumatchtype_dlz = 18 /* intentionally higher than _max */
+} dns_ssumatchtype_t;
+
+typedef struct dns_ssuruletype {
+ dns_rdatatype_t type; /* type allowed */
+ unsigned int max; /* maximum number of records allowed. */
+} dns_ssuruletype_t;
+
+void
+dns_ssutable_create(isc_mem_t *mctx, dns_ssutable_t **table);
+/*%<
+ * Creates a table that will be used to store simple-secure-update rules.
+ * Note: all locking must be provided by the client.
+ *
+ * Requires:
+ *\li 'mctx' is a valid memory context
+ *\li 'table' is not NULL, and '*table' is NULL
+ *
+ * Returns:
+ *\li ISC_R_SUCCESS
+ *\li ISC_R_NOMEMORY
+ */
+
+void
+dns_ssutable_createdlz(isc_mem_t *mctx, dns_ssutable_t **tablep,
+ dns_dlzdb_t *dlzdatabase);
+/*%<
+ * Create an SSU table that contains a dlzdatabase pointer, and a
+ * single rule with matchtype dns_ssumatchtype_dlz. This type of SSU
+ * table is used by writeable DLZ drivers to offload authorization for
+ * updates to the driver.
+ */
+
+void
+dns_ssutable_attach(dns_ssutable_t *source, dns_ssutable_t **targetp);
+/*%<
+ * Attach '*targetp' to 'source'.
+ *
+ * Requires:
+ *\li 'source' is a valid SSU table
+ *\li 'targetp' points to a NULL dns_ssutable_t *.
+ *
+ * Ensures:
+ *\li *targetp is attached to source.
+ */
+
+void
+dns_ssutable_detach(dns_ssutable_t **tablep);
+/*%<
+ * Detach '*tablep' from its simple-secure-update rule table.
+ *
+ * Requires:
+ *\li 'tablep' points to a valid dns_ssutable_t
+ *
+ * Ensures:
+ *\li *tablep is NULL
+ *\li If '*tablep' is the last reference to the SSU table, all
+ * resources used by the table will be freed.
+ */
+
+void
+dns_ssutable_addrule(dns_ssutable_t *table, bool grant,
+ const dns_name_t *identity, dns_ssumatchtype_t matchtype,
+ const dns_name_t *name, unsigned int ntypes,
+ dns_ssuruletype_t *types);
+/*%<
+ * Adds a new rule to a simple-secure-update rule table. The rule
+ * either grants or denies update privileges of an identity (or set of
+ * identities) to modify a name (or set of names) or certain types present
+ * at that name.
+ *
+ * Notes:
+ *\li If 'matchtype' is of SELF type, this rule only matches if the
+ * name to be updated matches the signing identity.
+ *
+ *\li If 'ntypes' is 0, this rule applies to all types except
+ * NS, SOA, RRSIG, and NSEC.
+ *
+ *\li If 'types' includes ANY, this rule applies to all types
+ * except NSEC.
+ *
+ * Requires:
+ *\li 'table' is a valid SSU table
+ *\li 'identity' is a valid absolute name
+ *\li 'matchtype' must be one of the defined constants.
+ *\li 'name' is a valid absolute name
+ *\li If 'ntypes' > 0, 'types' must not be NULL
+ *
+ * Returns:
+ *\li ISC_R_SUCCESS
+ *\li ISC_R_NOMEMORY
+ */
+
+bool
+dns_ssutable_checkrules(dns_ssutable_t *table, const dns_name_t *signer,
+ const dns_name_t *name, const isc_netaddr_t *addr,
+ bool tcp, dns_aclenv_t *env, dns_rdatatype_t type,
+ const dns_name_t *target, const dst_key_t *key,
+ const dns_ssurule_t **rulep);
+/*%<
+ * Checks that the attempted update of (name, type) is allowed according
+ * to the rules specified in the simple-secure-update rule table. If
+ * no rules are matched, access is denied.
+ *
+ * Notes:
+ * In dns_ssutable_checkrules(), 'addr' should only be
+ * set if the request received via TCP. This provides a
+ * weak assurance that the request was not spoofed.
+ * 'addr' is to to validate dns_ssumatchtype_tcpself
+ * and dns_ssumatchtype_6to4self rules.
+ *
+ * In dns_ssutable_checkrules2(), 'addr' can also be passed for
+ * UDP requests and TCP is specified via the 'tcp' parameter.
+ * In addition to dns_ssumatchtype_tcpself and
+ * tcp_ssumatchtype_6to4self rules, the address
+ * also be used to check dns_ssumatchtype_local rules.
+ * If 'addr' is set then 'env' must also be set so that
+ * requests from non-localhost addresses can be rejected.
+ *
+ * For dns_ssumatchtype_tcpself the addresses are mapped to
+ * the standard reverse names under IN-ADDR.ARPA and IP6.ARPA.
+ * RFC 1035, Section 3.5, "IN-ADDR.ARPA domain" and RFC 3596,
+ * Section 2.5, "IP6.ARPA Domain".
+ *
+ * For dns_ssumatchtype_6to4self, IPv4 address are converted
+ * to a 6to4 prefix (48 bits) per the rules in RFC 3056. Only
+ * the top 48 bits of the IPv6 address are mapped to the reverse
+ * name. This is independent of whether the most significant 16
+ * bits match 2002::/16, assigned for 6to4 prefixes, or not.
+ *
+ * Requires:
+ *\li 'table' is a valid SSU table
+ *\li 'signer' is NULL or a valid absolute name
+ *\li 'addr' is NULL or a valid network address.
+ *\li 'aclenv' is NULL or a valid ACL environment.
+ *\li 'name' is a valid absolute name
+ *\li if 'addr' is not NULL, 'env' is not NULL.
+ */
+
+/*% Accessor functions to extract rule components */
+bool
+dns_ssurule_isgrant(const dns_ssurule_t *rule);
+
+/*% Accessor functions to extract rule components */
+dns_name_t *
+dns_ssurule_identity(const dns_ssurule_t *rule);
+
+/*% Accessor functions to extract rule components */
+unsigned int
+dns_ssurule_matchtype(const dns_ssurule_t *rule);
+
+/*% Accessor functions to extract rule components */
+dns_name_t *
+dns_ssurule_name(const dns_ssurule_t *rule);
+
+/*% Accessor functions to extract rule components */
+unsigned int
+dns_ssurule_types(const dns_ssurule_t *rule, dns_ssuruletype_t **types);
+
+unsigned int
+dns_ssurule_max(const dns_ssurule_t *rule, dns_rdatatype_t type);
+/*%<
+ * Returns the maximum number of records configured for type `type`.
+ * If no maximum has been configured for `type` but one has been
+ * configured for ANY, return that value instead. Otherwise, return
+ * zero, which implies "unlimited".
+ */
+
+isc_result_t
+dns_ssutable_firstrule(const dns_ssutable_t *table, dns_ssurule_t **rule);
+/*%<
+ * Initiates a rule iterator. There is no need to maintain any state.
+ *
+ * Returns:
+ *\li #ISC_R_SUCCESS
+ *\li #ISC_R_NOMORE
+ */
+
+isc_result_t
+dns_ssutable_nextrule(dns_ssurule_t *rule, dns_ssurule_t **nextrule);
+/*%<
+ * Returns the next rule in the table.
+ *
+ * Returns:
+ *\li #ISC_R_SUCCESS
+ *\li #ISC_R_NOMORE
+ */
+
+bool
+dns_ssu_external_match(const dns_name_t *identity, const dns_name_t *signer,
+ const dns_name_t *name, const isc_netaddr_t *tcpaddr,
+ dns_rdatatype_t type, const dst_key_t *key,
+ isc_mem_t *mctx);
+/*%<
+ * Check a policy rule via an external application
+ */
+
+isc_result_t
+dns_ssu_mtypefromstring(const char *str, dns_ssumatchtype_t *mtype);
+/*%<
+ * Set 'mtype' from 'str'
+ *
+ * Requires:
+ *\li 'str' is not NULL.
+ *\li 'mtype' is not NULL,
+ *
+ * Returns:
+ *\li #ISC_R_SUCCESS
+ *\li #ISC_R_NOTFOUND
+ */
+
+ISC_LANG_ENDDECLS
diff --git a/lib/dns/include/dns/stats.h b/lib/dns/include/dns/stats.h
new file mode 100644
index 0000000..683f870
--- /dev/null
+++ b/lib/dns/include/dns/stats.h
@@ -0,0 +1,825 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#pragma once
+
+/*! \file dns/stats.h */
+
+#include <inttypes.h>
+
+#include <dns/types.h>
+
+/*%
+ * Statistics counters. Used as isc_statscounter_t values.
+ */
+enum {
+ /*%
+ * Resolver statistics counters.
+ */
+ dns_resstatscounter_queryv4 = 0,
+ dns_resstatscounter_queryv6 = 1,
+ dns_resstatscounter_responsev4 = 2,
+ dns_resstatscounter_responsev6 = 3,
+ dns_resstatscounter_nxdomain = 4,
+ dns_resstatscounter_servfail = 5,
+ dns_resstatscounter_formerr = 6,
+ dns_resstatscounter_othererror = 7,
+ dns_resstatscounter_edns0fail = 8,
+ dns_resstatscounter_mismatch = 9,
+ dns_resstatscounter_truncated = 10,
+ dns_resstatscounter_lame = 11,
+ dns_resstatscounter_retry = 12,
+ dns_resstatscounter_gluefetchv4 = 13,
+ dns_resstatscounter_gluefetchv6 = 14,
+ dns_resstatscounter_gluefetchv4fail = 15,
+ dns_resstatscounter_gluefetchv6fail = 16,
+ dns_resstatscounter_val = 17,
+ dns_resstatscounter_valsuccess = 18,
+ dns_resstatscounter_valnegsuccess = 19,
+ dns_resstatscounter_valfail = 20,
+ dns_resstatscounter_dispabort = 21,
+ dns_resstatscounter_dispsockfail = 22,
+ dns_resstatscounter_querytimeout = 23,
+ dns_resstatscounter_queryrtt0 = 24,
+ dns_resstatscounter_queryrtt1 = 25,
+ dns_resstatscounter_queryrtt2 = 26,
+ dns_resstatscounter_queryrtt3 = 27,
+ dns_resstatscounter_queryrtt4 = 28,
+ dns_resstatscounter_queryrtt5 = 29,
+ dns_resstatscounter_nfetch = 30,
+ dns_resstatscounter_disprequdp = 31,
+ dns_resstatscounter_dispreqtcp = 32,
+ dns_resstatscounter_buckets = 33,
+ dns_resstatscounter_refused = 34,
+ dns_resstatscounter_cookienew = 35,
+ dns_resstatscounter_cookieout = 36,
+ dns_resstatscounter_cookiein = 37,
+ dns_resstatscounter_cookieok = 38,
+ dns_resstatscounter_badvers = 39,
+ dns_resstatscounter_badcookie = 40,
+ dns_resstatscounter_zonequota = 41,
+ dns_resstatscounter_serverquota = 42,
+ dns_resstatscounter_clientquota = 43,
+ dns_resstatscounter_nextitem = 44,
+ dns_resstatscounter_priming = 45,
+ dns_resstatscounter_max = 46,
+
+ /*
+ * DNSSEC stats.
+ */
+ dns_dnssecstats_asis = 0,
+ dns_dnssecstats_downcase = 1,
+ dns_dnssecstats_wildcard = 2,
+ dns_dnssecstats_fail = 3,
+
+ dns_dnssecstats_max = 4,
+
+ /*%
+ * Zone statistics counters.
+ */
+ dns_zonestatscounter_notifyoutv4 = 0,
+ dns_zonestatscounter_notifyoutv6 = 1,
+ dns_zonestatscounter_notifyinv4 = 2,
+ dns_zonestatscounter_notifyinv6 = 3,
+ dns_zonestatscounter_notifyrej = 4,
+ dns_zonestatscounter_soaoutv4 = 5,
+ dns_zonestatscounter_soaoutv6 = 6,
+ dns_zonestatscounter_axfrreqv4 = 7,
+ dns_zonestatscounter_axfrreqv6 = 8,
+ dns_zonestatscounter_ixfrreqv4 = 9,
+ dns_zonestatscounter_ixfrreqv6 = 10,
+ dns_zonestatscounter_xfrsuccess = 11,
+ dns_zonestatscounter_xfrfail = 12,
+
+ dns_zonestatscounter_max = 13,
+
+ /*
+ * Adb statistics values.
+ */
+ dns_adbstats_nentries = 0,
+ dns_adbstats_entriescnt = 1,
+ dns_adbstats_nnames = 2,
+ dns_adbstats_namescnt = 3,
+
+ dns_adbstats_max = 4,
+
+ /*
+ * Cache statistics values.
+ */
+ dns_cachestatscounter_hits = 1,
+ dns_cachestatscounter_misses = 2,
+ dns_cachestatscounter_queryhits = 3,
+ dns_cachestatscounter_querymisses = 4,
+ dns_cachestatscounter_deletelru = 5,
+ dns_cachestatscounter_deletettl = 6,
+ dns_cachestatscounter_coveringnsec = 7,
+
+ dns_cachestatscounter_max = 8,
+
+ /*%
+ * Query statistics counters (obsolete).
+ */
+ dns_statscounter_success = 0, /*%< Successful lookup */
+ dns_statscounter_referral = 1, /*%< Referral result */
+ dns_statscounter_nxrrset = 2, /*%< NXRRSET result */
+ dns_statscounter_nxdomain = 3, /*%< NXDOMAIN result */
+ dns_statscounter_recursion = 4, /*%< Recursion was used */
+ dns_statscounter_failure = 5, /*%< Some other failure */
+ dns_statscounter_duplicate = 6, /*%< Duplicate query */
+ dns_statscounter_dropped = 7, /*%< Duplicate query (dropped) */
+
+ /*%
+ * DNSTAP statistics counters.
+ */
+ dns_dnstapcounter_success = 0,
+ dns_dnstapcounter_drop = 1,
+ dns_dnstapcounter_max = 2,
+
+ /*
+ * Glue cache statistics counters.
+ */
+ dns_gluecachestatscounter_hits_present = 0,
+ dns_gluecachestatscounter_hits_absent = 1,
+ dns_gluecachestatscounter_inserts_present = 2,
+ dns_gluecachestatscounter_inserts_absent = 3,
+
+ dns_gluecachestatscounter_max = 4,
+};
+
+/*%
+ * Traffic size statistics counters. Used as isc_statscounter_t values.
+ */
+enum {
+ dns_sizecounter_in_0 = 0,
+ dns_sizecounter_in_16 = 1,
+ dns_sizecounter_in_32 = 2,
+ dns_sizecounter_in_48 = 3,
+ dns_sizecounter_in_64 = 4,
+ dns_sizecounter_in_80 = 5,
+ dns_sizecounter_in_96 = 6,
+ dns_sizecounter_in_112 = 7,
+ dns_sizecounter_in_128 = 8,
+ dns_sizecounter_in_144 = 9,
+ dns_sizecounter_in_160 = 10,
+ dns_sizecounter_in_176 = 11,
+ dns_sizecounter_in_192 = 12,
+ dns_sizecounter_in_208 = 13,
+ dns_sizecounter_in_224 = 14,
+ dns_sizecounter_in_240 = 15,
+ dns_sizecounter_in_256 = 16,
+ dns_sizecounter_in_272 = 17,
+ dns_sizecounter_in_288 = 18,
+
+ dns_sizecounter_in_max = 19,
+};
+
+enum {
+ dns_sizecounter_out_0 = 0,
+ dns_sizecounter_out_16 = 1,
+ dns_sizecounter_out_32 = 2,
+ dns_sizecounter_out_48 = 3,
+ dns_sizecounter_out_64 = 4,
+ dns_sizecounter_out_80 = 5,
+ dns_sizecounter_out_96 = 6,
+ dns_sizecounter_out_112 = 7,
+ dns_sizecounter_out_128 = 8,
+ dns_sizecounter_out_144 = 9,
+ dns_sizecounter_out_160 = 10,
+ dns_sizecounter_out_176 = 11,
+ dns_sizecounter_out_192 = 12,
+ dns_sizecounter_out_208 = 13,
+ dns_sizecounter_out_224 = 14,
+ dns_sizecounter_out_240 = 15,
+ dns_sizecounter_out_256 = 16,
+ dns_sizecounter_out_272 = 17,
+ dns_sizecounter_out_288 = 18,
+ dns_sizecounter_out_304 = 19,
+ dns_sizecounter_out_320 = 20,
+ dns_sizecounter_out_336 = 21,
+ dns_sizecounter_out_352 = 22,
+ dns_sizecounter_out_368 = 23,
+ dns_sizecounter_out_384 = 24,
+ dns_sizecounter_out_400 = 25,
+ dns_sizecounter_out_416 = 26,
+ dns_sizecounter_out_432 = 27,
+ dns_sizecounter_out_448 = 28,
+ dns_sizecounter_out_464 = 29,
+ dns_sizecounter_out_480 = 30,
+ dns_sizecounter_out_496 = 31,
+ dns_sizecounter_out_512 = 32,
+ dns_sizecounter_out_528 = 33,
+ dns_sizecounter_out_544 = 34,
+ dns_sizecounter_out_560 = 35,
+ dns_sizecounter_out_576 = 36,
+ dns_sizecounter_out_592 = 37,
+ dns_sizecounter_out_608 = 38,
+ dns_sizecounter_out_624 = 39,
+ dns_sizecounter_out_640 = 40,
+ dns_sizecounter_out_656 = 41,
+ dns_sizecounter_out_672 = 42,
+ dns_sizecounter_out_688 = 43,
+ dns_sizecounter_out_704 = 44,
+ dns_sizecounter_out_720 = 45,
+ dns_sizecounter_out_736 = 46,
+ dns_sizecounter_out_752 = 47,
+ dns_sizecounter_out_768 = 48,
+ dns_sizecounter_out_784 = 49,
+ dns_sizecounter_out_800 = 50,
+ dns_sizecounter_out_816 = 51,
+ dns_sizecounter_out_832 = 52,
+ dns_sizecounter_out_848 = 53,
+ dns_sizecounter_out_864 = 54,
+ dns_sizecounter_out_880 = 55,
+ dns_sizecounter_out_896 = 56,
+ dns_sizecounter_out_912 = 57,
+ dns_sizecounter_out_928 = 58,
+ dns_sizecounter_out_944 = 59,
+ dns_sizecounter_out_960 = 60,
+ dns_sizecounter_out_976 = 61,
+ dns_sizecounter_out_992 = 62,
+ dns_sizecounter_out_1008 = 63,
+ dns_sizecounter_out_1024 = 64,
+ dns_sizecounter_out_1040 = 65,
+ dns_sizecounter_out_1056 = 66,
+ dns_sizecounter_out_1072 = 67,
+ dns_sizecounter_out_1088 = 68,
+ dns_sizecounter_out_1104 = 69,
+ dns_sizecounter_out_1120 = 70,
+ dns_sizecounter_out_1136 = 71,
+ dns_sizecounter_out_1152 = 72,
+ dns_sizecounter_out_1168 = 73,
+ dns_sizecounter_out_1184 = 74,
+ dns_sizecounter_out_1200 = 75,
+ dns_sizecounter_out_1216 = 76,
+ dns_sizecounter_out_1232 = 77,
+ dns_sizecounter_out_1248 = 78,
+ dns_sizecounter_out_1264 = 79,
+ dns_sizecounter_out_1280 = 80,
+ dns_sizecounter_out_1296 = 81,
+ dns_sizecounter_out_1312 = 82,
+ dns_sizecounter_out_1328 = 83,
+ dns_sizecounter_out_1344 = 84,
+ dns_sizecounter_out_1360 = 85,
+ dns_sizecounter_out_1376 = 86,
+ dns_sizecounter_out_1392 = 87,
+ dns_sizecounter_out_1408 = 88,
+ dns_sizecounter_out_1424 = 89,
+ dns_sizecounter_out_1440 = 90,
+ dns_sizecounter_out_1456 = 91,
+ dns_sizecounter_out_1472 = 92,
+ dns_sizecounter_out_1488 = 93,
+ dns_sizecounter_out_1504 = 94,
+ dns_sizecounter_out_1520 = 95,
+ dns_sizecounter_out_1536 = 96,
+ dns_sizecounter_out_1552 = 97,
+ dns_sizecounter_out_1568 = 98,
+ dns_sizecounter_out_1584 = 99,
+ dns_sizecounter_out_1600 = 100,
+ dns_sizecounter_out_1616 = 101,
+ dns_sizecounter_out_1632 = 102,
+ dns_sizecounter_out_1648 = 103,
+ dns_sizecounter_out_1664 = 104,
+ dns_sizecounter_out_1680 = 105,
+ dns_sizecounter_out_1696 = 106,
+ dns_sizecounter_out_1712 = 107,
+ dns_sizecounter_out_1728 = 108,
+ dns_sizecounter_out_1744 = 109,
+ dns_sizecounter_out_1760 = 110,
+ dns_sizecounter_out_1776 = 111,
+ dns_sizecounter_out_1792 = 112,
+ dns_sizecounter_out_1808 = 113,
+ dns_sizecounter_out_1824 = 114,
+ dns_sizecounter_out_1840 = 115,
+ dns_sizecounter_out_1856 = 116,
+ dns_sizecounter_out_1872 = 117,
+ dns_sizecounter_out_1888 = 118,
+ dns_sizecounter_out_1904 = 119,
+ dns_sizecounter_out_1920 = 120,
+ dns_sizecounter_out_1936 = 121,
+ dns_sizecounter_out_1952 = 122,
+ dns_sizecounter_out_1968 = 123,
+ dns_sizecounter_out_1984 = 124,
+ dns_sizecounter_out_2000 = 125,
+ dns_sizecounter_out_2016 = 126,
+ dns_sizecounter_out_2032 = 127,
+ dns_sizecounter_out_2048 = 128,
+ dns_sizecounter_out_2064 = 129,
+ dns_sizecounter_out_2080 = 130,
+ dns_sizecounter_out_2096 = 131,
+ dns_sizecounter_out_2112 = 132,
+ dns_sizecounter_out_2128 = 133,
+ dns_sizecounter_out_2144 = 134,
+ dns_sizecounter_out_2160 = 135,
+ dns_sizecounter_out_2176 = 136,
+ dns_sizecounter_out_2192 = 137,
+ dns_sizecounter_out_2208 = 138,
+ dns_sizecounter_out_2224 = 139,
+ dns_sizecounter_out_2240 = 140,
+ dns_sizecounter_out_2256 = 141,
+ dns_sizecounter_out_2272 = 142,
+ dns_sizecounter_out_2288 = 143,
+ dns_sizecounter_out_2304 = 144,
+ dns_sizecounter_out_2320 = 145,
+ dns_sizecounter_out_2336 = 146,
+ dns_sizecounter_out_2352 = 147,
+ dns_sizecounter_out_2368 = 148,
+ dns_sizecounter_out_2384 = 149,
+ dns_sizecounter_out_2400 = 150,
+ dns_sizecounter_out_2416 = 151,
+ dns_sizecounter_out_2432 = 152,
+ dns_sizecounter_out_2448 = 153,
+ dns_sizecounter_out_2464 = 154,
+ dns_sizecounter_out_2480 = 155,
+ dns_sizecounter_out_2496 = 156,
+ dns_sizecounter_out_2512 = 157,
+ dns_sizecounter_out_2528 = 158,
+ dns_sizecounter_out_2544 = 159,
+ dns_sizecounter_out_2560 = 160,
+ dns_sizecounter_out_2576 = 161,
+ dns_sizecounter_out_2592 = 162,
+ dns_sizecounter_out_2608 = 163,
+ dns_sizecounter_out_2624 = 164,
+ dns_sizecounter_out_2640 = 165,
+ dns_sizecounter_out_2656 = 166,
+ dns_sizecounter_out_2672 = 167,
+ dns_sizecounter_out_2688 = 168,
+ dns_sizecounter_out_2704 = 169,
+ dns_sizecounter_out_2720 = 170,
+ dns_sizecounter_out_2736 = 171,
+ dns_sizecounter_out_2752 = 172,
+ dns_sizecounter_out_2768 = 173,
+ dns_sizecounter_out_2784 = 174,
+ dns_sizecounter_out_2800 = 175,
+ dns_sizecounter_out_2816 = 176,
+ dns_sizecounter_out_2832 = 177,
+ dns_sizecounter_out_2848 = 178,
+ dns_sizecounter_out_2864 = 179,
+ dns_sizecounter_out_2880 = 180,
+ dns_sizecounter_out_2896 = 181,
+ dns_sizecounter_out_2912 = 182,
+ dns_sizecounter_out_2928 = 183,
+ dns_sizecounter_out_2944 = 184,
+ dns_sizecounter_out_2960 = 185,
+ dns_sizecounter_out_2976 = 186,
+ dns_sizecounter_out_2992 = 187,
+ dns_sizecounter_out_3008 = 188,
+ dns_sizecounter_out_3024 = 189,
+ dns_sizecounter_out_3040 = 190,
+ dns_sizecounter_out_3056 = 191,
+ dns_sizecounter_out_3072 = 192,
+ dns_sizecounter_out_3088 = 193,
+ dns_sizecounter_out_3104 = 194,
+ dns_sizecounter_out_3120 = 195,
+ dns_sizecounter_out_3136 = 196,
+ dns_sizecounter_out_3152 = 197,
+ dns_sizecounter_out_3168 = 198,
+ dns_sizecounter_out_3184 = 199,
+ dns_sizecounter_out_3200 = 200,
+ dns_sizecounter_out_3216 = 201,
+ dns_sizecounter_out_3232 = 202,
+ dns_sizecounter_out_3248 = 203,
+ dns_sizecounter_out_3264 = 204,
+ dns_sizecounter_out_3280 = 205,
+ dns_sizecounter_out_3296 = 206,
+ dns_sizecounter_out_3312 = 207,
+ dns_sizecounter_out_3328 = 208,
+ dns_sizecounter_out_3344 = 209,
+ dns_sizecounter_out_3360 = 210,
+ dns_sizecounter_out_3376 = 211,
+ dns_sizecounter_out_3392 = 212,
+ dns_sizecounter_out_3408 = 213,
+ dns_sizecounter_out_3424 = 214,
+ dns_sizecounter_out_3440 = 215,
+ dns_sizecounter_out_3456 = 216,
+ dns_sizecounter_out_3472 = 217,
+ dns_sizecounter_out_3488 = 218,
+ dns_sizecounter_out_3504 = 219,
+ dns_sizecounter_out_3520 = 220,
+ dns_sizecounter_out_3536 = 221,
+ dns_sizecounter_out_3552 = 222,
+ dns_sizecounter_out_3568 = 223,
+ dns_sizecounter_out_3584 = 224,
+ dns_sizecounter_out_3600 = 225,
+ dns_sizecounter_out_3616 = 226,
+ dns_sizecounter_out_3632 = 227,
+ dns_sizecounter_out_3648 = 228,
+ dns_sizecounter_out_3664 = 229,
+ dns_sizecounter_out_3680 = 230,
+ dns_sizecounter_out_3696 = 231,
+ dns_sizecounter_out_3712 = 232,
+ dns_sizecounter_out_3728 = 233,
+ dns_sizecounter_out_3744 = 234,
+ dns_sizecounter_out_3760 = 235,
+ dns_sizecounter_out_3776 = 236,
+ dns_sizecounter_out_3792 = 237,
+ dns_sizecounter_out_3808 = 238,
+ dns_sizecounter_out_3824 = 239,
+ dns_sizecounter_out_3840 = 240,
+ dns_sizecounter_out_3856 = 241,
+ dns_sizecounter_out_3872 = 242,
+ dns_sizecounter_out_3888 = 243,
+ dns_sizecounter_out_3904 = 244,
+ dns_sizecounter_out_3920 = 245,
+ dns_sizecounter_out_3936 = 246,
+ dns_sizecounter_out_3952 = 247,
+ dns_sizecounter_out_3968 = 248,
+ dns_sizecounter_out_3984 = 249,
+ dns_sizecounter_out_4000 = 250,
+ dns_sizecounter_out_4016 = 251,
+ dns_sizecounter_out_4032 = 252,
+ dns_sizecounter_out_4048 = 253,
+ dns_sizecounter_out_4064 = 254,
+ dns_sizecounter_out_4080 = 255,
+ dns_sizecounter_out_4096 = 256,
+
+ dns_sizecounter_out_max = 257
+};
+
+#define DNS_STATS_NCOUNTERS 8
+
+#if 0
+/*%<
+ * Flag(s) for dns_xxxstats_dump(). DNS_STATSDUMP_VERBOSE is obsolete.
+ * ISC_STATSDUMP_VERBOSE should be used instead. These two values are
+ * intentionally defined to be the same value to ensure binary compatibility.
+ */
+#define DNS_STATSDUMP_VERBOSE 0x00000001 /*%< dump 0-value counters */
+#endif /* if 0 */
+
+/*%<
+ * (Obsoleted)
+ */
+extern const char *dns_statscounter_names[];
+
+/*%
+ * Attributes for statistics counters of RRset and Rdatatype types.
+ *
+ * _OTHERTYPE
+ * The rdata type is not explicitly supported and the corresponding counter
+ * is counted for other such types, too. When this attribute is set,
+ * the base type is of no use.
+ *
+ * _NXRRSET
+ * RRset type counters only. Indicates the RRset is non existent.
+ *
+ * _NXDOMAIN
+ * RRset type counters only. Indicates a non existent name. When this
+ * attribute is set, the base type is of no use.
+ *
+ * _STALE
+ * RRset type counters only. This indicates a record that is stale
+ * but may still be served.
+ *
+ * _ANCIENT
+ * RRset type counters only. This indicates a record that is marked for
+ * removal.
+ */
+#define DNS_RDATASTATSTYPE_ATTR_OTHERTYPE 0x0001
+#define DNS_RDATASTATSTYPE_ATTR_NXRRSET 0x0002
+#define DNS_RDATASTATSTYPE_ATTR_NXDOMAIN 0x0004
+#define DNS_RDATASTATSTYPE_ATTR_STALE 0x0008
+#define DNS_RDATASTATSTYPE_ATTR_ANCIENT 0x0010
+
+/*%<
+ * Conversion macros among dns_rdatatype_t, attributes and isc_statscounter_t.
+ */
+#define DNS_RDATASTATSTYPE_BASE(type) ((dns_rdatatype_t)((type)&0xFFFF))
+#define DNS_RDATASTATSTYPE_ATTR(type) ((type) >> 16)
+#define DNS_RDATASTATSTYPE_VALUE(b, a) (((a) << 16) | (b))
+
+/*%
+ * Types of DNSSEC sign statistics operations.
+ */
+typedef enum {
+ dns_dnssecsignstats_sign = 1,
+ dns_dnssecsignstats_refresh = 2
+} dnssecsignstats_type_t;
+
+/*%<
+ * Types of dump callbacks.
+ */
+typedef void (*dns_generalstats_dumper_t)(isc_statscounter_t, uint64_t, void *);
+typedef void (*dns_rdatatypestats_dumper_t)(dns_rdatastatstype_t, uint64_t,
+ void *);
+typedef void (*dns_dnssecsignstats_dumper_t)(dns_keytag_t, uint64_t, void *);
+typedef void (*dns_opcodestats_dumper_t)(dns_opcode_t, uint64_t, void *);
+typedef void (*dns_rcodestats_dumper_t)(dns_rcode_t, uint64_t, void *);
+
+ISC_LANG_BEGINDECLS
+
+isc_result_t
+dns_generalstats_create(isc_mem_t *mctx, dns_stats_t **statsp, int ncounters);
+/*%<
+ * Create a statistics counter structure of general type. It counts a general
+ * set of counters indexed by an ID between 0 and ncounters -1.
+ * This function is obsolete. A more general function, isc_stats_create(),
+ * should be used.
+ *
+ * Requires:
+ *\li 'mctx' must be a valid memory context.
+ *
+ *\li 'statsp' != NULL && '*statsp' == NULL.
+ *
+ * Returns:
+ *\li ISC_R_SUCCESS -- all ok
+ *
+ *\li anything else -- failure
+ */
+
+isc_result_t
+dns_rdatatypestats_create(isc_mem_t *mctx, dns_stats_t **statsp);
+/*%<
+ * Create a statistics counter structure per rdatatype.
+ *
+ * Requires:
+ *\li 'mctx' must be a valid memory context.
+ *
+ *\li 'statsp' != NULL && '*statsp' == NULL.
+ *
+ * Returns:
+ *\li ISC_R_SUCCESS -- all ok
+ *
+ *\li anything else -- failure
+ */
+
+isc_result_t
+dns_rdatasetstats_create(isc_mem_t *mctx, dns_stats_t **statsp);
+/*%<
+ * Create a statistics counter structure per RRset.
+ *
+ * Requires:
+ *\li 'mctx' must be a valid memory context.
+ *
+ *\li 'statsp' != NULL && '*statsp' == NULL.
+ *
+ * Returns:
+ *\li ISC_R_SUCCESS -- all ok
+ *
+ *\li anything else -- failure
+ */
+
+isc_result_t
+dns_opcodestats_create(isc_mem_t *mctx, dns_stats_t **statsp);
+/*%<
+ * Create a statistics counter structure per opcode.
+ *
+ * Requires:
+ *\li 'mctx' must be a valid memory context.
+ *
+ *\li 'statsp' != NULL && '*statsp' == NULL.
+ *
+ * Returns:
+ *\li ISC_R_SUCCESS -- all ok
+ *
+ *\li anything else -- failure
+ */
+
+isc_result_t
+dns_rcodestats_create(isc_mem_t *mctx, dns_stats_t **statsp);
+/*%<
+ * Create a statistics counter structure per assigned rcode.
+ *
+ * Requires:
+ *\li 'mctx' must be a valid memory context.
+ *
+ *\li 'statsp' != NULL && '*statsp' == NULL.
+ *
+ * Returns:
+ *\li ISC_R_SUCCESS -- all ok
+ *
+ *\li anything else -- failure
+ */
+
+isc_result_t
+dns_dnssecsignstats_create(isc_mem_t *mctx, dns_stats_t **statsp);
+/*%<
+ * Create a statistics counter structure per assigned DNSKEY id.
+ *
+ * Requires:
+ *\li 'mctx' must be a valid memory context.
+ *
+ *\li 'statsp' != NULL && '*statsp' == NULL.
+ *
+ * Returns:
+ *\li ISC_R_SUCCESS -- all ok
+ *
+ *\li anything else -- failure
+ */
+
+void
+dns_stats_attach(dns_stats_t *stats, dns_stats_t **statsp);
+/*%<
+ * Attach to a statistics set.
+ *
+ * Requires:
+ *\li 'stats' is a valid dns_stats_t.
+ *
+ *\li 'statsp' != NULL && '*statsp' == NULL
+ */
+
+void
+dns_stats_detach(dns_stats_t **statsp);
+/*%<
+ * Detaches from the statistics set.
+ *
+ * Requires:
+ *\li 'statsp' != NULL and '*statsp' is a valid dns_stats_t.
+ */
+
+void
+dns_generalstats_increment(dns_stats_t *stats, isc_statscounter_t counter);
+/*%<
+ * Increment the counter-th counter of stats. This function is obsolete.
+ * A more general function, isc_stats_increment(), should be used.
+ *
+ * Requires:
+ *\li 'stats' is a valid dns_stats_t created by dns_generalstats_create().
+ *
+ *\li counter is less than the maximum available ID for the stats specified
+ * on creation.
+ */
+
+void
+dns_rdatatypestats_increment(dns_stats_t *stats, dns_rdatatype_t type);
+/*%<
+ * Increment the statistics counter for 'type'.
+ *
+ * Requires:
+ *\li 'stats' is a valid dns_stats_t created by dns_rdatatypestats_create().
+ */
+
+void
+dns_rdatasetstats_increment(dns_stats_t *stats, dns_rdatastatstype_t rrsettype);
+/*%<
+ * Increment the statistics counter for 'rrsettype'.
+ *
+ * Note: if 'rrsettype' has the _STALE attribute set the corresponding
+ * non-stale counter will be decremented.
+ *
+ * Requires:
+ *\li 'stats' is a valid dns_stats_t created by dns_rdatasetstats_create().
+ */
+
+void
+dns_rdatasetstats_decrement(dns_stats_t *stats, dns_rdatastatstype_t rrsettype);
+/*%<
+ * Decrement the statistics counter for 'rrsettype'.
+ *
+ * Requires:
+ *\li 'stats' is a valid dns_stats_t created by dns_rdatasetstats_create().
+ */
+
+void
+dns_opcodestats_increment(dns_stats_t *stats, dns_opcode_t code);
+/*%<
+ * Increment the statistics counter for 'code'.
+ *
+ * Requires:
+ *\li 'stats' is a valid dns_stats_t created by dns_opcodestats_create().
+ */
+
+void
+dns_rcodestats_increment(dns_stats_t *stats, dns_opcode_t code);
+/*%<
+ * Increment the statistics counter for 'code'.
+ *
+ * Requires:
+ *\li 'stats' is a valid dns_stats_t created by dns_rcodestats_create().
+ */
+
+void
+dns_dnssecsignstats_increment(dns_stats_t *stats, dns_keytag_t id, uint8_t alg,
+ dnssecsignstats_type_t operation);
+/*%<
+ * Increment the statistics counter for the DNSKEY 'id' with algorithm 'alg'.
+ * The 'operation' determines what counter is incremented.
+ *
+ * Requires:
+ *\li 'stats' is a valid dns_stats_t created by dns_dnssecsignstats_create().
+ */
+
+void
+dns_dnssecsignstats_clear(dns_stats_t *stats, dns_keytag_t id, uint8_t alg);
+/*%<
+ * Clear the statistics counter for the DNSKEY 'id' with algorithm 'alg'.
+ *
+ * Requires:
+ *\li 'stats' is a valid dns_stats_t created by dns_dnssecsignstats_create().
+ */
+
+void
+dns_generalstats_dump(dns_stats_t *stats, dns_generalstats_dumper_t dump_fn,
+ void *arg, unsigned int options);
+/*%<
+ * Dump the current statistics counters in a specified way. For each counter
+ * in stats, dump_fn is called with its current value and the given argument
+ * arg. By default counters that have a value of 0 is skipped; if options has
+ * the ISC_STATSDUMP_VERBOSE flag, even such counters are dumped.
+ *
+ * This function is obsolete. A more general function, isc_stats_dump(),
+ * should be used.
+ *
+ * Requires:
+ *\li 'stats' is a valid dns_stats_t created by dns_generalstats_create().
+ */
+
+void
+dns_rdatatypestats_dump(dns_stats_t *stats, dns_rdatatypestats_dumper_t dump_fn,
+ void *arg, unsigned int options);
+/*%<
+ * Dump the current statistics counters in a specified way. For each counter
+ * in stats, dump_fn is called with the corresponding type in the form of
+ * dns_rdatastatstype_t, the current counter value and the given argument
+ * arg. By default counters that have a value of 0 is skipped; if options has
+ * the ISC_STATSDUMP_VERBOSE flag, even such counters are dumped.
+ *
+ * Requires:
+ *\li 'stats' is a valid dns_stats_t created by dns_generalstats_create().
+ */
+
+void
+dns_rdatasetstats_dump(dns_stats_t *stats, dns_rdatatypestats_dumper_t dump_fn,
+ void *arg, unsigned int options);
+/*%<
+ * Dump the current statistics counters in a specified way. For each counter
+ * in stats, dump_fn is called with the corresponding type in the form of
+ * dns_rdatastatstype_t, the current counter value and the given argument
+ * arg. By default counters that have a value of 0 is skipped; if options has
+ * the ISC_STATSDUMP_VERBOSE flag, even such counters are dumped.
+ *
+ * Requires:
+ *\li 'stats' is a valid dns_stats_t created by dns_generalstats_create().
+ */
+
+void
+dns_dnssecsignstats_dump(dns_stats_t *stats, dnssecsignstats_type_t operation,
+ dns_dnssecsignstats_dumper_t dump_fn, void *arg,
+ unsigned int options);
+/*%<
+ * Dump the current statistics counters in a specified way. For each counter
+ * in stats, dump_fn is called with the corresponding type in the form of
+ * dns_rdatastatstype_t, the current counter value and the given argument
+ * arg. By default counters that have a value of 0 is skipped; if options has
+ * the ISC_STATSDUMP_VERBOSE flag, even such counters are dumped.
+ *
+ * Requires:
+ *\li 'stats' is a valid dns_stats_t created by dns_generalstats_create().
+ */
+
+void
+dns_opcodestats_dump(dns_stats_t *stats, dns_opcodestats_dumper_t dump_fn,
+ void *arg, unsigned int options);
+/*%<
+ * Dump the current statistics counters in a specified way. For each counter
+ * in stats, dump_fn is called with the corresponding opcode, the current
+ * counter value and the given argument arg. By default counters that have a
+ * value of 0 is skipped; if options has the ISC_STATSDUMP_VERBOSE flag, even
+ * such counters are dumped.
+ *
+ * Requires:
+ *\li 'stats' is a valid dns_stats_t created by dns_generalstats_create().
+ */
+
+void
+dns_rcodestats_dump(dns_stats_t *stats, dns_rcodestats_dumper_t dump_fn,
+ void *arg, unsigned int options);
+/*%<
+ * Dump the current statistics counters in a specified way. For each counter
+ * in stats, dump_fn is called with the corresponding rcode, the current
+ * counter value and the given argument arg. By default counters that have a
+ * value of 0 is skipped; if options has the ISC_STATSDUMP_VERBOSE flag, even
+ * such counters are dumped.
+ *
+ * Requires:
+ *\li 'stats' is a valid dns_stats_t created by dns_generalstats_create().
+ */
+
+isc_result_t
+dns_stats_alloccounters(isc_mem_t *mctx, uint64_t **ctrp);
+/*%<
+ * Allocate an array of query statistics counters from the memory
+ * context 'mctx'.
+ *
+ * This function is obsoleted. Use dns_xxxstats_create() instead.
+ */
+
+void
+dns_stats_freecounters(isc_mem_t *mctx, uint64_t **ctrp);
+/*%<
+ * Free an array of query statistics counters allocated from the memory
+ * context 'mctx'.
+ *
+ * This function is obsoleted. Use dns_stats_destroy() instead.
+ */
+
+ISC_LANG_ENDDECLS
diff --git a/lib/dns/include/dns/time.h b/lib/dns/include/dns/time.h
new file mode 100644
index 0000000..db86a49
--- /dev/null
+++ b/lib/dns/include/dns/time.h
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#pragma once
+
+/*! \file dns/time.h */
+
+/***
+ *** Imports
+ ***/
+
+#include <inttypes.h>
+
+#include <isc/buffer.h>
+#include <isc/lang.h>
+
+ISC_LANG_BEGINDECLS
+
+/***
+ *** Functions
+ ***/
+
+isc_result_t
+dns_time64_fromtext(const char *source, int64_t *target);
+/*%<
+ * Convert a date and time in YYYYMMDDHHMMSS text format at 'source'
+ * into to a 64-bit count of seconds since Jan 1 1970 0:00 GMT.
+ * Store the count at 'target'.
+ */
+
+isc_result_t
+dns_time32_fromtext(const char *source, uint32_t *target);
+/*%<
+ * Like dns_time64_fromtext, but returns the second count modulo 2^32
+ * as per RFC2535.
+ */
+
+isc_result_t
+dns_time64_totext(int64_t value, isc_buffer_t *target);
+/*%<
+ * Convert a 64-bit count of seconds since Jan 1 1970 0:00 GMT into
+ * a YYYYMMDDHHMMSS text representation and append it to 'target'.
+ */
+
+isc_result_t
+dns_time32_totext(uint32_t value, isc_buffer_t *target);
+/*%<
+ * Like dns_time64_totext, but for a 32-bit cyclic time value.
+ * Of those dates whose counts of seconds since Jan 1 1970 0:00 GMT
+ * are congruent with 'value' modulo 2^32, the one closest to the
+ * current date is chosen.
+ */
+
+int64_t
+dns_time64_from32(uint32_t value);
+/*%<
+ * Covert a 32-bit cyclic time value into a 64 bit time stamp.
+ */
+
+ISC_LANG_ENDDECLS
diff --git a/lib/dns/include/dns/tkey.h b/lib/dns/include/dns/tkey.h
new file mode 100644
index 0000000..08c76b7
--- /dev/null
+++ b/lib/dns/include/dns/tkey.h
@@ -0,0 +1,243 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#pragma once
+
+/*! \file dns/tkey.h */
+
+#include <inttypes.h>
+#include <stdbool.h>
+
+#include <isc/lang.h>
+
+#include <dns/types.h>
+
+#include <dst/dst.h>
+#include <dst/gssapi.h>
+
+ISC_LANG_BEGINDECLS
+
+/* Key agreement modes */
+#define DNS_TKEYMODE_SERVERASSIGNED 1
+#define DNS_TKEYMODE_DIFFIEHELLMAN 2
+#define DNS_TKEYMODE_GSSAPI 3
+#define DNS_TKEYMODE_RESOLVERASSIGNED 4
+#define DNS_TKEYMODE_DELETE 5
+
+struct dns_tkeyctx {
+ dst_key_t *dhkey;
+ dns_name_t *domain;
+ dns_gss_cred_id_t gsscred;
+ isc_mem_t *mctx;
+ char *gssapi_keytab;
+};
+
+isc_result_t
+dns_tkeyctx_create(isc_mem_t *mctx, dns_tkeyctx_t **tctxp);
+/*%<
+ * Create an empty TKEY context.
+ *
+ * Requires:
+ *\li 'mctx' is not NULL
+ *\li 'tctx' is not NULL
+ *\li '*tctx' is NULL
+ *
+ * Returns
+ *\li #ISC_R_SUCCESS
+ *\li #ISC_R_NOMEMORY
+ *\li return codes from dns_name_fromtext()
+ */
+
+void
+dns_tkeyctx_destroy(dns_tkeyctx_t **tctxp);
+/*%<
+ * Frees all data associated with the TKEY context
+ *
+ * Requires:
+ *\li 'tctx' is not NULL
+ *\li '*tctx' is not NULL
+ */
+
+isc_result_t
+dns_tkey_processquery(dns_message_t *msg, dns_tkeyctx_t *tctx,
+ dns_tsig_keyring_t *ring);
+/*%<
+ * Processes a query containing a TKEY record, adding or deleting TSIG
+ * keys if necessary, and modifies the message to contain the response.
+ *
+ * Requires:
+ *\li 'msg' is a valid message
+ *\li 'tctx' is a valid TKEY context
+ *\li 'ring' is a valid TSIG keyring
+ *
+ * Returns
+ *\li #ISC_R_SUCCESS msg was updated (the TKEY operation succeeded,
+ * or msg now includes a TKEY with an error set)
+ * DNS_R_FORMERR the packet was malformed (missing a TKEY
+ * or KEY).
+ *\li other An error occurred while processing the message
+ */
+
+isc_result_t
+dns_tkey_builddhquery(dns_message_t *msg, dst_key_t *key,
+ const dns_name_t *name, const dns_name_t *algorithm,
+ isc_buffer_t *nonce, uint32_t lifetime);
+/*%<
+ * Builds a query containing a TKEY that will generate a shared
+ * secret using a Diffie-Hellman key exchange. The shared key
+ * will be of the specified algorithm (only DNS_TSIG_HMACMD5_NAME
+ * is supported), and will be named either 'name',
+ * 'name' + server chosen domain, or random data + server chosen domain
+ * if 'name' == dns_rootname. If nonce is not NULL, it supplies
+ * random data used in the shared secret computation. The key is
+ * requested to have the specified lifetime (in seconds)
+ *
+ *
+ * Requires:
+ *\li 'msg' is a valid message
+ *\li 'key' is a valid Diffie Hellman dst key
+ *\li 'name' is a valid name
+ *\li 'algorithm' is a valid name
+ *
+ * Returns:
+ *\li #ISC_R_SUCCESS msg was successfully updated to include the
+ * query to be sent
+ *\li other an error occurred while building the message
+ */
+
+isc_result_t
+dns_tkey_buildgssquery(dns_message_t *msg, const dns_name_t *name,
+ const dns_name_t *gname, isc_buffer_t *intoken,
+ uint32_t lifetime, dns_gss_ctx_id_t *context, bool win2k,
+ isc_mem_t *mctx, char **err_message);
+/*%<
+ * Builds a query containing a TKEY that will generate a GSSAPI context.
+ * The key is requested to have the specified lifetime (in seconds).
+ *
+ * Requires:
+ *\li 'msg' is a valid message
+ *\li 'name' is a valid name
+ *\li 'gname' is a valid name
+ *\li 'context' is a pointer to a valid gss_ctx_id_t
+ * (which may have the value GSS_C_NO_CONTEXT)
+ *\li 'win2k' when true says to turn on some hacks to work
+ * with the non-standard GSS-TSIG of Windows 2000
+ *
+ * Returns:
+ *\li ISC_R_SUCCESS msg was successfully updated to include the
+ * query to be sent
+ *\li other an error occurred while building the message
+ *\li *err_message optional error message
+ */
+
+isc_result_t
+dns_tkey_builddeletequery(dns_message_t *msg, dns_tsigkey_t *key);
+/*%<
+ * Builds a query containing a TKEY record that will delete the
+ * specified shared secret from the server.
+ *
+ * Requires:
+ *\li 'msg' is a valid message
+ *\li 'key' is a valid TSIG key
+ *
+ * Returns:
+ *\li #ISC_R_SUCCESS msg was successfully updated to include the
+ * query to be sent
+ *\li other an error occurred while building the message
+ */
+
+isc_result_t
+dns_tkey_processdhresponse(dns_message_t *qmsg, dns_message_t *rmsg,
+ dst_key_t *key, isc_buffer_t *nonce,
+ dns_tsigkey_t **outkey, dns_tsig_keyring_t *ring);
+/*%<
+ * Processes a response to a query containing a TKEY that was
+ * designed to generate a shared secret using a Diffie-Hellman key
+ * exchange. If the query was successful, a new shared key
+ * is created and added to the list of shared keys.
+ *
+ * Requires:
+ *\li 'qmsg' is a valid message (the query)
+ *\li 'rmsg' is a valid message (the response)
+ *\li 'key' is a valid Diffie Hellman dst key
+ *\li 'outkey' is either NULL or a pointer to NULL
+ *\li 'ring' is a valid keyring or NULL
+ *
+ * Returns:
+ *\li #ISC_R_SUCCESS the shared key was successfully added
+ *\li #ISC_R_NOTFOUND an error occurred while looking for a
+ * component of the query or response
+ */
+
+isc_result_t
+dns_tkey_processgssresponse(dns_message_t *qmsg, dns_message_t *rmsg,
+ const dns_name_t *gname, dns_gss_ctx_id_t *context,
+ isc_buffer_t *outtoken, dns_tsigkey_t **outkey,
+ dns_tsig_keyring_t *ring, char **err_message);
+/*%<
+ * XXX
+ */
+
+isc_result_t
+dns_tkey_processdeleteresponse(dns_message_t *qmsg, dns_message_t *rmsg,
+ dns_tsig_keyring_t *ring);
+/*%<
+ * Processes a response to a query containing a TKEY that was
+ * designed to delete a shared secret. If the query was successful,
+ * the shared key is deleted from the list of shared keys.
+ *
+ * Requires:
+ *\li 'qmsg' is a valid message (the query)
+ *\li 'rmsg' is a valid message (the response)
+ *\li 'ring' is not NULL
+ *
+ * Returns:
+ *\li #ISC_R_SUCCESS the shared key was successfully deleted
+ *\li #ISC_R_NOTFOUND an error occurred while looking for a
+ * component of the query or response
+ */
+
+isc_result_t
+dns_tkey_gssnegotiate(dns_message_t *qmsg, dns_message_t *rmsg,
+ const dns_name_t *server, dns_gss_ctx_id_t *context,
+ dns_tsigkey_t **outkey, dns_tsig_keyring_t *ring,
+ bool win2k, char **err_message);
+
+/*
+ * Client side negotiation of GSS-TSIG. Process the response
+ * to a TKEY, and establish a TSIG key if negotiation was successful.
+ * Build a response to the input TKEY message. Can take multiple
+ * calls to successfully establish the context.
+ *
+ * Requires:
+ * 'qmsg' is a valid message, the original TKEY request;
+ * it will be filled with the new message to send
+ * 'rmsg' is a valid message, the incoming TKEY message
+ * 'server' is the server name
+ * 'context' is the input context handle
+ * 'outkey' receives the established key, if non-NULL;
+ * if non-NULL must point to NULL
+ * 'ring' is the keyring in which to establish the key,
+ * or NULL
+ * 'win2k' when true says to turn on some hacks to work
+ * with the non-standard GSS-TSIG of Windows 2000
+ *
+ * Returns:
+ * ISC_R_SUCCESS context was successfully established
+ * ISC_R_NOTFOUND couldn't find a needed part of the query
+ * or response
+ * DNS_R_CONTINUE additional context negotiation is required;
+ * send the new qmsg to the server
+ */
+
+ISC_LANG_ENDDECLS
diff --git a/lib/dns/include/dns/transport.h b/lib/dns/include/dns/transport.h
new file mode 100644
index 0000000..e74ccd7
--- /dev/null
+++ b/lib/dns/include/dns/transport.h
@@ -0,0 +1,168 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#pragma once
+
+#include <dns/name.h>
+
+typedef enum {
+ DNS_TRANSPORT_NONE = 0,
+ DNS_TRANSPORT_UDP = 1,
+ DNS_TRANSPORT_TCP = 2,
+ DNS_TRANSPORT_TLS = 3,
+ DNS_TRANSPORT_HTTP = 4,
+ DNS_TRANSPORT_COUNT = 5,
+} dns_transport_type_t;
+
+typedef enum {
+ DNS_HTTP_GET = 0,
+ DNS_HTTP_POST = 1,
+} dns_http_mode_t;
+
+typedef struct dns_transport dns_transport_t;
+typedef struct dns_transport_list dns_transport_list_t;
+
+dns_transport_t *
+dns_transport_new(const dns_name_t *name, dns_transport_type_t type,
+ dns_transport_list_t *list);
+/*%<
+ * Create a new transport object with name 'name' and type 'type',
+ * and append it to 'list'.
+ */
+
+dns_transport_type_t
+dns_transport_get_type(dns_transport_t *transport);
+char *
+dns_transport_get_certfile(dns_transport_t *transport);
+char *
+dns_transport_get_keyfile(dns_transport_t *transport);
+char *
+dns_transport_get_cafile(dns_transport_t *transport);
+char *
+dns_transport_get_remote_hostname(dns_transport_t *transport);
+char *
+dns_transport_get_endpoint(dns_transport_t *transport);
+dns_http_mode_t
+dns_transport_get_mode(dns_transport_t *transport);
+char *
+dns_transport_get_ciphers(dns_transport_t *transport);
+char *
+dns_transport_get_tlsname(dns_transport_t *transport);
+uint32_t
+dns_transport_get_tls_versions(const dns_transport_t *transport);
+bool
+dns_transport_get_prefer_server_ciphers(const dns_transport_t *transport,
+ bool *preferp);
+/*%<
+ * Getter functions: return the type, cert file, key file, CA file,
+ * hostname, HTTP endpoint, or HTTP mode (GET or POST) for 'transport'.
+ *
+ * dns_transport_get_prefer_server_ciphers() returns 'true' is value
+ * was set, 'false' otherwise. The actual value is returned via
+ * 'preferp' pointer.
+ */
+
+void
+dns_transport_set_certfile(dns_transport_t *transport, const char *certfile);
+void
+dns_transport_set_keyfile(dns_transport_t *transport, const char *keyfile);
+void
+dns_transport_set_cafile(dns_transport_t *transport, const char *cafile);
+void
+dns_transport_set_remote_hostname(dns_transport_t *transport,
+ const char *hostname);
+void
+dns_transport_set_endpoint(dns_transport_t *transport, const char *endpoint);
+void
+dns_transport_set_mode(dns_transport_t *transport, dns_http_mode_t mode);
+void
+dns_transport_set_ciphers(dns_transport_t *transport, const char *ciphers);
+void
+dns_transport_set_tlsname(dns_transport_t *transport, const char *tlsname);
+
+void
+dns_transport_set_tls_versions(dns_transport_t *transport,
+ const uint32_t tls_versions);
+void
+dns_transport_set_prefer_server_ciphers(dns_transport_t *transport,
+ const bool prefer);
+/*%<
+ * Setter functions: set the type, cert file, key file, CA file,
+ * hostname, HTTP endpoint, or HTTP mode (GET or POST) for 'transport'.
+ *
+ * Requires:
+ *\li 'transport' is valid.
+ *\li 'transport' is of type DNS_TRANSPORT_TLS or DNS_TRANSPORT_HTTP
+ * (for certfile, keyfile, cafile, or hostname).
+ *\li 'transport' is of type DNS_TRANSPORT_HTTP (for endpoint or mode).
+ */
+
+void
+dns_transport_attach(dns_transport_t *source, dns_transport_t **targetp);
+/*%<
+ * Attach to a transport object.
+ *
+ * Requires:
+ *\li 'source' is a valid transport.
+ *\li 'targetp' is not NULL and '*targetp' is NULL.
+ */
+
+void
+dns_transport_detach(dns_transport_t **transportp);
+/*%<
+ * Detach a transport object; destroy it if there are no remaining
+ * references.
+ *
+ * Requires:
+ *\li 'transportp' is not NULL.
+ *\li '*transportp' is a valid transport.
+ */
+
+dns_transport_t *
+dns_transport_find(const dns_transport_type_t type, const dns_name_t *name,
+ dns_transport_list_t *list);
+/*%<
+ * Find a transport matching type 'type' and name `name` in 'list'.
+ *
+ * Requires:
+ *\li 'list' is valid.
+ *\li 'list' contains a table of type 'type' transports.
+ */
+
+dns_transport_list_t *
+dns_transport_list_new(isc_mem_t *mctx);
+/*%<
+ * Create a new transport list.
+ */
+
+void
+dns_transport_list_attach(dns_transport_list_t *source,
+ dns_transport_list_t **targetp);
+/*%<
+ * Attach to a transport list.
+ *
+ * Requires:
+ *\li 'source' is a valid transport list.
+ *\li 'targetp' is not NULL and '*targetp' is NULL.
+ */
+
+void
+dns_transport_list_detach(dns_transport_list_t **listp);
+/*%<
+ * Detach a transport list; destroy it if there are no remaining
+ * references.
+ *
+ * Requires:
+ *\li 'listp' is not NULL.
+ *\li '*listp' is a valid transport list.
+ */
diff --git a/lib/dns/include/dns/tsec.h b/lib/dns/include/dns/tsec.h
new file mode 100644
index 0000000..3d9577b
--- /dev/null
+++ b/lib/dns/include/dns/tsec.h
@@ -0,0 +1,129 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#pragma once
+
+/*****
+***** Module Info
+*****/
+
+/*! \file
+ *
+ * \brief
+ * The TSEC (Transaction Security) module is an abstraction layer for managing
+ * DNS transaction mechanisms such as TSIG or SIG(0). A TSEC structure is a
+ * mechanism-independent object containing key information specific to the
+ * mechanism, and is expected to be used as an argument to other modules
+ * that use transaction security in a mechanism-independent manner.
+ *
+ * MP:
+ *\li A TSEC structure is expected to be thread-specific. No inter-thread
+ * synchronization is ensured in multiple access to a single TSEC
+ * structure.
+ *
+ * Resources:
+ *\li TBS
+ *
+ * Security:
+ *\li This module does not handle any low-level data directly, and so no
+ * security issue specific to this module is anticipated.
+ */
+
+#include <dns/types.h>
+
+#include <dst/dst.h>
+
+ISC_LANG_BEGINDECLS
+
+/***
+ *** Types
+ ***/
+
+/*%
+ * Transaction security types.
+ */
+typedef enum {
+ dns_tsectype_none,
+ dns_tsectype_tsig,
+ dns_tsectype_sig0
+} dns_tsectype_t;
+
+isc_result_t
+dns_tsec_create(isc_mem_t *mctx, dns_tsectype_t type, dst_key_t *key,
+ dns_tsec_t **tsecp);
+/*%<
+ * Create a TSEC structure and stores a type-dependent key structure in it.
+ * For a TSIG key (type is dns_tsectype_tsig), dns_tsec_create() creates a
+ * TSIG key structure from '*key' and keeps it in the structure. For other
+ * types, this function simply retains '*key' in the structure. In either
+ * case, the ownership of '*key' is transferred to the TSEC module; the caller
+ * must not modify or destroy it after the call to dns_tsec_create().
+ *
+ * Requires:
+ *
+ *\li 'mctx' is a valid memory context.
+ *
+ *\li 'type' is a valid value of dns_tsectype_t (see above).
+ *
+ *\li 'key' is a valid key.
+ *
+ *\li tsecp != NULL && *tsecp == NULL.
+ *
+ * Returns:
+ *
+ *\li #ISC_R_SUCCESS On success.
+ *
+ *\li Anything else Failure.
+ */
+
+void
+dns_tsec_destroy(dns_tsec_t **tsecp);
+/*%<
+ * Destroy the TSEC structure. The stored key is also detached or destroyed.
+ *
+ * Requires
+ *
+ *\li '*tsecp' is a valid TSEC structure.
+ *
+ * Ensures
+ *
+ *\li *tsecp == NULL.
+ *
+ */
+
+dns_tsectype_t
+dns_tsec_gettype(dns_tsec_t *tsec);
+/*%<
+ * Return the TSEC type of '*tsec'.
+ *
+ * Requires
+ *
+ *\li 'tsec' is a valid TSEC structure.
+ *
+ */
+
+void
+dns_tsec_getkey(dns_tsec_t *tsec, void *keyp);
+/*%<
+ * Return the TSEC key of '*tsec' in '*keyp'.
+ *
+ * Requires
+ *
+ *\li keyp != NULL
+ *
+ * Ensures
+ *
+ *\li *tsecp points to a valid key structure depending on the TSEC type.
+ */
+
+ISC_LANG_ENDDECLS
diff --git a/lib/dns/include/dns/tsig.h b/lib/dns/include/dns/tsig.h
new file mode 100644
index 0000000..c66fc39
--- /dev/null
+++ b/lib/dns/include/dns/tsig.h
@@ -0,0 +1,295 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#pragma once
+
+/*! \file dns/tsig.h */
+
+#include <stdbool.h>
+
+#include <isc/lang.h>
+#include <isc/refcount.h>
+#include <isc/rwlock.h>
+#include <isc/stdio.h>
+#include <isc/stdtime.h>
+
+#include <dns/name.h>
+#include <dns/types.h>
+
+#include <dst/dst.h>
+
+/*
+ * Algorithms.
+ */
+extern const dns_name_t *dns_tsig_hmacmd5_name;
+#define DNS_TSIG_HMACMD5_NAME dns_tsig_hmacmd5_name
+extern const dns_name_t *dns_tsig_gssapi_name;
+#define DNS_TSIG_GSSAPI_NAME dns_tsig_gssapi_name
+extern const dns_name_t *dns_tsig_gssapims_name;
+#define DNS_TSIG_GSSAPIMS_NAME dns_tsig_gssapims_name
+extern const dns_name_t *dns_tsig_hmacsha1_name;
+#define DNS_TSIG_HMACSHA1_NAME dns_tsig_hmacsha1_name
+extern const dns_name_t *dns_tsig_hmacsha224_name;
+#define DNS_TSIG_HMACSHA224_NAME dns_tsig_hmacsha224_name
+extern const dns_name_t *dns_tsig_hmacsha256_name;
+#define DNS_TSIG_HMACSHA256_NAME dns_tsig_hmacsha256_name
+extern const dns_name_t *dns_tsig_hmacsha384_name;
+#define DNS_TSIG_HMACSHA384_NAME dns_tsig_hmacsha384_name
+extern const dns_name_t *dns_tsig_hmacsha512_name;
+#define DNS_TSIG_HMACSHA512_NAME dns_tsig_hmacsha512_name
+
+/*%
+ * Default fudge value.
+ */
+#define DNS_TSIG_FUDGE 300
+
+struct dns_tsig_keyring {
+ dns_rbt_t *keys;
+ unsigned int writecount;
+ isc_rwlock_t lock;
+ isc_mem_t *mctx;
+ /*
+ * LRU list of generated key along with a count of the keys on the
+ * list and a maximum size.
+ */
+ unsigned int generated;
+ unsigned int maxgenerated;
+ ISC_LIST(dns_tsigkey_t) lru;
+ isc_refcount_t references;
+};
+
+struct dns_tsigkey {
+ /* Unlocked */
+ unsigned int magic; /*%< Magic number. */
+ isc_mem_t *mctx;
+ dst_key_t *key; /*%< Key */
+ dns_name_t name; /*%< Key name */
+ const dns_name_t *algorithm; /*%< Algorithm name */
+ dns_name_t *creator; /*%< name that created secret */
+ bool generated; /*%< was this generated? */
+ isc_stdtime_t inception; /*%< start of validity period */
+ isc_stdtime_t expire; /*%< end of validity period */
+ dns_tsig_keyring_t *ring; /*%< the enclosing keyring */
+ isc_refcount_t refs; /*%< reference counter */
+ ISC_LINK(dns_tsigkey_t) link;
+};
+
+ISC_LANG_BEGINDECLS
+
+const dns_name_t *
+dns_tsigkey_identity(const dns_tsigkey_t *tsigkey);
+/*%<
+ * Returns the identity of the provided TSIG key.
+ *
+ * Requires:
+ *\li 'tsigkey' is a valid TSIG key or NULL
+ *
+ * Returns:
+ *\li NULL if 'tsigkey' was NULL
+ *\li identity of the provided TSIG key
+ */
+
+isc_result_t
+dns_tsigkey_create(const dns_name_t *name, const dns_name_t *algorithm,
+ unsigned char *secret, int length, bool generated,
+ const dns_name_t *creator, isc_stdtime_t inception,
+ isc_stdtime_t expire, isc_mem_t *mctx,
+ dns_tsig_keyring_t *ring, dns_tsigkey_t **key);
+
+isc_result_t
+dns_tsigkey_createfromkey(const dns_name_t *name, const dns_name_t *algorithm,
+ dst_key_t *dstkey, bool generated,
+ const dns_name_t *creator, isc_stdtime_t inception,
+ isc_stdtime_t expire, isc_mem_t *mctx,
+ dns_tsig_keyring_t *ring, dns_tsigkey_t **key);
+/*%<
+ * Creates a tsig key structure and saves it in the keyring. If key is
+ * not NULL, *key will contain a copy of the key. The keys validity
+ * period is specified by (inception, expire), and will not expire if
+ * inception == expire. If the key was generated, the creating identity,
+ * if there is one, should be in the creator parameter. Specifying an
+ * unimplemented algorithm will cause failure only if dstkey != NULL; this
+ * allows a transient key with an invalid algorithm to exist long enough
+ * to generate a BADKEY response.
+ *
+ * If dns_tsigkey_createfromkey is successful a new reference to 'dstkey'
+ * will have been made.
+ *
+ * Requires:
+ *\li 'name' is a valid dns_name_t
+ *\li 'algorithm' is a valid dns_name_t
+ *\li 'secret' is a valid pointer
+ *\li 'length' is an integer >= 0
+ *\li 'dstkey' is a valid dst key or NULL
+ *\li 'creator' points to a valid dns_name_t or is NULL
+ *\li 'mctx' is a valid memory context
+ *\li 'ring' is a valid TSIG keyring or NULL
+ *\li 'key' or '*key' must be NULL
+ *
+ * Returns:
+ *\li #ISC_R_SUCCESS
+ *\li #ISC_R_EXISTS - a key with this name already exists
+ *\li #ISC_R_NOTIMPLEMENTED - algorithm is not implemented
+ *\li #ISC_R_NOMEMORY
+ */
+
+void
+dns_tsigkey_attach(dns_tsigkey_t *source, dns_tsigkey_t **targetp);
+/*%<
+ * Attach '*targetp' to 'source'.
+ *
+ * Requires:
+ *\li 'key' is a valid TSIG key
+ *
+ * Ensures:
+ *\li *targetp is attached to source.
+ */
+
+void
+dns_tsigkey_detach(dns_tsigkey_t **keyp);
+/*%<
+ * Detaches from the tsig key structure pointed to by '*key'.
+ *
+ * Requires:
+ *\li 'keyp' is not NULL and '*keyp' is a valid TSIG key
+ *
+ * Ensures:
+ *\li 'keyp' points to NULL
+ */
+
+void
+dns_tsigkey_setdeleted(dns_tsigkey_t *key);
+/*%<
+ * Prevents this key from being used again. It will be deleted when
+ * no references exist.
+ *
+ * Requires:
+ *\li 'key' is a valid TSIG key on a keyring
+ */
+
+isc_result_t
+dns_tsig_sign(dns_message_t *msg);
+/*%<
+ * Generates a TSIG record for this message
+ *
+ * Requires:
+ *\li 'msg' is a valid message
+ *\li 'msg->tsigkey' is a valid TSIG key
+ *\li 'msg->tsig' is NULL
+ *
+ * Returns:
+ *\li #ISC_R_SUCCESS
+ *\li #ISC_R_NOMEMORY
+ *\li #ISC_R_NOSPACE
+ *\li #DNS_R_EXPECTEDTSIG
+ * - this is a response & msg->querytsig is NULL
+ */
+
+isc_result_t
+dns_tsig_verify(isc_buffer_t *source, dns_message_t *msg,
+ dns_tsig_keyring_t *ring1, dns_tsig_keyring_t *ring2);
+/*%<
+ * Verifies the TSIG record in this message
+ *
+ * Requires:
+ *\li 'source' is a valid buffer containing the unparsed message
+ *\li 'msg' is a valid message
+ *\li 'msg->tsigkey' is a valid TSIG key if this is a response
+ *\li 'msg->tsig' is NULL
+ *\li 'msg->querytsig' is not NULL if this is a response
+ *\li 'ring1' and 'ring2' are each either a valid keyring or NULL
+ *
+ * Returns:
+ *\li #ISC_R_SUCCESS
+ *\li #ISC_R_NOMEMORY
+ *\li #DNS_R_EXPECTEDTSIG - A TSIG was expected but not seen
+ *\li #DNS_R_UNEXPECTEDTSIG - A TSIG was seen but not expected
+ *\li #DNS_R_TSIGERRORSET - the TSIG verified but ->error was set
+ * and this is a query
+ *\li #DNS_R_CLOCKSKEW - the TSIG failed to verify because of
+ * the time was out of the allowed range.
+ *\li #DNS_R_TSIGVERIFYFAILURE - the TSIG failed to verify
+ *\li #DNS_R_EXPECTEDRESPONSE - the message was set over TCP and
+ * should have been a response,
+ * but was not.
+ */
+
+isc_result_t
+dns_tsigkey_find(dns_tsigkey_t **tsigkey, const dns_name_t *name,
+ const dns_name_t *algorithm, dns_tsig_keyring_t *ring);
+/*%<
+ * Returns the TSIG key corresponding to this name and (possibly)
+ * algorithm. Also increments the key's reference counter.
+ *
+ * Requires:
+ *\li 'tsigkey' is not NULL
+ *\li '*tsigkey' is NULL
+ *\li 'name' is a valid dns_name_t
+ *\li 'algorithm' is a valid dns_name_t or NULL
+ *\li 'ring' is a valid keyring
+ *
+ * Returns:
+ *\li #ISC_R_SUCCESS
+ *\li #ISC_R_NOTFOUND
+ */
+
+isc_result_t
+dns_tsigkeyring_create(isc_mem_t *mctx, dns_tsig_keyring_t **ringp);
+/*%<
+ * Create an empty TSIG key ring.
+ *
+ * Requires:
+ *\li 'mctx' is not NULL
+ *\li 'ringp' is not NULL, and '*ringp' is NULL
+ *
+ * Returns:
+ *\li #ISC_R_SUCCESS
+ *\li #ISC_R_NOMEMORY
+ */
+
+isc_result_t
+dns_tsigkeyring_add(dns_tsig_keyring_t *ring, const dns_name_t *name,
+ dns_tsigkey_t *tkey);
+/*%<
+ * Place a TSIG key onto a key ring.
+ *
+ * Requires:
+ *\li 'name' and 'ring' are not NULL
+ *\li 'tkey' is a valid TSIG key, which has not been
+ * added to any other keyrings
+ *
+ * Returns:
+ *\li #ISC_R_SUCCESS
+ *\li Any other value indicates failure.
+ */
+
+void
+dns_tsigkeyring_attach(dns_tsig_keyring_t *source, dns_tsig_keyring_t **target);
+
+void
+dns_tsigkeyring_detach(dns_tsig_keyring_t **ringp);
+
+isc_result_t
+dns_tsigkeyring_dumpanddetach(dns_tsig_keyring_t **ringp, FILE *fp);
+
+/*%<
+ * Destroy a TSIG key ring.
+ *
+ * Requires:
+ *\li 'ringp' is not NULL
+ */
+
+void
+dns_keyring_restore(dns_tsig_keyring_t *ring, FILE *fp);
+
+ISC_LANG_ENDDECLS
diff --git a/lib/dns/include/dns/ttl.h b/lib/dns/include/dns/ttl.h
new file mode 100644
index 0000000..5495368
--- /dev/null
+++ b/lib/dns/include/dns/ttl.h
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#pragma once
+
+/*! \file dns/ttl.h */
+
+/***
+ *** Imports
+ ***/
+
+#include <inttypes.h>
+#include <stdbool.h>
+
+#include <isc/lang.h>
+#include <isc/types.h>
+
+ISC_LANG_BEGINDECLS
+
+/***
+ *** Functions
+ ***/
+
+isc_result_t
+dns_ttl_totext(uint32_t src, bool verbose, bool upcase, isc_buffer_t *target);
+/*%<
+ * Output a TTL or other time interval in a human-readable form.
+ * The time interval is given as a count of seconds in 'src'.
+ * The text representation is appended to 'target'.
+ *
+ * If 'verbose' is false, use the terse BIND 8 style, like "1w2d3h4m5s".
+ *
+ * If 'verbose' is true, use a verbose style like the SOA comments
+ * in "dig", like "1 week 2 days 3 hours 4 minutes 5 seconds".
+ *
+ * If 'upcase' is true, we conform to the BIND 8 style in which
+ * the unit letter is capitalized if there is only a single unit
+ * letter to print (for example, "1m30s", but "2M")
+ *
+ * If 'upcase' is false, unit letters are always in lower case.
+ *
+ * Returns:
+ * \li ISC_R_SUCCESS
+ * \li ISC_R_NOSPACE
+ */
+
+isc_result_t
+dns_counter_fromtext(isc_textregion_t *source, uint32_t *ttl);
+/*%<
+ * Converts a counter from either a plain number or a BIND 8 style value.
+ *
+ * Returns:
+ *\li ISC_R_SUCCESS
+ *\li DNS_R_SYNTAX
+ */
+
+isc_result_t
+dns_ttl_fromtext(isc_textregion_t *source, uint32_t *ttl);
+/*%<
+ * Converts a ttl from either a plain number or a BIND 8 style value.
+ *
+ * Returns:
+ *\li ISC_R_SUCCESS
+ *\li DNS_R_BADTTL
+ */
+
+ISC_LANG_ENDDECLS
diff --git a/lib/dns/include/dns/types.h b/lib/dns/include/dns/types.h
new file mode 100644
index 0000000..6465962
--- /dev/null
+++ b/lib/dns/include/dns/types.h
@@ -0,0 +1,430 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#pragma once
+
+/*! \file dns/types.h
+ * \brief
+ * Including this file gives you type declarations suitable for use in
+ * .h files, which lets us avoid circular type reference problems.
+ * \brief
+ * To actually use a type or get declarations of its methods, you must
+ * include the appropriate .h file too.
+ */
+
+#include <inttypes.h>
+#include <stdbool.h>
+#include <stdio.h>
+
+#include <isc/types.h>
+
+typedef struct dns_acl dns_acl_t;
+typedef struct dns_aclelement dns_aclelement_t;
+typedef struct dns_aclenv dns_aclenv_t;
+typedef struct dns_adb dns_adb_t;
+typedef struct dns_adbaddrinfo dns_adbaddrinfo_t;
+typedef ISC_LIST(dns_adbaddrinfo_t) dns_adbaddrinfolist_t;
+typedef struct dns_adbentry dns_adbentry_t;
+typedef struct dns_adbfind dns_adbfind_t;
+typedef ISC_LIST(dns_adbfind_t) dns_adbfindlist_t;
+typedef struct dns_badcache dns_badcache_t;
+typedef struct dns_byaddr dns_byaddr_t;
+typedef struct dns_catz_zonemodmethods dns_catz_zonemodmethods_t;
+typedef struct dns_catz_entry_options dns_catz_options_t;
+typedef struct dns_catz_entry dns_catz_entry_t;
+typedef struct dns_catz_coo dns_catz_coo_t;
+typedef struct dns_catz_zone dns_catz_zone_t;
+typedef struct dns_catz_changed dns_catz_changed_t;
+typedef struct dns_catz_zones dns_catz_zones_t;
+typedef struct dns_client dns_client_t;
+typedef void dns_clientrestrans_t;
+typedef void dns_clientreqtrans_t;
+typedef void dns_clientupdatetrans_t;
+typedef struct dns_cache dns_cache_t;
+typedef uint16_t dns_cert_t;
+typedef struct dns_compress dns_compress_t;
+typedef struct dns_db dns_db_t;
+typedef struct dns_dbimplementation dns_dbimplementation_t;
+typedef struct dns_dbiterator dns_dbiterator_t;
+typedef void dns_dbload_t;
+typedef void dns_dbnode_t;
+typedef struct dns_dbonupdatelistener dns_dbonupdatelistener_t;
+typedef void dns_dbversion_t;
+typedef struct dns_dlzimplementation dns_dlzimplementation_t;
+typedef struct dns_dlzdb dns_dlzdb_t;
+typedef ISC_LIST(dns_dlzdb_t) dns_dlzdblist_t;
+typedef struct dns_dyndbctx dns_dyndbctx_t;
+typedef struct dns_sdlzimplementation dns_sdlzimplementation_t;
+typedef struct dns_decompress dns_decompress_t;
+typedef struct dns_dispatch dns_dispatch_t;
+typedef struct dns_dispatchlist dns_dispatchlist_t;
+typedef struct dns_dispatchset dns_dispatchset_t;
+typedef struct dns_dispatchmgr dns_dispatchmgr_t;
+typedef struct dns_dispentry dns_dispentry_t;
+typedef struct dns_dns64 dns_dns64_t;
+typedef ISC_LIST(dns_dns64_t) dns_dns64list_t;
+typedef struct dns_dnsseckey dns_dnsseckey_t;
+typedef ISC_LIST(dns_dnsseckey_t) dns_dnsseckeylist_t;
+typedef uint8_t dns_dsdigest_t;
+typedef struct dns_dtdata dns_dtdata_t;
+typedef struct dns_dtenv dns_dtenv_t;
+typedef struct dns_dtmsg dns_dtmsg_t;
+typedef uint16_t dns_dtmsgtype_t;
+typedef struct dns_dumpctx dns_dumpctx_t;
+typedef struct dns_ecs dns_ecs_t;
+typedef struct dns_ednsopt dns_ednsopt_t;
+typedef struct dns_fetch dns_fetch_t;
+typedef struct dns_fixedname dns_fixedname_t;
+typedef struct dns_forwarders dns_forwarders_t;
+typedef struct dns_forwarder dns_forwarder_t;
+typedef struct dns_fwdtable dns_fwdtable_t;
+typedef struct dns_geoip_databases dns_geoip_databases_t;
+typedef struct dns_iptable dns_iptable_t;
+typedef uint32_t dns_iterations_t;
+typedef struct dns_kasp dns_kasp_t;
+typedef ISC_LIST(dns_kasp_t) dns_kasplist_t;
+typedef struct dns_kasp_key dns_kasp_key_t;
+typedef ISC_LIST(dns_kasp_key_t) dns_kasp_keylist_t;
+typedef struct dns_kasp_nsec3param dns_kasp_nsec3param_t;
+typedef uint16_t dns_keyflags_t;
+typedef struct dns_keynode dns_keynode_t;
+typedef ISC_LIST(dns_keynode_t) dns_keynodelist_t;
+typedef struct dns_keytable dns_keytable_t;
+typedef uint16_t dns_keytag_t;
+typedef struct dns_loadctx dns_loadctx_t;
+typedef struct dns_loadmgr dns_loadmgr_t;
+typedef struct dns_masterrawheader dns_masterrawheader_t;
+typedef uint64_t dns_masterstyle_flags_t;
+typedef struct dns_message dns_message_t;
+typedef uint16_t dns_messageid_t;
+typedef isc_region_t dns_label_t;
+typedef struct dns_lookup dns_lookup_t;
+typedef struct dns_name dns_name_t;
+typedef ISC_LIST(dns_name_t) dns_namelist_t;
+typedef struct dns_nta dns_nta_t;
+typedef struct dns_ntatable dns_ntatable_t;
+typedef uint16_t dns_opcode_t;
+typedef unsigned char dns_offsets_t[128];
+typedef struct dns_order dns_order_t;
+typedef struct dns_peer dns_peer_t;
+typedef struct dns_peerlist dns_peerlist_t;
+typedef struct dns_rbt dns_rbt_t;
+typedef uint16_t dns_rcode_t;
+typedef struct dns_rdata dns_rdata_t;
+typedef struct dns_rdatacallbacks dns_rdatacallbacks_t;
+typedef uint16_t dns_rdataclass_t;
+typedef struct dns_rdatalist dns_rdatalist_t;
+typedef struct dns_rdataset dns_rdataset_t;
+typedef ISC_LIST(dns_rdataset_t) dns_rdatasetlist_t;
+typedef struct dns_rdatasetiter dns_rdatasetiter_t;
+typedef uint16_t dns_rdatatype_t;
+typedef struct dns_request dns_request_t;
+typedef struct dns_requestmgr dns_requestmgr_t;
+typedef struct dns_resolver dns_resolver_t;
+typedef struct dns_sdbimplementation dns_sdbimplementation_t;
+typedef uint8_t dns_secalg_t;
+typedef uint8_t dns_secproto_t;
+typedef struct dns_signature dns_signature_t;
+typedef struct dns_sortlist_arg dns_sortlist_arg_t;
+typedef struct dns_ssurule dns_ssurule_t;
+typedef struct dns_ssutable dns_ssutable_t;
+typedef struct dns_stats dns_stats_t;
+typedef uint32_t dns_rdatastatstype_t;
+typedef struct dns_tkeyctx dns_tkeyctx_t;
+typedef uint16_t dns_trust_t;
+typedef struct dns_tsec dns_tsec_t;
+typedef struct dns_tsig_keyring dns_tsig_keyring_t;
+typedef struct dns_tsigkey dns_tsigkey_t;
+typedef uint32_t dns_ttl_t;
+typedef struct dns_update_state dns_update_state_t;
+typedef struct dns_validator dns_validator_t;
+typedef struct dns_view dns_view_t;
+typedef ISC_LIST(dns_view_t) dns_viewlist_t;
+typedef struct dns_zone dns_zone_t;
+typedef ISC_LIST(dns_zone_t) dns_zonelist_t;
+typedef struct dns_zonemgr dns_zonemgr_t;
+typedef struct dns_zt dns_zt_t;
+typedef struct dns_ipkeylist dns_ipkeylist_t;
+
+typedef struct dst_gssapi_signverifyctx dst_gssapi_signverifyctx_t;
+
+typedef enum { dns_hash_sha1 = 1 } dns_hash_t;
+
+typedef enum {
+ dns_fwdpolicy_none = 0,
+ dns_fwdpolicy_first = 1,
+ dns_fwdpolicy_only = 2
+} dns_fwdpolicy_t;
+
+typedef enum {
+ dns_namereln_none = 0,
+ dns_namereln_contains = 1,
+ dns_namereln_subdomain = 2,
+ dns_namereln_equal = 3,
+ dns_namereln_commonancestor = 4
+} dns_namereln_t;
+
+typedef enum { dns_one_answer, dns_many_answers } dns_transfer_format_t;
+
+typedef enum {
+ dns_dbtype_zone = 0,
+ dns_dbtype_cache = 1,
+ dns_dbtype_stub = 3
+} dns_dbtype_t;
+
+typedef enum {
+ dns_dbtree_main = 0,
+ dns_dbtree_nsec = 1,
+ dns_dbtree_nsec3 = 2
+} dns_dbtree_t;
+
+typedef enum {
+ dns_notifytype_no = 0,
+ dns_notifytype_yes = 1,
+ dns_notifytype_explicit = 2,
+ dns_notifytype_masteronly = 3
+} dns_notifytype_t;
+
+typedef enum {
+ dns_minimal_no = 0,
+ dns_minimal_yes = 1,
+ dns_minimal_noauth = 2,
+ dns_minimal_noauthrec = 3
+} dns_minimaltype_t;
+
+typedef enum {
+ dns_dialuptype_no = 0,
+ dns_dialuptype_yes = 1,
+ dns_dialuptype_notify = 2,
+ dns_dialuptype_notifypassive = 3,
+ dns_dialuptype_refresh = 4,
+ dns_dialuptype_passive = 5
+} dns_dialuptype_t;
+
+typedef enum {
+ dns_masterformat_none = 0,
+ dns_masterformat_text = 1,
+ dns_masterformat_raw = 2,
+} dns_masterformat_t;
+
+/*
+ * These are generated by gen.c.
+ */
+#include <dns/enumclass.h> /* Provides dns_rdataclass_t. */
+#include <dns/enumtype.h> /* Provides dns_rdatatype_t. */
+
+/*%
+ * rcodes.
+ */
+enum {
+ /*
+ * Standard rcodes.
+ */
+ dns_rcode_noerror = 0,
+#define dns_rcode_noerror ((dns_rcode_t)dns_rcode_noerror)
+ dns_rcode_formerr = 1,
+#define dns_rcode_formerr ((dns_rcode_t)dns_rcode_formerr)
+ dns_rcode_servfail = 2,
+#define dns_rcode_servfail ((dns_rcode_t)dns_rcode_servfail)
+ dns_rcode_nxdomain = 3,
+#define dns_rcode_nxdomain ((dns_rcode_t)dns_rcode_nxdomain)
+ dns_rcode_notimp = 4,
+#define dns_rcode_notimp ((dns_rcode_t)dns_rcode_notimp)
+ dns_rcode_refused = 5,
+#define dns_rcode_refused ((dns_rcode_t)dns_rcode_refused)
+ dns_rcode_yxdomain = 6,
+#define dns_rcode_yxdomain ((dns_rcode_t)dns_rcode_yxdomain)
+ dns_rcode_yxrrset = 7,
+#define dns_rcode_yxrrset ((dns_rcode_t)dns_rcode_yxrrset)
+ dns_rcode_nxrrset = 8,
+#define dns_rcode_nxrrset ((dns_rcode_t)dns_rcode_nxrrset)
+ dns_rcode_notauth = 9,
+#define dns_rcode_notauth ((dns_rcode_t)dns_rcode_notauth)
+ dns_rcode_notzone = 10,
+#define dns_rcode_notzone ((dns_rcode_t)dns_rcode_notzone)
+ /*
+ * Extended rcodes.
+ */
+ dns_rcode_badvers = 16,
+#define dns_rcode_badvers ((dns_rcode_t)dns_rcode_badvers)
+ dns_rcode_badcookie = 23
+#define dns_rcode_badcookie ((dns_rcode_t)dns_rcode_badcookie)
+ /*
+ * Update dns_rcodestats_create() and
+ *dns_rcodestats_increment()
+ * and this comment if a rcode >
+ *dns_rcode_badcookie is assigned.
+ */
+ /* Private space [3841..4095] */
+};
+
+/*%
+ * TSIG errors.
+ */
+enum {
+ dns_tsigerror_badsig = 16,
+ dns_tsigerror_badkey = 17,
+ dns_tsigerror_badtime = 18,
+ dns_tsigerror_badmode = 19,
+ dns_tsigerror_badname = 20,
+ dns_tsigerror_badalg = 21,
+ dns_tsigerror_badtrunc = 22
+};
+
+/*%
+ * Opcodes.
+ */
+enum {
+ dns_opcode_query = 0,
+#define dns_opcode_query ((dns_opcode_t)dns_opcode_query)
+ dns_opcode_iquery = 1,
+#define dns_opcode_iquery ((dns_opcode_t)dns_opcode_iquery)
+ dns_opcode_status = 2,
+#define dns_opcode_status ((dns_opcode_t)dns_opcode_status)
+ dns_opcode_notify = 4,
+#define dns_opcode_notify ((dns_opcode_t)dns_opcode_notify)
+ dns_opcode_update = 5 /* dynamic update */
+#define dns_opcode_update ((dns_opcode_t)dns_opcode_update)
+};
+
+/*%
+ * Trust levels. Must be kept in sync with trustnames[] in masterdump.c.
+ */
+enum {
+ /* Sentinel value; no data should have this trust level. */
+ dns_trust_none = 0,
+#define dns_trust_none ((dns_trust_t)dns_trust_none)
+
+ /*%
+ * Subject to DNSSEC validation but has not yet been validated
+ * dns_trust_pending_additional (from the additional section).
+ */
+ dns_trust_pending_additional = 1,
+#define dns_trust_pending_additional ((dns_trust_t)dns_trust_pending_additional)
+
+ dns_trust_pending_answer = 2,
+#define dns_trust_pending_answer ((dns_trust_t)dns_trust_pending_answer)
+
+ /*% Received in the additional section of a response. */
+ dns_trust_additional = 3,
+#define dns_trust_additional ((dns_trust_t)dns_trust_additional)
+
+ /* Received in a referral response. */
+ dns_trust_glue = 4,
+#define dns_trust_glue ((dns_trust_t)dns_trust_glue)
+
+ /* Answer from a non-authoritative server */
+ dns_trust_answer = 5,
+#define dns_trust_answer ((dns_trust_t)dns_trust_answer)
+
+ /* Received in the authority section as part of an
+ * authoritative response */
+ dns_trust_authauthority = 6,
+#define dns_trust_authauthority ((dns_trust_t)dns_trust_authauthority)
+
+ /* Answer from an authoritative server */
+ dns_trust_authanswer = 7,
+#define dns_trust_authanswer ((dns_trust_t)dns_trust_authanswer)
+
+ /* Successfully DNSSEC validated */
+ dns_trust_secure = 8,
+#define dns_trust_secure ((dns_trust_t)dns_trust_secure)
+
+ /* This server is authoritative */
+ dns_trust_ultimate = 9
+#define dns_trust_ultimate ((dns_trust_t)dns_trust_ultimate)
+};
+
+#define DNS_TRUST_PENDING(x) \
+ ((x) == dns_trust_pending_answer || (x) == dns_trust_pending_additional)
+#define DNS_TRUST_ADDITIONAL(x) \
+ ((x) == dns_trust_additional || (x) == dns_trust_pending_additional)
+#define DNS_TRUST_GLUE(x) ((x) == dns_trust_glue)
+#define DNS_TRUST_ANSWER(x) ((x) == dns_trust_answer)
+
+/*%
+ * Name checking severities.
+ */
+typedef enum {
+ dns_severity_ignore,
+ dns_severity_warn,
+ dns_severity_fail
+} dns_severity_t;
+
+/*%
+ * DNS Serial Number Update Method.
+ *
+ * \li _none: Keep the current serial.
+ * \li _increment: Add one to the current serial, skipping 0.
+ * \li _unixtime: Set to the seconds since 00:00 Jan 1, 1970,
+ * if possible.
+ * \li _date: Set to today's date in YYYYMMDDVV format:
+ * (Year, Month, Day, Version)
+ */
+typedef enum {
+ dns_updatemethod_none = 0,
+ dns_updatemethod_increment,
+ dns_updatemethod_unixtime,
+ dns_updatemethod_date
+} dns_updatemethod_t;
+
+typedef enum {
+ dns_stale_answer_no,
+ dns_stale_answer_yes,
+ dns_stale_answer_conf
+} dns_stale_answer_t;
+
+typedef struct {
+ const char *string;
+ size_t count;
+} dns_indent_t;
+
+/*
+ * Functions.
+ */
+typedef void (*dns_dumpdonefunc_t)(void *, isc_result_t);
+
+typedef void (*dns_loaddonefunc_t)(void *, isc_result_t);
+
+typedef void (*dns_rawdatafunc_t)(dns_zone_t *, dns_masterrawheader_t *);
+
+typedef isc_result_t (*dns_addrdatasetfunc_t)(void *, const dns_name_t *,
+ dns_rdataset_t *);
+
+typedef isc_result_t (*dns_additionaldatafunc_t)(void *, const dns_name_t *,
+ dns_rdatatype_t,
+ dns_rdataset_t *);
+
+typedef isc_result_t (*dns_digestfunc_t)(void *, isc_region_t *);
+
+typedef void (*dns_xfrindone_t)(dns_zone_t *, isc_result_t);
+
+typedef void (*dns_updatecallback_t)(void *, isc_result_t, dns_message_t *);
+
+typedef int (*dns_rdatasetorderfunc_t)(const dns_rdata_t *, const void *);
+
+typedef bool (*dns_checkmxfunc_t)(dns_zone_t *, const dns_name_t *,
+ const dns_name_t *);
+
+typedef bool (*dns_checksrvfunc_t)(dns_zone_t *, const dns_name_t *,
+ const dns_name_t *);
+
+typedef bool (*dns_checknsfunc_t)(dns_zone_t *, const dns_name_t *,
+ const dns_name_t *, dns_rdataset_t *,
+ dns_rdataset_t *);
+
+typedef bool (*dns_isselffunc_t)(dns_view_t *, dns_tsigkey_t *,
+ const isc_sockaddr_t *, const isc_sockaddr_t *,
+ dns_rdataclass_t, void *);
+
+typedef void (*dns_nseclog_t)(void *val, int, const char *, ...);
diff --git a/lib/dns/include/dns/update.h b/lib/dns/include/dns/update.h
new file mode 100644
index 0000000..8ca1bf0
--- /dev/null
+++ b/lib/dns/include/dns/update.h
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#pragma once
+
+/*! \file dns/update.h */
+
+/***
+ *** Imports
+ ***/
+
+#include <inttypes.h>
+
+#include <isc/lang.h>
+
+#include <dns/diff.h>
+#include <dns/types.h>
+
+typedef struct {
+ void (*func)(void *arg, dns_zone_t *zone, int level,
+ const char *message);
+ void *arg;
+} dns_update_log_t;
+
+ISC_LANG_BEGINDECLS
+
+/***
+ *** Functions
+ ***/
+
+uint32_t
+dns_update_soaserial(uint32_t serial, dns_updatemethod_t method,
+ dns_updatemethod_t *used);
+/*%<
+ * Return the next serial number after 'serial', depending on the
+ * update method 'method':
+ *
+ *\li * dns_updatemethod_increment increments the serial number by one
+ *\li * dns_updatemethod_date sets the serial number to YYYYMMDD00
+ *\li * dns_updatemethod_unixtime sets the serial number to the current
+ * time (seconds since UNIX epoch)
+ *\li * dns_updatemethod_none just returns the given serial
+ *
+ * NOTE: The dns_updatemethod_increment will be used if dns_updatemethod_date or
+ * dns_updatemethod_unixtime is used and the new serial number would be lower
+ * than current serial number.
+ *
+ * Sets *used to the method that was used.
+ */
+
+isc_result_t
+dns_update_signatures(dns_update_log_t *log, dns_zone_t *zone, dns_db_t *db,
+ dns_dbversion_t *oldver, dns_dbversion_t *newver,
+ dns_diff_t *diff, uint32_t sigvalidityinterval);
+
+isc_result_t
+dns_update_signaturesinc(dns_update_log_t *log, dns_zone_t *zone, dns_db_t *db,
+ dns_dbversion_t *oldver, dns_dbversion_t *newver,
+ dns_diff_t *diff, uint32_t sigvalidityinterval,
+ dns_update_state_t **state);
+
+ISC_LANG_ENDDECLS
diff --git a/lib/dns/include/dns/validator.h b/lib/dns/include/dns/validator.h
new file mode 100644
index 0000000..383dcb4
--- /dev/null
+++ b/lib/dns/include/dns/validator.h
@@ -0,0 +1,240 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#pragma once
+
+/*****
+***** Module Info
+*****/
+
+/*! \file dns/validator.h
+ *
+ * \brief
+ * DNS Validator
+ * This is the BIND 9 validator, the module responsible for validating the
+ * rdatasets and negative responses (messages). It makes use of zones in
+ * the view and may fetch RRset to complete trust chains. It implements
+ * DNSSEC as specified in RFC 4033, 4034 and 4035.
+ *
+ * Correct operation is critical to preventing spoofed answers from secure
+ * zones being accepted.
+ *
+ * MP:
+ *\li The module ensures appropriate synchronization of data structures it
+ * creates and manipulates.
+ *
+ * Reliability:
+ *\li No anticipated impact.
+ *
+ * Resources:
+ *\li TBS
+ *
+ * Security:
+ *\li No anticipated impact.
+ *
+ * Standards:
+ *\li RFCs: 1034, 1035, 2181, 4033, 4034, 4035.
+ */
+
+#include <stdbool.h>
+
+#include <isc/event.h>
+#include <isc/lang.h>
+#include <isc/mutex.h>
+
+#include <dns/fixedname.h>
+#include <dns/rdataset.h>
+#include <dns/rdatastruct.h> /* for dns_rdata_rrsig_t */
+#include <dns/types.h>
+
+#include <dst/dst.h>
+
+/*%
+ * A dns_validatorevent_t is sent when a 'validation' completes.
+ * \brief
+ * 'name', 'rdataset', 'sigrdataset', and 'message' are the values that were
+ * supplied when dns_validator_create() was called. They are returned to the
+ * caller so that they may be freed.
+ *
+ * If the RESULT is ISC_R_SUCCESS and the answer is secure then
+ * proofs[] will contain the names of the NSEC records that hold the
+ * various proofs. Note the same name may appear multiple times.
+ */
+typedef struct dns_validatorevent {
+ ISC_EVENT_COMMON(struct dns_validatorevent);
+ dns_validator_t *validator;
+ isc_result_t result;
+ /*
+ * Name and type of the response to be validated.
+ */
+ dns_name_t *name;
+ dns_rdatatype_t type;
+ /*
+ * Rdata and RRSIG (if any) for positive responses.
+ */
+ dns_rdataset_t *rdataset;
+ dns_rdataset_t *sigrdataset;
+ /*
+ * The full response. Required for negative responses.
+ * Also required for positive wildcard responses.
+ */
+ dns_message_t *message;
+ /*
+ * Proofs to be cached.
+ */
+ dns_name_t *proofs[4];
+ /*
+ * Optout proof seen.
+ */
+ bool optout;
+ /*
+ * Answer is secure.
+ */
+ bool secure;
+} dns_validatorevent_t;
+
+#define DNS_VALIDATOR_NOQNAMEPROOF 0
+#define DNS_VALIDATOR_NODATAPROOF 1
+#define DNS_VALIDATOR_NOWILDCARDPROOF 2
+#define DNS_VALIDATOR_CLOSESTENCLOSER 3
+
+/*%
+ * A validator object represents a validation in progress.
+ * \brief
+ * Clients are strongly discouraged from using this type directly, with
+ * the exception of the 'link' field, which may be used directly for
+ * whatever purpose the client desires.
+ */
+struct dns_validator {
+ /* Unlocked. */
+ unsigned int magic;
+ isc_mutex_t lock;
+ dns_view_t *view;
+ /* Locked by lock. */
+ unsigned int options;
+ unsigned int attributes;
+ dns_validatorevent_t *event;
+ dns_fetch_t *fetch;
+ dns_validator_t *subvalidator;
+ dns_validator_t *parent;
+ dns_keytable_t *keytable;
+ dst_key_t *key;
+ dns_rdata_rrsig_t *siginfo;
+ isc_task_t *task;
+ isc_taskaction_t action;
+ void *arg;
+ unsigned int labels;
+ dns_rdataset_t *currentset;
+ dns_rdataset_t *keyset;
+ dns_rdataset_t *dsset;
+ dns_rdataset_t fdsset;
+ dns_rdataset_t frdataset;
+ dns_rdataset_t fsigrdataset;
+ dns_fixedname_t fname;
+ dns_fixedname_t wild;
+ dns_fixedname_t closest;
+ ISC_LINK(dns_validator_t) link;
+ bool mustbesecure;
+ unsigned int depth;
+ unsigned int authcount;
+ unsigned int authfail;
+ isc_stdtime_t start;
+};
+
+/*%
+ * dns_validator_create() options.
+ */
+/* obsolete: #define DNS_VALIDATOR_DLV 0x0001U */
+#define DNS_VALIDATOR_DEFER 0x0002U
+#define DNS_VALIDATOR_NOCDFLAG 0x0004U
+#define DNS_VALIDATOR_NONTA 0x0008U /*% Ignore NTA table */
+
+ISC_LANG_BEGINDECLS
+
+isc_result_t
+dns_validator_create(dns_view_t *view, dns_name_t *name, dns_rdatatype_t type,
+ dns_rdataset_t *rdataset, dns_rdataset_t *sigrdataset,
+ dns_message_t *message, unsigned int options,
+ isc_task_t *task, isc_taskaction_t action, void *arg,
+ dns_validator_t **validatorp);
+/*%<
+ * Start a DNSSEC validation.
+ *
+ * This validates a response to the question given by
+ * 'name' and 'type'.
+ *
+ * To validate a positive response, the response data is
+ * given by 'rdataset' and 'sigrdataset'. If 'sigrdataset'
+ * is NULL, the data is presumed insecure and an attempt
+ * is made to prove its insecurity by finding the appropriate
+ * null key.
+ *
+ * The complete response message may be given in 'message',
+ * to make available any authority section NSECs that may be
+ * needed for validation of a response resulting from a
+ * wildcard expansion (though no such wildcard validation
+ * is implemented yet). If the complete response message
+ * is not available, 'message' is NULL.
+ *
+ * To validate a negative response, the complete negative response
+ * message is given in 'message'. The 'rdataset', and
+ * 'sigrdataset' arguments must be NULL, but the 'name' and 'type'
+ * arguments must be provided.
+ *
+ * The validation is performed in the context of 'view'.
+ *
+ * When the validation finishes, a dns_validatorevent_t with
+ * the given 'action' and 'arg' are sent to 'task'.
+ * Its 'result' field will be ISC_R_SUCCESS iff the
+ * response was successfully proven to be either secure or
+ * part of a known insecure domain.
+ */
+
+void
+dns_validator_send(dns_validator_t *validator);
+/*%<
+ * Send a deferred validation request
+ *
+ * Requires:
+ * 'validator' to points to a valid DNSSEC validator.
+ */
+
+void
+dns_validator_cancel(dns_validator_t *validator);
+/*%<
+ * Cancel a DNSSEC validation in progress.
+ *
+ * Requires:
+ *\li 'validator' points to a valid DNSSEC validator, which
+ * may or may not already have completed.
+ *
+ * Ensures:
+ *\li It the validator has not already sent its completion
+ * event, it will send it with result code ISC_R_CANCELED.
+ */
+
+void
+dns_validator_destroy(dns_validator_t **validatorp);
+/*%<
+ * Destroy a DNSSEC validator.
+ *
+ * Requires:
+ *\li '*validatorp' points to a valid DNSSEC validator.
+ * \li The validator must have completed and sent its completion
+ * event.
+ *
+ * Ensures:
+ *\li All resources used by the validator are freed.
+ */
+
+ISC_LANG_ENDDECLS
diff --git a/lib/dns/include/dns/view.h b/lib/dns/include/dns/view.h
new file mode 100644
index 0000000..18b0b33
--- /dev/null
+++ b/lib/dns/include/dns/view.h
@@ -0,0 +1,1416 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#pragma once
+
+/*****
+***** Module Info
+*****/
+
+/*! \file dns/view.h
+ * \brief
+ * DNS View
+ *
+ * A "view" is a DNS namespace, together with an optional resolver and a
+ * forwarding policy. A "DNS namespace" is a (possibly empty) set of
+ * authoritative zones together with an optional cache and optional
+ * "hints" information.
+ *
+ * Views start out "unfrozen". In this state, core attributes like
+ * the cache, set of zones, and forwarding policy may be set. While
+ * "unfrozen", the caller (e.g. nameserver configuration loading
+ * code), must ensure exclusive access to the view. When the view is
+ * "frozen", the core attributes become immutable, and the view module
+ * will ensure synchronization. Freezing allows the view's core attributes
+ * to be accessed without locking.
+ *
+ * MP:
+ *\li Before the view is frozen, the caller must ensure synchronization.
+ *
+ *\li After the view is frozen, the module guarantees appropriate
+ * synchronization of any data structures it creates and manipulates.
+ *
+ * Reliability:
+ *\li No anticipated impact.
+ *
+ * Resources:
+ *\li TBS
+ *
+ * Security:
+ *\li No anticipated impact.
+ *
+ * Standards:
+ *\li None.
+ */
+
+#include <inttypes.h>
+#include <stdbool.h>
+#include <stdio.h>
+
+#include <isc/event.h>
+#include <isc/lang.h>
+#include <isc/magic.h>
+#include <isc/mutex.h>
+#include <isc/net.h>
+#include <isc/refcount.h>
+#include <isc/rwlock.h>
+#include <isc/stdtime.h>
+
+#include <dns/acl.h>
+#include <dns/catz.h>
+#include <dns/clientinfo.h>
+#include <dns/dnstap.h>
+#include <dns/fixedname.h>
+#include <dns/rdatastruct.h>
+#include <dns/rpz.h>
+#include <dns/rrl.h>
+#include <dns/transport.h>
+#include <dns/types.h>
+#include <dns/zt.h>
+
+ISC_LANG_BEGINDECLS
+
+struct dns_view {
+ /* Unlocked. */
+ unsigned int magic;
+ isc_mem_t *mctx;
+ dns_rdataclass_t rdclass;
+ char *name;
+ dns_zt_t *zonetable;
+ dns_resolver_t *resolver;
+ dns_adb_t *adb;
+ dns_requestmgr_t *requestmgr;
+ dns_cache_t *cache;
+ dns_db_t *cachedb;
+ dns_db_t *hints;
+
+ /*
+ * security roots and negative trust anchors.
+ * internal use only; access via * dns_view_getsecroots()
+ */
+ dns_keytable_t *secroots_priv;
+ dns_ntatable_t *ntatable_priv;
+
+ isc_mutex_t lock;
+ bool frozen;
+ isc_task_t *task;
+ isc_event_t resevent;
+ isc_event_t adbevent;
+ isc_event_t reqevent;
+ isc_stats_t *adbstats;
+ isc_stats_t *resstats;
+ dns_stats_t *resquerystats;
+ bool cacheshared;
+
+ /* Configurable data. */
+ dns_transport_list_t *transports;
+ dns_tsig_keyring_t *statickeys;
+ dns_tsig_keyring_t *dynamickeys;
+ dns_peerlist_t *peers;
+ dns_order_t *order;
+ dns_fwdtable_t *fwdtable;
+ bool recursion;
+ bool qminimization;
+ bool qmin_strict;
+ bool auth_nxdomain;
+ bool use_glue_cache;
+ bool minimal_any;
+ dns_minimaltype_t minimalresponses;
+ bool enablevalidation;
+ bool acceptexpired;
+ bool requireservercookie;
+ bool synthfromdnssec;
+ bool trust_anchor_telemetry;
+ bool root_key_sentinel;
+ dns_transfer_format_t transfer_format;
+ dns_acl_t *cacheacl;
+ dns_acl_t *cacheonacl;
+ dns_acl_t *queryacl;
+ dns_acl_t *queryonacl;
+ dns_acl_t *recursionacl;
+ dns_acl_t *recursiononacl;
+ dns_acl_t *sortlist;
+ dns_acl_t *notifyacl;
+ dns_acl_t *transferacl;
+ dns_acl_t *updateacl;
+ dns_acl_t *upfwdacl;
+ dns_acl_t *denyansweracl;
+ dns_acl_t *nocasecompress;
+ bool msgcompression;
+ dns_rbt_t *answeracl_exclude;
+ dns_rbt_t *denyanswernames;
+ dns_rbt_t *answernames_exclude;
+ dns_rrl_t *rrl;
+ dns_rbt_t *sfd;
+ isc_rwlock_t sfd_lock;
+ bool provideixfr;
+ bool requestnsid;
+ bool sendcookie;
+ dns_ttl_t maxcachettl;
+ dns_ttl_t maxncachettl;
+ dns_ttl_t mincachettl;
+ dns_ttl_t minncachettl;
+ uint32_t nta_lifetime;
+ uint32_t nta_recheck;
+ char *nta_file;
+ dns_ttl_t prefetch_trigger;
+ dns_ttl_t prefetch_eligible;
+ in_port_t dstport;
+ dns_aclenv_t *aclenv;
+ dns_rdatatype_t preferred_glue;
+ bool flush;
+ dns_namelist_t *delonly;
+ bool rootdelonly;
+ dns_namelist_t *rootexclude;
+ bool checknames;
+ uint16_t maxudp;
+ dns_ttl_t staleanswerttl;
+ dns_stale_answer_t staleanswersok; /* rndc setting */
+ bool staleanswersenable; /* named.conf setting
+ * */
+ uint32_t staleanswerclienttimeout;
+ uint16_t nocookieudp;
+ uint16_t padding;
+ dns_acl_t *pad_acl;
+ unsigned int maxbits;
+ dns_dns64list_t dns64;
+ unsigned int dns64cnt;
+ dns_rpz_zones_t *rpzs;
+ dns_catz_zones_t *catzs;
+ dns_dlzdblist_t dlz_searched;
+ dns_dlzdblist_t dlz_unsearched;
+ uint32_t fail_ttl;
+ dns_badcache_t *failcache;
+
+ /*
+ * Configurable data for server use only,
+ * locked by server configuration lock.
+ */
+ dns_acl_t *matchclients;
+ dns_acl_t *matchdestinations;
+ bool matchrecursiveonly;
+
+ /* Locked by themselves. */
+ isc_refcount_t references;
+ isc_refcount_t weakrefs;
+ atomic_uint_fast32_t attributes;
+
+ /* Under owner's locking control. */
+ ISC_LINK(struct dns_view) link;
+ dns_viewlist_t *viewlist;
+
+ dns_zone_t *managed_keys;
+ dns_zone_t *redirect;
+ dns_name_t *redirectzone; /* points to
+ * redirectfixed
+ * when valid */
+ dns_fixedname_t redirectfixed;
+
+ /*
+ * File and configuration data for zones added at runtime
+ * (only used in BIND9).
+ *
+ * XXX: This should be a pointer to an opaque type that
+ * named implements.
+ */
+ char *new_zone_dir;
+ char *new_zone_file;
+ char *new_zone_db;
+ void *new_zone_dbenv;
+ uint64_t new_zone_mapsize;
+ void *new_zone_config;
+ void (*cfg_destroy)(void **);
+ isc_mutex_t new_zone_lock;
+
+ unsigned char secret[32]; /* Client secret */
+ unsigned int v6bias;
+
+ dns_dtenv_t *dtenv; /* Dnstap environment */
+ dns_dtmsgtype_t dttypes; /* Dnstap message types
+ * to log */
+
+ /* Registered module instances */
+ void *plugins;
+ void (*plugins_free)(isc_mem_t *, void **);
+
+ /* Hook table */
+ void *hooktable; /* ns_hooktable */
+ void (*hooktable_free)(isc_mem_t *, void **);
+};
+
+#define DNS_VIEW_MAGIC ISC_MAGIC('V', 'i', 'e', 'w')
+#define DNS_VIEW_VALID(view) ISC_MAGIC_VALID(view, DNS_VIEW_MAGIC)
+
+#define DNS_VIEWATTR_RESSHUTDOWN 0x01
+#define DNS_VIEWATTR_ADBSHUTDOWN 0x02
+#define DNS_VIEWATTR_REQSHUTDOWN 0x04
+
+#ifdef HAVE_LMDB
+#define DNS_LMDB_COMMON_FLAGS (MDB_CREATE | MDB_NOSUBDIR | MDB_NOLOCK)
+#ifndef __OpenBSD__
+#define DNS_LMDB_FLAGS (DNS_LMDB_COMMON_FLAGS)
+#else /* __OpenBSD__ */
+/*
+ * OpenBSD does not have a unified buffer cache, which requires both reads and
+ * writes to be performed using mmap().
+ */
+#define DNS_LMDB_FLAGS (DNS_LMDB_COMMON_FLAGS | MDB_WRITEMAP)
+#endif /* __OpenBSD__ */
+#endif /* HAVE_LMDB */
+
+isc_result_t
+dns_view_create(isc_mem_t *mctx, dns_rdataclass_t rdclass, const char *name,
+ dns_view_t **viewp);
+/*%<
+ * Create a view.
+ *
+ * Notes:
+ *
+ *\li The newly created view has no cache, no resolver, and an empty
+ * zone table. The view is not frozen.
+ *
+ * Requires:
+ *
+ *\li 'mctx' is a valid memory context.
+ *
+ *\li 'rdclass' is a valid class.
+ *
+ *\li 'name' is a valid C string.
+ *
+ *\li viewp != NULL && *viewp == NULL
+ *
+ * Returns:
+ *
+ *\li #ISC_R_SUCCESS
+ *\li #ISC_R_NOMEMORY
+ *
+ *\li Other errors are possible.
+ */
+
+void
+dns_view_attach(dns_view_t *source, dns_view_t **targetp);
+/*%<
+ * Attach '*targetp' to 'source'.
+ *
+ * Requires:
+ *
+ *\li 'source' is a valid, frozen view.
+ *
+ *\li 'targetp' points to a NULL dns_view_t *.
+ *
+ * Ensures:
+ *
+ *\li *targetp is attached to source.
+ *
+ *\li While *targetp is attached, the view will not shut down.
+ */
+
+void
+dns_view_detach(dns_view_t **viewp);
+/*%<
+ * Detach '*viewp' from its view.
+ *
+ * Requires:
+ *
+ *\li 'viewp' points to a valid dns_view_t *
+ *
+ * Ensures:
+ *
+ *\li *viewp is NULL.
+ */
+
+void
+dns_view_flushanddetach(dns_view_t **viewp);
+/*%<
+ * Detach '*viewp' from its view. If this was the last reference
+ * uncommitted changed in zones will be flushed to disk.
+ *
+ * Requires:
+ *
+ *\li 'viewp' points to a valid dns_view_t *
+ *
+ * Ensures:
+ *
+ *\li *viewp is NULL.
+ */
+
+void
+dns_view_weakattach(dns_view_t *source, dns_view_t **targetp);
+/*%<
+ * Weakly attach '*targetp' to 'source'.
+ *
+ * Requires:
+ *
+ *\li 'source' is a valid, frozen view.
+ *
+ *\li 'targetp' points to a NULL dns_view_t *.
+ *
+ * Ensures:
+ *
+ *\li *targetp is attached to source.
+ *
+ * \li While *targetp is attached, the view will not be freed.
+ */
+
+void
+dns_view_weakdetach(dns_view_t **targetp);
+/*%<
+ * Detach '*viewp' from its view.
+ *
+ * Requires:
+ *
+ *\li 'viewp' points to a valid dns_view_t *.
+ *
+ * Ensures:
+ *
+ *\li *viewp is NULL.
+ */
+
+isc_result_t
+dns_view_createzonetable(dns_view_t *view);
+/*%<
+ * Create a zonetable for the view.
+ *
+ * Requires:
+ *
+ *\li 'view' is a valid, unfrozen view.
+ *
+ *\li 'view' does not have a zonetable already.
+ *
+ * Returns:
+ *
+ *\li #ISC_R_SUCCESS
+ *
+ *\li Any error that dns_zt_create() can return.
+ */
+
+isc_result_t
+dns_view_createresolver(dns_view_t *view, isc_taskmgr_t *taskmgr,
+ unsigned int ntasks, unsigned int ndisp, isc_nm_t *nm,
+ isc_timermgr_t *timermgr, unsigned int options,
+ dns_dispatchmgr_t *dispatchmgr,
+ dns_dispatch_t *dispatchv4, dns_dispatch_t *dispatchv6);
+/*%<
+ * Create a resolver and address database for the view.
+ *
+ * Requires:
+ *
+ *\li 'view' is a valid, unfrozen view.
+ *
+ *\li 'view' does not have a resolver already.
+ *
+ *\li The requirements of dns_resolver_create() apply to 'taskmgr',
+ * 'ntasks', 'nm', 'timermgr', 'options', 'dispatchv4', and
+ * 'dispatchv6'.
+ *
+ * Returns:
+ *
+ *\li #ISC_R_SUCCESS
+ *
+ *\li Any error that dns_resolver_create() can return.
+ */
+
+void
+dns_view_setcache(dns_view_t *view, dns_cache_t *cache, bool shared);
+/*%<
+ * Set the view's cache database. If 'shared' is true, this means the cache
+ * is created by another view and is shared with that view. dns_view_setcache()
+ * is a backward compatible version equivalent to setcache2(..., false).
+ *
+ * Requires:
+ *
+ *\li 'view' is a valid, unfrozen view.
+ *
+ *\li 'cache' is a valid cache.
+ *
+ * Ensures:
+ *
+ * \li The cache of 'view' is 'cached.
+ *
+ *\li If this is not the first call to dns_view_setcache() for this
+ * view, then previously set cache is detached.
+ */
+
+void
+dns_view_sethints(dns_view_t *view, dns_db_t *hints);
+/*%<
+ * Set the view's hints database.
+ *
+ * Requires:
+ *
+ *\li 'view' is a valid, unfrozen view, whose hints database has not been
+ * set.
+ *
+ *\li 'hints' is a valid zone database.
+ *
+ * Ensures:
+ *
+ * \li The hints database of 'view' is 'hints'.
+ */
+
+void
+dns_view_settransports(dns_view_t *view, dns_transport_list_t *list);
+
+void
+dns_view_setkeyring(dns_view_t *view, dns_tsig_keyring_t *ring);
+void
+dns_view_setdynamickeyring(dns_view_t *view, dns_tsig_keyring_t *ring);
+/*%<
+ * Set the view's static TSIG keys
+ *
+ * Requires:
+ *
+ * \li 'view' is a valid, unfrozen view, whose static TSIG keyring has not
+ * been set.
+ *
+ *\li 'ring' is a valid TSIG keyring
+ *
+ * Ensures:
+ *
+ *\li The static TSIG keyring of 'view' is 'ring'.
+ */
+
+void
+dns_view_getdynamickeyring(dns_view_t *view, dns_tsig_keyring_t **ringp);
+/*%<
+ * Return the views dynamic keys.
+ *
+ * \li 'view' is a valid, unfrozen view.
+ * \li 'ringp' != NULL && ringp == NULL.
+ */
+
+void
+dns_view_setdstport(dns_view_t *view, in_port_t dstport);
+/*%<
+ * Set the view's destination port. This is the port to
+ * which outgoing queries are sent. The default is 53,
+ * the standard DNS port.
+ *
+ * Requires:
+ *
+ *\li 'view' is a valid view.
+ *
+ *\li 'dstport' is a valid TCP/UDP port number.
+ *
+ * Ensures:
+ *\li External name servers will be assumed to be listening
+ * on 'dstport'. For servers whose address has already
+ * obtained obtained at the time of the call, the view may
+ * continue to use the previously set port until the address
+ * times out from the view's address database.
+ */
+
+isc_result_t
+dns_view_addzone(dns_view_t *view, dns_zone_t *zone);
+/*%<
+ * Add zone 'zone' to 'view'.
+ *
+ * Requires:
+ *
+ *\li 'view' is a valid, unfrozen view.
+ *
+ *\li 'zone' is a valid zone.
+ */
+
+void
+dns_view_freeze(dns_view_t *view);
+/*%<
+ * Freeze view. No changes can be made to view configuration while frozen.
+ *
+ * Requires:
+ *
+ *\li 'view' is a valid, unfrozen view.
+ *
+ * Ensures:
+ *
+ *\li 'view' is frozen.
+ */
+
+void
+dns_view_thaw(dns_view_t *view);
+/*%<
+ * Thaw view. This allows zones to be added or removed at runtime. This is
+ * NOT thread-safe; the caller MUST have run isc_task_exclusive() prior to
+ * thawing the view.
+ *
+ * Requires:
+ *
+ *\li 'view' is a valid, frozen view.
+ *
+ * Ensures:
+ *
+ *\li 'view' is no longer frozen.
+ */
+
+isc_result_t
+dns_view_find(dns_view_t *view, const dns_name_t *name, dns_rdatatype_t type,
+ isc_stdtime_t now, unsigned int options, bool use_hints,
+ bool use_static_stub, dns_db_t **dbp, dns_dbnode_t **nodep,
+ dns_name_t *foundname, dns_rdataset_t *rdataset,
+ dns_rdataset_t *sigrdataset);
+/*%<
+ * Find an rdataset whose owner name is 'name', and whose type is
+ * 'type'.
+ * In general, this function first searches view's zone and cache DBs for the
+ * best match data against 'name'. If nothing found there, and if 'use_hints'
+ * is true, the view's hint DB (if configured) is searched.
+ * If the view is configured with a static-stub zone which gives the longest
+ * match for 'name' among the zones, however, the cache DB is not consulted
+ * unless 'use_static_stub' is false (see below about this argument).
+ *
+ * dns_view_find() is a backward compatible version equivalent to
+ * dns_view_find2() with use_static_stub argument being false.
+ *
+ * Notes:
+ *
+ *\li See the description of dns_db_find() for information about 'options'.
+ * If the caller sets #DNS_DBFIND_GLUEOK, it must ensure that 'name'
+ * and 'type' are appropriate for glue retrieval.
+ *
+ *\li If 'now' is zero, then the current time will be used.
+ *
+ *\li If 'use_hints' is true, and the view has a hints database, then
+ * it will be searched last. If the answer is found in the hints
+ * database, the result code will be DNS_R_HINT. If the name is found
+ * in the hints database but not the type, the result code will be
+ * #DNS_R_HINTNXRRSET.
+ *
+ *\li If 'use_static_stub' is false and the longest match zone for 'name'
+ * is a static-stub zone, it's ignored and the cache and/or hints will be
+ * searched. In the majority of the cases this argument should be
+ * false. The only known usage of this argument being true is
+ * if this search is for a "bailiwick" glue A or AAAA RRset that may
+ * best match a static-stub zone. Consider the following example:
+ * this view is configured with a static-stub zone "example.com",
+ * and an attempt of recursive resolution needs to send a query for the
+ * zone. In this case it's quite likely that the resolver is trying to
+ * find A/AAAA RRs for the apex name "example.com". And, to honor the
+ * static-stub configuration it needs to return the glue RRs in the
+ * static-stub zone even if that exact RRs coming from the authoritative
+ * zone has been cached.
+ * In other general cases, the requested data is better to be
+ * authoritative, either locally configured or retrieved from an external
+ * server, and the data in the static-stub zone should better be ignored.
+ *
+ *\li 'foundname' must meet the requirements of dns_db_find().
+ *
+ *\li If 'sigrdataset' is not NULL, and there is a SIG rdataset which
+ * covers 'type', then 'sigrdataset' will be bound to it.
+ *
+ * Requires:
+ *
+ *\li 'view' is a valid, frozen view.
+ *
+ *\li 'name' is valid name.
+ *
+ *\li 'type' is a valid dns_rdatatype_t, and is not a meta query type
+ * except dns_rdatatype_any.
+ *
+ *\li dbp == NULL || *dbp == NULL
+ *
+ *\li nodep == NULL || *nodep == NULL. If nodep != NULL, dbp != NULL.
+ *
+ *\li 'foundname' is a valid name with a dedicated buffer or NULL.
+ *
+ *\li 'rdataset' is a valid, disassociated rdataset.
+ *
+ *\li 'sigrdataset' is NULL, or is a valid, disassociated rdataset.
+ *
+ * Ensures:
+ *
+ *\li In successful cases, 'rdataset', and possibly 'sigrdataset', are
+ * bound to the found data.
+ *
+ *\li If dbp != NULL, it points to the database containing the data.
+ *
+ *\li If nodep != NULL, it points to the database node containing the data.
+ *
+ *\li If foundname != NULL, it contains the full name of the found data.
+ *
+ * Returns:
+ *
+ *\li Any result that dns_db_find() can return, with the exception of
+ * #DNS_R_DELEGATION.
+ */
+
+isc_result_t
+dns_view_simplefind(dns_view_t *view, const dns_name_t *name,
+ dns_rdatatype_t type, isc_stdtime_t now,
+ unsigned int options, bool use_hints,
+ dns_rdataset_t *rdataset, dns_rdataset_t *sigrdataset);
+/*%<
+ * Find an rdataset whose owner name is 'name', and whose type is
+ * 'type'.
+ *
+ * Notes:
+ *
+ *\li This routine is appropriate for simple, exact-match queries of the
+ * view. 'name' must be a canonical name; there is no DNAME or CNAME
+ * processing.
+ *
+ *\li See the description of dns_db_find() for information about 'options'.
+ * If the caller sets DNS_DBFIND_GLUEOK, it must ensure that 'name'
+ * and 'type' are appropriate for glue retrieval.
+ *
+ *\li If 'now' is zero, then the current time will be used.
+ *
+ *\li If 'use_hints' is true, and the view has a hints database, then
+ * it will be searched last. If the answer is found in the hints
+ * database, the result code will be DNS_R_HINT. If the name is found
+ * in the hints database but not the type, the result code will be
+ * DNS_R_HINTNXRRSET.
+ *
+ *\li If 'sigrdataset' is not NULL, and there is a SIG rdataset which
+ * covers 'type', then 'sigrdataset' will be bound to it.
+ *
+ * Requires:
+ *
+ *\li 'view' is a valid, frozen view.
+ *
+ *\li 'name' is valid name.
+ *
+ *\li 'type' is a valid dns_rdatatype_t, and is not a meta query type
+ * (e.g. dns_rdatatype_any), or dns_rdatatype_rrsig.
+ *
+ *\li 'rdataset' is a valid, disassociated rdataset.
+ *
+ *\li 'sigrdataset' is NULL, or is a valid, disassociated rdataset.
+ *
+ * Ensures:
+ *
+ *\li In successful cases, 'rdataset', and possibly 'sigrdataset', are
+ * bound to the found data.
+ *
+ * Returns:
+ *
+ *\li #ISC_R_SUCCESS Success; result is desired type.
+ *\li DNS_R_GLUE Success; result is glue.
+ *\li DNS_R_HINT Success; result is a hint.
+ *\li DNS_R_NCACHENXDOMAIN Success; result is a ncache entry.
+ *\li DNS_R_NCACHENXRRSET Success; result is a ncache entry.
+ *\li DNS_R_NXDOMAIN The name does not exist.
+ *\li DNS_R_NXRRSET The rrset does not exist.
+ *\li #ISC_R_NOTFOUND No matching data found,
+ * or an error occurred.
+ */
+
+isc_result_t
+dns_view_findzonecut(dns_view_t *view, const dns_name_t *name,
+ dns_name_t *fname, dns_name_t *dcname, isc_stdtime_t now,
+ unsigned int options, bool use_hints, bool use_cache,
+ dns_rdataset_t *rdataset, dns_rdataset_t *sigrdataset);
+/*%<
+ * Find the best known zonecut containing 'name'.
+ *
+ * This uses local authority, cache, and optionally hints data.
+ * No external queries are performed.
+ *
+ * Notes:
+ *
+ *\li If 'now' is zero, then the current time will be used.
+ *
+ *\li If 'use_hints' is true, and the view has a hints database, then
+ * it will be searched last.
+ *
+ *\li If 'use_cache' is true, and the view has a cache, then it will be
+ * searched.
+ *
+ *\li If 'sigrdataset' is not NULL, and there is a SIG rdataset which
+ * covers 'type', then 'sigrdataset' will be bound to it.
+ *
+ *\li If the DNS_DBFIND_NOEXACT option is set, then the zonecut returned
+ * (if any) will be the deepest known ancestor of 'name'.
+ *
+ *\li If dcname is not NULL the deepest cached name is copied to it.
+ *
+ * Requires:
+ *
+ *\li 'view' is a valid, frozen view.
+ *
+ *\li 'name' is valid name.
+ *
+ *\li 'rdataset' is a valid, disassociated rdataset.
+ *
+ *\li 'sigrdataset' is NULL, or is a valid, disassociated rdataset.
+ *
+ * Returns:
+ *
+ *\li #ISC_R_SUCCESS Success.
+ *
+ *\li Many other results are possible.
+ */
+
+isc_result_t
+dns_viewlist_find(dns_viewlist_t *list, const char *name,
+ dns_rdataclass_t rdclass, dns_view_t **viewp);
+/*%<
+ * Search for a view with name 'name' and class 'rdclass' in 'list'.
+ * If found, '*viewp' is (strongly) attached to it.
+ *
+ * Requires:
+ *
+ *\li 'viewp' points to a NULL dns_view_t *.
+ *
+ * Returns:
+ *
+ *\li #ISC_R_SUCCESS A matching view was found.
+ *\li #ISC_R_NOTFOUND No matching view was found.
+ */
+
+isc_result_t
+dns_viewlist_findzone(dns_viewlist_t *list, const dns_name_t *name,
+ bool allclasses, dns_rdataclass_t rdclass,
+ dns_zone_t **zonep);
+
+/*%<
+ * Search zone with 'name' in view with 'rdclass' in viewlist 'list'
+ * If found, zone is returned in *zonep. If allclasses is set rdclass is ignored
+ *
+ * Returns:
+ *\li #ISC_R_SUCCESS A matching zone was found.
+ *\li #ISC_R_NOTFOUND No matching zone was found.
+ *\li #ISC_R_MULTIPLE Multiple zones with the same name were found.
+ */
+
+isc_result_t
+dns_view_findzone(dns_view_t *view, const dns_name_t *name, dns_zone_t **zonep);
+/*%<
+ * Search for the zone 'name' in the zone table of 'view'.
+ * If found, 'zonep' is (strongly) attached to it. There
+ * are no partial matches.
+ *
+ * Requires:
+ *
+ *\li 'zonep' points to a NULL dns_zone_t *.
+ *
+ * Returns:
+ *\li #ISC_R_SUCCESS A matching zone was found.
+ *\li #ISC_R_NOTFOUND No matching zone was found.
+ *\li others An error occurred.
+ */
+
+isc_result_t
+dns_view_load(dns_view_t *view, bool stop, bool newonly);
+
+isc_result_t
+dns_view_asyncload(dns_view_t *view, bool newonly, dns_zt_allloaded_t callback,
+ void *arg);
+/*%<
+ * Load zones attached to this view. dns_view_load() loads
+ * all zones whose master file has changed since the last
+ * load
+ *
+ * dns_view_asyncload() loads zones asynchronously. When all zones
+ * in the view have finished loading, 'callback' is called with argument
+ * 'arg' to inform the caller.
+ *
+ * If 'stop' is true, stop on the first error and return it.
+ * If 'stop' is false (or we are loading asynchronously), ignore errors.
+ *
+ * If 'newonly' is true load only zones that were never loaded.
+ *
+ * Requires:
+ *
+ *\li 'view' is valid.
+ */
+
+isc_result_t
+dns_view_gettransport(dns_view_t *view, const dns_transport_type_t type,
+ const dns_name_t *name, dns_transport_t **transportp);
+
+isc_result_t
+dns_view_gettsig(dns_view_t *view, const dns_name_t *keyname,
+ dns_tsigkey_t **keyp);
+/*%<
+ * Find the TSIG key configured in 'view' with name 'keyname',
+ * if any.
+ *
+ * Requires:
+ *\li keyp points to a NULL dns_tsigkey_t *.
+ *
+ * Returns:
+ *\li #ISC_R_SUCCESS A key was found and '*keyp' now points to it.
+ *\li #ISC_R_NOTFOUND No key was found.
+ *\li others An error occurred.
+ */
+
+isc_result_t
+dns_view_getpeertsig(dns_view_t *view, const isc_netaddr_t *peeraddr,
+ dns_tsigkey_t **keyp);
+/*%<
+ * Find the TSIG key configured in 'view' for the server whose
+ * address is 'peeraddr', if any.
+ *
+ * Requires:
+ * keyp points to a NULL dns_tsigkey_t *.
+ *
+ * Returns:
+ *\li #ISC_R_SUCCESS A key was found and '*keyp' now points to it.
+ *\li #ISC_R_NOTFOUND No key was found.
+ *\li others An error occurred.
+ */
+
+isc_result_t
+dns_view_checksig(dns_view_t *view, isc_buffer_t *source, dns_message_t *msg);
+/*%<
+ * Verifies the signature of a message.
+ *
+ * Requires:
+ *
+ *\li 'view' is a valid view.
+ *\li 'source' is a valid buffer containing the message
+ *\li 'msg' is a valid message
+ *
+ * Returns:
+ *\li see dns_tsig_verify()
+ */
+
+void
+dns_view_dialup(dns_view_t *view);
+/*%<
+ * Perform dialup-time maintenance on the zones of 'view'.
+ */
+
+isc_result_t
+dns_view_dumpdbtostream(dns_view_t *view, FILE *fp);
+/*%<
+ * Dump the current state of the view 'view' to the stream 'fp'
+ * for purposes of analysis or debugging.
+ *
+ * Currently the dumped state includes the view's cache; in the future
+ * it may also include other state such as the address database.
+ * It will not not include authoritative data since it is voluminous and
+ * easily obtainable by other means.
+ *
+ * Requires:
+ *
+ *\li 'view' is valid.
+ *
+ *\li 'fp' refers to a file open for writing.
+ *
+ * Returns:
+ * \li ISC_R_SUCCESS The cache was successfully dumped.
+ * \li others An error occurred (see dns_master_dump)
+ */
+
+isc_result_t
+dns_view_flushcache(dns_view_t *view, bool fixuponly);
+/*%<
+ * Flush the view's cache (and ADB). If 'fixuponly' is true, it only updates
+ * the internal reference to the cache DB with omitting actual flush operation.
+ * 'fixuponly' is intended to be used for a view that shares a cache with
+ * a different view. dns_view_flushcache() is a backward compatible version
+ * that always sets fixuponly to false.
+ *
+ * Requires:
+ * 'view' is valid.
+ *
+ * No other tasks are executing.
+ *
+ * Returns:
+ *\li #ISC_R_SUCCESS
+ *\li #ISC_R_NOMEMORY
+ */
+
+isc_result_t
+dns_view_flushnode(dns_view_t *view, const dns_name_t *name, bool tree);
+/*%<
+ * Flush the given name from the view's cache (and optionally ADB/badcache).
+ *
+ * Flush the given name from the cache, ADB, and bad cache. If 'tree'
+ * is true, also flush all subdomains of 'name'.
+ *
+ * Requires:
+ *\li 'view' is valid.
+ *\li 'name' is valid.
+ *
+ * Returns:
+ *\li #ISC_R_SUCCESS
+ * other returns are failures.
+ */
+
+isc_result_t
+dns_view_flushname(dns_view_t *view, const dns_name_t *name);
+/*%<
+ * Flush the given name from the view's cache, ADB and badcache.
+ * Equivalent to dns_view_flushnode(view, name, false).
+ *
+ *
+ * Requires:
+ *\li 'view' is valid.
+ *\li 'name' is valid.
+ *
+ * Returns:
+ *\li #ISC_R_SUCCESS
+ * other returns are failures.
+ */
+
+void
+dns_view_adddelegationonly(dns_view_t *view, const dns_name_t *name);
+/*%<
+ * Add the given name to the delegation only table.
+ *
+ * Requires:
+ *\li 'view' is valid.
+ *\li 'name' is valid.
+ *
+ * Returns:
+ *\li #ISC_R_SUCCESS
+ *\li #ISC_R_NOMEMORY
+ */
+
+void
+dns_view_excludedelegationonly(dns_view_t *view, const dns_name_t *name);
+/*%<
+ * Add the given name to be excluded from the root-delegation-only.
+ *
+ *
+ * Requires:
+ *\li 'view' is valid.
+ *\li 'name' is valid.
+ *
+ * Returns:
+ *\li #ISC_R_SUCCESS
+ *\li #ISC_R_NOMEMORY
+ */
+
+bool
+dns_view_isdelegationonly(dns_view_t *view, const dns_name_t *name);
+/*%<
+ * Check if 'name' is in the delegation only table or if
+ * rootdelonly is set that name is not being excluded.
+ *
+ * Requires:
+ *\li 'view' is valid.
+ *\li 'name' is valid.
+ *
+ * Returns:
+ *\li #true if the name is the table.
+ *\li #false otherwise.
+ */
+
+void
+dns_view_setrootdelonly(dns_view_t *view, bool value);
+/*%<
+ * Set the root delegation only flag.
+ *
+ * Requires:
+ *\li 'view' is valid.
+ */
+
+bool
+dns_view_getrootdelonly(dns_view_t *view);
+/*%<
+ * Get the root delegation only flag.
+ *
+ * Requires:
+ *\li 'view' is valid.
+ */
+
+isc_result_t
+dns_view_freezezones(dns_view_t *view, bool freeze);
+/*%<
+ * Freeze/thaw updates to primary zones.
+ *
+ * Requires:
+ * \li 'view' is valid.
+ */
+
+void
+dns_view_setadbstats(dns_view_t *view, isc_stats_t *stats);
+/*%<
+ * Set a adb statistics set 'stats' for 'view'.
+ *
+ * Requires:
+ * \li 'view' is valid and is not frozen.
+ *
+ *\li stats is a valid statistics supporting adb statistics
+ * (see dns/stats.h).
+ */
+
+void
+dns_view_getadbstats(dns_view_t *view, isc_stats_t **statsp);
+/*%<
+ * Get the adb statistics counter set for 'view'. If a statistics set is
+ * set '*statsp' will be attached to the set; otherwise, '*statsp' will be
+ * untouched.
+ *
+ * Requires:
+ * \li 'view' is valid and is not frozen.
+ *
+ *\li 'statsp' != NULL && '*statsp' != NULL
+ */
+
+void
+dns_view_setresstats(dns_view_t *view, isc_stats_t *stats);
+/*%<
+ * Set a general resolver statistics counter set 'stats' for 'view'.
+ *
+ * Requires:
+ * \li 'view' is valid and is not frozen.
+ *
+ *\li stats is a valid statistics supporting resolver statistics counters
+ * (see dns/stats.h).
+ */
+
+void
+dns_view_getresstats(dns_view_t *view, isc_stats_t **statsp);
+/*%<
+ * Get the general statistics counter set for 'view'. If a statistics set is
+ * set '*statsp' will be attached to the set; otherwise, '*statsp' will be
+ * untouched.
+ *
+ * Requires:
+ * \li 'view' is valid and is not frozen.
+ *
+ *\li 'statsp' != NULL && '*statsp' != NULL
+ */
+
+void
+dns_view_setresquerystats(dns_view_t *view, dns_stats_t *stats);
+/*%<
+ * Set a statistics counter set of rdata type, 'stats', for 'view'. Once the
+ * statistic set is installed, view's resolver will count outgoing queries
+ * per rdata type.
+ *
+ * Requires:
+ * \li 'view' is valid and is not frozen.
+ *
+ *\li stats is a valid statistics created by dns_rdatatypestats_create().
+ */
+
+void
+dns_view_getresquerystats(dns_view_t *view, dns_stats_t **statsp);
+/*%<
+ * Get the rdatatype statistics counter set for 'view'. If a statistics set is
+ * set '*statsp' will be attached to the set; otherwise, '*statsp' will be
+ * untouched.
+ *
+ * Requires:
+ * \li 'view' is valid and is not frozen.
+ *
+ *\li 'statsp' != NULL && '*statsp' != NULL
+ */
+
+bool
+dns_view_iscacheshared(dns_view_t *view);
+/*%<
+ * Check if the view shares the cache created by another view.
+ *
+ * Requires:
+ * \li 'view' is valid.
+ *
+ * Returns:
+ *\li #true if the cache is shared.
+ *\li #false otherwise.
+ */
+
+isc_result_t
+dns_view_initntatable(dns_view_t *view, isc_taskmgr_t *taskmgr,
+ isc_timermgr_t *timermgr);
+/*%<
+ * Initialize the negative trust anchor table for the view.
+ *
+ * Requires:
+ * \li 'view' is valid.
+ *
+ * Returns:
+ *\li ISC_R_SUCCESS
+ *\li Any other result indicates failure
+ */
+
+isc_result_t
+dns_view_getntatable(dns_view_t *view, dns_ntatable_t **ntp);
+/*%<
+ * Get the negative trust anchor table for this view. Returns
+ * ISC_R_NOTFOUND if the table not been initialized for the view.
+ *
+ * '*ntp' is attached on success; the caller is responsible for
+ * detaching it with dns_ntatable_detach().
+ *
+ * Requires:
+ * \li 'view' is valid.
+ * \li 'nta' is not NULL and '*nta' is NULL.
+ *
+ * Returns:
+ *\li ISC_R_SUCCESS
+ *\li ISC_R_NOTFOUND
+ */
+
+isc_result_t
+dns_view_initsecroots(dns_view_t *view, isc_mem_t *mctx);
+/*%<
+ * Initialize security roots for the view, detaching any previously
+ * existing security roots first. (Note that secroots_priv is
+ * NULL until this function is called, so any function using
+ * security roots must check that they have been initialized first.
+ * One way to do this is use dns_view_getsecroots() and check its
+ * return value.)
+ *
+ * Requires:
+ * \li 'view' is valid.
+ *
+ * Returns:
+ *\li ISC_R_SUCCESS
+ *\li Any other result indicates failure
+ */
+
+isc_result_t
+dns_view_getsecroots(dns_view_t *view, dns_keytable_t **ktp);
+/*%<
+ * Get the security roots for this view. Returns ISC_R_NOTFOUND if
+ * the security roots keytable has not been initialized for the view.
+ *
+ * '*ktp' is attached on success; the caller is responsible for
+ * detaching it with dns_keytable_detach().
+ *
+ * Requires:
+ * \li 'view' is valid.
+ * \li 'ktp' is not NULL and '*ktp' is NULL.
+ *
+ * Returns:
+ *\li ISC_R_SUCCESS
+ *\li ISC_R_NOTFOUND
+ */
+
+isc_result_t
+dns_view_issecuredomain(dns_view_t *view, const dns_name_t *name,
+ isc_stdtime_t now, bool checknta, bool *ntap,
+ bool *secure_domain);
+/*%<
+ * Is 'name' at or beneath a trusted key, and not covered by a valid
+ * negative trust anchor? Put answer in '*secure_domain'.
+ *
+ * If 'checknta' is false, ignore the NTA table in determining
+ * whether this is a secure domain. If 'checknta' is not false, and if
+ * 'ntap' is non-NULL, then '*ntap' will be updated with true if the
+ * name is covered by an NTA.
+ *
+ * Requires:
+ * \li 'view' is valid.
+ *
+ * Returns:
+ *\li ISC_R_SUCCESS
+ *\li Any other value indicates failure
+ */
+
+bool
+dns_view_ntacovers(dns_view_t *view, isc_stdtime_t now, const dns_name_t *name,
+ const dns_name_t *anchor);
+/*%<
+ * Is there a current negative trust anchor above 'name' and below 'anchor'?
+ *
+ * Requires:
+ * \li 'view' is valid.
+ *
+ * Returns:
+ *\li ISC_R_TRUE
+ *\li ISC_R_FALSE
+ */
+
+void
+dns_view_untrust(dns_view_t *view, const dns_name_t *keyname,
+ const dns_rdata_dnskey_t *dnskey);
+/*%<
+ * Remove keys that match 'keyname' and 'dnskey' from the views trust
+ * anchors.
+ *
+ * (NOTE: If the configuration specifies that there should be a
+ * trust anchor at 'keyname', but no keys are left after this
+ * operation, that is an error. We fail closed, inserting a NULL
+ * key so as to prevent validation until a legimitate key has been
+ * provided.)
+ *
+ * Requires:
+ * \li 'view' is valid.
+ * \li 'keyname' is valid.
+ * \li 'dnskey' is valid.
+ */
+
+bool
+dns_view_istrusted(dns_view_t *view, const dns_name_t *keyname,
+ const dns_rdata_dnskey_t *dnskey);
+/*%<
+ * Determine if the key defined by 'keyname' and 'dnskey' is
+ * trusted by 'view'.
+ *
+ * Requires:
+ * \li 'view' is valid.
+ * \li 'keyname' is valid.
+ * \li 'dnskey' is valid.
+ */
+
+isc_result_t
+dns_view_setnewzones(dns_view_t *view, bool allow, void *cfgctx,
+ void (*cfg_destroy)(void **), uint64_t mapsize);
+/*%<
+ * Set whether or not to allow zones to be created or deleted at runtime.
+ *
+ * If 'allow' is true, determines the filename into which new zone
+ * configuration will be written. Preserves the configuration context
+ * (a pointer to which is passed in 'cfgctx') for use when parsing new
+ * zone configuration. 'cfg_destroy' points to a callback routine to
+ * destroy the configuration context when the view is destroyed. (This
+ * roundabout method is used in order to avoid libdns having a dependency
+ * on libisccfg and libbind9.)
+ *
+ * If 'allow' is false, removes any existing references to
+ * configuration context and frees any memory.
+ *
+ * Requires:
+ * \li 'view' is valid.
+ *
+ * Returns:
+ * \li ISC_R_SUCCESS
+ * \li ISC_R_NOSPACE
+ */
+
+void
+dns_view_setnewzonedir(dns_view_t *view, const char *dir);
+const char *
+dns_view_getnewzonedir(dns_view_t *view);
+/*%<
+ * Set/get the path to the directory in which NZF or NZD files should
+ * be stored. If the path was previously set to a non-NULL value,
+ * the previous value is freed.
+ *
+ * Requires:
+ * \li 'view' is valid.
+ */
+
+void
+dns_view_restorekeyring(dns_view_t *view);
+
+isc_result_t
+dns_view_searchdlz(dns_view_t *view, const dns_name_t *name,
+ unsigned int minlabels, dns_clientinfomethods_t *methods,
+ dns_clientinfo_t *clientinfo, dns_db_t **dbp);
+
+/*%<
+ * Search through the DLZ database(s) in view->dlz_searched to find
+ * one that can answer a query for 'name', using the DLZ driver's
+ * findzone method. If successful, '*dbp' is set to point to the
+ * DLZ database.
+ *
+ * Returns:
+ * \li ISC_R_SUCCESS
+ * \li ISC_R_NOTFOUND
+ *
+ * Requires:
+ * \li 'view' is valid.
+ * \li 'name' is not NULL.
+ * \li 'dbp' is not NULL and *dbp is NULL.
+ */
+
+uint32_t
+dns_view_getfailttl(dns_view_t *view);
+/*%<
+ * Get the view's servfail-ttl. zero => no servfail caching.
+ *
+ * Requires:
+ *\li 'view' to be valid.
+ */
+
+void
+dns_view_setfailttl(dns_view_t *view, uint32_t failttl);
+/*%<
+ * Set the view's servfail-ttl. zero => no servfail caching.
+ *
+ * Requires:
+ *\li 'view' to be valid.
+ */
+
+isc_result_t
+dns_view_saventa(dns_view_t *view);
+/*%<
+ * Save NTA for names in this view to a file.
+ *
+ * Requires:
+ *\li 'view' to be valid.
+ */
+
+isc_result_t
+dns_view_loadnta(dns_view_t *view);
+/*%<
+ * Loads NTA for names in this view from a file.
+ *
+ * Requires:
+ *\li 'view' to be valid.
+ */
+
+void
+dns_view_setviewcommit(dns_view_t *view);
+/*%<
+ * Commit dns_zone_setview() calls previously made for all zones in this
+ * view.
+ *
+ * Requires:
+ *\li 'view' to be valid.
+ */
+
+void
+dns_view_setviewrevert(dns_view_t *view);
+/*%<
+ * Revert dns_zone_setview() calls previously made for all zones in this
+ * view.
+ *
+ * Requires:
+ *\li 'view' to be valid.
+ */
+
+bool
+dns_view_staleanswerenabled(dns_view_t *view);
+/*%<
+ * Check if stale answers are enabled for this view.
+ *
+ * Requires:
+ *\li 'view' to be valid.
+ */
+
+void
+dns_view_sfd_add(dns_view_t *view, const dns_name_t *name);
+/*%<
+ * Add 'name' to the synth-from-dnssec namespace tree for the
+ * view. If the tree does not already exist create it.
+ *
+ * Requires:
+ *\li 'view' to be valid.
+ *\li 'name' to be valid.
+ */
+
+void
+dns_view_sfd_del(dns_view_t *view, const dns_name_t *name);
+/*%<
+ * Delete 'name' to the synth-from-dnssec namespace tree for
+ * the view when the count of previous adds and deletes becomes
+ * zero.
+ *
+ * Requires:
+ *\li 'view' to be valid.
+ *\li 'name' to be valid.
+ */
+
+void
+dns_view_sfd_find(dns_view_t *view, const dns_name_t *name,
+ dns_name_t *foundname);
+/*%<
+ * Find the enclosing name to the synth-from-dnssec namespace tree for 'name'
+ * in the specified view.
+ *
+ * Requires:
+ *\li 'view' to be valid.
+ *\li 'name' to be valid.
+ *\li 'foundname' to be valid with a buffer sufficient to hold the name.
+ */
+
+ISC_LANG_ENDDECLS
diff --git a/lib/dns/include/dns/xfrin.h b/lib/dns/include/dns/xfrin.h
new file mode 100644
index 0000000..dd47c8c
--- /dev/null
+++ b/lib/dns/include/dns/xfrin.h
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#pragma once
+
+/*****
+***** Module Info
+*****/
+
+/*! \file dns/xfrin.h
+ * \brief
+ * Incoming zone transfers (AXFR + IXFR).
+ */
+
+/***
+ *** Imports
+ ***/
+
+#include <isc/lang.h>
+#include <isc/refcount.h>
+#include <isc/tls.h>
+
+#include <dns/transport.h>
+#include <dns/types.h>
+
+/***
+ *** Types
+ ***/
+
+/*%
+ * A transfer in progress. This is an opaque type.
+ */
+typedef struct dns_xfrin_ctx dns_xfrin_ctx_t;
+
+/***
+ *** Functions
+ ***/
+
+ISC_LANG_BEGINDECLS
+
+isc_result_t
+dns_xfrin_create(dns_zone_t *zone, dns_rdatatype_t xfrtype,
+ const isc_sockaddr_t *primaryaddr,
+ const isc_sockaddr_t *sourceaddr, dns_tsigkey_t *tsigkey,
+ dns_transport_t *transport, isc_tlsctx_cache_t *tlsctx_cache,
+ isc_mem_t *mctx, isc_nm_t *netmgr, dns_xfrindone_t done,
+ dns_xfrin_ctx_t **xfrp);
+/*%<
+ * Attempt to start an incoming zone transfer of 'zone'
+ * from 'primaryaddr', creating a dns_xfrin_ctx_t object to
+ * manage it. Attach '*xfrp' to the newly created object.
+ *
+ * Iff ISC_R_SUCCESS is returned, '*done' is called with
+ * 'zone' and a result code as arguments when the transfer finishes.
+ *
+ * Requires:
+ *\li 'xfrtype' is dns_rdatatype_axfr, dns_rdatatype_ixfr
+ * or dns_rdatatype_soa (soa query followed by axfr if
+ * serial is greater than current serial).
+ *
+ *\li If 'xfrtype' is dns_rdatatype_ixfr or dns_rdatatype_soa,
+ * the zone has a database.
+ */
+
+void
+dns_xfrin_shutdown(dns_xfrin_ctx_t *xfr);
+/*%<
+ * If the zone transfer 'xfr' has already finished,
+ * do nothing. Otherwise, abort it and cause it to call
+ * its done callback with a status of ISC_R_CANCELED.
+ */
+
+void
+dns_xfrin_detach(dns_xfrin_ctx_t **xfrp);
+/*%<
+ * Detach a reference to a zone transfer object.
+ * Caller to maintain external locking if required.
+ */
+
+void
+dns_xfrin_attach(dns_xfrin_ctx_t *source, dns_xfrin_ctx_t **target);
+/*%<
+ * Caller to maintain external locking if required.
+ */
+
+ISC_LANG_ENDDECLS
diff --git a/lib/dns/include/dns/zone.h b/lib/dns/include/dns/zone.h
new file mode 100644
index 0000000..10ed86c
--- /dev/null
+++ b/lib/dns/include/dns/zone.h
@@ -0,0 +1,2657 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#pragma once
+
+/*! \file dns/zone.h */
+
+/***
+ *** Imports
+ ***/
+
+#include <inttypes.h>
+#include <stdbool.h>
+#include <stdio.h>
+
+#include <isc/formatcheck.h>
+#include <isc/lang.h>
+#include <isc/rwlock.h>
+#include <isc/tls.h>
+
+#include <dns/catz.h>
+#include <dns/diff.h>
+#include <dns/master.h>
+#include <dns/masterdump.h>
+#include <dns/rdatastruct.h>
+#include <dns/rpz.h>
+#include <dns/types.h>
+#include <dns/zt.h>
+
+typedef enum {
+ dns_zone_none,
+ dns_zone_primary,
+ dns_zone_secondary,
+ dns_zone_mirror,
+ dns_zone_stub,
+ dns_zone_staticstub,
+ dns_zone_key,
+ dns_zone_dlz,
+ dns_zone_redirect
+} dns_zonetype_t;
+
+#ifndef dns_zone_master
+#define dns_zone_master dns_zone_primary
+#endif /* dns_zone_master */
+
+#ifndef dns_zone_slave
+#define dns_zone_slave dns_zone_secondary
+#endif /* dns_zone_slave */
+
+typedef enum {
+ dns_zonestat_none = 0,
+ dns_zonestat_terse,
+ dns_zonestat_full
+} dns_zonestat_level_t;
+
+typedef enum {
+ DNS_ZONEOPT_MANYERRORS = 1 << 0, /*%< return many errors on load */
+ DNS_ZONEOPT_IXFRFROMDIFFS = 1 << 1, /*%< calculate differences */
+ DNS_ZONEOPT_NOMERGE = 1 << 2, /*%< don't merge journal */
+ DNS_ZONEOPT_CHECKNS = 1 << 3, /*%< check if NS's are addresses */
+ DNS_ZONEOPT_FATALNS = 1 << 4, /*%< DNS_ZONEOPT_CHECKNS is fatal */
+ DNS_ZONEOPT_MULTIMASTER = 1 << 5, /*%< this zone has multiple
+ primaries */
+ DNS_ZONEOPT_USEALTXFRSRC = 1 << 6, /*%< use alternate transfer sources
+ */
+ DNS_ZONEOPT_CHECKNAMES = 1 << 7, /*%< check-names */
+ DNS_ZONEOPT_CHECKNAMESFAIL = 1 << 8, /*%< fatal check-name failures */
+ DNS_ZONEOPT_CHECKWILDCARD = 1 << 9, /*%< check for internal wildcards */
+ DNS_ZONEOPT_CHECKMX = 1 << 10, /*%< check-mx */
+ DNS_ZONEOPT_CHECKMXFAIL = 1 << 11, /*%< fatal check-mx failures */
+ DNS_ZONEOPT_CHECKINTEGRITY = 1 << 12, /*%< perform integrity checks */
+ DNS_ZONEOPT_CHECKSIBLING = 1 << 13, /*%< perform sibling glue checks */
+ DNS_ZONEOPT_NOCHECKNS = 1 << 14, /*%< disable IN NS address checks */
+ DNS_ZONEOPT_WARNMXCNAME = 1 << 15, /*%< warn on MX CNAME check */
+ DNS_ZONEOPT_IGNOREMXCNAME = 1 << 16, /*%< ignore MX CNAME check */
+ DNS_ZONEOPT_WARNSRVCNAME = 1 << 17, /*%< warn on SRV CNAME check */
+ DNS_ZONEOPT_IGNORESRVCNAME = 1 << 18, /*%< ignore SRV CNAME check */
+ DNS_ZONEOPT_UPDATECHECKKSK = 1 << 19, /*%< check dnskey KSK flag */
+ DNS_ZONEOPT_TRYTCPREFRESH = 1 << 20, /*%< try tcp refresh on udp failure
+ */
+ DNS_ZONEOPT_NOTIFYTOSOA = 1 << 21, /*%< Notify the SOA MNAME */
+ DNS_ZONEOPT_NSEC3TESTZONE = 1 << 22, /*%< nsec3-test-zone */
+ DNS_ZONEOPT_SECURETOINSECURE = 1 << 23, /*%< dnssec-secure-to-insecure
+ */
+ DNS_ZONEOPT_DNSKEYKSKONLY = 1 << 24, /*%< dnssec-dnskey-kskonly */
+ DNS_ZONEOPT_CHECKDUPRR = 1 << 25, /*%< check-dup-records */
+ DNS_ZONEOPT_CHECKDUPRRFAIL = 1 << 26, /*%< fatal check-dup-records
+ * failures */
+ DNS_ZONEOPT_CHECKSPF = 1 << 27, /*%< check SPF records */
+ DNS_ZONEOPT_CHECKTTL = 1 << 28, /*%< check max-zone-ttl */
+ DNS_ZONEOPT_AUTOEMPTY = 1 << 29, /*%< automatic empty zone */
+ DNS_ZONEOPT___MAX = UINT64_MAX, /* trick to make the ENUM 64-bit wide */
+} dns_zoneopt_t;
+
+/*
+ * Zone key maintenance options
+ */
+typedef enum {
+ DNS_ZONEKEY_ALLOW = 0x00000001U, /*%< fetch keys on command */
+ DNS_ZONEKEY_MAINTAIN = 0x00000002U, /*%< publish/sign on schedule */
+ DNS_ZONEKEY_CREATE = 0x00000004U, /*%< make keys when needed */
+ DNS_ZONEKEY_FULLSIGN = 0x00000008U, /*%< roll to new keys immediately */
+ DNS_ZONEKEY_NORESIGN = 0x00000010U, /*%< no automatic resigning */
+ DNS_ZONEKEY___MAX = UINT64_MAX, /* trick to make the ENUM 64-bit wide */
+} dns_zonekey_t;
+
+#ifndef DNS_ZONE_MINREFRESH
+#define DNS_ZONE_MINREFRESH 300 /*%< 5 minutes */
+#endif /* ifndef DNS_ZONE_MINREFRESH */
+#ifndef DNS_ZONE_MAXREFRESH
+#define DNS_ZONE_MAXREFRESH 2419200 /*%< 4 weeks */
+#endif /* ifndef DNS_ZONE_MAXREFRESH */
+#ifndef DNS_ZONE_DEFAULTREFRESH
+#define DNS_ZONE_DEFAULTREFRESH 3600 /*%< 1 hour */
+#endif /* ifndef DNS_ZONE_DEFAULTREFRESH */
+#ifndef DNS_ZONE_MINRETRY
+#define DNS_ZONE_MINRETRY 300 /*%< 5 minutes */
+#endif /* ifndef DNS_ZONE_MINRETRY */
+#ifndef DNS_ZONE_MAXRETRY
+#define DNS_ZONE_MAXRETRY 1209600 /*%< 2 weeks */
+#endif /* ifndef DNS_ZONE_MAXRETRY */
+#ifndef DNS_ZONE_DEFAULTRETRY
+#define DNS_ZONE_DEFAULTRETRY \
+ 60 /*%< 1 minute, subject to \
+ * exponential backoff */
+#endif /* ifndef DNS_ZONE_DEFAULTRETRY */
+
+#define DNS_ZONESTATE_XFERRUNNING 1
+#define DNS_ZONESTATE_XFERDEFERRED 2
+#define DNS_ZONESTATE_SOAQUERY 3
+#define DNS_ZONESTATE_ANY 4
+#define DNS_ZONESTATE_AUTOMATIC 5
+
+ISC_LANG_BEGINDECLS
+
+/***
+ *** Functions
+ ***/
+
+isc_result_t
+dns_zone_create(dns_zone_t **zonep, isc_mem_t *mctx);
+/*%<
+ * Creates a new empty zone and attach '*zonep' to it.
+ *
+ * Requires:
+ *\li 'zonep' to point to a NULL pointer.
+ *\li 'mctx' to be a valid memory context.
+ *
+ * Ensures:
+ *\li '*zonep' refers to a valid zone.
+ *
+ * Returns:
+ *\li #ISC_R_SUCCESS
+ *\li #ISC_R_NOMEMORY
+ *\li #ISC_R_UNEXPECTED
+ */
+
+void
+dns_zone_setclass(dns_zone_t *zone, dns_rdataclass_t rdclass);
+/*%<
+ * Sets the class of a zone. This operation can only be performed
+ * once on a zone.
+ *
+ * Require:
+ *\li 'zone' to be a valid zone.
+ *\li dns_zone_setclass() not to have been called since the zone was
+ * created.
+ *\li 'rdclass' != dns_rdataclass_none.
+ */
+
+dns_rdataclass_t
+dns_zone_getclass(dns_zone_t *zone);
+/*%<
+ * Returns the current zone class.
+ *
+ * Requires:
+ *\li 'zone' to be a valid zone.
+ */
+
+isc_result_t
+dns_zone_getserial(dns_zone_t *zone, uint32_t *serialp);
+/*%<
+ * Returns the current serial number of the zone. On success, the SOA
+ * serial of the zone will be copied into '*serialp'.
+ *
+ * Requires:
+ *\li 'zone' to be a valid zone.
+ *\li 'serialp' to be non NULL
+ *
+ * Returns:
+ *\li #ISC_R_SUCCESS
+ *\li #DNS_R_NOTLOADED zone DB is not loaded
+ */
+
+void
+dns_zone_settype(dns_zone_t *zone, dns_zonetype_t type);
+/*%<
+ * Sets the zone type. This operation can only be performed once on
+ * a zone.
+ *
+ * Requires:
+ *\li 'zone' to be a valid zone.
+ *\li dns_zone_settype() not to have been called since the zone was
+ * created.
+ *\li 'type' != dns_zone_none
+ */
+
+void
+dns_zone_setview(dns_zone_t *zone, dns_view_t *view);
+/*%<
+ * Associate the zone with a view.
+ *
+ * Require:
+ *\li 'zone' to be a valid zone.
+ */
+
+dns_view_t *
+dns_zone_getview(dns_zone_t *zone);
+/*%<
+ * Returns the zone's associated view.
+ *
+ * Requires:
+ *\li 'zone' to be a valid zone.
+ */
+
+void
+dns_zone_setviewcommit(dns_zone_t *zone);
+/*%<
+ * Commit the previous view saved internally via dns_zone_setview().
+ *
+ * Require:
+ *\li 'zone' to be a valid zone.
+ */
+
+void
+dns_zone_setviewrevert(dns_zone_t *zone);
+/*%<
+ * Revert the most recent dns_zone_setview() on this zone,
+ * restoring the previous view.
+ *
+ * Require:
+ *\li 'zone' to be a valid zone.
+ */
+
+isc_result_t
+dns_zone_setorigin(dns_zone_t *zone, const dns_name_t *origin);
+/*%<
+ * Sets the zones origin to 'origin'.
+ *
+ * Require:
+ *\li 'zone' to be a valid zone.
+ *\li 'origin' to be non NULL.
+ *
+ * Returns:
+ *\li #ISC_R_SUCCESS
+ *\li #ISC_R_NOMEMORY
+ */
+
+dns_name_t *
+dns_zone_getorigin(dns_zone_t *zone);
+/*%<
+ * Returns the value of the origin.
+ *
+ * Require:
+ *\li 'zone' to be a valid zone.
+ */
+
+isc_result_t
+dns_zone_setfile(dns_zone_t *zone, const char *file, dns_masterformat_t format,
+ const dns_master_style_t *style);
+/*%<
+ * Sets the name of the master file in the format of 'format' from which
+ * the zone loads its database to 'file'.
+ *
+ * For zones that have no associated master file, 'file' will be NULL.
+ *
+ * For zones with persistent databases, the file name
+ * setting is ignored.
+ *
+ * Require:
+ *\li 'zone' to be a valid zone.
+ *
+ * Returns:
+ *\li #ISC_R_NOMEMORY
+ *\li #ISC_R_SUCCESS
+ */
+
+const char *
+dns_zone_getfile(dns_zone_t *zone);
+/*%<
+ * Gets the name of the zone's master file, if any.
+ *
+ * Requires:
+ *\li 'zone' to be valid initialised zone.
+ *
+ * Returns:
+ *\li Pointer to null-terminated file name, or NULL.
+ */
+
+isc_result_t
+dns_zone_setstream(dns_zone_t *zone, const FILE *stream,
+ dns_masterformat_t format, const dns_master_style_t *style);
+/*%<
+ * Sets the source stream from which the zone will load its database.
+ *
+ * Requires:
+ *\li 'zone' to be a valid zone.
+ *\li 'stream' to be a valid and open FILE *.
+ *\li 'zone->masterfile' to be NULL, since we should load data either from
+ * 'stream' or from a master file, but not both.
+ *
+ * Returns:
+ *\li #ISC_R_NOMEMORY
+ *\li #ISC_R_SUCCESS
+ */
+
+void
+dns_zone_setmaxrecords(dns_zone_t *zone, uint32_t records);
+/*%<
+ * Sets the maximum number of records permitted in a zone.
+ * 0 implies unlimited.
+ *
+ * Requires:
+ *\li 'zone' to be valid initialised zone.
+ *
+ * Returns:
+ *\li void
+ */
+
+uint32_t
+dns_zone_getmaxrecords(dns_zone_t *zone);
+/*%<
+ * Gets the maximum number of records permitted in a zone.
+ * 0 implies unlimited.
+ *
+ * Requires:
+ *\li 'zone' to be valid initialised zone.
+ *
+ * Returns:
+ *\li uint32_t maxrecords.
+ */
+
+void
+dns_zone_setmaxttl(dns_zone_t *zone, uint32_t maxttl);
+/*%<
+ * Sets the max ttl of the zone.
+ *
+ * Requires:
+ *\li 'zone' to be valid initialised zone.
+ *
+ * Returns:
+ *\li void
+ */
+
+dns_ttl_t
+dns_zone_getmaxttl(dns_zone_t *zone);
+/*%<
+ * Gets the max ttl of the zone.
+ *
+ * Requires:
+ *\li 'zone' to be valid initialised zone.
+ *
+ * Returns:
+ *\li dns_ttl_t maxttl.
+ */
+
+void
+dns_zone_lock_keyfiles(dns_zone_t *zone);
+/*%<
+ * Lock associated keyfiles for this zone.
+ *
+ * Require:
+ *\li 'zone' to be a valid zone.
+ */
+
+void
+dns_zone_unlock_keyfiles(dns_zone_t *zone);
+/*%<
+ * Unlock associated keyfiles for this zone.
+ *
+ * Require:
+ *\li 'zone' to be a valid zone.
+ */
+
+isc_result_t
+dns_zone_load(dns_zone_t *zone, bool newonly);
+
+isc_result_t
+dns_zone_loadandthaw(dns_zone_t *zone);
+
+/*%<
+ * Cause the database to be loaded from its backing store.
+ * Confirm that the minimum requirements for the zone type are
+ * met, otherwise DNS_R_BADZONE is returned.
+ *
+ * If newonly is set dns_zone_load() only loads new zones.
+ * dns_zone_loadandthaw() is similar to dns_zone_load() but will
+ * also re-enable DNS UPDATEs when the load completes.
+ *
+ * Require:
+ *\li 'zone' to be a valid zone.
+ *
+ * Returns:
+ *\li #ISC_R_UNEXPECTED
+ *\li #ISC_R_SUCCESS
+ *\li DNS_R_CONTINUE Incremental load has been queued.
+ *\li DNS_R_UPTODATE The zone has already been loaded based on
+ * file system timestamps.
+ *\li DNS_R_BADZONE
+ *\li Any result value from dns_db_load().
+ */
+
+isc_result_t
+dns_zone_asyncload(dns_zone_t *zone, bool newonly, dns_zt_zoneloaded_t done,
+ void *arg);
+/*%<
+ * Cause the database to be loaded from its backing store asynchronously.
+ * Other zone maintenance functions are suspended until this is complete.
+ * When finished, 'done' is called to inform the caller, with 'arg' as
+ * its first argument and 'zone' as its second. (Normally, 'arg' is
+ * expected to point to the zone table but is left undefined for testing
+ * purposes.)
+ *
+ * Require:
+ *\li 'zone' to be a valid zone.
+ *
+ * Returns:
+ *\li #ISC_R_ALREADYRUNNING
+ *\li #ISC_R_SUCCESS
+ *\li #ISC_R_FAILURE
+ *\li #ISC_R_NOMEMORY
+ */
+
+bool
+dns__zone_loadpending(dns_zone_t *zone);
+/*%<
+ * Indicates whether the zone is waiting to be loaded asynchronously.
+ * (Not currently intended for use outside of this module and associated
+ * tests.)
+ */
+
+void
+dns_zone_attach(dns_zone_t *source, dns_zone_t **target);
+/*%<
+ * Attach '*target' to 'source' incrementing its external
+ * reference count.
+ *
+ * Require:
+ *\li 'zone' to be a valid zone.
+ *\li 'target' to be non NULL and '*target' to be NULL.
+ */
+
+void
+dns_zone_detach(dns_zone_t **zonep);
+/*%<
+ * Detach from a zone decrementing its external reference count.
+ * If this was the last external reference to the zone it will be
+ * shut down and eventually freed.
+ *
+ * Require:
+ *\li 'zonep' to point to a valid zone.
+ */
+
+void
+dns_zone_iattach(dns_zone_t *source, dns_zone_t **target);
+/*%<
+ * Attach '*target' to 'source' incrementing its internal
+ * reference count. This is intended for use by operations
+ * such as zone transfers that need to prevent the zone
+ * object from being freed but not from shutting down.
+ *
+ * Require:
+ *\li The caller is running in the context of the zone's task.
+ *\li 'zone' to be a valid zone.
+ *\li 'target' to be non NULL and '*target' to be NULL.
+ */
+
+void
+dns_zone_idetach(dns_zone_t **zonep);
+/*%<
+ * Detach from a zone decrementing its internal reference count.
+ * If there are no more internal or external references to the
+ * zone, it will be freed.
+ *
+ * Require:
+ *\li The caller is running in the context of the zone's task.
+ *\li 'zonep' to point to a valid zone.
+ */
+
+isc_result_t
+dns_zone_getdb(dns_zone_t *zone, dns_db_t **dbp);
+/*%<
+ * Attach '*dbp' to the database to if it exists otherwise
+ * return DNS_R_NOTLOADED.
+ *
+ * Require:
+ *\li 'zone' to be a valid zone.
+ *\li 'dbp' to be != NULL && '*dbp' == NULL.
+ *
+ * Returns:
+ *\li #ISC_R_SUCCESS
+ *\li DNS_R_NOTLOADED
+ */
+
+void
+dns_zone_setdb(dns_zone_t *zone, dns_db_t *db);
+/*%<
+ * Sets the zone database to 'db'.
+ *
+ * This function is expected to be used to configure a zone with a
+ * database which is not loaded from a file or zone transfer.
+ * It can be used for a general purpose zone, but right now its use
+ * is limited to static-stub zones to avoid possible undiscovered
+ * problems in the general cases.
+ *
+ * Require:
+ *\li 'zone' to be a valid zone of static-stub.
+ *\li zone doesn't have a database.
+ */
+
+void
+dns_zone_setdbtype(dns_zone_t *zone, unsigned int dbargc,
+ const char *const *dbargv);
+/*%<
+ * Sets the database type to dbargv[0] and database arguments
+ * to subsequent dbargv elements.
+ * 'db_type' is not checked to see if it is a valid database type.
+ *
+ * Require:
+ *\li 'zone' to be a valid zone.
+ *\li 'database' to be non NULL.
+ *\li 'dbargc' to be >= 1
+ *\li 'dbargv' to point to dbargc NULL-terminated strings
+ */
+
+isc_result_t
+dns_zone_getdbtype(dns_zone_t *zone, char ***argv, isc_mem_t *mctx);
+/*%<
+ * Returns the current dbtype. isc_mem_free() should be used
+ * to free 'argv' after use.
+ *
+ * Require:
+ *\li 'zone' to be a valid zone.
+ *\li 'argv' to be non NULL and *argv to be NULL.
+ *\li 'mctx' to be valid.
+ *
+ * Returns:
+ *\li #ISC_R_NOMEMORY
+ *\li #ISC_R_SUCCESS
+ */
+
+void
+dns_zone_markdirty(dns_zone_t *zone);
+/*%<
+ * Mark a zone as 'dirty'.
+ *
+ * Require:
+ *\li 'zone' to be a valid zone.
+ */
+
+void
+dns_zone_expire(dns_zone_t *zone);
+/*%<
+ * Mark the zone as expired. If the zone requires dumping cause it to
+ * be initiated. Set the refresh and retry intervals to there default
+ * values and unload the zone.
+ *
+ * Require
+ *\li 'zone' to be a valid zone.
+ */
+
+void
+dns_zone_refresh(dns_zone_t *zone);
+/*%<
+ * Initiate zone up to date checks. The zone must already be being
+ * managed.
+ *
+ * Require
+ *\li 'zone' to be a valid zone.
+ */
+
+isc_result_t
+dns_zone_flush(dns_zone_t *zone);
+/*%<
+ * Write the zone to database if there are uncommitted changes.
+ *
+ * Require:
+ *\li 'zone' to be a valid zone.
+ */
+
+isc_result_t
+dns_zone_dump(dns_zone_t *zone);
+/*%<
+ * Write the zone to database.
+ *
+ * Require:
+ *\li 'zone' to be a valid zone.
+ */
+
+isc_result_t
+dns_zone_dumptostream(dns_zone_t *zone, FILE *fd, dns_masterformat_t format,
+ const dns_master_style_t *style,
+ const uint32_t rawversion);
+/*%<
+ * Write the zone to stream 'fd' in the specified 'format'.
+ * If the 'format' is dns_masterformat_text (RFC1035), 'style' also
+ * specifies the file style (e.g., &dns_master_style_default).
+ *
+ * dns_zone_dumptostream() is a backward-compatible form of
+ * dns_zone_dumptostream2(), which always uses the dns_masterformat_text
+ * format and the dns_master_style_default style.
+ *
+ * dns_zone_dumptostream2() is a backward-compatible form of
+ * dns_zone_dumptostream3(), which always uses the current
+ * default raw file format version.
+ *
+ * Note that dns_zone_dumptostream3() is the most flexible form. It
+ * can also provide the functionality of dns_zone_fulldumptostream().
+ *
+ * Require:
+ *\li 'zone' to be a valid zone.
+ *\li 'fd' to be a stream open for writing.
+ */
+
+void
+dns_zone_maintenance(dns_zone_t *zone);
+/*%<
+ * Perform regular maintenance on the zone. This is called as a
+ * result of a zone being managed.
+ *
+ * Require
+ *\li 'zone' to be a valid zone.
+ */
+
+void
+dns_zone_setprimaries(dns_zone_t *zone, const isc_sockaddr_t *primaries,
+ dns_name_t **keynames, dns_name_t **tlsnames,
+ uint32_t count);
+/*%<
+ * Set the list of primary servers for the zone.
+ *
+ * Require:
+ *\li 'zone' to be a valid zone.
+ *\li 'primaries' array of isc_sockaddr_t with port set or NULL.
+ *\li 'count' the number of primaries.
+ *\li 'keynames' array of dns_name_t's for tsig keys or NULL.
+ *
+ *\li If 'primaries' is NULL then 'count' must be zero.
+ *
+ * Returns:
+ *\li #ISC_R_SUCCESS
+ *\li #ISC_R_NOMEMORY
+ *\li Any result dns_name_dup() can return, if keynames!=NULL
+ */
+
+void
+dns_zone_setparentals(dns_zone_t *zone, const isc_sockaddr_t *parentals,
+ dns_name_t **keynames, dns_name_t **tlsnames,
+ uint32_t count);
+/*%<
+ * Set the list of parental agents for the zone.
+ *
+ * Require:
+ *\li 'zone' to be a valid zone.
+ *\li 'parentals' array of isc_sockaddr_t with port set or NULL.
+ *\li 'count' the number of primaries.
+ *\li 'keynames' array of dns_name_t's for tsig keys or NULL.
+ *
+ *\li If 'parentals' is NULL then 'count' must be zero.
+ *
+ * Returns:
+ *\li #ISC_R_SUCCESS
+ *\li #ISC_R_NOMEMORY
+ *\li Any result dns_name_dup() can return, if keynames!=NULL
+ */
+
+void
+dns_zone_setparentals(dns_zone_t *zone, const isc_sockaddr_t *parentals,
+ dns_name_t **keynames, dns_name_t **tlsnames,
+ uint32_t count);
+/*%<
+ * Set the list of parental agents for the zone.
+ *
+ * Require:
+ *\li 'zone' to be a valid zone.
+ *\li 'parentals' array of isc_sockaddr_t with port set or NULL.
+ *\li 'count' the number of parentals.
+ *\li 'keynames' array of dns_name_t's for tsig keys or NULL.
+ *
+ *\li If 'parentals' is NULL then 'count' must be zero.
+ *
+ * Returns:
+ *\li #ISC_R_SUCCESS
+ *\li #ISC_R_NOMEMORY
+ *\li Any result dns_name_dup() can return, if keynames!=NULL
+ */
+
+void
+dns_zone_setalsonotify(dns_zone_t *zone, const isc_sockaddr_t *notify,
+ dns_name_t **keynames, dns_name_t **tlsnames,
+ uint32_t count);
+/*%<
+ * Set the list of additional servers to be notified when
+ * a zone changes. To clear the list use 'count = 0'.
+ *
+ * dns_zone_alsonotifywithkeys() allows each notify address to
+ * be associated with a TSIG key.
+ *
+ * Require:
+ *\li 'zone' to be a valid zone.
+ *\li 'notify' to be non-NULL if count != 0.
+ *\li 'count' to be the number of notifiees.
+ *
+ * Returns:
+ *\li #ISC_R_SUCCESS
+ *\li #ISC_R_NOMEMORY
+ */
+
+void
+dns_zone_unload(dns_zone_t *zone);
+/*%<
+ * detach the database from the zone structure.
+ *
+ * Require:
+ *\li 'zone' to be a valid zone.
+ */
+
+dns_kasp_t *
+dns_zone_getkasp(dns_zone_t *zone);
+/*%<
+ * Returns the current kasp.
+ *
+ * Require:
+ *\li 'zone' to be a valid zone.
+ */
+
+void
+dns_zone_setkasp(dns_zone_t *zone, dns_kasp_t *kasp);
+/*%<
+ * Set kasp for zone. If a kasp is already set, it will be detached.
+ *
+ * Requires:
+ *\li 'zone' to be a valid zone.
+ */
+
+void
+dns_zone_setoption(dns_zone_t *zone, dns_zoneopt_t option, bool value);
+/*%<
+ * Set the given options on ('value' == true) or off
+ * ('value' == #false).
+ *
+ * Require:
+ *\li 'zone' to be a valid zone.
+ */
+
+dns_zoneopt_t
+dns_zone_getoptions(dns_zone_t *zone);
+/*%<
+ * Returns the current zone options.
+ *
+ * Require:
+ *\li 'zone' to be a valid zone.
+ */
+
+void
+dns_zone_setkeyopt(dns_zone_t *zone, unsigned int option, bool value);
+/*%<
+ * Set key options on ('value' == true) or off ('value' ==
+ * #false).
+ *
+ * Require:
+ *\li 'zone' to be a valid zone.
+ */
+
+unsigned int
+dns_zone_getkeyopts(dns_zone_t *zone);
+/*%<
+ * Returns the current zone key options.
+ *
+ * Require:
+ *\li 'zone' to be a valid zone.
+ */
+
+void
+dns_zone_setminrefreshtime(dns_zone_t *zone, uint32_t val);
+/*%<
+ * Set the minimum refresh time.
+ *
+ * Requires:
+ *\li 'zone' is valid.
+ *\li val > 0.
+ */
+
+void
+dns_zone_setmaxrefreshtime(dns_zone_t *zone, uint32_t val);
+/*%<
+ * Set the maximum refresh time.
+ *
+ * Requires:
+ *\li 'zone' is valid.
+ *\li val > 0.
+ */
+
+void
+dns_zone_setminretrytime(dns_zone_t *zone, uint32_t val);
+/*%<
+ * Set the minimum retry time.
+ *
+ * Requires:
+ *\li 'zone' is valid.
+ *\li val > 0.
+ */
+
+void
+dns_zone_setmaxretrytime(dns_zone_t *zone, uint32_t val);
+/*%<
+ * Set the maximum retry time.
+ *
+ * Requires:
+ *\li 'zone' is valid.
+ * val > 0.
+ */
+
+isc_result_t
+dns_zone_setxfrsource4(dns_zone_t *zone, const isc_sockaddr_t *xfrsource);
+isc_result_t
+dns_zone_setaltxfrsource4(dns_zone_t *zone, const isc_sockaddr_t *xfrsource);
+/*%<
+ * Set the source address to be used in IPv4 zone transfers.
+ *
+ * Require:
+ *\li 'zone' to be a valid zone.
+ *\li 'xfrsource' to contain the address.
+ *
+ * Returns:
+ *\li #ISC_R_SUCCESS
+ */
+
+isc_sockaddr_t *
+dns_zone_getxfrsource4(dns_zone_t *zone);
+isc_sockaddr_t *
+dns_zone_getaltxfrsource4(dns_zone_t *zone);
+/*%<
+ * Returns the source address set by a previous dns_zone_setxfrsource4
+ * call, or the default of inaddr_any, port 0.
+ *
+ * Require:
+ *\li 'zone' to be a valid zone.
+ */
+
+isc_result_t
+dns_zone_setxfrsource6(dns_zone_t *zone, const isc_sockaddr_t *xfrsource);
+isc_result_t
+dns_zone_setaltxfrsource6(dns_zone_t *zone, const isc_sockaddr_t *xfrsource);
+/*%<
+ * Set the source address to be used in IPv6 zone transfers.
+ *
+ * Require:
+ *\li 'zone' to be a valid zone.
+ *\li 'xfrsource' to contain the address.
+ *
+ * Returns:
+ *\li #ISC_R_SUCCESS
+ */
+
+isc_sockaddr_t *
+dns_zone_getxfrsource6(dns_zone_t *zone);
+isc_sockaddr_t *
+dns_zone_getaltxfrsource6(dns_zone_t *zone);
+/*%<
+ * Returns the source address set by a previous dns_zone_setxfrsource6
+ * call, or the default of in6addr_any, port 0.
+ *
+ * Require:
+ *\li 'zone' to be a valid zone.
+ */
+
+isc_result_t
+dns_zone_setparentalsrc4(dns_zone_t *zone, const isc_sockaddr_t *parentalsrc);
+/*%<
+ * Set the source address to be used with IPv4 parental DS queries.
+ *
+ * Require:
+ *\li 'zone' to be a valid zone.
+ *\li 'parentalsrc' to contain the address.
+ *
+ * Returns:
+ *\li #ISC_R_SUCCESS
+ */
+
+isc_sockaddr_t *
+dns_zone_getparentalsrc4(dns_zone_t *zone);
+/*%<
+ * Returns the source address set by a previous dns_zone_setparentalsrc4
+ * call, or the default of inaddr_any, port 0.
+ *
+ * Require:
+ *\li 'zone' to be a valid zone.
+ */
+
+isc_result_t
+dns_zone_setparentalsrc6(dns_zone_t *zone, const isc_sockaddr_t *parentalsrc);
+/*%<
+ * Set the source address to be used with IPv6 parental DS queries.
+ *
+ * Require:
+ *\li 'zone' to be a valid zone.
+ *\li 'parentalsrc' to contain the address.
+ *
+ * Returns:
+ *\li #ISC_R_SUCCESS
+ */
+
+isc_sockaddr_t *
+dns_zone_getparentalsrc6(dns_zone_t *zone);
+/*%<
+ * Returns the source address set by a previous dns_zone_setparentalsrc6
+ * call, or the default of in6addr_any, port 0.
+ *
+ * Require:
+ *\li 'zone' to be a valid zone.
+ */
+
+isc_result_t
+dns_zone_setnotifysrc4(dns_zone_t *zone, const isc_sockaddr_t *notifysrc);
+/*%<
+ * Set the source address to be used with IPv4 NOTIFY messages.
+ *
+ * Require:
+ *\li 'zone' to be a valid zone.
+ *\li 'notifysrc' to contain the address.
+ *
+ * Returns:
+ *\li #ISC_R_SUCCESS
+ */
+
+isc_sockaddr_t *
+dns_zone_getnotifysrc4(dns_zone_t *zone);
+/*%<
+ * Returns the source address set by a previous dns_zone_setnotifysrc4
+ * call, or the default of inaddr_any, port 0.
+ *
+ * Require:
+ *\li 'zone' to be a valid zone.
+ */
+
+isc_result_t
+dns_zone_setnotifysrc6(dns_zone_t *zone, const isc_sockaddr_t *notifysrc);
+/*%<
+ * Set the source address to be used with IPv6 NOTIFY messages.
+ *
+ * Require:
+ *\li 'zone' to be a valid zone.
+ *\li 'notifysrc' to contain the address.
+ *
+ * Returns:
+ *\li #ISC_R_SUCCESS
+ */
+
+isc_sockaddr_t *
+dns_zone_getnotifysrc6(dns_zone_t *zone);
+/*%<
+ * Returns the source address set by a previous dns_zone_setnotifysrc6
+ * call, or the default of in6addr_any, port 0.
+ *
+ * Require:
+ *\li 'zone' to be a valid zone.
+ */
+
+void
+dns_zone_setnotifyacl(dns_zone_t *zone, dns_acl_t *acl);
+/*%<
+ * Sets the notify acl list for the zone.
+ *
+ * Require:
+ *\li 'zone' to be a valid zone.
+ *\li 'acl' to be a valid acl.
+ */
+
+void
+dns_zone_setqueryacl(dns_zone_t *zone, dns_acl_t *acl);
+/*%<
+ * Sets the query acl list for the zone.
+ *
+ * Require:
+ *\li 'zone' to be a valid zone.
+ *\li 'acl' to be a valid acl.
+ */
+
+void
+dns_zone_setqueryonacl(dns_zone_t *zone, dns_acl_t *acl);
+/*%<
+ * Sets the query-on acl list for the zone.
+ *
+ * Require:
+ *\li 'zone' to be a valid zone.
+ *\li 'acl' to be a valid acl.
+ */
+
+void
+dns_zone_setupdateacl(dns_zone_t *zone, dns_acl_t *acl);
+/*%<
+ * Sets the update acl list for the zone.
+ *
+ * Require:
+ *\li 'zone' to be a valid zone.
+ *\li 'acl' to be valid acl.
+ */
+
+void
+dns_zone_setforwardacl(dns_zone_t *zone, dns_acl_t *acl);
+/*%<
+ * Sets the forward unsigned updates acl list for the zone.
+ *
+ * Require:
+ *\li 'zone' to be a valid zone.
+ *\li 'acl' to be valid acl.
+ */
+
+void
+dns_zone_setxfracl(dns_zone_t *zone, dns_acl_t *acl);
+/*%<
+ * Sets the transfer acl list for the zone.
+ *
+ * Require:
+ *\li 'zone' to be a valid zone.
+ *\li 'acl' to be valid acl.
+ */
+
+dns_acl_t *
+dns_zone_getnotifyacl(dns_zone_t *zone);
+/*%<
+ * Returns the current notify acl or NULL.
+ *
+ * Require:
+ *\li 'zone' to be a valid zone.
+ *
+ * Returns:
+ *\li acl a pointer to the acl.
+ *\li NULL
+ */
+
+dns_acl_t *
+dns_zone_getqueryacl(dns_zone_t *zone);
+/*%<
+ * Returns the current query acl or NULL.
+ *
+ * Require:
+ *\li 'zone' to be a valid zone.
+ *
+ * Returns:
+ *\li acl a pointer to the acl.
+ *\li NULL
+ */
+
+dns_acl_t *
+dns_zone_getqueryonacl(dns_zone_t *zone);
+/*%<
+ * Returns the current query-on acl or NULL.
+ *
+ * Require:
+ *\li 'zone' to be a valid zone.
+ *
+ * Returns:
+ *\li acl a pointer to the acl.
+ *\li NULL
+ */
+
+dns_acl_t *
+dns_zone_getupdateacl(dns_zone_t *zone);
+/*%<
+ * Returns the current update acl or NULL.
+ *
+ * Require:
+ *\li 'zone' to be a valid zone.
+ *
+ * Returns:
+ *\li acl a pointer to the acl.
+ *\li NULL
+ */
+
+dns_acl_t *
+dns_zone_getforwardacl(dns_zone_t *zone);
+/*%<
+ * Returns the current forward unsigned updates acl or NULL.
+ *
+ * Require:
+ *\li 'zone' to be a valid zone.
+ *
+ * Returns:
+ *\li acl a pointer to the acl.
+ *\li NULL
+ */
+
+dns_acl_t *
+dns_zone_getxfracl(dns_zone_t *zone);
+/*%<
+ * Returns the current transfer acl or NULL.
+ *
+ * Require:
+ *\li 'zone' to be a valid zone.
+ *
+ * Returns:
+ *\li acl a pointer to the acl.
+ *\li NULL
+ */
+
+void
+dns_zone_clearupdateacl(dns_zone_t *zone);
+/*%<
+ * Clear the current update acl.
+ *
+ * Require:
+ *\li 'zone' to be a valid zone.
+ */
+
+void
+dns_zone_clearforwardacl(dns_zone_t *zone);
+/*%<
+ * Clear the current forward unsigned updates acl.
+ *
+ * Require:
+ *\li 'zone' to be a valid zone.
+ */
+
+void
+dns_zone_clearnotifyacl(dns_zone_t *zone);
+/*%<
+ * Clear the current notify acl.
+ *
+ * Require:
+ *\li 'zone' to be a valid zone.
+ */
+
+void
+dns_zone_clearqueryacl(dns_zone_t *zone);
+/*%<
+ * Clear the current query acl.
+ *
+ * Require:
+ *\li 'zone' to be a valid zone.
+ */
+
+void
+dns_zone_clearqueryonacl(dns_zone_t *zone);
+/*%<
+ * Clear the current query-on acl.
+ *
+ * Require:
+ *\li 'zone' to be a valid zone.
+ */
+
+void
+dns_zone_clearxfracl(dns_zone_t *zone);
+/*%<
+ * Clear the current transfer acl.
+ *
+ * Require:
+ *\li 'zone' to be a valid zone.
+ */
+
+bool
+dns_zone_getupdatedisabled(dns_zone_t *zone);
+/*%<
+ * Return update disabled.
+ * Transient unless called when running in isc_task_exclusive() mode.
+ */
+
+void
+dns_zone_setupdatedisabled(dns_zone_t *zone, bool state);
+/*%<
+ * Set update disabled.
+ * Should only be called only when running in isc_task_exclusive() mode.
+ * Failure to do so may result in updates being committed after the
+ * call has been made.
+ */
+
+bool
+dns_zone_getzeronosoattl(dns_zone_t *zone);
+/*%<
+ * Return zero-no-soa-ttl status.
+ */
+
+void
+dns_zone_setzeronosoattl(dns_zone_t *zone, bool state);
+/*%<
+ * Set zero-no-soa-ttl status.
+ */
+
+void
+dns_zone_setchecknames(dns_zone_t *zone, dns_severity_t severity);
+/*%<
+ * Set the severity of name checking when loading a zone.
+ *
+ * Require:
+ * \li 'zone' to be a valid zone.
+ */
+
+dns_severity_t
+dns_zone_getchecknames(dns_zone_t *zone);
+/*%<
+ * Return the current severity of name checking.
+ *
+ * Require:
+ *\li 'zone' to be a valid zone.
+ */
+
+void
+dns_zone_setjournalsize(dns_zone_t *zone, int32_t size);
+/*%<
+ * Sets the journal size for the zone.
+ *
+ * Requires:
+ *\li 'zone' to be a valid zone.
+ */
+
+int32_t
+dns_zone_getjournalsize(dns_zone_t *zone);
+/*%<
+ * Return the journal size as set with a previous call to
+ * dns_zone_setjournalsize().
+ *
+ * Requires:
+ *\li 'zone' to be a valid zone.
+ */
+
+isc_result_t
+dns_zone_notifyreceive(dns_zone_t *zone, isc_sockaddr_t *from,
+ isc_sockaddr_t *to, dns_message_t *msg);
+/*%<
+ * Tell the zone that it has received a NOTIFY message from another
+ * server. This may cause some zone maintenance activity to occur.
+ *
+ * Requires:
+ *\li 'zone' to be a valid zone.
+ *\li '*from' to contain the address of the server from which 'msg'
+ * was received.
+ *\li 'msg' a message with opcode NOTIFY and qr clear.
+ *
+ * Returns:
+ *\li DNS_R_REFUSED
+ *\li DNS_R_NOTIMP
+ *\li DNS_R_FORMERR
+ *\li DNS_R_SUCCESS
+ */
+
+void
+dns_zone_setmaxxfrin(dns_zone_t *zone, uint32_t maxxfrin);
+/*%<
+ * Set the maximum time (in seconds) that a zone transfer in (AXFR/IXFR)
+ * of this zone will use before being aborted.
+ *
+ * Requires:
+ * \li 'zone' to be valid initialised zone.
+ */
+
+uint32_t
+dns_zone_getmaxxfrin(dns_zone_t *zone);
+/*%<
+ * Returns the maximum transfer time for this zone. This will be
+ * either the value set by the last call to dns_zone_setmaxxfrin() or
+ * the default value of 1 hour.
+ *
+ * Requires:
+ *\li 'zone' to be valid initialised zone.
+ */
+
+void
+dns_zone_setmaxxfrout(dns_zone_t *zone, uint32_t maxxfrout);
+/*%<
+ * Set the maximum time (in seconds) that a zone transfer out (AXFR/IXFR)
+ * of this zone will use before being aborted.
+ *
+ * Requires:
+ * \li 'zone' to be valid initialised zone.
+ */
+
+uint32_t
+dns_zone_getmaxxfrout(dns_zone_t *zone);
+/*%<
+ * Returns the maximum transfer time for this zone. This will be
+ * either the value set by the last call to dns_zone_setmaxxfrout() or
+ * the default value of 1 hour.
+ *
+ * Requires:
+ *\li 'zone' to be valid initialised zone.
+ */
+
+isc_result_t
+dns_zone_setjournal(dns_zone_t *zone, const char *myjournal);
+/*%<
+ * Sets the filename used for journaling updates / IXFR transfers.
+ * The default journal name is set by dns_zone_setfile() to be
+ * "file.jnl". If 'myjournal' is NULL, the zone will have no
+ * journal name.
+ *
+ * Requires:
+ *\li 'zone' to be a valid zone.
+ *
+ * Returns:
+ *\li #ISC_R_SUCCESS
+ *\li #ISC_R_NOMEMORY
+ */
+
+char *
+dns_zone_getjournal(dns_zone_t *zone);
+/*%<
+ * Returns the journal name associated with this zone.
+ * If no journal has been set this will be NULL.
+ *
+ * Requires:
+ *\li 'zone' to be valid initialised zone.
+ */
+
+dns_zonetype_t
+dns_zone_gettype(dns_zone_t *zone);
+/*%<
+ * Returns the type of the zone (primary/secondary/etc.)
+ *
+ * Requires:
+ *\li 'zone' to be valid initialised zone.
+ */
+
+dns_zonetype_t
+dns_zone_getredirecttype(dns_zone_t *zone);
+/*%<
+ * Returns whether the redirect zone is configured as a primary or a
+ * secondary zone.
+ *
+ * Requires:
+ *\li 'zone' to be valid initialised zone.
+ *\li 'zone' to be a redirect zone.
+ *
+ * Returns:
+ *\li 'dns_zone_primary'
+ *\li 'dns_zone_secondary'
+ */
+
+void
+dns_zone_settask(dns_zone_t *zone, isc_task_t *task);
+/*%<
+ * Give a zone a task to work with. Any current task will be detached.
+ *
+ * Requires:
+ *\li 'zone' to be valid.
+ *\li 'task' to be valid.
+ */
+
+void
+dns_zone_gettask(dns_zone_t *zone, isc_task_t **target);
+/*%<
+ * Attach '*target' to the zone's task.
+ *
+ * Requires:
+ *\li 'zone' to be valid initialised zone.
+ *\li 'zone' to have a task.
+ *\li 'target' to be != NULL && '*target' == NULL.
+ */
+
+void
+dns_zone_notify(dns_zone_t *zone);
+/*%<
+ * Generate notify events for this zone.
+ *
+ * Requires:
+ *\li 'zone' to be a valid zone.
+ */
+
+isc_result_t
+dns_zone_replacedb(dns_zone_t *zone, dns_db_t *db, bool dump);
+/*%<
+ * Replace the database of "zone" with a new database "db".
+ *
+ * If "dump" is true, then the new zone contents are dumped
+ * into to the zone's master file for persistence. When replacing
+ * a zone database by one just loaded from a master file, set
+ * "dump" to false to avoid a redundant redump of the data just
+ * loaded. Otherwise, it should be set to true.
+ *
+ * If the "diff-on-reload" option is enabled in the configuration file,
+ * the differences between the old and the new database are added to the
+ * journal file, and the master file dump is postponed.
+ *
+ * Requires:
+ * \li 'zone' to be a valid zone.
+ *
+ * Returns:
+ * \li DNS_R_SUCCESS
+ * \li DNS_R_BADZONE zone failed basic consistency checks:
+ * * a single SOA must exist
+ * * some NS records must exist.
+ * Others
+ */
+
+uint32_t
+dns_zone_getidlein(dns_zone_t *zone);
+/*%<
+ * Requires:
+ * \li 'zone' to be a valid zone.
+ *
+ * Returns:
+ * \li number of seconds of idle time before we abort the transfer in.
+ */
+
+void
+dns_zone_setidlein(dns_zone_t *zone, uint32_t idlein);
+/*%<
+ * \li Set the idle timeout for transfer the.
+ * \li Zero set the default value, 1 hour.
+ *
+ * Requires:
+ * \li 'zone' to be a valid zone.
+ */
+
+uint32_t
+dns_zone_getidleout(dns_zone_t *zone);
+/*%<
+ *
+ * Requires:
+ * \li 'zone' to be a valid zone.
+ *
+ * Returns:
+ * \li number of seconds of idle time before we abort a transfer out.
+ */
+
+void
+dns_zone_setidleout(dns_zone_t *zone, uint32_t idleout);
+/*%<
+ * \li Set the idle timeout for transfers out.
+ * \li Zero set the default value, 1 hour.
+ *
+ * Requires:
+ * \li 'zone' to be a valid zone.
+ */
+
+void
+dns_zone_getssutable(dns_zone_t *zone, dns_ssutable_t **table);
+/*%<
+ * Get the simple-secure-update policy table.
+ *
+ * Requires:
+ * \li 'zone' to be a valid zone.
+ */
+
+void
+dns_zone_setssutable(dns_zone_t *zone, dns_ssutable_t *table);
+/*%<
+ * Set / clear the simple-secure-update policy table.
+ *
+ * Requires:
+ * \li 'zone' to be a valid zone.
+ */
+
+isc_mem_t *
+dns_zone_getmctx(dns_zone_t *zone);
+/*%<
+ * Get the memory context of a zone.
+ *
+ * Requires:
+ * \li 'zone' to be a valid zone.
+ */
+
+dns_zonemgr_t *
+dns_zone_getmgr(dns_zone_t *zone);
+/*%<
+ * If 'zone' is managed return the zone manager otherwise NULL.
+ *
+ * Requires:
+ * \li 'zone' to be a valid zone.
+ */
+
+void
+dns_zone_setsigvalidityinterval(dns_zone_t *zone, uint32_t interval);
+/*%<
+ * Set the zone's general signature validity interval. This is the length
+ * of time for which DNSSEC signatures created as a result of dynamic
+ * updates to secure zones will remain valid, in seconds.
+ *
+ * Requires:
+ * \li 'zone' to be a valid zone.
+ */
+
+uint32_t
+dns_zone_getsigvalidityinterval(dns_zone_t *zone);
+/*%<
+ * Get the zone's general signature validity interval.
+ *
+ * Requires:
+ * \li 'zone' to be a valid zone.
+ */
+
+void
+dns_zone_setkeyvalidityinterval(dns_zone_t *zone, uint32_t interval);
+/*%<
+ * Set the zone's DNSKEY signature validity interval. This is the length
+ * of time for which DNSSEC signatures created for DNSKEY records
+ * will remain valid, in seconds.
+ *
+ * If this value is set to zero, then the regular signature validity
+ * interval (see dns_zone_setsigvalidityinterval(), above) is used
+ * for all RRSIGs. However, if this value is nonzero, then it is used
+ * as the validity interval for RRSIGs covering DNSKEY and CDNSKEY
+ * RRsets.
+ *
+ * Requires:
+ * \li 'zone' to be a valid zone.
+ */
+
+uint32_t
+dns_zone_getkeyvalidityinterval(dns_zone_t *zone);
+/*%<
+ * Get the zone's DNSKEY signature validity interval.
+ *
+ * Requires:
+ * \li 'zone' to be a valid zone.
+ */
+
+void
+dns_zone_setsigresigninginterval(dns_zone_t *zone, uint32_t interval);
+/*%<
+ * Set the zone's RRSIG re-signing interval. A dynamic zone's RRSIG's
+ * will be re-signed 'interval' amount of time before they expire.
+ *
+ * Requires:
+ * \li 'zone' to be a valid zone.
+ */
+
+uint32_t
+dns_zone_getsigresigninginterval(dns_zone_t *zone);
+/*%<
+ * Get the zone's RRSIG re-signing interval.
+ *
+ * Requires:
+ * \li 'zone' to be a valid zone.
+ */
+
+void
+dns_zone_setnotifytype(dns_zone_t *zone, dns_notifytype_t notifytype);
+/*%<
+ * Sets zone notify method to "notifytype"
+ */
+
+isc_result_t
+dns_zone_forwardupdate(dns_zone_t *zone, dns_message_t *msg,
+ dns_updatecallback_t callback, void *callback_arg);
+/*%<
+ * Forward 'msg' to each primary in turn until we get an answer or we
+ * have exhausted the list of primaries. 'callback' will be called with
+ * ISC_R_SUCCESS if we get an answer and the returned message will be
+ * passed as 'answer_message', otherwise a non ISC_R_SUCCESS result code
+ * will be passed and answer_message will be NULL. The callback function
+ * is responsible for destroying 'answer_message'.
+ * (callback)(callback_arg, result, answer_message);
+ *
+ * Require:
+ *\li 'zone' to be valid
+ *\li 'msg' to be valid.
+ *\li 'callback' to be non NULL.
+ * Returns:
+ *\li #ISC_R_SUCCESS if the message has been forwarded,
+ *\li #ISC_R_NOMEMORY
+ *\li Others
+ */
+
+isc_result_t
+dns_zone_next(dns_zone_t *zone, dns_zone_t **next);
+/*%<
+ * Find the next zone in the list of managed zones.
+ *
+ * Requires:
+ *\li 'zone' to be valid
+ *\li The zone manager for the indicated zone MUST be locked
+ * by the caller. This is not checked.
+ *\li 'next' be non-NULL, and '*next' be NULL.
+ *
+ * Ensures:
+ *\li 'next' points to a valid zone (result ISC_R_SUCCESS) or to NULL
+ * (result ISC_R_NOMORE).
+ */
+
+isc_result_t
+dns_zone_first(dns_zonemgr_t *zmgr, dns_zone_t **first);
+/*%<
+ * Find the first zone in the list of managed zones.
+ *
+ * Requires:
+ *\li 'zonemgr' to be valid
+ *\li The zone manager for the indicated zone MUST be locked
+ * by the caller. This is not checked.
+ *\li 'first' be non-NULL, and '*first' be NULL
+ *
+ * Ensures:
+ *\li 'first' points to a valid zone (result ISC_R_SUCCESS) or to NULL
+ * (result ISC_R_NOMORE).
+ */
+
+isc_result_t
+dns_zone_setkeydirectory(dns_zone_t *zone, const char *directory);
+/*%<
+ * Sets the name of the directory where private keys used for
+ * online signing of dynamic zones are found.
+ *
+ * Require:
+ *\li 'zone' to be a valid zone.
+ *
+ * Returns:
+ *\li #ISC_R_NOMEMORY
+ *\li #ISC_R_SUCCESS
+ */
+
+const char *
+dns_zone_getkeydirectory(dns_zone_t *zone);
+/*%<
+ * Gets the name of the directory where private keys used for
+ * online signing of dynamic zones are found.
+ *
+ * Requires:
+ *\li 'zone' to be valid initialised zone.
+ *
+ * Returns:
+ * Pointer to null-terminated file name, or NULL.
+ */
+
+isc_result_t
+dns_zone_getdnsseckeys(dns_zone_t *zone, dns_db_t *db, dns_dbversion_t *ver,
+ isc_stdtime_t now, dns_dnsseckeylist_t *keys);
+/*%
+ * Find DNSSEC keys used for signing with dnssec-policy. Load these keys
+ * into 'keys'.
+ *
+ * Requires:
+ *\li 'zone' to be valid initialised zone.
+ *\li 'keys' to be an initialised DNSSEC keylist.
+ *
+ * Returns:
+ *\li #ISC_R_SUCCESS
+ *\li Error
+ */
+
+isc_result_t
+dns_zonemgr_create(isc_mem_t *mctx, isc_taskmgr_t *taskmgr,
+ isc_timermgr_t *timermgr, isc_nm_t *netmgr,
+ dns_zonemgr_t **zmgrp);
+/*%<
+ * Create a zone manager. Note: the zone manager will not be able to
+ * manage any zones until dns_zonemgr_setsize() has been run.
+ *
+ * Requires:
+ *\li 'mctx' to be a valid memory context.
+ *\li 'taskmgr' to be a valid task manager.
+ *\li 'timermgr' to be a valid timer manager.
+ *\li 'zmgrp' to point to a NULL pointer.
+ */
+
+isc_result_t
+dns_zonemgr_setsize(dns_zonemgr_t *zmgr, int num_zones);
+/*%<
+ * Set the size of the zone manager task pool. This must be run
+ * before zmgr can be used for managing zones. Currently, it can only
+ * be run once; the task pool cannot be resized.
+ *
+ * Requires:
+ *\li zmgr is a valid zone manager.
+ *\li zmgr->zonetasks has been initialized.
+ */
+
+isc_result_t
+dns_zonemgr_createzone(dns_zonemgr_t *zmgr, dns_zone_t **zonep);
+/*%<
+ * Allocate a new zone using a memory context from the
+ * zone manager's memory context pool.
+ *
+ * Require:
+ *\li 'zmgr' to be a valid zone manager.
+ *\li 'zonep' != NULL and '*zonep' == NULL.
+ */
+
+isc_result_t
+dns_zonemgr_managezone(dns_zonemgr_t *zmgr, dns_zone_t *zone);
+/*%<
+ * Bring the zone under control of a zone manager.
+ *
+ * Require:
+ *\li 'zmgr' to be a valid zone manager.
+ *\li 'zone' to be a valid zone.
+ */
+
+isc_result_t
+dns_zonemgr_forcemaint(dns_zonemgr_t *zmgr);
+/*%<
+ * Force zone maintenance of all loaded zones managed by 'zmgr'
+ * to take place at the system's earliest convenience.
+ */
+
+void
+dns__zonemgr_run(isc_task_t *task, isc_event_t *event);
+/*%<
+ * Event handler to call dns_zonemgr_forcemaint(); used to start
+ * zone operations from a unit test. Not intended for use outside
+ * libdns or related tests.
+ */
+
+void
+dns_zonemgr_resumexfrs(dns_zonemgr_t *zmgr);
+/*%<
+ * Attempt to start any stalled zone transfers.
+ */
+
+void
+dns_zonemgr_shutdown(dns_zonemgr_t *zmgr);
+/*%<
+ * Shut down the zone manager.
+ *
+ * Requires:
+ *\li 'zmgr' to be a valid zone manager.
+ */
+
+void
+dns_zonemgr_attach(dns_zonemgr_t *source, dns_zonemgr_t **target);
+/*%<
+ * Attach '*target' to 'source' incrementing its external
+ * reference count.
+ *
+ * Require:
+ *\li 'zone' to be a valid zone.
+ *\li 'target' to be non NULL and '*target' to be NULL.
+ */
+
+void
+dns_zonemgr_detach(dns_zonemgr_t **zmgrp);
+/*%<
+ * Detach from a zone manager.
+ *
+ * Requires:
+ *\li '*zmgrp' is a valid, non-NULL zone manager pointer.
+ *
+ * Ensures:
+ *\li '*zmgrp' is NULL.
+ */
+
+void
+dns_zonemgr_releasezone(dns_zonemgr_t *zmgr, dns_zone_t *zone);
+/*%<
+ * Release 'zone' from the managed by 'zmgr'. 'zmgr' is implicitly
+ * detached from 'zone'.
+ *
+ * Requires:
+ *\li 'zmgr' to be a valid zone manager.
+ *\li 'zone' to be a valid zone.
+ *\li 'zmgr' == 'zone->zmgr'
+ *
+ * Ensures:
+ *\li 'zone->zmgr' == NULL;
+ */
+
+isc_taskmgr_t *
+dns_zonemgr_gettaskmgr(dns_zonemgr_t *zmgr);
+/*%
+ * Get the tasmkgr object attached to 'zmgr'.
+ */
+
+isc_timermgr_t *
+dns_zonemgr_gettimermgr(dns_zonemgr_t *zmgr);
+/*%
+ * Get the timermgr object attached to 'zmgr'.
+ */
+
+void
+dns_zonemgr_settransfersin(dns_zonemgr_t *zmgr, uint32_t value);
+/*%<
+ * Set the maximum number of simultaneous transfers in allowed by
+ * the zone manager.
+ *
+ * Requires:
+ *\li 'zmgr' to be a valid zone manager.
+ */
+
+uint32_t
+dns_zonemgr_gettransfersin(dns_zonemgr_t *zmgr);
+/*%<
+ * Return the maximum number of simultaneous transfers in allowed.
+ *
+ * Requires:
+ *\li 'zmgr' to be a valid zone manager.
+ */
+
+void
+dns_zonemgr_settransfersperns(dns_zonemgr_t *zmgr, uint32_t value);
+/*%<
+ * Set the number of zone transfers allowed per nameserver.
+ *
+ * Requires:
+ *\li 'zmgr' to be a valid zone manager
+ */
+
+uint32_t
+dns_zonemgr_gettransfersperns(dns_zonemgr_t *zmgr);
+/*%<
+ * Return the number of transfers allowed per nameserver.
+ *
+ * Requires:
+ *\li 'zmgr' to be a valid zone manager.
+ */
+
+void
+dns_zonemgr_setiolimit(dns_zonemgr_t *zmgr, uint32_t iolimit);
+/*%<
+ * Set the number of simultaneous file descriptors available for
+ * reading and writing masterfiles.
+ *
+ * Requires:
+ *\li 'zmgr' to be a valid zone manager.
+ *\li 'iolimit' to be positive.
+ */
+
+uint32_t
+dns_zonemgr_getiolimit(dns_zonemgr_t *zmgr);
+/*%<
+ * Get the number of simultaneous file descriptors available for
+ * reading and writing masterfiles.
+ *
+ * Requires:
+ *\li 'zmgr' to be a valid zone manager.
+ */
+
+void
+dns_zonemgr_setcheckdsrate(dns_zonemgr_t *zmgr, unsigned int value);
+/*%<
+ * Set the number of parental DS queries sent per second.
+ *
+ * Requires:
+ *\li 'zmgr' to be a valid zone manager
+ */
+
+void
+dns_zonemgr_setnotifyrate(dns_zonemgr_t *zmgr, unsigned int value);
+/*%<
+ * Set the number of NOTIFY requests sent per second.
+ *
+ * Requires:
+ *\li 'zmgr' to be a valid zone manager
+ */
+
+void
+dns_zonemgr_setstartupnotifyrate(dns_zonemgr_t *zmgr, unsigned int value);
+/*%<
+ * Set the number of startup NOTIFY requests sent per second.
+ *
+ * Requires:
+ *\li 'zmgr' to be a valid zone manager
+ */
+
+void
+dns_zonemgr_setserialqueryrate(dns_zonemgr_t *zmgr, unsigned int value);
+/*%<
+ * Set the number of SOA queries sent per second.
+ *
+ * Requires:
+ *\li 'zmgr' to be a valid zone manager
+ */
+
+unsigned int
+dns_zonemgr_getnotifyrate(dns_zonemgr_t *zmgr);
+/*%<
+ * Return the number of NOTIFY requests sent per second.
+ *
+ * Requires:
+ *\li 'zmgr' to be a valid zone manager.
+ */
+
+unsigned int
+dns_zonemgr_getstartupnotifyrate(dns_zonemgr_t *zmgr);
+/*%<
+ * Return the number of startup NOTIFY requests sent per second.
+ *
+ * Requires:
+ *\li 'zmgr' to be a valid zone manager.
+ */
+
+unsigned int
+dns_zonemgr_getserialqueryrate(dns_zonemgr_t *zmgr);
+/*%<
+ * Return the number of SOA queries sent per second.
+ *
+ * Requires:
+ *\li 'zmgr' to be a valid zone manager.
+ */
+
+unsigned int
+dns_zonemgr_getcount(dns_zonemgr_t *zmgr, int state);
+/*%<
+ * Returns the number of zones in the specified state.
+ *
+ * Requires:
+ *\li 'zmgr' to be a valid zone manager.
+ *\li 'state' to be a valid DNS_ZONESTATE_ constant.
+ */
+
+void
+dns_zonemgr_unreachableadd(dns_zonemgr_t *zmgr, isc_sockaddr_t *remote,
+ isc_sockaddr_t *local, isc_time_t *now);
+/*%<
+ * Add the pair of addresses to the unreachable cache.
+ *
+ * Requires:
+ *\li 'zmgr' to be a valid zone manager.
+ *\li 'remote' to be a valid sockaddr.
+ *\li 'local' to be a valid sockaddr.
+ */
+
+bool
+dns_zonemgr_unreachable(dns_zonemgr_t *zmgr, isc_sockaddr_t *remote,
+ isc_sockaddr_t *local, isc_time_t *now);
+/*%<
+ * Returns true if the given local/remote address pair
+ * is found in the zone maanger's unreachable cache.
+ *
+ * Requires:
+ *\li 'zmgr' to be a valid zone manager.
+ *\li 'remote' to be a valid sockaddr.
+ *\li 'local' to be a valid sockaddr.
+ *\li 'now' != NULL
+ */
+
+void
+dns_zonemgr_unreachabledel(dns_zonemgr_t *zmgr, isc_sockaddr_t *remote,
+ isc_sockaddr_t *local);
+/*%<
+ * Remove the pair of addresses from the unreachable cache.
+ *
+ * Requires:
+ *\li 'zmgr' to be a valid zone manager.
+ *\li 'remote' to be a valid sockaddr.
+ *\li 'local' to be a valid sockaddr.
+ */
+
+void
+dns_zonemgr_set_tlsctx_cache(dns_zonemgr_t *zmgr,
+ isc_tlsctx_cache_t *tlsctx_cache);
+/*%<
+ * Set the TLS client context cache used for zone transfers via
+ * encrypted transports (e.g. XoT).
+ *
+ * Requires:
+ *\li 'zmgr' is a valid zone manager.
+ *\li 'tlsctx_cache' is a valid TLS context cache.
+ */
+
+void
+dns_zone_forcereload(dns_zone_t *zone);
+/*%<
+ * Force a reload of specified zone.
+ *
+ * Requires:
+ *\li 'zone' to be a valid zone.
+ */
+
+bool
+dns_zone_isforced(dns_zone_t *zone);
+/*%<
+ * Check if the zone is waiting a forced reload.
+ *
+ * Requires:
+ * \li 'zone' to be a valid zone.
+ */
+
+isc_result_t
+dns_zone_setstatistics(dns_zone_t *zone, bool on);
+/*%<
+ * This function is obsoleted by dns_zone_setrequeststats().
+ */
+
+uint64_t *
+dns_zone_getstatscounters(dns_zone_t *zone);
+/*%<
+ * This function is obsoleted by dns_zone_getrequeststats().
+ */
+
+void
+dns_zone_setstats(dns_zone_t *zone, isc_stats_t *stats);
+/*%<
+ * Set a general zone-maintenance statistics set 'stats' for 'zone'. This
+ * function is expected to be called only on zone creation (when necessary).
+ * Once installed, it cannot be removed or replaced. Also, there is no
+ * interface to get the installed stats from the zone; the caller must keep the
+ * stats to reference (e.g. dump) it later.
+ *
+ * Requires:
+ * \li 'zone' to be a valid zone and does not have a statistics set already
+ * installed.
+ *
+ *\li stats is a valid statistics supporting zone statistics counters
+ * (see dns/stats.h).
+ */
+
+void
+dns_zone_setrequeststats(dns_zone_t *zone, isc_stats_t *stats);
+
+void
+dns_zone_setrcvquerystats(dns_zone_t *zone, dns_stats_t *stats);
+
+void
+dns_zone_setdnssecsignstats(dns_zone_t *zone, dns_stats_t *stats);
+/*%<
+ * Set additional statistics sets to zone. These are attached to the zone
+ * but are not counted in the zone module; only the caller updates the
+ * counters.
+ *
+ * Requires:
+ * \li 'zone' to be a valid zone.
+ *
+ *\li stats is a valid statistics.
+ */
+
+isc_stats_t *
+dns_zone_getrequeststats(dns_zone_t *zone);
+
+dns_stats_t *
+dns_zone_getrcvquerystats(dns_zone_t *zone);
+
+dns_stats_t *
+dns_zone_getdnssecsignstats(dns_zone_t *zone);
+/*%<
+ * Get the additional statistics for zone, if one is installed.
+ *
+ * Requires:
+ * \li 'zone' to be a valid zone.
+ *
+ * Returns:
+ * \li when available, a pointer to the statistics set installed in zone;
+ * otherwise NULL.
+ */
+
+void
+dns_zone_dialup(dns_zone_t *zone);
+/*%<
+ * Perform dialup-time maintenance on 'zone'.
+ */
+
+void
+dns_zone_setdialup(dns_zone_t *zone, dns_dialuptype_t dialup);
+/*%<
+ * Set the dialup type of 'zone' to 'dialup'.
+ *
+ * Requires:
+ * \li 'zone' to be valid initialised zone.
+ *\li 'dialup' to be a valid dialup type.
+ */
+
+void
+dns_zone_logv(dns_zone_t *zone, isc_logcategory_t *category, int level,
+ const char *prefix, const char *msg, va_list ap);
+/*%<
+ * Log the message 'msg...' at 'level' using log category 'category', including
+ * text that identifies the message as applying to 'zone'. If the (optional)
+ * 'prefix' is not NULL, it will be placed at the start of the entire log line.
+ */
+
+void
+dns_zone_log(dns_zone_t *zone, int level, const char *msg, ...)
+ ISC_FORMAT_PRINTF(3, 4);
+/*%<
+ * Log the message 'msg...' at 'level', including text that identifies
+ * the message as applying to 'zone'.
+ */
+
+void
+dns_zone_logc(dns_zone_t *zone, isc_logcategory_t *category, int level,
+ const char *msg, ...) ISC_FORMAT_PRINTF(4, 5);
+/*%<
+ * Log the message 'msg...' at 'level', including text that identifies
+ * the message as applying to 'zone'.
+ */
+
+void
+dns_zone_name(dns_zone_t *zone, char *buf, size_t len);
+/*%<
+ * Return the name of the zone with class and view.
+ *
+ * Requires:
+ *\li 'zone' to be valid.
+ *\li 'buf' to be non NULL.
+ */
+
+void
+dns_zone_nameonly(dns_zone_t *zone, char *buf, size_t len);
+/*%<
+ * Return the name of the zone only.
+ *
+ * Requires:
+ *\li 'zone' to be valid.
+ *\li 'buf' to be non NULL.
+ */
+
+isc_result_t
+dns_zone_checknames(dns_zone_t *zone, const dns_name_t *name,
+ dns_rdata_t *rdata);
+/*%<
+ * Check if this record meets the check-names policy.
+ *
+ * Requires:
+ * 'zone' to be valid.
+ * 'name' to be valid.
+ * 'rdata' to be valid.
+ *
+ * Returns:
+ * DNS_R_SUCCESS passed checks.
+ * DNS_R_BADOWNERNAME failed ownername checks.
+ * DNS_R_BADNAME failed rdata checks.
+ */
+
+void
+dns_zone_setcheckmx(dns_zone_t *zone, dns_checkmxfunc_t checkmx);
+/*%<
+ * Set the post load integrity callback function 'checkmx'.
+ * 'checkmx' will be called if the MX TARGET is not within the zone.
+ *
+ * Require:
+ * 'zone' to be a valid zone.
+ */
+
+void
+dns_zone_setchecksrv(dns_zone_t *zone, dns_checkmxfunc_t checksrv);
+/*%<
+ * Set the post load integrity callback function 'checksrv'.
+ * 'checksrv' will be called if the SRV TARGET is not within the zone.
+ *
+ * Require:
+ * 'zone' to be a valid zone.
+ */
+
+void
+dns_zone_setcheckns(dns_zone_t *zone, dns_checknsfunc_t checkns);
+/*%<
+ * Set the post load integrity callback function 'checkns'.
+ * 'checkns' will be called if the NS TARGET is not within the zone.
+ *
+ * Require:
+ * 'zone' to be a valid zone.
+ */
+
+void
+dns_zone_setnotifydelay(dns_zone_t *zone, uint32_t delay);
+/*%<
+ * Set the minimum delay between sets of notify messages.
+ *
+ * Requires:
+ * 'zone' to be valid.
+ */
+
+uint32_t
+dns_zone_getnotifydelay(dns_zone_t *zone);
+/*%<
+ * Get the minimum delay between sets of notify messages.
+ *
+ * Requires:
+ * 'zone' to be valid.
+ */
+
+void
+dns_zone_setisself(dns_zone_t *zone, dns_isselffunc_t isself, void *arg);
+/*%<
+ * Set the isself callback function and argument.
+ *
+ * bool
+ * isself(dns_view_t *myview, dns_tsigkey_t *mykey,
+ * const isc_netaddr_t *srcaddr, const isc_netaddr_t *destaddr,
+ * dns_rdataclass_t rdclass, void *arg);
+ *
+ * 'isself' returns true if a non-recursive query from 'srcaddr' to
+ * 'destaddr' with optional key 'mykey' for class 'rdclass' would be
+ * delivered to 'myview'.
+ */
+
+void
+dns_zone_setnodes(dns_zone_t *zone, uint32_t nodes);
+/*%<
+ * Set the number of nodes that will be checked per quantum.
+ */
+
+void
+dns_zone_setsignatures(dns_zone_t *zone, uint32_t signatures);
+/*%<
+ * Set the number of signatures that will be generated per quantum.
+ */
+
+uint32_t
+dns_zone_getsignatures(dns_zone_t *zone);
+/*%<
+ * Get the number of signatures that will be generated per quantum.
+ */
+
+isc_result_t
+dns_zone_signwithkey(dns_zone_t *zone, dns_secalg_t algorithm, uint16_t keyid,
+ bool deleteit);
+/*%<
+ * Initiate/resume signing of the entire zone with the zone DNSKEY(s)
+ * that match the given algorithm and keyid.
+ */
+
+isc_result_t
+dns_zone_addnsec3chain(dns_zone_t *zone, dns_rdata_nsec3param_t *nsec3param);
+/*%<
+ * Incrementally add a NSEC3 chain that corresponds to 'nsec3param'.
+ */
+
+void
+dns_zone_setprivatetype(dns_zone_t *zone, dns_rdatatype_t type);
+dns_rdatatype_t
+dns_zone_getprivatetype(dns_zone_t *zone);
+/*
+ * Get/Set the private record type. It is expected that these interfaces
+ * will not be permanent.
+ */
+
+void
+dns_zone_rekey(dns_zone_t *zone, bool fullsign);
+/*%<
+ * Update the zone's DNSKEY set from the key repository.
+ *
+ * If 'fullsign' is true, trigger an immediate full signing of
+ * the zone with the new key. Otherwise, if there are no keys or
+ * if the new keys are for algorithms that have already signed the
+ * zone, then the zone can be re-signed incrementally.
+ */
+
+isc_result_t
+dns_zone_nscheck(dns_zone_t *zone, dns_db_t *db, dns_dbversion_t *version,
+ unsigned int *errors);
+/*%
+ * Check if the name servers for the zone are sane (have address, don't
+ * refer to CNAMEs/DNAMEs. The number of constiancy errors detected in
+ * returned in '*errors'
+ *
+ * Requires:
+ * \li 'zone' to be valid.
+ * \li 'db' to be valid.
+ * \li 'version' to be valid or NULL.
+ * \li 'errors' to be non NULL.
+ *
+ * Returns:
+ * ISC_R_SUCCESS if there were no errors examining the zone contents.
+ */
+
+isc_result_t
+dns_zone_cdscheck(dns_zone_t *zone, dns_db_t *db, dns_dbversion_t *version);
+/*%
+ * Check if CSD, CDNSKEY and DNSKEY are consistent.
+ *
+ * Requires:
+ * \li 'zone' to be valid.
+ * \li 'db' to be valid.
+ * \li 'version' to be valid or NULL.
+ *
+ * Returns:
+ *\li #ISC_R_SUCCESS
+ *\li #DNS_R_BADCDS
+ *\li #DNS_R_BADCDNSKEY
+ * Others
+ */
+
+void
+dns_zone_setadded(dns_zone_t *zone, bool added);
+/*%
+ * Sets the value of zone->added, which should be true for
+ * zones that were originally added by "rndc addzone".
+ *
+ * Requires:
+ * \li 'zone' to be valid.
+ */
+
+bool
+dns_zone_getadded(dns_zone_t *zone);
+/*%
+ * Returns true if the zone was originally added at runtime
+ * using "rndc addzone".
+ *
+ * Requires:
+ * \li 'zone' to be valid.
+ */
+
+void
+dns_zone_setautomatic(dns_zone_t *zone, bool automatic);
+/*%
+ * Sets the value of zone->automatic, which should be true for
+ * zones that were automatically added by named.
+ *
+ * Requires:
+ * \li 'zone' to be valid.
+ */
+
+bool
+dns_zone_getautomatic(dns_zone_t *zone);
+/*%
+ * Returns true if the zone was added automatically by named.
+ *
+ * Requires:
+ * \li 'zone' to be valid.
+ */
+
+isc_result_t
+dns_zone_dlzpostload(dns_zone_t *zone, dns_db_t *db);
+/*%
+ * Load the origin names for a writeable DLZ database.
+ */
+
+bool
+dns_zone_isdynamic(dns_zone_t *zone, bool ignore_freeze);
+/*%
+ * Return true iff the zone is "dynamic", in the sense that the zone's
+ * master file (if any) is written by the server, rather than being
+ * updated manually and read by the server.
+ *
+ * This is true for secondary zones, stub zones, key zones, and zones that
+ * allow dynamic updates either by having an update policy ("ssutable")
+ * or an "allow-update" ACL with a value other than exactly "{ none; }".
+ *
+ * If 'ignore_freeze' is true, then the zone which has had updates disabled
+ * will still report itself to be dynamic.
+ *
+ * Requires:
+ * \li 'zone' to be valid.
+ */
+
+isc_result_t
+dns_zone_setrefreshkeyinterval(dns_zone_t *zone, uint32_t interval);
+/*%
+ * Sets the frequency, in minutes, with which the key repository will be
+ * checked to see if the keys for this zone have been updated. Any value
+ * higher than 1440 minutes (24 hours) will be silently reduced. A
+ * value of zero will return an out-of-range error.
+ *
+ * Requires:
+ * \li 'zone' to be valid.
+ */
+
+bool
+dns_zone_getrequestexpire(dns_zone_t *zone);
+/*%
+ * Returns the true/false value of the request-expire option in the zone.
+ *
+ * Requires:
+ * \li 'zone' to be valid.
+ */
+
+void
+dns_zone_setrequestexpire(dns_zone_t *zone, bool flag);
+/*%
+ * Sets the request-expire option for the zone. Either true or false. The
+ * default value is determined by the setting of this option in the view.
+ *
+ * Requires:
+ * \li 'zone' to be valid.
+ */
+
+bool
+dns_zone_getrequestixfr(dns_zone_t *zone);
+/*%
+ * Returns the true/false value of the request-ixfr option in the zone.
+ *
+ * Requires:
+ * \li 'zone' to be valid.
+ */
+
+void
+dns_zone_setrequestixfr(dns_zone_t *zone, bool flag);
+/*%
+ * Sets the request-ixfr option for the zone. Either true or false. The
+ * default value is determined by the setting of this option in the view.
+ *
+ * Requires:
+ * \li 'zone' to be valid.
+ */
+
+uint32_t
+dns_zone_getixfrratio(dns_zone_t *zone);
+/*%
+ * Returns the zone's current IXFR ratio.
+ *
+ * Requires:
+ * \li 'zone' to be valid.
+ */
+
+void
+dns_zone_setixfrratio(dns_zone_t *zone, uint32_t ratio);
+/*%
+ * Sets the ratio of IXFR size to zone size above which we use an AXFR
+ * response, expressed as a percentage. Cannot exceed 100.
+ *
+ * Requires:
+ * \li 'zone' to be valid.
+ */
+
+void
+dns_zone_setserialupdatemethod(dns_zone_t *zone, dns_updatemethod_t method);
+/*%
+ * Sets the update method to use when incrementing the zone serial number
+ * due to a DDNS update. Valid options are dns_updatemethod_increment
+ * and dns_updatemethod_unixtime.
+ *
+ * Requires:
+ * \li 'zone' to be valid.
+ */
+
+dns_updatemethod_t
+dns_zone_getserialupdatemethod(dns_zone_t *zone);
+/*%
+ * Returns the update method to be used when incrementing the zone serial
+ * number due to a DDNS update.
+ *
+ * Requires:
+ * \li 'zone' to be valid.
+ */
+
+isc_result_t
+dns_zone_link(dns_zone_t *zone, dns_zone_t *raw);
+
+void
+dns_zone_getraw(dns_zone_t *zone, dns_zone_t **raw);
+
+isc_result_t
+dns_zone_keydone(dns_zone_t *zone, const char *data);
+
+isc_result_t
+dns_zone_setnsec3param(dns_zone_t *zone, uint8_t hash, uint8_t flags,
+ uint16_t iter, uint8_t saltlen, unsigned char *salt,
+ bool replace, bool resalt);
+/*%
+ * Set the NSEC3 parameters for the zone.
+ *
+ * If 'replace' is true, then the existing NSEC3 chain, if any, will
+ * be replaced with the new one. If 'hash' is zero, then the replacement
+ * chain will be NSEC rather than NSEC3. If 'resalt' is true, or if 'salt'
+ * is NULL, generate a new salt with the given salt length.
+ *
+ * Requires:
+ * \li 'zone' to be valid.
+ */
+
+void
+dns_zone_setrawdata(dns_zone_t *zone, dns_masterrawheader_t *header);
+/*%
+ * Set the data to be included in the header when the zone is dumped in
+ * binary format.
+ */
+
+isc_result_t
+dns_zone_synckeyzone(dns_zone_t *zone);
+/*%
+ * Force the managed key zone to synchronize, and start the key
+ * maintenance timer.
+ */
+
+isc_result_t
+dns_zone_getloadtime(dns_zone_t *zone, isc_time_t *loadtime);
+/*%
+ * Return the time when the zone was last loaded.
+ */
+
+isc_result_t
+dns_zone_getrefreshtime(dns_zone_t *zone, isc_time_t *refreshtime);
+/*%
+ * Return the time when the (secondary) zone will need to be refreshed.
+ */
+
+isc_result_t
+dns_zone_getexpiretime(dns_zone_t *zone, isc_time_t *expiretime);
+/*%
+ * Return the time when the (secondary) zone will expire.
+ */
+
+isc_result_t
+dns_zone_getrefreshkeytime(dns_zone_t *zone, isc_time_t *refreshkeytime);
+/*%
+ * Return the time of the next scheduled DNSSEC key event.
+ */
+
+unsigned int
+dns_zone_getincludes(dns_zone_t *zone, char ***includesp);
+/*%
+ * Return the number include files that were encountered
+ * during load. If the number is greater than zero, 'includesp'
+ * will point to an array containing the filenames.
+ *
+ * The array and its contents need to be freed using isc_mem_free.
+ */
+
+isc_result_t
+dns_zone_rpz_enable(dns_zone_t *zone, dns_rpz_zones_t *rpzs,
+ dns_rpz_num_t rpz_num);
+/*%
+ * Set the response policy associated with a zone.
+ */
+
+void
+dns_zone_rpz_enable_db(dns_zone_t *zone, dns_db_t *db);
+/*%
+ * If a zone is a response policy zone, mark its new database.
+ */
+
+dns_rpz_num_t
+dns_zone_get_rpz_num(dns_zone_t *zone);
+
+void
+dns_zone_catz_enable(dns_zone_t *zone, dns_catz_zones_t *catzs);
+/*%<
+ * Enable zone as catalog zone.
+ *
+ * Requires:
+ *
+ * \li 'zone' is a valid zone object
+ * \li 'catzs' is not NULL
+ * \li prior to calling, zone->catzs is NULL or is equal to 'catzs'
+ */
+
+void
+dns_zone_catz_disable(dns_zone_t *zone);
+/*%<
+ * Disable zone as catalog zone, if it is one. Also disables any
+ * registered callbacks for the catalog zone.
+ *
+ * Requires:
+ *
+ * \li 'zone' is a valid zone object
+ */
+
+bool
+dns_zone_catz_is_enabled(dns_zone_t *zone);
+/*%<
+ * Return a boolean indicating whether the zone is enabled as catalog zone.
+ *
+ * Requires:
+ *
+ * \li 'zone' is a valid zone object
+ */
+
+void
+dns_zone_catz_enable_db(dns_zone_t *zone, dns_db_t *db);
+/*%<
+ * If 'zone' is a catalog zone, then set up a notify-on-update trigger
+ * in its database. (If not a catalog zone, this function has no effect.)
+ *
+ * Requires:
+ *
+ * \li 'zone' is a valid zone object
+ * \li 'db' is not NULL
+ */
+void
+dns_zone_set_parentcatz(dns_zone_t *zone, dns_catz_zone_t *catz);
+/*%<
+ * Set parent catalog zone for this zone
+ *
+ * Requires:
+ *
+ * \li 'zone' is a valid zone object
+ * \li 'catz' is not NULL
+ */
+
+dns_catz_zone_t *
+dns_zone_get_parentcatz(const dns_zone_t *zone);
+/*%<
+ * Get parent catalog zone for this zone
+ *
+ * Requires:
+ *
+ * \li 'zone' is a valid zone object
+ */
+
+void
+dns_zone_setstatlevel(dns_zone_t *zone, dns_zonestat_level_t level);
+
+dns_zonestat_level_t
+dns_zone_getstatlevel(dns_zone_t *zone);
+/*%
+ * Set and get the statistics reporting level for the zone;
+ * full, terse, or none.
+ */
+
+isc_result_t
+dns_zone_setserial(dns_zone_t *zone, uint32_t serial);
+/*%
+ * Set the zone's serial to 'serial'.
+ */
+ISC_LANG_ENDDECLS
+
+isc_stats_t *
+dns_zone_getgluecachestats(dns_zone_t *zone);
+/*%<
+ * Get the glue cache statistics for zone.
+ *
+ * Requires:
+ * \li 'zone' to be a valid zone.
+ *
+ * Returns:
+ * \li if present, a pointer to the statistics set installed in zone;
+ * otherwise NULL.
+ */
+
+bool
+dns_zone_isloaded(dns_zone_t *zone);
+/*%<
+ * Return true if 'zone' was loaded and has not expired yet, return
+ * false otherwise.
+ */
+
+isc_result_t
+dns_zone_verifydb(dns_zone_t *zone, dns_db_t *db, dns_dbversion_t *ver);
+/*%<
+ * If 'zone' is a mirror zone, perform DNSSEC validation of version 'ver' of
+ * its database, 'db'. Ensure that the DNSKEY RRset at zone apex is signed by
+ * at least one trust anchor specified for the view that 'zone' is assigned to.
+ * If 'ver' is NULL, use the current version of 'db'.
+ *
+ * If 'zone' is not a mirror zone, return ISC_R_SUCCESS immediately.
+ *
+ * Returns:
+ *
+ * \li #ISC_R_SUCCESS either 'zone' is not a mirror zone or 'zone' is
+ * a mirror zone and all DNSSEC checks succeeded
+ * and the DNSKEY RRset at zone apex is signed by
+ * a trusted key
+ *
+ * \li #DNS_R_VERIFYFAILURE any other case
+ */
+
+const char *
+dns_zonetype_name(dns_zonetype_t type);
+/*%<
+ * Return the name of the zone type 'type'.
+ */
+
+bool
+dns_zone_check_dnskey_nsec3(dns_zone_t *zone, dns_db_t *db,
+ dns_dbversion_t *ver, dns_diff_t *diff,
+ dst_key_t **keys, unsigned int numkeys);
+/**<
+ * Return whether the zone would enter an inconsistent state where NSEC only
+ * DNSKEYs are present along NSEC3 chains.
+ *
+ * Requires:
+ * \li 'zone' to be a valid zone.
+ * \li 'db'is not NULL.
+ *
+ * Returns:
+ * \li 'true' if the check passes, that is the zone remains consistent,
+ * 'false' if the zone would have NSEC only DNSKEYs and an NSEC3 chain.
+ */
diff --git a/lib/dns/include/dns/zonekey.h b/lib/dns/include/dns/zonekey.h
new file mode 100644
index 0000000..b093fa9
--- /dev/null
+++ b/lib/dns/include/dns/zonekey.h
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#pragma once
+
+/*! \file dns/zonekey.h */
+
+#include <stdbool.h>
+
+#include <isc/lang.h>
+
+#include <dns/types.h>
+
+ISC_LANG_BEGINDECLS
+
+bool
+dns_zonekey_iszonekey(dns_rdata_t *keyrdata);
+/*%<
+ * Determines if the key record contained in the rdata is a zone key.
+ *
+ * Requires:
+ * 'keyrdata' is not NULL.
+ */
+
+ISC_LANG_ENDDECLS
diff --git a/lib/dns/include/dns/zoneverify.h b/lib/dns/include/dns/zoneverify.h
new file mode 100644
index 0000000..22a680a
--- /dev/null
+++ b/lib/dns/include/dns/zoneverify.h
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#pragma once
+
+/*! \file dns/zoneverify.h */
+
+#include <stdbool.h>
+
+#include <isc/types.h>
+
+#include <dns/types.h>
+
+ISC_LANG_BEGINDECLS
+
+/*%
+ * Verify that certain things are sane:
+ *
+ * The apex has a DNSKEY record with at least one KSK, and at least
+ * one ZSK if the -x flag was not used.
+ *
+ * The DNSKEY record was signed with at least one of the KSKs in this
+ * set.
+ *
+ * The rest of the zone was signed with at least one of the ZSKs
+ * present in the DNSKEY RRSET.
+ *
+ * Mark all RRsets correctly signed by one of the keys in the DNSKEY RRset at
+ * zone apex as secure.
+ *
+ * If 'secroots' is not NULL, mark the DNSKEY RRset as secure if it is
+ * correctly signed by at least one key present in 'secroots'.
+ */
+isc_result_t
+dns_zoneverify_dnssec(dns_zone_t *zone, dns_db_t *db, dns_dbversion_t *ver,
+ dns_name_t *origin, dns_keytable_t *secroots,
+ isc_mem_t *mctx, bool ignore_kskflag, bool keyset_kskonly,
+ void (*report)(const char *, ...));
+
+ISC_LANG_ENDDECLS
diff --git a/lib/dns/include/dns/zt.h b/lib/dns/include/dns/zt.h
new file mode 100644
index 0000000..03601a4
--- /dev/null
+++ b/lib/dns/include/dns/zt.h
@@ -0,0 +1,220 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#pragma once
+
+/*! \file dns/zt.h */
+
+#include <stdbool.h>
+
+#include <isc/lang.h>
+#include <isc/rwlock.h>
+
+#include <dns/types.h>
+
+#define DNS_ZTFIND_NOEXACT 0x01
+#define DNS_ZTFIND_MIRROR 0x02
+
+ISC_LANG_BEGINDECLS
+
+typedef isc_result_t (*dns_zt_allloaded_t)(void *arg);
+/*%<
+ * Method prototype: when all pending zone loads are complete,
+ * the zone table can inform the caller via a callback function with
+ * this signature.
+ */
+
+typedef isc_result_t (*dns_zt_zoneloaded_t)(dns_zt_t *zt, dns_zone_t *zone,
+ isc_task_t *task);
+/*%<
+ * Method prototype: when a zone finishes loading, the zt object
+ * can be informed via a callback function with this signature.
+ */
+
+isc_result_t
+dns_zt_create(isc_mem_t *mctx, dns_rdataclass_t rdclass, dns_zt_t **zt);
+/*%<
+ * Creates a new zone table.
+ *
+ * Requires:
+ * \li 'mctx' to be initialized.
+ *
+ * Returns:
+ * \li #ISC_R_SUCCESS on success.
+ * \li #ISC_R_NOMEMORY
+ */
+
+isc_result_t
+dns_zt_mount(dns_zt_t *zt, dns_zone_t *zone);
+/*%<
+ * Mounts the zone on the zone table.
+ *
+ * Requires:
+ * \li 'zt' to be valid
+ * \li 'zone' to be valid
+ *
+ * Returns:
+ * \li #ISC_R_SUCCESS
+ * \li #ISC_R_EXISTS
+ * \li #ISC_R_NOSPACE
+ * \li #ISC_R_NOMEMORY
+ */
+
+isc_result_t
+dns_zt_unmount(dns_zt_t *zt, dns_zone_t *zone);
+/*%<
+ * Unmount the given zone from the table.
+ *
+ * Requires:
+ * 'zt' to be valid
+ * \li 'zone' to be valid
+ *
+ * Returns:
+ * \li #ISC_R_SUCCESS
+ * \li #ISC_R_NOTFOUND
+ * \li #ISC_R_NOMEMORY
+ */
+
+isc_result_t
+dns_zt_find(dns_zt_t *zt, const dns_name_t *name, unsigned int options,
+ dns_name_t *foundname, dns_zone_t **zone);
+/*%<
+ * Find the best match for 'name' in 'zt'. If foundname is non NULL
+ * then the name of the zone found is returned.
+ *
+ * Notes:
+ * \li If the DNS_ZTFIND_NOEXACT is set, the best partial match (if any)
+ * to 'name' will be returned.
+ *
+ * Requires:
+ * \li 'zt' to be valid
+ * \li 'name' to be valid
+ * \li 'foundname' to be initialized and associated with a fixedname or NULL
+ * \li 'zone' to be non NULL and '*zone' to be NULL
+ *
+ * Returns:
+ * \li #ISC_R_SUCCESS
+ * \li #DNS_R_PARTIALMATCH
+ * \li #ISC_R_NOTFOUND
+ * \li #ISC_R_NOSPACE
+ */
+
+void
+dns_zt_detach(dns_zt_t **ztp);
+/*%<
+ * Detach the given zonetable, if the reference count goes to zero the
+ * zonetable will be freed. In either case 'ztp' is set to NULL.
+ *
+ * Requires:
+ * \li '*ztp' to be valid
+ */
+
+void
+dns_zt_flush(dns_zt_t *ztp);
+/*%<
+ * Schedule flushing of the given zonetable, when reference count goes
+ * to zero.
+ *
+ * Requires:
+ * \li 'ztp' to be valid
+ */
+
+void
+dns_zt_attach(dns_zt_t *zt, dns_zt_t **ztp);
+/*%<
+ * Attach 'zt' to '*ztp'.
+ *
+ * Requires:
+ * \li 'zt' to be valid
+ * \li '*ztp' to be NULL
+ */
+
+isc_result_t
+dns_zt_load(dns_zt_t *zt, bool stop, bool newonly);
+
+isc_result_t
+dns_zt_asyncload(dns_zt_t *zt, bool newonly, dns_zt_allloaded_t alldone,
+ void *arg);
+/*%<
+ * Load all zones in the table. If 'stop' is true,
+ * stop on the first error and return it. If 'stop'
+ * is false, ignore errors.
+ *
+ * if newonly is set only zones that were never loaded are loaded.
+ * dns_zt_asyncload() loads zones asynchronously; when all
+ * zones in the zone table have finished loaded (or failed due
+ * to errors), the caller is informed by calling 'alldone'
+ * with an argument of 'arg'.
+ *
+ * Requires:
+ * \li 'zt' to be valid
+ */
+
+isc_result_t
+dns_zt_freezezones(dns_zt_t *zt, dns_view_t *view, bool freeze);
+/*%<
+ * Freeze/thaw updates to primary zones.
+ * Any pending updates will be flushed.
+ * Zones will be reloaded on thaw.
+ */
+
+isc_result_t
+dns_zt_apply(dns_zt_t *zt, isc_rwlocktype_t lock, bool stop, isc_result_t *sub,
+ isc_result_t (*action)(dns_zone_t *, void *), void *uap);
+/*%<
+ * Apply a given 'action' to all zone zones in the table.
+ * If 'stop' is 'true' then walking the zone tree will stop if
+ * 'action' does not return ISC_R_SUCCESS.
+ *
+ * Requires:
+ * \li 'zt' to be valid.
+ * \li 'action' to be non NULL.
+ *
+ * Returns:
+ * \li ISC_R_SUCCESS if action was applied to all nodes. If 'stop' is
+ * false and 'sub' is non NULL then the first error (if any)
+ * reported by 'action' is returned in '*sub';
+ * any error code from 'action'.
+ */
+
+bool
+dns_zt_loadspending(dns_zt_t *zt);
+/*%<
+ * Returns true if and only if there are zones still waiting to
+ * be loaded in zone table 'zt'.
+ *
+ * Requires:
+ * \li 'zt' to be valid.
+ */
+
+void
+dns_zt_setviewcommit(dns_zt_t *zt);
+/*%<
+ * Commit dns_zone_setview() calls previously made for all zones in this
+ * zone table.
+ *
+ * Requires:
+ *\li 'view' to be valid.
+ */
+
+void
+dns_zt_setviewrevert(dns_zt_t *zt);
+/*%<
+ * Revert dns_zone_setview() calls previously made for all zones in this
+ * zone table.
+ *
+ * Requires:
+ *\li 'view' to be valid.
+ */
+
+ISC_LANG_ENDDECLS
diff --git a/lib/dns/include/dst/dst.h b/lib/dns/include/dst/dst.h
new file mode 100644
index 0000000..ca292b0
--- /dev/null
+++ b/lib/dns/include/dst/dst.h
@@ -0,0 +1,1245 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#pragma once
+
+/*! \file dst/dst.h */
+
+#include <inttypes.h>
+#include <stdbool.h>
+
+#include <isc/lang.h>
+#include <isc/stdtime.h>
+
+#include <dns/ds.h>
+#include <dns/dsdigest.h>
+#include <dns/log.h>
+#include <dns/name.h>
+#include <dns/secalg.h>
+#include <dns/types.h>
+
+#include <dst/gssapi.h>
+
+ISC_LANG_BEGINDECLS
+
+/***
+ *** Types
+ ***/
+
+/*%
+ * The dst_key structure is opaque. Applications should use the accessor
+ * functions provided to retrieve key attributes. If an application needs
+ * to set attributes, new accessor functions will be written.
+ */
+
+typedef struct dst_key dst_key_t;
+typedef struct dst_context dst_context_t;
+
+/*%
+ * Key states for the DNSSEC records related to a key: DNSKEY, RRSIG (ksk),
+ * RRSIG (zsk), and DS.
+ *
+ * DST_KEY_STATE_HIDDEN: Records of this type are not published in zone.
+ * This may be because the key parts were never
+ * introduced in the zone, or because the key has
+ * retired and has no records of this type left in
+ * the zone.
+ * DST_KEY_STATE_RUMOURED: Records of this type are published in zone, but
+ * not long enough to ensure all resolvers know
+ * about it.
+ * DST_KEY_STATE_OMNIPRESENT: Records of this type are published in zone long
+ * enough so that all resolvers that know about
+ * these records, no longer have outdated data.
+ * DST_KEY_STATE_UNRETENTIVE: Records of this type have been removed from the
+ * zone, but there may be resolvers that still have
+ * have predecessor records cached. Note that RRSIG
+ * records in this state may actually still be in the
+ * zone because they are reused, but retired RRSIG
+ * records will never be refreshed: A successor key
+ * is used to create signatures.
+ * DST_KEY_STATE_NA: The state is not applicable for this record type.
+ */
+typedef enum dst_key_state {
+ DST_KEY_STATE_HIDDEN = 0,
+ DST_KEY_STATE_RUMOURED = 1,
+ DST_KEY_STATE_OMNIPRESENT = 2,
+ DST_KEY_STATE_UNRETENTIVE = 3,
+ DST_KEY_STATE_NA = 4
+} dst_key_state_t;
+
+/* DST algorithm codes */
+typedef enum dst_algorithm {
+ DST_ALG_UNKNOWN = 0,
+ DST_ALG_RSA = 1, /* Used for parsing RSASHA1, RSASHA256 and RSASHA512 */
+ DST_ALG_RSAMD5 = 1,
+ DST_ALG_DH = 2,
+ DST_ALG_DSA = 3,
+ DST_ALG_ECC = 4,
+ DST_ALG_RSASHA1 = 5,
+ DST_ALG_NSEC3DSA = 6,
+ DST_ALG_NSEC3RSASHA1 = 7,
+ DST_ALG_RSASHA256 = 8,
+ DST_ALG_RSASHA512 = 10,
+ DST_ALG_ECCGOST = 12,
+ DST_ALG_ECDSA256 = 13,
+ DST_ALG_ECDSA384 = 14,
+ DST_ALG_ED25519 = 15,
+ DST_ALG_ED448 = 16,
+
+ /*
+ * Do not renumber HMAC algorithms as they are used externally to named
+ * in legacy K* key pair files.
+ * Do not add non HMAC between DST_ALG_HMACMD5 and DST_ALG_HMACSHA512.
+ */
+ DST_ALG_HMACMD5 = 157,
+ DST_ALG_HMAC_FIRST = DST_ALG_HMACMD5,
+ DST_ALG_GSSAPI = 160, /* Internal use only. Exception. */
+ DST_ALG_HMACSHA1 = 161, /* XXXMPA */
+ DST_ALG_HMACSHA224 = 162, /* XXXMPA */
+ DST_ALG_HMACSHA256 = 163, /* XXXMPA */
+ DST_ALG_HMACSHA384 = 164, /* XXXMPA */
+ DST_ALG_HMACSHA512 = 165, /* XXXMPA */
+ DST_ALG_HMAC_LAST = DST_ALG_HMACSHA512,
+
+ DST_ALG_INDIRECT = 252,
+ DST_ALG_PRIVATE = 254,
+ DST_MAX_ALGS = 256,
+} dst_algorithm_t;
+
+/*% A buffer of this size is large enough to hold any key */
+#define DST_KEY_MAXSIZE 1280
+
+/*%
+ * A buffer of this size is large enough to hold the textual representation
+ * of any key
+ */
+#define DST_KEY_MAXTEXTSIZE 2048
+
+/*% 'Type' for dst_read_key() */
+#define DST_TYPE_KEY 0x1000000 /* KEY key */
+#define DST_TYPE_PRIVATE 0x2000000
+#define DST_TYPE_PUBLIC 0x4000000
+#define DST_TYPE_STATE 0x8000000
+#define DST_TYPE_TEMPLATE 0x10000000
+
+/* Key timing metadata definitions */
+#define DST_TIME_CREATED 0
+#define DST_TIME_PUBLISH 1
+#define DST_TIME_ACTIVATE 2
+#define DST_TIME_REVOKE 3
+#define DST_TIME_INACTIVE 4
+#define DST_TIME_DELETE 5
+#define DST_TIME_DSPUBLISH 6
+#define DST_TIME_SYNCPUBLISH 7
+#define DST_TIME_SYNCDELETE 8
+#define DST_TIME_DNSKEY 9
+#define DST_TIME_ZRRSIG 10
+#define DST_TIME_KRRSIG 11
+#define DST_TIME_DS 12
+#define DST_TIME_DSDELETE 13
+#define DST_MAX_TIMES 13
+
+/* Numeric metadata definitions */
+#define DST_NUM_PREDECESSOR 0
+#define DST_NUM_SUCCESSOR 1
+#define DST_NUM_MAXTTL 2
+#define DST_NUM_ROLLPERIOD 3
+#define DST_NUM_LIFETIME 4
+#define DST_NUM_DSPUBCOUNT 5
+#define DST_NUM_DSDELCOUNT 6
+#define DST_MAX_NUMERIC 6
+
+/* Boolean metadata definitions */
+#define DST_BOOL_KSK 0
+#define DST_BOOL_ZSK 1
+#define DST_MAX_BOOLEAN 1
+
+/* Key state metadata definitions */
+#define DST_KEY_DNSKEY 0
+#define DST_KEY_ZRRSIG 1
+#define DST_KEY_KRRSIG 2
+#define DST_KEY_DS 3
+#define DST_KEY_GOAL 4
+#define DST_MAX_KEYSTATES 4
+
+/*
+ * Current format version number of the private key parser.
+ *
+ * When parsing a key file with the same major number but a higher minor
+ * number, the key parser will ignore any fields it does not recognize.
+ * Thus, DST_MINOR_VERSION should be incremented whenever new
+ * fields are added to the private key file (such as new metadata).
+ *
+ * When rewriting these keys, those fields will be dropped, and the
+ * format version set back to the current one..
+ *
+ * When a key is seen with a higher major number, the key parser will
+ * reject it as invalid. Thus, DST_MAJOR_VERSION should be incremented
+ * and DST_MINOR_VERSION set to zero whenever there is a format change
+ * which is not backward compatible to previous versions of the dst_key
+ * parser, such as change in the syntax of an existing field, the removal
+ * of a currently mandatory field, or a new field added which would
+ * alter the functioning of the key if it were absent.
+ */
+#define DST_MAJOR_VERSION 1
+#define DST_MINOR_VERSION 3
+
+/***
+ *** Functions
+ ***/
+isc_result_t
+dst_lib_init(isc_mem_t *mctx, const char *engine);
+/*%<
+ * Initializes the DST subsystem.
+ *
+ * Requires:
+ * \li "mctx" is a valid memory context
+ *
+ * Returns:
+ * \li ISC_R_SUCCESS
+ * \li ISC_R_NOMEMORY
+ * \li DST_R_NOENGINE
+ *
+ * Ensures:
+ * \li DST is properly initialized.
+ */
+
+void
+dst_lib_destroy(void);
+/*%<
+ * Releases all resources allocated by DST.
+ */
+
+bool
+dst_algorithm_supported(unsigned int alg);
+/*%<
+ * Checks that a given algorithm is supported by DST.
+ *
+ * Returns:
+ * \li true
+ * \li false
+ */
+
+bool
+dst_ds_digest_supported(unsigned int digest_type);
+/*%<
+ * Checks that a given digest algorithm is supported by DST.
+ *
+ * Returns:
+ * \li true
+ * \li false
+ */
+
+isc_result_t
+dst_context_create(dst_key_t *key, isc_mem_t *mctx, isc_logcategory_t *category,
+ bool useforsigning, int maxbits, dst_context_t **dctxp);
+/*%<
+ * Creates a context to be used for a sign or verify operation.
+ *
+ * Requires:
+ * \li "key" is a valid key.
+ * \li "mctx" is a valid memory context.
+ * \li dctxp != NULL && *dctxp == NULL
+ *
+ * Returns:
+ * \li ISC_R_SUCCESS
+ * \li ISC_R_NOMEMORY
+ *
+ * Ensures:
+ * \li *dctxp will contain a usable context.
+ */
+
+void
+dst_context_destroy(dst_context_t **dctxp);
+/*%<
+ * Destroys all memory associated with a context.
+ *
+ * Requires:
+ * \li *dctxp != NULL && *dctxp == NULL
+ *
+ * Ensures:
+ * \li *dctxp == NULL
+ */
+
+isc_result_t
+dst_context_adddata(dst_context_t *dctx, const isc_region_t *data);
+/*%<
+ * Incrementally adds data to the context to be used in a sign or verify
+ * operation.
+ *
+ * Requires:
+ * \li "dctx" is a valid context
+ * \li "data" is a valid region
+ *
+ * Returns:
+ * \li ISC_R_SUCCESS
+ * \li DST_R_SIGNFAILURE
+ * \li all other errors indicate failure
+ */
+
+isc_result_t
+dst_context_sign(dst_context_t *dctx, isc_buffer_t *sig);
+/*%<
+ * Computes a signature using the data and key stored in the context.
+ *
+ * Requires:
+ * \li "dctx" is a valid context.
+ * \li "sig" is a valid buffer.
+ *
+ * Returns:
+ * \li ISC_R_SUCCESS
+ * \li DST_R_VERIFYFAILURE
+ * \li all other errors indicate failure
+ *
+ * Ensures:
+ * \li "sig" will contain the signature
+ */
+
+isc_result_t
+dst_context_verify(dst_context_t *dctx, isc_region_t *sig);
+
+isc_result_t
+dst_context_verify2(dst_context_t *dctx, unsigned int maxbits,
+ isc_region_t *sig);
+/*%<
+ * Verifies the signature using the data and key stored in the context.
+ *
+ * 'maxbits' specifies the maximum number of bits permitted in the RSA
+ * exponent.
+ *
+ * Requires:
+ * \li "dctx" is a valid context.
+ * \li "sig" is a valid region.
+ *
+ * Returns:
+ * \li ISC_R_SUCCESS
+ * \li all other errors indicate failure
+ *
+ * Ensures:
+ * \li "sig" will contain the signature
+ */
+
+isc_result_t
+dst_key_computesecret(const dst_key_t *pub, const dst_key_t *priv,
+ isc_buffer_t *secret);
+/*%<
+ * Computes a shared secret from two (Diffie-Hellman) keys.
+ *
+ * Requires:
+ * \li "pub" is a valid key that can be used to derive a shared secret
+ * \li "priv" is a valid private key that can be used to derive a shared secret
+ * \li "secret" is a valid buffer
+ *
+ * Returns:
+ * \li ISC_R_SUCCESS
+ * \li any other result indicates failure
+ *
+ * Ensures:
+ * \li If successful, secret will contain the derived shared secret.
+ */
+
+isc_result_t
+dst_key_getfilename(dns_name_t *name, dns_keytag_t id, unsigned int alg,
+ int type, const char *directory, isc_mem_t *mctx,
+ isc_buffer_t *buf);
+/*%<
+ * Generates a key filename for the name, algorithm, and
+ * id, and places it in the buffer 'buf'. If directory is NULL, the
+ * current directory is assumed.
+ *
+ * Requires:
+ * \li "name" is a valid absolute dns name.
+ * \li "id" is a valid key tag identifier.
+ * \li "alg" is a supported key algorithm.
+ * \li "type" is DST_TYPE_PUBLIC, DST_TYPE_PRIVATE, or the bitwise union.
+ * DST_TYPE_KEY look for a KEY record otherwise DNSKEY
+ * \li "mctx" is a valid memory context.
+ * \li "buf" is not NULL.
+ *
+ * Returns:
+ * \li ISC_R_SUCCESS
+ * \li any other result indicates failure
+ */
+
+isc_result_t
+dst_key_fromfile(dns_name_t *name, dns_keytag_t id, unsigned int alg, int type,
+ const char *directory, isc_mem_t *mctx, dst_key_t **keyp);
+/*%<
+ * Reads a key from permanent storage. The key can either be a public or
+ * private key, or a key state. It specified by name, algorithm, and id. If
+ * a private key or key state is specified, the public key must also be
+ * present. If directory is NULL, the current directory is assumed.
+ *
+ * Requires:
+ * \li "name" is a valid absolute dns name.
+ * \li "id" is a valid key tag identifier.
+ * \li "alg" is a supported key algorithm.
+ * \li "type" is DST_TYPE_PUBLIC, DST_TYPE_PRIVATE or the bitwise union.
+ * DST_TYPE_KEY look for a KEY record otherwise DNSKEY.
+ * DST_TYPE_STATE to also read the key state.
+ * \li "mctx" is a valid memory context.
+ * \li "keyp" is not NULL and "*keyp" is NULL.
+ *
+ * Returns:
+ * \li ISC_R_SUCCESS
+ * \li any other result indicates failure
+ *
+ * Ensures:
+ * \li If successful, *keyp will contain a valid key.
+ */
+
+isc_result_t
+dst_key_fromnamedfile(const char *filename, const char *dirname, int type,
+ isc_mem_t *mctx, dst_key_t **keyp);
+/*%<
+ * Reads a key from permanent storage. The key can either be a public or
+ * private key, or a key state. It is specified by filename. If a private key
+ * or key state is specified, the public key must also be present.
+ *
+ * If 'dirname' is not NULL, and 'filename' is a relative path,
+ * then the file is looked up relative to the given directory.
+ * If 'filename' is an absolute path, 'dirname' is ignored.
+ *
+ * Requires:
+ * \li "filename" is not NULL
+ * \li "type" is DST_TYPE_PUBLIC, DST_TYPE_PRIVATE, or the bitwise union.
+ * DST_TYPE_KEY look for a KEY record otherwise DNSKEY.
+ * DST_TYPE_STATE to also read the key state.
+ * \li "mctx" is a valid memory context
+ * \li "keyp" is not NULL and "*keyp" is NULL.
+ *
+ * Returns:
+ * \li ISC_R_SUCCESS
+ * \li any other result indicates failure
+ *
+ * Ensures:
+ * \li If successful, *keyp will contain a valid key.
+ */
+
+isc_result_t
+dst_key_read_public(const char *filename, int type, isc_mem_t *mctx,
+ dst_key_t **keyp);
+/*%<
+ * Reads a public key from permanent storage. The key must be a public key.
+ *
+ * Requires:
+ * \li "filename" is not NULL.
+ * \li "type" is DST_TYPE_KEY look for a KEY record otherwise DNSKEY.
+ * \li "mctx" is a valid memory context.
+ * \li "keyp" is not NULL and "*keyp" is NULL.
+ *
+ * Returns:
+ * \li ISC_R_SUCCESS
+ * \li DST_R_BADKEYTYPE if the key type is not the expected one
+ * \li ISC_R_UNEXPECTEDTOKEN if the file can not be parsed as a public key
+ * \li any other result indicates failure
+ *
+ * Ensures:
+ * \li If successful, *keyp will contain a valid key.
+ */
+
+isc_result_t
+dst_key_read_state(const char *filename, isc_mem_t *mctx, dst_key_t **keyp);
+/*%<
+ * Reads a key state from permanent storage.
+ *
+ * Requires:
+ * \li "filename" is not NULL.
+ * \li "mctx" is a valid memory context.
+ * \li "keyp" is not NULL and "*keyp" is NULL.
+ *
+ * Returns:
+ * \li ISC_R_SUCCESS
+ * \li ISC_R_UNEXPECTEDTOKEN if the file can not be parsed as a public key
+ * \li any other result indicates failure
+ */
+
+isc_result_t
+dst_key_tofile(const dst_key_t *key, int type, const char *directory);
+/*%<
+ * Writes a key to permanent storage. The key can either be a public or
+ * private key. Public keys are written in DNS format and private keys
+ * are written as a set of base64 encoded values. If directory is NULL,
+ * the current directory is assumed.
+ *
+ * Requires:
+ * \li "key" is a valid key.
+ * \li "type" is DST_TYPE_PUBLIC, DST_TYPE_PRIVATE, or the bitwise union
+ *
+ * Returns:
+ * \li ISC_R_SUCCESS
+ * \li any other result indicates failure
+ */
+
+isc_result_t
+dst_key_fromdns(const dns_name_t *name, dns_rdataclass_t rdclass,
+ isc_buffer_t *source, isc_mem_t *mctx, dst_key_t **keyp);
+/*%<
+ * Converts a DNS KEY record into a DST key.
+ *
+ * Requires:
+ * \li "name" is a valid absolute dns name.
+ * \li "source" is a valid buffer. There must be at least 4 bytes available.
+ * \li "mctx" is a valid memory context.
+ * \li "keyp" is not NULL and "*keyp" is NULL.
+ *
+ * Returns:
+ * \li ISC_R_SUCCESS
+ * \li any other result indicates failure
+ *
+ * Ensures:
+ * \li If successful, *keyp will contain a valid key, and the consumed
+ * pointer in data will be advanced.
+ */
+
+isc_result_t
+dst_key_todns(const dst_key_t *key, isc_buffer_t *target);
+/*%<
+ * Converts a DST key into a DNS KEY record.
+ *
+ * Requires:
+ * \li "key" is a valid key.
+ * \li "target" is a valid buffer. There must be at least 4 bytes unused.
+ *
+ * Returns:
+ * \li ISC_R_SUCCESS
+ * \li any other result indicates failure
+ *
+ * Ensures:
+ * \li If successful, the used pointer in 'target' is advanced by at least 4.
+ */
+
+isc_result_t
+dst_key_frombuffer(const dns_name_t *name, unsigned int alg, unsigned int flags,
+ unsigned int protocol, dns_rdataclass_t rdclass,
+ isc_buffer_t *source, isc_mem_t *mctx, dst_key_t **keyp);
+/*%<
+ * Converts a buffer containing DNS KEY RDATA into a DST key.
+ *
+ * Requires:
+ *\li "name" is a valid absolute dns name.
+ *\li "alg" is a supported key algorithm.
+ *\li "source" is a valid buffer.
+ *\li "mctx" is a valid memory context.
+ *\li "keyp" is not NULL and "*keyp" is NULL.
+ *
+ * Returns:
+ *\li ISC_R_SUCCESS
+ * \li any other result indicates failure
+ *
+ * Ensures:
+ *\li If successful, *keyp will contain a valid key, and the consumed
+ * pointer in source will be advanced.
+ */
+
+isc_result_t
+dst_key_tobuffer(const dst_key_t *key, isc_buffer_t *target);
+/*%<
+ * Converts a DST key into DNS KEY RDATA format.
+ *
+ * Requires:
+ *\li "key" is a valid key.
+ *\li "target" is a valid buffer.
+ *
+ * Returns:
+ *\li ISC_R_SUCCESS
+ * \li any other result indicates failure
+ *
+ * Ensures:
+ *\li If successful, the used pointer in 'target' is advanced.
+ */
+
+isc_result_t
+dst_key_privatefrombuffer(dst_key_t *key, isc_buffer_t *buffer);
+/*%<
+ * Converts a public key into a private key, reading the private key
+ * information from the buffer. The buffer should contain the same data
+ * as the .private key file would.
+ *
+ * Requires:
+ *\li "key" is a valid public key.
+ *\li "buffer" is not NULL.
+ *
+ * Returns:
+ *\li ISC_R_SUCCESS
+ * \li any other result indicates failure
+ *
+ * Ensures:
+ *\li If successful, key will contain a valid private key.
+ */
+
+dns_gss_ctx_id_t
+dst_key_getgssctx(const dst_key_t *key);
+/*%<
+ * Returns the opaque key data.
+ * Be cautions when using this value unless you know what you are doing.
+ *
+ * Requires:
+ *\li "key" is not NULL.
+ *
+ * Returns:
+ *\li gssctx key data, possibly NULL.
+ */
+
+isc_result_t
+dst_key_fromgssapi(const dns_name_t *name, dns_gss_ctx_id_t gssctx,
+ isc_mem_t *mctx, dst_key_t **keyp, isc_region_t *intoken);
+/*%<
+ * Converts a GSSAPI opaque context id into a DST key.
+ *
+ * Requires:
+ *\li "name" is a valid absolute dns name.
+ *\li "gssctx" is a GSSAPI context id.
+ *\li "mctx" is a valid memory context.
+ *\li "keyp" is not NULL and "*keyp" is NULL.
+ *
+ * Returns:
+ *\li ISC_R_SUCCESS
+ * \li any other result indicates failure
+ *
+ * Ensures:
+ *\li If successful, *keyp will contain a valid key and be responsible for
+ * the context id.
+ */
+
+#ifdef DST_KEY_INTERNAL
+isc_result_t
+dst_key_buildinternal(const dns_name_t *name, unsigned int alg,
+ unsigned int bits, unsigned int flags,
+ unsigned int protocol, dns_rdataclass_t rdclass,
+ void *data, isc_mem_t *mctx, dst_key_t **keyp);
+#endif /* ifdef DST_KEY_INTERNAL */
+
+isc_result_t
+dst_key_fromlabel(const dns_name_t *name, int alg, unsigned int flags,
+ unsigned int protocol, dns_rdataclass_t rdclass,
+ const char *engine, const char *label, const char *pin,
+ isc_mem_t *mctx, dst_key_t **keyp);
+
+isc_result_t
+dst_key_generate(const dns_name_t *name, unsigned int alg, unsigned int bits,
+ unsigned int param, unsigned int flags, unsigned int protocol,
+ dns_rdataclass_t rdclass, isc_mem_t *mctx, dst_key_t **keyp,
+ void (*callback)(int));
+
+/*%<
+ * Generate a DST key (or keypair) with the supplied parameters. The
+ * interpretation of the "param" field depends on the algorithm:
+ * \code
+ * RSA: exponent
+ * 0 use exponent 3
+ * !0 use Fermat4 (2^16 + 1)
+ * DH: generator
+ * 0 default - use well known prime if bits == 768 or 1024,
+ * otherwise use 2 as the generator.
+ * !0 use this value as the generator.
+ * DSA: unused
+ * HMACMD5: entropy
+ * 0 default - require good entropy
+ * !0 lack of good entropy is ok
+ *\endcode
+ *
+ * Requires:
+ *\li "name" is a valid absolute dns name.
+ *\li "keyp" is not NULL and "*keyp" is NULL.
+ *
+ * Returns:
+ *\li ISC_R_SUCCESS
+ * \li any other result indicates failure
+ *
+ * Ensures:
+ *\li If successful, *keyp will contain a valid key.
+ */
+
+bool
+dst_key_compare(const dst_key_t *key1, const dst_key_t *key2);
+/*%<
+ * Compares two DST keys. Returns true if they match, false otherwise.
+ *
+ * Keys ARE NOT considered to match if one of them is the revoked version
+ * of the other.
+ *
+ * Requires:
+ *\li "key1" is a valid key.
+ *\li "key2" is a valid key.
+ *
+ * Returns:
+ *\li true
+ * \li false
+ */
+
+bool
+dst_key_pubcompare(const dst_key_t *key1, const dst_key_t *key2,
+ bool match_revoked_key);
+/*%<
+ * Compares only the public portions of two DST keys. Returns true
+ * if they match, false otherwise. This allows us, for example, to
+ * determine whether a public key found in a zone matches up with a
+ * key pair found on disk.
+ *
+ * If match_revoked_key is TRUE, then keys ARE considered to match if one
+ * of them is the revoked version of the other. Otherwise, they are not.
+ *
+ * Requires:
+ *\li "key1" is a valid key.
+ *\li "key2" is a valid key.
+ *
+ * Returns:
+ *\li true
+ * \li false
+ */
+
+bool
+dst_key_paramcompare(const dst_key_t *key1, const dst_key_t *key2);
+/*%<
+ * Compares the parameters of two DST keys. This is used to determine if
+ * two (Diffie-Hellman) keys can be used to derive a shared secret.
+ *
+ * Requires:
+ *\li "key1" is a valid key.
+ *\li "key2" is a valid key.
+ *
+ * Returns:
+ *\li true
+ * \li false
+ */
+
+void
+dst_key_attach(dst_key_t *source, dst_key_t **target);
+/*
+ * Attach to a existing key increasing the reference count.
+ *
+ * Requires:
+ *\li 'source' to be a valid key.
+ *\li 'target' to be non-NULL and '*target' to be NULL.
+ */
+
+void
+dst_key_free(dst_key_t **keyp);
+/*%<
+ * Decrement the key's reference counter and, when it reaches zero,
+ * release all memory associated with the key.
+ *
+ * Requires:
+ *\li "keyp" is not NULL and "*keyp" is a valid key.
+ *\li reference counter greater than zero.
+ *
+ * Ensures:
+ *\li All memory associated with "*keyp" will be freed.
+ *\li *keyp == NULL
+ */
+
+/*%<
+ * Accessor functions to obtain key fields.
+ *
+ * Require:
+ *\li "key" is a valid key.
+ */
+dns_name_t *
+dst_key_name(const dst_key_t *key);
+
+unsigned int
+dst_key_size(const dst_key_t *key);
+
+unsigned int
+dst_key_proto(const dst_key_t *key);
+
+unsigned int
+dst_key_alg(const dst_key_t *key);
+
+uint32_t
+dst_key_flags(const dst_key_t *key);
+
+dns_keytag_t
+dst_key_id(const dst_key_t *key);
+
+dns_keytag_t
+dst_key_rid(const dst_key_t *key);
+
+dns_rdataclass_t
+dst_key_class(const dst_key_t *key);
+
+bool
+dst_key_isprivate(const dst_key_t *key);
+
+bool
+dst_key_iszonekey(const dst_key_t *key);
+
+bool
+dst_key_isnullkey(const dst_key_t *key);
+
+isc_result_t
+dst_key_buildfilename(const dst_key_t *key, int type, const char *directory,
+ isc_buffer_t *out);
+/*%<
+ * Generates the filename used by dst to store the specified key.
+ * If directory is NULL, the current directory is assumed.
+ * If tmp is not NULL, generates a template for mkstemp().
+ *
+ * Requires:
+ *\li "key" is a valid key
+ *\li "type" is either DST_TYPE_PUBLIC, DST_TYPE_PRIVATE, or 0 for no suffix.
+ *\li "out" is a valid buffer
+ *\li "tmp" is a valid buffer or NULL
+ *
+ * Ensures:
+ *\li the file name will be written to "out", and the used pointer will
+ * be advanced.
+ */
+
+isc_result_t
+dst_key_sigsize(const dst_key_t *key, unsigned int *n);
+/*%<
+ * Computes the size of a signature generated by the given key.
+ *
+ * Requires:
+ *\li "key" is a valid key.
+ *\li "n" is not NULL
+ *
+ * Returns:
+ *\li #ISC_R_SUCCESS
+ *\li DST_R_UNSUPPORTEDALG
+ *
+ * Ensures:
+ *\li "n" stores the size of a generated signature
+ */
+
+isc_result_t
+dst_key_secretsize(const dst_key_t *key, unsigned int *n);
+/*%<
+ * Computes the size of a shared secret generated by the given key.
+ *
+ * Requires:
+ *\li "key" is a valid key.
+ *\li "n" is not NULL
+ *
+ * Returns:
+ *\li #ISC_R_SUCCESS
+ *\li DST_R_UNSUPPORTEDALG
+ *
+ * Ensures:
+ *\li "n" stores the size of a generated shared secret
+ */
+
+uint16_t
+dst_region_computeid(const isc_region_t *source);
+uint16_t
+dst_region_computerid(const isc_region_t *source);
+/*%<
+ * Computes the (revoked) key id of the key stored in the provided
+ * region.
+ *
+ * Requires:
+ *\li "source" contains a valid, non-NULL region.
+ *
+ * Returns:
+ *\li the key id
+ */
+
+uint16_t
+dst_key_getbits(const dst_key_t *key);
+/*%<
+ * Get the number of digest bits required (0 == MAX).
+ *
+ * Requires:
+ * "key" is a valid key.
+ */
+
+void
+dst_key_setbits(dst_key_t *key, uint16_t bits);
+/*%<
+ * Set the number of digest bits required (0 == MAX).
+ *
+ * Requires:
+ * "key" is a valid key.
+ */
+
+void
+dst_key_setttl(dst_key_t *key, dns_ttl_t ttl);
+/*%<
+ * Set the default TTL to use when converting the key
+ * to a KEY or DNSKEY RR.
+ *
+ * Requires:
+ * "key" is a valid key.
+ */
+
+dns_ttl_t
+dst_key_getttl(const dst_key_t *key);
+/*%<
+ * Get the default TTL to use when converting the key
+ * to a KEY or DNSKEY RR.
+ *
+ * Requires:
+ * "key" is a valid key.
+ */
+
+isc_result_t
+dst_key_setflags(dst_key_t *key, uint32_t flags);
+/*
+ * Set the key flags, and recompute the key ID.
+ *
+ * Requires:
+ * "key" is a valid key.
+ */
+
+isc_result_t
+dst_key_getbool(const dst_key_t *key, int type, bool *valuep);
+/*%<
+ * Get a member of the boolean metadata array and place it in '*valuep'.
+ *
+ * Requires:
+ * "key" is a valid key.
+ * "type" is no larger than DST_MAX_BOOLEAN
+ * "valuep" is not null.
+ */
+
+void
+dst_key_setbool(dst_key_t *key, int type, bool value);
+/*%<
+ * Set a member of the boolean metadata array.
+ *
+ * Requires:
+ * "key" is a valid key.
+ * "type" is no larger than DST_MAX_BOOLEAN
+ */
+
+void
+dst_key_unsetbool(dst_key_t *key, int type);
+/*%<
+ * Flag a member of the boolean metadata array as "not set".
+ *
+ * Requires:
+ * "key" is a valid key.
+ * "type" is no larger than DST_MAX_BOOLEAN
+ */
+
+isc_result_t
+dst_key_getnum(const dst_key_t *key, int type, uint32_t *valuep);
+/*%<
+ * Get a member of the numeric metadata array and place it in '*valuep'.
+ *
+ * Requires:
+ * "key" is a valid key.
+ * "type" is no larger than DST_MAX_NUMERIC
+ * "valuep" is not null.
+ */
+
+void
+dst_key_setnum(dst_key_t *key, int type, uint32_t value);
+/*%<
+ * Set a member of the numeric metadata array.
+ *
+ * Requires:
+ * "key" is a valid key.
+ * "type" is no larger than DST_MAX_NUMERIC
+ */
+
+void
+dst_key_unsetnum(dst_key_t *key, int type);
+/*%<
+ * Flag a member of the numeric metadata array as "not set".
+ *
+ * Requires:
+ * "key" is a valid key.
+ * "type" is no larger than DST_MAX_NUMERIC
+ */
+
+isc_result_t
+dst_key_gettime(const dst_key_t *key, int type, isc_stdtime_t *timep);
+/*%<
+ * Get a member of the timing metadata array and place it in '*timep'.
+ *
+ * Requires:
+ * "key" is a valid key.
+ * "type" is no larger than DST_MAX_TIMES
+ * "timep" is not null.
+ */
+
+void
+dst_key_settime(dst_key_t *key, int type, isc_stdtime_t when);
+/*%<
+ * Set a member of the timing metadata array.
+ *
+ * Requires:
+ * "key" is a valid key.
+ * "type" is no larger than DST_MAX_TIMES
+ */
+
+void
+dst_key_unsettime(dst_key_t *key, int type);
+/*%<
+ * Flag a member of the timing metadata array as "not set".
+ *
+ * Requires:
+ * "key" is a valid key.
+ * "type" is no larger than DST_MAX_TIMES
+ */
+
+isc_result_t
+dst_key_getstate(const dst_key_t *key, int type, dst_key_state_t *statep);
+/*%<
+ * Get a member of the keystate metadata array and place it in '*statep'.
+ *
+ * Requires:
+ * "key" is a valid key.
+ * "type" is no larger than DST_MAX_KEYSTATES
+ * "statep" is not null.
+ */
+
+void
+dst_key_setstate(dst_key_t *key, int type, dst_key_state_t state);
+/*%<
+ * Set a member of the keystate metadata array.
+ *
+ * Requires:
+ * "key" is a valid key.
+ * "state" is a valid state.
+ * "type" is no larger than DST_MAX_KEYSTATES
+ */
+
+void
+dst_key_unsetstate(dst_key_t *key, int type);
+/*%<
+ * Flag a member of the keystate metadata array as "not set".
+ *
+ * Requires:
+ * "key" is a valid key.
+ * "type" is no larger than DST_MAX_KEYSTATES
+ */
+
+isc_result_t
+dst_key_getprivateformat(const dst_key_t *key, int *majorp, int *minorp);
+/*%<
+ * Get the private key format version number. (If the key does not have
+ * a private key associated with it, the version will be 0.0.) The major
+ * version number is placed in '*majorp', and the minor version number in
+ * '*minorp'.
+ *
+ * Requires:
+ * "key" is a valid key.
+ * "majorp" is not NULL.
+ * "minorp" is not NULL.
+ */
+
+void
+dst_key_setprivateformat(dst_key_t *key, int major, int minor);
+/*%<
+ * Set the private key format version number.
+ *
+ * Requires:
+ * "key" is a valid key.
+ */
+
+#define DST_KEY_FORMATSIZE (DNS_NAME_FORMATSIZE + DNS_SECALG_FORMATSIZE + 7)
+
+void
+dst_key_format(const dst_key_t *key, char *cp, unsigned int size);
+/*%<
+ * Write the uniquely identifying information about the key (name,
+ * algorithm, key ID) into a string 'cp' of size 'size'.
+ */
+
+isc_buffer_t *
+dst_key_tkeytoken(const dst_key_t *key);
+/*%<
+ * Return the token from the TKEY request, if any. If this key was
+ * not negotiated via TKEY, return NULL.
+ *
+ * Requires:
+ * "key" is a valid key.
+ */
+
+isc_result_t
+dst_key_dump(dst_key_t *key, isc_mem_t *mctx, char **buffer, int *length);
+/*%<
+ * Allocate 'buffer' and dump the key into it in base64 format. The buffer
+ * is not NUL terminated. The length of the buffer is returned in *length.
+ *
+ * 'buffer' needs to be freed using isc_mem_put(mctx, buffer, length);
+ *
+ * Requires:
+ * 'buffer' to be non NULL and *buffer to be NULL.
+ * 'length' to be non NULL and *length to be zero.
+ *
+ * Returns:
+ * ISC_R_SUCCESS
+ * ISC_R_NOMEMORY
+ * ISC_R_NOTIMPLEMENTED
+ * others.
+ */
+
+isc_result_t
+dst_key_restore(dns_name_t *name, unsigned int alg, unsigned int flags,
+ unsigned int protocol, dns_rdataclass_t rdclass,
+ isc_mem_t *mctx, const char *keystr, dst_key_t **keyp);
+
+bool
+dst_key_inactive(const dst_key_t *key);
+/*%<
+ * Determines if the private key is missing due the key being deemed inactive.
+ *
+ * Requires:
+ * 'key' to be valid.
+ */
+
+void
+dst_key_setinactive(dst_key_t *key, bool inactive);
+/*%<
+ * Set key inactive state.
+ *
+ * Requires:
+ * 'key' to be valid.
+ */
+
+void
+dst_key_setexternal(dst_key_t *key, bool value);
+/*%<
+ * Set key external state.
+ *
+ * Requires:
+ * 'key' to be valid.
+ */
+
+bool
+dst_key_isexternal(dst_key_t *key);
+/*%<
+ * Check if this is an external key.
+ *
+ * Requires:
+ * 'key' to be valid.
+ */
+
+void
+dst_key_setmodified(dst_key_t *key, bool value);
+/*%<
+ * If 'value' is true, this marks the key to indicate that key file metadata
+ * has been modified. If 'value' is false, this resets the value, for example
+ * after you have written the key to file.
+ *
+ * Requires:
+ * 'key' to be valid.
+ */
+
+bool
+dst_key_ismodified(const dst_key_t *key);
+/*%<
+ * Check if the key file has been modified.
+ *
+ * Requires:
+ * 'key' to be valid.
+ */
+
+bool
+dst_key_haskasp(dst_key_t *key);
+/*%<
+ * Check if this key has state (and thus uses KASP).
+ *
+ * Requires:
+ * 'key' to be valid.
+ */
+
+bool
+dst_key_is_unused(dst_key_t *key);
+/*%<
+ * Check if this key is unused.
+ *
+ * Requires:
+ * 'key' to be valid.
+ */
+
+bool
+dst_key_is_published(dst_key_t *key, isc_stdtime_t now, isc_stdtime_t *publish);
+/*%<
+ * Check if it is safe to publish this key (e.g. put the DNSKEY in the zone).
+ *
+ * Requires:
+ * 'key' to be valid.
+ */
+
+bool
+dst_key_is_active(dst_key_t *key, isc_stdtime_t now);
+/*%<
+ * Check if this key is active. This means that it is creating RRSIG records
+ * (ZSK), or that it is used to create a chain of trust (KSK), or both (CSK).
+ *
+ * Requires:
+ * 'key' to be valid.
+ */
+
+bool
+dst_key_is_signing(dst_key_t *key, int role, isc_stdtime_t now,
+ isc_stdtime_t *active);
+/*%<
+ * Check if it is safe to use this key for signing, given the role.
+ *
+ * Requires:
+ * 'key' to be valid.
+ */
+
+bool
+dst_key_is_revoked(dst_key_t *key, isc_stdtime_t now, isc_stdtime_t *revoke);
+/*%<
+ * Check if this key is revoked.
+ *
+ * Requires:
+ * 'key' to be valid.
+ */
+
+bool
+dst_key_is_removed(dst_key_t *key, isc_stdtime_t now, isc_stdtime_t *remove);
+/*%<
+ * Check if this key is removed from the zone (e.g. the DNSKEY record should
+ * no longer be in the zone).
+ *
+ * Requires:
+ * 'key' to be valid.
+ */
+
+dst_key_state_t
+dst_key_goal(dst_key_t *key);
+/*%<
+ * Get the key goal. Should be OMNIPRESENT or HIDDEN.
+ * This can be used to determine if the key is being introduced or
+ * is on its way out.
+ *
+ * Requires:
+ * 'key' to be valid.
+ */
+
+isc_result_t
+dst_key_role(dst_key_t *key, bool *ksk, bool *zsk);
+/*%<
+ * Get the key role. A key can have the KSK or the ZSK role, or both.
+ *
+ * Requires:
+ * 'key' to be valid.
+ */
+
+void
+dst_key_copy_metadata(dst_key_t *to, dst_key_t *from);
+/*%<
+ * Copy key metadata from one key to another.
+ *
+ * Requires:
+ * 'to' and 'from' to be valid.
+ */
+
+const char *
+dst_hmac_algorithm_totext(dst_algorithm_t alg);
+/*$<
+ * Return the name associtated with the HMAC algorithm 'alg'
+ * or return "unknown".
+ */
+
+ISC_LANG_ENDDECLS
diff --git a/lib/dns/include/dst/gssapi.h b/lib/dns/include/dst/gssapi.h
new file mode 100644
index 0000000..494b4b0
--- /dev/null
+++ b/lib/dns/include/dst/gssapi.h
@@ -0,0 +1,192 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#pragma once
+
+/*! \file dst/gssapi.h */
+
+#include <inttypes.h>
+#include <stdbool.h>
+
+#include <isc/formatcheck.h>
+#include <isc/lang.h>
+#include <isc/types.h>
+
+#include <dns/types.h>
+
+typedef void *dns_gss_cred_id_t;
+typedef void *dns_gss_ctx_id_t;
+
+ISC_LANG_BEGINDECLS
+
+/***
+ *** Types
+ ***/
+
+/***
+ *** Functions
+ ***/
+
+isc_result_t
+dst_gssapi_acquirecred(const dns_name_t *name, bool initiate,
+ dns_gss_cred_id_t *cred);
+/*
+ * Acquires GSS credentials.
+ *
+ * Requires:
+ * 'name' is a valid name, preferably one known by the GSS provider
+ * 'initiate' indicates whether the credentials are for initiating or
+ * accepting contexts
+ * 'cred' is a pointer to NULL, which will be allocated with the
+ * credential handle. Call dst_gssapi_releasecred to free
+ * the memory.
+ *
+ * Returns:
+ * ISC_R_SUCCESS msg was successfully updated to include the
+ * query to be sent
+ * other an error occurred while building the message
+ */
+
+isc_result_t
+dst_gssapi_releasecred(dns_gss_cred_id_t *cred);
+/*
+ * Releases GSS credentials. Calling this function does release the
+ * memory allocated for the credential in dst_gssapi_acquirecred()
+ *
+ * Requires:
+ * 'mctx' is a valid memory context
+ * 'cred' is a pointer to the credential to be released
+ *
+ * Returns:
+ * ISC_R_SUCCESS credential was released successfully
+ * other an error occurred while releaseing
+ * the credential
+ */
+
+isc_result_t
+dst_gssapi_initctx(const dns_name_t *name, isc_buffer_t *intoken,
+ isc_buffer_t *outtoken, dns_gss_ctx_id_t *gssctx,
+ isc_mem_t *mctx, char **err_message);
+/*
+ * Initiates a GSS context.
+ *
+ * Requires:
+ * 'name' is a valid name, preferably one known by the GSS
+ * provider
+ * 'intoken' is a token received from the acceptor, or NULL if
+ * there isn't one
+ * 'outtoken' is a buffer to receive the token generated by
+ * gss_init_sec_context() to be sent to the acceptor
+ * 'context' is a pointer to a valid dns_gss_ctx_id_t
+ * (which may have the value DNS_GSS_C_NO_CONTEXT)
+ *
+ * Returns:
+ * ISC_R_SUCCESS msg was successfully updated to include the
+ * query to be sent
+ * other an error occurred while building the message
+ * *err_message optional error message
+ */
+
+isc_result_t
+dst_gssapi_acceptctx(dns_gss_cred_id_t cred, const char *gssapi_keytab,
+ isc_region_t *intoken, isc_buffer_t **outtoken,
+ dns_gss_ctx_id_t *context, dns_name_t *principal,
+ isc_mem_t *mctx);
+/*
+ * Accepts a GSS context.
+ *
+ * Requires:
+ * 'mctx' is a valid memory context
+ * 'cred' is the acceptor's valid GSS credential handle
+ * 'intoken' is a token received from the initiator
+ * 'outtoken' is a pointer a buffer pointer used to return the token
+ * generated by gss_accept_sec_context() to be sent to the
+ * initiator
+ * 'context' is a valid pointer to receive the generated context handle.
+ * On the initial call, it should be a pointer to NULL, which
+ * will be allocated as a dns_gss_ctx_id_t. Subsequent calls
+ * should pass in the handle generated on the first call.
+ * Call dst_gssapi_releasecred to delete the context and free
+ * the memory.
+ *
+ * Requires:
+ * 'outtoken' to != NULL && *outtoken == NULL.
+ *
+ * Returns:
+ * ISC_R_SUCCESS msg was successfully updated to include the
+ * query to be sent
+ * DNS_R_CONTINUE transaction still in progress
+ * other an error occurred while building the message
+ */
+
+isc_result_t
+dst_gssapi_deletectx(isc_mem_t *mctx, dns_gss_ctx_id_t *gssctx);
+/*
+ * Destroys a GSS context. This function deletes the context from the GSS
+ * provider and then frees the memory used by the context pointer.
+ *
+ * Requires:
+ * 'mctx' is a valid memory context
+ * 'context' is a valid GSS context
+ *
+ * Returns:
+ * ISC_R_SUCCESS
+ */
+
+void
+gss_log(int level, const char *fmt, ...) ISC_FORMAT_PRINTF(2, 3);
+/*
+ * Logging function for GSS.
+ *
+ * Requires
+ * 'level' is the log level to be used, as an integer
+ * 'fmt' is a printf format specifier
+ */
+
+char *
+gss_error_tostring(uint32_t major, uint32_t minor, char *buf, size_t buflen);
+/*
+ * Render a GSS major status/minor status pair into a string
+ *
+ * Requires:
+ * 'major' is a GSS major status code
+ * 'minor' is a GSS minor status code
+ *
+ * Returns:
+ * A string containing the text representation of the error codes.
+ * Users should copy the string if they wish to keep it.
+ */
+
+bool
+dst_gssapi_identitymatchesrealmkrb5(const dns_name_t *signer,
+ const dns_name_t *name,
+ const dns_name_t *realm, bool subdomain);
+/*
+ * Compare a "signer" (in the format of a Kerberos-format Kerberos5
+ * principal: host/example.com@EXAMPLE.COM) to the realm name stored
+ * in "name" (which represents the realm name).
+ *
+ */
+
+bool
+dst_gssapi_identitymatchesrealmms(const dns_name_t *signer,
+ const dns_name_t *name,
+ const dns_name_t *realm, bool subdomain);
+/*
+ * Compare a "signer" (in the format of a Kerberos-format Kerberos5
+ * principal: host/example.com@EXAMPLE.COM) to the realm name stored
+ * in "name" (which represents the realm name).
+ *
+ */
+
+ISC_LANG_ENDDECLS
diff --git a/lib/dns/ipkeylist.c b/lib/dns/ipkeylist.c
new file mode 100644
index 0000000..431284b
--- /dev/null
+++ b/lib/dns/ipkeylist.c
@@ -0,0 +1,226 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#include <inttypes.h>
+#include <string.h>
+
+#include <isc/mem.h>
+#include <isc/sockaddr.h>
+#include <isc/util.h>
+
+#include <dns/ipkeylist.h>
+#include <dns/name.h>
+
+void
+dns_ipkeylist_init(dns_ipkeylist_t *ipkl) {
+ ipkl->count = 0;
+ ipkl->allocated = 0;
+ ipkl->addrs = NULL;
+ ipkl->keys = NULL;
+ ipkl->tlss = NULL;
+ ipkl->labels = NULL;
+}
+
+void
+dns_ipkeylist_clear(isc_mem_t *mctx, dns_ipkeylist_t *ipkl) {
+ uint32_t i;
+
+ REQUIRE(ipkl != NULL);
+
+ if (ipkl->allocated == 0) {
+ return;
+ }
+
+ if (ipkl->addrs != NULL) {
+ isc_mem_put(mctx, ipkl->addrs,
+ ipkl->allocated * sizeof(isc_sockaddr_t));
+ }
+
+ if (ipkl->keys != NULL) {
+ for (i = 0; i < ipkl->allocated; i++) {
+ if (ipkl->keys[i] == NULL) {
+ continue;
+ }
+ if (dns_name_dynamic(ipkl->keys[i])) {
+ dns_name_free(ipkl->keys[i], mctx);
+ }
+ isc_mem_put(mctx, ipkl->keys[i], sizeof(dns_name_t));
+ }
+ isc_mem_put(mctx, ipkl->keys,
+ ipkl->allocated * sizeof(dns_name_t *));
+ }
+
+ if (ipkl->tlss != NULL) {
+ for (i = 0; i < ipkl->allocated; i++) {
+ if (ipkl->tlss[i] == NULL) {
+ continue;
+ }
+ if (dns_name_dynamic(ipkl->tlss[i])) {
+ dns_name_free(ipkl->tlss[i], mctx);
+ }
+ isc_mem_put(mctx, ipkl->tlss[i], sizeof(dns_name_t));
+ }
+ isc_mem_put(mctx, ipkl->tlss,
+ ipkl->allocated * sizeof(dns_name_t *));
+ }
+
+ if (ipkl->labels != NULL) {
+ for (i = 0; i < ipkl->allocated; i++) {
+ if (ipkl->labels[i] == NULL) {
+ continue;
+ }
+ if (dns_name_dynamic(ipkl->labels[i])) {
+ dns_name_free(ipkl->labels[i], mctx);
+ }
+ isc_mem_put(mctx, ipkl->labels[i], sizeof(dns_name_t));
+ }
+ isc_mem_put(mctx, ipkl->labels,
+ ipkl->allocated * sizeof(dns_name_t *));
+ }
+
+ dns_ipkeylist_init(ipkl);
+}
+
+isc_result_t
+dns_ipkeylist_copy(isc_mem_t *mctx, const dns_ipkeylist_t *src,
+ dns_ipkeylist_t *dst) {
+ isc_result_t result = ISC_R_SUCCESS;
+ uint32_t i;
+
+ REQUIRE(dst != NULL);
+ /* dst might be preallocated, we don't care, but it must be empty */
+ REQUIRE(dst->count == 0);
+
+ if (src->count == 0) {
+ return (ISC_R_SUCCESS);
+ }
+
+ result = dns_ipkeylist_resize(mctx, dst, src->count);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+
+ memmove(dst->addrs, src->addrs, src->count * sizeof(isc_sockaddr_t));
+
+ if (src->keys != NULL) {
+ for (i = 0; i < src->count; i++) {
+ if (src->keys[i] != NULL) {
+ dst->keys[i] = isc_mem_get(mctx,
+ sizeof(dns_name_t));
+ dns_name_init(dst->keys[i], NULL);
+ dns_name_dup(src->keys[i], mctx, dst->keys[i]);
+ } else {
+ dst->keys[i] = NULL;
+ }
+ }
+ }
+
+ if (src->tlss != NULL) {
+ for (i = 0; i < src->count; i++) {
+ if (src->tlss[i] != NULL) {
+ dst->tlss[i] = isc_mem_get(mctx,
+ sizeof(dns_name_t));
+ dns_name_init(dst->tlss[i], NULL);
+ dns_name_dup(src->tlss[i], mctx, dst->tlss[i]);
+ } else {
+ dst->tlss[i] = NULL;
+ }
+ }
+ }
+
+ if (src->labels != NULL) {
+ for (i = 0; i < src->count; i++) {
+ if (src->labels[i] != NULL) {
+ dst->labels[i] =
+ isc_mem_get(mctx, sizeof(dns_name_t));
+ dns_name_init(dst->labels[i], NULL);
+ dns_name_dup(src->labels[i], mctx,
+ dst->labels[i]);
+ } else {
+ dst->labels[i] = NULL;
+ }
+ }
+ }
+ dst->count = src->count;
+ return (ISC_R_SUCCESS);
+}
+
+isc_result_t
+dns_ipkeylist_resize(isc_mem_t *mctx, dns_ipkeylist_t *ipkl, unsigned int n) {
+ isc_sockaddr_t *addrs = NULL;
+ dns_name_t **keys = NULL;
+ dns_name_t **tlss = NULL;
+ dns_name_t **labels = NULL;
+
+ REQUIRE(ipkl != NULL);
+ REQUIRE(n > ipkl->count);
+
+ if (n <= ipkl->allocated) {
+ return (ISC_R_SUCCESS);
+ }
+
+ addrs = isc_mem_get(mctx, n * sizeof(isc_sockaddr_t));
+ keys = isc_mem_get(mctx, n * sizeof(dns_name_t *));
+ tlss = isc_mem_get(mctx, n * sizeof(dns_name_t *));
+ labels = isc_mem_get(mctx, n * sizeof(dns_name_t *));
+
+ if (ipkl->addrs != NULL) {
+ memmove(addrs, ipkl->addrs,
+ ipkl->allocated * sizeof(isc_sockaddr_t));
+ isc_mem_put(mctx, ipkl->addrs,
+ ipkl->allocated * sizeof(isc_sockaddr_t));
+ }
+ ipkl->addrs = addrs;
+ memset(&ipkl->addrs[ipkl->allocated], 0,
+ (n - ipkl->allocated) * sizeof(isc_sockaddr_t));
+
+ if (ipkl->keys) {
+ memmove(keys, ipkl->keys,
+ ipkl->allocated * sizeof(dns_name_t *));
+ isc_mem_put(mctx, ipkl->keys,
+ ipkl->allocated * sizeof(dns_name_t *));
+ }
+ ipkl->keys = keys;
+ memset(&ipkl->keys[ipkl->allocated], 0,
+ (n - ipkl->allocated) * sizeof(dns_name_t *));
+
+ if (ipkl->tlss) {
+ memmove(tlss, ipkl->tlss,
+ ipkl->allocated * sizeof(dns_name_t *));
+ isc_mem_put(mctx, ipkl->tlss,
+ ipkl->allocated * sizeof(dns_name_t *));
+ }
+ ipkl->tlss = tlss;
+ memset(&ipkl->tlss[ipkl->allocated], 0,
+ (n - ipkl->allocated) * sizeof(dns_name_t *));
+
+ if (ipkl->labels != NULL) {
+ memmove(labels, ipkl->labels,
+ ipkl->allocated * sizeof(dns_name_t *));
+ isc_mem_put(mctx, ipkl->labels,
+ ipkl->allocated * sizeof(dns_name_t *));
+ }
+ ipkl->labels = labels;
+ memset(&ipkl->labels[ipkl->allocated], 0,
+ (n - ipkl->allocated) * sizeof(dns_name_t *));
+
+ ipkl->allocated = n;
+ return (ISC_R_SUCCESS);
+
+ isc_mem_put(mctx, addrs, n * sizeof(isc_sockaddr_t));
+ isc_mem_put(mctx, tlss, n * sizeof(dns_name_t *));
+ isc_mem_put(mctx, keys, n * sizeof(dns_name_t *));
+ isc_mem_put(mctx, labels, n * sizeof(dns_name_t *));
+
+ return (ISC_R_NOMEMORY);
+}
diff --git a/lib/dns/iptable.c b/lib/dns/iptable.c
new file mode 100644
index 0000000..f479d71
--- /dev/null
+++ b/lib/dns/iptable.c
@@ -0,0 +1,174 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#include <inttypes.h>
+#include <stdbool.h>
+
+#include <isc/mem.h>
+#include <isc/radix.h>
+#include <isc/util.h>
+
+#include <dns/acl.h>
+
+static void
+destroy_iptable(dns_iptable_t *dtab);
+
+/*
+ * Create a new IP table and the underlying radix structure
+ */
+isc_result_t
+dns_iptable_create(isc_mem_t *mctx, dns_iptable_t **target) {
+ isc_result_t result;
+ dns_iptable_t *tab;
+
+ tab = isc_mem_get(mctx, sizeof(*tab));
+ tab->mctx = NULL;
+ isc_mem_attach(mctx, &tab->mctx);
+ isc_refcount_init(&tab->refcount, 1);
+ tab->radix = NULL;
+ tab->magic = DNS_IPTABLE_MAGIC;
+
+ result = isc_radix_create(mctx, &tab->radix, RADIX_MAXBITS);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+
+ *target = tab;
+ return (ISC_R_SUCCESS);
+
+cleanup:
+ dns_iptable_detach(&tab);
+ return (result);
+}
+
+static bool dns_iptable_neg = false;
+static bool dns_iptable_pos = true;
+
+/*
+ * Add an IP prefix to an existing IP table
+ */
+isc_result_t
+dns_iptable_addprefix(dns_iptable_t *tab, const isc_netaddr_t *addr,
+ uint16_t bitlen, bool pos) {
+ isc_result_t result;
+ isc_prefix_t pfx;
+ isc_radix_node_t *node = NULL;
+ int i;
+
+ INSIST(DNS_IPTABLE_VALID(tab));
+ INSIST(tab->radix != NULL);
+
+ NETADDR_TO_PREFIX_T(addr, pfx, bitlen);
+
+ result = isc_radix_insert(tab->radix, &node, NULL, &pfx);
+ if (result != ISC_R_SUCCESS) {
+ isc_refcount_destroy(&pfx.refcount);
+ return (result);
+ }
+
+ /* If a node already contains data, don't overwrite it */
+ if (pfx.family == AF_UNSPEC) {
+ /* "any" or "none" */
+ INSIST(pfx.bitlen == 0);
+ for (i = 0; i < RADIX_FAMILIES; i++) {
+ if (node->data[i] == NULL) {
+ node->data[i] = pos ? &dns_iptable_pos
+ : &dns_iptable_neg;
+ }
+ }
+ } else {
+ /* any other prefix */
+ int fam = ISC_RADIX_FAMILY(&pfx);
+ if (node->data[fam] == NULL) {
+ node->data[fam] = pos ? &dns_iptable_pos
+ : &dns_iptable_neg;
+ }
+ }
+
+ isc_refcount_destroy(&pfx.refcount);
+ return (ISC_R_SUCCESS);
+}
+
+/*
+ * Merge one IP table into another one.
+ */
+isc_result_t
+dns_iptable_merge(dns_iptable_t *tab, dns_iptable_t *source, bool pos) {
+ isc_result_t result;
+ isc_radix_node_t *node, *new_node;
+ int i, max_node = 0;
+
+ RADIX_WALK(source->radix->head, node) {
+ new_node = NULL;
+ result = isc_radix_insert(tab->radix, &new_node, node, NULL);
+
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+
+ /*
+ * If we're negating a nested ACL, then we should
+ * reverse the sense of every node. However, this
+ * could lead to a negative node in a nested ACL
+ * becoming a positive match in the parent, which
+ * could be a security risk. To prevent this, we
+ * just leave the negative nodes negative.
+ */
+ for (i = 0; i < RADIX_FAMILIES; i++) {
+ if (!pos) {
+ if (node->data[i] && *(bool *)node->data[i]) {
+ new_node->data[i] = &dns_iptable_neg;
+ }
+ }
+ if (node->node_num[i] > max_node) {
+ max_node = node->node_num[i];
+ }
+ }
+ }
+ RADIX_WALK_END;
+
+ tab->radix->num_added_node += max_node;
+ return (ISC_R_SUCCESS);
+}
+
+void
+dns_iptable_attach(dns_iptable_t *source, dns_iptable_t **target) {
+ REQUIRE(DNS_IPTABLE_VALID(source));
+ isc_refcount_increment(&source->refcount);
+ *target = source;
+}
+
+void
+dns_iptable_detach(dns_iptable_t **tabp) {
+ REQUIRE(tabp != NULL && DNS_IPTABLE_VALID(*tabp));
+ dns_iptable_t *tab = *tabp;
+ *tabp = NULL;
+
+ if (isc_refcount_decrement(&tab->refcount) == 1) {
+ isc_refcount_destroy(&tab->refcount);
+ destroy_iptable(tab);
+ }
+}
+
+static void
+destroy_iptable(dns_iptable_t *dtab) {
+ REQUIRE(DNS_IPTABLE_VALID(dtab));
+
+ if (dtab->radix != NULL) {
+ isc_radix_destroy(dtab->radix, NULL);
+ dtab->radix = NULL;
+ }
+
+ dtab->magic = 0;
+ isc_mem_putanddetach(&dtab->mctx, dtab, sizeof(*dtab));
+}
diff --git a/lib/dns/journal.c b/lib/dns/journal.c
new file mode 100644
index 0000000..c158c41
--- /dev/null
+++ b/lib/dns/journal.c
@@ -0,0 +1,2856 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#include <errno.h>
+#include <inttypes.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#include <isc/dir.h>
+#include <isc/file.h>
+#include <isc/mem.h>
+#include <isc/print.h>
+#include <isc/result.h>
+#include <isc/serial.h>
+#include <isc/stdio.h>
+#include <isc/string.h>
+#include <isc/util.h>
+
+#include <dns/compress.h>
+#include <dns/db.h>
+#include <dns/dbiterator.h>
+#include <dns/diff.h>
+#include <dns/fixedname.h>
+#include <dns/journal.h>
+#include <dns/log.h>
+#include <dns/rdataset.h>
+#include <dns/rdatasetiter.h>
+#include <dns/soa.h>
+
+/*! \file
+ * \brief Journaling.
+ *
+ * A journal file consists of
+ *
+ * \li A fixed-size header of type journal_rawheader_t.
+ *
+ * \li The index. This is an unordered array of index entries
+ * of type journal_rawpos_t giving the locations
+ * of some arbitrary subset of the journal's addressable
+ * transactions. The index entries are used as hints to
+ * speed up the process of locating a transaction with a given
+ * serial number. Unused index entries have an "offset"
+ * field of zero. The size of the index can vary between
+ * journal files, but does not change during the lifetime
+ * of a file. The size can be zero.
+ *
+ * \li The journal data. This consists of one or more transactions.
+ * Each transaction begins with a transaction header of type
+ * journal_rawxhdr_t. The transaction header is followed by a
+ * sequence of RRs, similar in structure to an IXFR difference
+ * sequence (RFC1995). That is, the pre-transaction SOA,
+ * zero or more other deleted RRs, the post-transaction SOA,
+ * and zero or more other added RRs. Unlike in IXFR, each RR
+ * is prefixed with a 32-bit length.
+ *
+ * The journal data part grows as new transactions are
+ * appended to the file. Only those transactions
+ * whose serial number is current-(2^31-1) to current
+ * are considered "addressable" and may be pointed
+ * to from the header or index. They may be preceded
+ * by old transactions that are no longer addressable,
+ * and they may be followed by transactions that were
+ * appended to the journal but never committed by updating
+ * the "end" position in the header. The latter will
+ * be overwritten when new transactions are added.
+ */
+
+/**************************************************************************/
+/*
+ * Miscellaneous utilities.
+ */
+
+#define JOURNAL_COMMON_LOGARGS \
+ dns_lctx, DNS_LOGCATEGORY_GENERAL, DNS_LOGMODULE_JOURNAL
+
+#define JOURNAL_DEBUG_LOGARGS(n) JOURNAL_COMMON_LOGARGS, ISC_LOG_DEBUG(n)
+
+/*%
+ * It would be non-sensical (or at least obtuse) to use FAIL() with an
+ * ISC_R_SUCCESS code, but the test is there to keep the Solaris compiler
+ * from complaining about "end-of-loop code not reached".
+ */
+#define FAIL(code) \
+ do { \
+ result = (code); \
+ if (result != ISC_R_SUCCESS) \
+ goto failure; \
+ } while (0)
+
+#define CHECK(op) \
+ do { \
+ result = (op); \
+ if (result != ISC_R_SUCCESS) \
+ goto failure; \
+ } while (0)
+
+#define JOURNAL_SERIALSET 0x01U
+
+static isc_result_t
+index_to_disk(dns_journal_t *);
+
+static uint32_t
+decode_uint32(unsigned char *p) {
+ return (((uint32_t)p[0] << 24) + ((uint32_t)p[1] << 16) +
+ ((uint32_t)p[2] << 8) + ((uint32_t)p[3] << 0));
+}
+
+static void
+encode_uint32(uint32_t val, unsigned char *p) {
+ p[0] = (uint8_t)(val >> 24);
+ p[1] = (uint8_t)(val >> 16);
+ p[2] = (uint8_t)(val >> 8);
+ p[3] = (uint8_t)(val >> 0);
+}
+
+isc_result_t
+dns_db_createsoatuple(dns_db_t *db, dns_dbversion_t *ver, isc_mem_t *mctx,
+ dns_diffop_t op, dns_difftuple_t **tp) {
+ isc_result_t result;
+ dns_dbnode_t *node;
+ dns_rdataset_t rdataset;
+ dns_rdata_t rdata = DNS_RDATA_INIT;
+ dns_fixedname_t fixed;
+ dns_name_t *zonename;
+
+ zonename = dns_fixedname_initname(&fixed);
+ dns_name_copy(dns_db_origin(db), zonename);
+
+ node = NULL;
+ result = dns_db_findnode(db, zonename, false, &node);
+ if (result != ISC_R_SUCCESS) {
+ goto nonode;
+ }
+
+ dns_rdataset_init(&rdataset);
+ result = dns_db_findrdataset(db, node, ver, dns_rdatatype_soa, 0,
+ (isc_stdtime_t)0, &rdataset, NULL);
+ if (result != ISC_R_SUCCESS) {
+ goto freenode;
+ }
+
+ result = dns_rdataset_first(&rdataset);
+ if (result != ISC_R_SUCCESS) {
+ goto freenode;
+ }
+
+ dns_rdataset_current(&rdataset, &rdata);
+ dns_rdataset_getownercase(&rdataset, zonename);
+
+ result = dns_difftuple_create(mctx, op, zonename, rdataset.ttl, &rdata,
+ tp);
+
+ dns_rdataset_disassociate(&rdataset);
+ dns_db_detachnode(db, &node);
+ return (result);
+
+freenode:
+ dns_db_detachnode(db, &node);
+nonode:
+ UNEXPECTED_ERROR("missing SOA");
+ return (result);
+}
+
+/* Journaling */
+
+/*%
+ * On-disk representation of a "pointer" to a journal entry.
+ * These are used in the journal header to locate the beginning
+ * and end of the journal, and in the journal index to locate
+ * other transactions.
+ */
+typedef struct {
+ unsigned char serial[4]; /*%< SOA serial before update. */
+ /*
+ * XXXRTH Should offset be 8 bytes?
+ * XXXDCL ... probably, since isc_offset_t is 8 bytes on many OSs.
+ * XXXAG ... but we will not be able to seek >2G anyway on many
+ * platforms as long as we are using fseek() rather
+ * than lseek().
+ */
+ unsigned char offset[4]; /*%< Offset from beginning of file. */
+} journal_rawpos_t;
+
+/*%
+ * The header is of a fixed size, with some spare room for future
+ * extensions.
+ */
+#define JOURNAL_HEADER_SIZE 64 /* Bytes. */
+
+typedef enum {
+ XHDR_VERSION1 = 1,
+ XHDR_VERSION2 = 2,
+} xhdr_version_t;
+
+/*%
+ * The on-disk representation of the journal header.
+ * All numbers are stored in big-endian order.
+ */
+typedef union {
+ struct {
+ /*% File format version ID. */
+ unsigned char format[16];
+ /*% Position of the first addressable transaction */
+ journal_rawpos_t begin;
+ /*% Position of the next (yet nonexistent) transaction. */
+ journal_rawpos_t end;
+ /*% Number of index entries following the header. */
+ unsigned char index_size[4];
+ /*% Source serial number. */
+ unsigned char sourceserial[4];
+ unsigned char flags;
+ } h;
+ /* Pad the header to a fixed size. */
+ unsigned char pad[JOURNAL_HEADER_SIZE];
+} journal_rawheader_t;
+
+/*%
+ * The on-disk representation of the transaction header, version 2.
+ * There is one of these at the beginning of each transaction.
+ */
+typedef struct {
+ unsigned char size[4]; /*%< In bytes, excluding header. */
+ unsigned char count[4]; /*%< Number of records in transaction */
+ unsigned char serial0[4]; /*%< SOA serial before update. */
+ unsigned char serial1[4]; /*%< SOA serial after update. */
+} journal_rawxhdr_t;
+
+/*%
+ * Old-style raw transaction header, version 1, used for backward
+ * compatibility mode.
+ */
+typedef struct {
+ unsigned char size[4];
+ unsigned char serial0[4];
+ unsigned char serial1[4];
+} journal_rawxhdr_ver1_t;
+
+/*%
+ * The on-disk representation of the RR header.
+ * There is one of these at the beginning of each RR.
+ */
+typedef struct {
+ unsigned char size[4]; /*%< In bytes, excluding header. */
+} journal_rawrrhdr_t;
+
+/*%
+ * The in-core representation of the journal header.
+ */
+typedef struct {
+ uint32_t serial;
+ isc_offset_t offset;
+} journal_pos_t;
+
+#define POS_VALID(pos) ((pos).offset != 0)
+#define POS_INVALIDATE(pos) ((pos).offset = 0, (pos).serial = 0)
+
+typedef struct {
+ unsigned char format[16];
+ journal_pos_t begin;
+ journal_pos_t end;
+ uint32_t index_size;
+ uint32_t sourceserial;
+ bool serialset;
+} journal_header_t;
+
+/*%
+ * The in-core representation of the transaction header.
+ */
+typedef struct {
+ uint32_t size;
+ uint32_t count;
+ uint32_t serial0;
+ uint32_t serial1;
+} journal_xhdr_t;
+
+/*%
+ * The in-core representation of the RR header.
+ */
+typedef struct {
+ uint32_t size;
+} journal_rrhdr_t;
+
+/*%
+ * Initial contents to store in the header of a newly created
+ * journal file.
+ *
+ * The header starts with the magic string ";BIND LOG V9.2\n"
+ * to identify the file as a BIND 9 journal file. An ASCII
+ * identification string is used rather than a binary magic
+ * number to be consistent with BIND 8 (BIND 8 journal files
+ * are ASCII text files).
+ */
+
+static journal_header_t journal_header_ver1 = {
+ ";BIND LOG V9\n", { 0, 0 }, { 0, 0 }, 0, 0, 0
+};
+static journal_header_t initial_journal_header = {
+ ";BIND LOG V9.2\n", { 0, 0 }, { 0, 0 }, 0, 0, 0
+};
+
+#define JOURNAL_EMPTY(h) ((h)->begin.offset == (h)->end.offset)
+
+typedef enum {
+ JOURNAL_STATE_INVALID,
+ JOURNAL_STATE_READ,
+ JOURNAL_STATE_WRITE,
+ JOURNAL_STATE_TRANSACTION,
+ JOURNAL_STATE_INLINE
+} journal_state_t;
+
+struct dns_journal {
+ unsigned int magic; /*%< JOUR */
+ isc_mem_t *mctx; /*%< Memory context */
+ journal_state_t state;
+ xhdr_version_t xhdr_version; /*%< Expected transaction header version */
+ bool header_ver1; /*%< Transaction header compatibility
+ * mode is allowed */
+ bool recovered; /*%< A recoverable error was found
+ * while reading the journal */
+ char *filename; /*%< Journal file name */
+ FILE *fp; /*%< File handle */
+ isc_offset_t offset; /*%< Current file offset */
+ journal_xhdr_t curxhdr; /*%< Current transaction header */
+ journal_header_t header; /*%< In-core journal header */
+ unsigned char *rawindex; /*%< In-core buffer for journal index
+ * in on-disk format */
+ journal_pos_t *index; /*%< In-core journal index */
+
+ /*% Current transaction state (when writing). */
+ struct {
+ unsigned int n_soa; /*%< Number of SOAs seen */
+ unsigned int n_rr; /*%< Number of RRs to write */
+ journal_pos_t pos[2]; /*%< Begin/end position */
+ } x;
+
+ /*% Iteration state (when reading). */
+ struct {
+ /* These define the part of the journal we iterate over. */
+ journal_pos_t bpos; /*%< Position before first, */
+ journal_pos_t cpos; /*%< before current, */
+ journal_pos_t epos; /*%< and after last transaction */
+ /* The rest is iterator state. */
+ uint32_t current_serial; /*%< Current SOA serial */
+ isc_buffer_t source; /*%< Data from disk */
+ isc_buffer_t target; /*%< Data from _fromwire check */
+ dns_decompress_t dctx; /*%< Dummy decompression ctx */
+ dns_name_t name; /*%< Current domain name */
+ dns_rdata_t rdata; /*%< Current rdata */
+ uint32_t ttl; /*%< Current TTL */
+ unsigned int xsize; /*%< Size of transaction data */
+ unsigned int xpos; /*%< Current position in it */
+ isc_result_t result; /*%< Result of last call */
+ } it;
+};
+
+#define DNS_JOURNAL_MAGIC ISC_MAGIC('J', 'O', 'U', 'R')
+#define DNS_JOURNAL_VALID(t) ISC_MAGIC_VALID(t, DNS_JOURNAL_MAGIC)
+
+static void
+journal_pos_decode(journal_rawpos_t *raw, journal_pos_t *cooked) {
+ cooked->serial = decode_uint32(raw->serial);
+ cooked->offset = decode_uint32(raw->offset);
+}
+
+static void
+journal_pos_encode(journal_rawpos_t *raw, journal_pos_t *cooked) {
+ encode_uint32(cooked->serial, raw->serial);
+ encode_uint32(cooked->offset, raw->offset);
+}
+
+static void
+journal_header_decode(journal_rawheader_t *raw, journal_header_t *cooked) {
+ INSIST(sizeof(cooked->format) == sizeof(raw->h.format));
+
+ memmove(cooked->format, raw->h.format, sizeof(cooked->format));
+ journal_pos_decode(&raw->h.begin, &cooked->begin);
+ journal_pos_decode(&raw->h.end, &cooked->end);
+ cooked->index_size = decode_uint32(raw->h.index_size);
+ cooked->sourceserial = decode_uint32(raw->h.sourceserial);
+ cooked->serialset = ((raw->h.flags & JOURNAL_SERIALSET) != 0);
+}
+
+static void
+journal_header_encode(journal_header_t *cooked, journal_rawheader_t *raw) {
+ unsigned char flags = 0;
+
+ INSIST(sizeof(cooked->format) == sizeof(raw->h.format));
+
+ memset(raw->pad, 0, sizeof(raw->pad));
+ memmove(raw->h.format, cooked->format, sizeof(raw->h.format));
+ journal_pos_encode(&raw->h.begin, &cooked->begin);
+ journal_pos_encode(&raw->h.end, &cooked->end);
+ encode_uint32(cooked->index_size, raw->h.index_size);
+ encode_uint32(cooked->sourceserial, raw->h.sourceserial);
+ if (cooked->serialset) {
+ flags |= JOURNAL_SERIALSET;
+ }
+ raw->h.flags = flags;
+}
+
+/*
+ * Journal file I/O subroutines, with error checking and reporting.
+ */
+static isc_result_t
+journal_seek(dns_journal_t *j, uint32_t offset) {
+ isc_result_t result;
+
+ result = isc_stdio_seek(j->fp, (off_t)offset, SEEK_SET);
+ if (result != ISC_R_SUCCESS) {
+ isc_log_write(JOURNAL_COMMON_LOGARGS, ISC_LOG_ERROR,
+ "%s: seek: %s", j->filename,
+ isc_result_totext(result));
+ return (ISC_R_UNEXPECTED);
+ }
+ j->offset = offset;
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+journal_read(dns_journal_t *j, void *mem, size_t nbytes) {
+ isc_result_t result;
+
+ result = isc_stdio_read(mem, 1, nbytes, j->fp, NULL);
+ if (result != ISC_R_SUCCESS) {
+ if (result == ISC_R_EOF) {
+ return (ISC_R_NOMORE);
+ }
+ isc_log_write(JOURNAL_COMMON_LOGARGS, ISC_LOG_ERROR,
+ "%s: read: %s", j->filename,
+ isc_result_totext(result));
+ return (ISC_R_UNEXPECTED);
+ }
+ j->offset += (isc_offset_t)nbytes;
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+journal_write(dns_journal_t *j, void *mem, size_t nbytes) {
+ isc_result_t result;
+
+ result = isc_stdio_write(mem, 1, nbytes, j->fp, NULL);
+ if (result != ISC_R_SUCCESS) {
+ isc_log_write(JOURNAL_COMMON_LOGARGS, ISC_LOG_ERROR,
+ "%s: write: %s", j->filename,
+ isc_result_totext(result));
+ return (ISC_R_UNEXPECTED);
+ }
+ j->offset += (isc_offset_t)nbytes;
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+journal_fsync(dns_journal_t *j) {
+ isc_result_t result;
+
+ result = isc_stdio_flush(j->fp);
+ if (result != ISC_R_SUCCESS) {
+ isc_log_write(JOURNAL_COMMON_LOGARGS, ISC_LOG_ERROR,
+ "%s: flush: %s", j->filename,
+ isc_result_totext(result));
+ return (ISC_R_UNEXPECTED);
+ }
+ result = isc_stdio_sync(j->fp);
+ if (result != ISC_R_SUCCESS) {
+ isc_log_write(JOURNAL_COMMON_LOGARGS, ISC_LOG_ERROR,
+ "%s: fsync: %s", j->filename,
+ isc_result_totext(result));
+ return (ISC_R_UNEXPECTED);
+ }
+ return (ISC_R_SUCCESS);
+}
+
+/*
+ * Read/write a transaction header at the current file position.
+ */
+static isc_result_t
+journal_read_xhdr(dns_journal_t *j, journal_xhdr_t *xhdr) {
+ isc_result_t result;
+
+ j->it.cpos.offset = j->offset;
+
+ switch (j->xhdr_version) {
+ case XHDR_VERSION1: {
+ journal_rawxhdr_ver1_t raw;
+ result = journal_read(j, &raw, sizeof(raw));
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+ xhdr->size = decode_uint32(raw.size);
+ xhdr->count = 0;
+ xhdr->serial0 = decode_uint32(raw.serial0);
+ xhdr->serial1 = decode_uint32(raw.serial1);
+ j->curxhdr = *xhdr;
+ return (ISC_R_SUCCESS);
+ }
+
+ case XHDR_VERSION2: {
+ journal_rawxhdr_t raw;
+ result = journal_read(j, &raw, sizeof(raw));
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+ xhdr->size = decode_uint32(raw.size);
+ xhdr->count = decode_uint32(raw.count);
+ xhdr->serial0 = decode_uint32(raw.serial0);
+ xhdr->serial1 = decode_uint32(raw.serial1);
+ j->curxhdr = *xhdr;
+ return (ISC_R_SUCCESS);
+ }
+
+ default:
+ return (ISC_R_NOTIMPLEMENTED);
+ }
+}
+
+static isc_result_t
+journal_write_xhdr(dns_journal_t *j, uint32_t size, uint32_t count,
+ uint32_t serial0, uint32_t serial1) {
+ if (j->header_ver1) {
+ journal_rawxhdr_ver1_t raw;
+ encode_uint32(size, raw.size);
+ encode_uint32(serial0, raw.serial0);
+ encode_uint32(serial1, raw.serial1);
+ return (journal_write(j, &raw, sizeof(raw)));
+ } else {
+ journal_rawxhdr_t raw;
+ encode_uint32(size, raw.size);
+ encode_uint32(count, raw.count);
+ encode_uint32(serial0, raw.serial0);
+ encode_uint32(serial1, raw.serial1);
+ return (journal_write(j, &raw, sizeof(raw)));
+ }
+}
+
+/*
+ * Read an RR header at the current file position.
+ */
+
+static isc_result_t
+journal_read_rrhdr(dns_journal_t *j, journal_rrhdr_t *rrhdr) {
+ journal_rawrrhdr_t raw;
+ isc_result_t result;
+
+ result = journal_read(j, &raw, sizeof(raw));
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+ rrhdr->size = decode_uint32(raw.size);
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+journal_file_create(isc_mem_t *mctx, bool downgrade, const char *filename) {
+ FILE *fp = NULL;
+ isc_result_t result;
+ journal_header_t header;
+ journal_rawheader_t rawheader;
+ int index_size = 56; /* XXX configurable */
+ int size;
+ void *mem = NULL; /* Memory for temporary index image. */
+
+ INSIST(sizeof(journal_rawheader_t) == JOURNAL_HEADER_SIZE);
+
+ result = isc_stdio_open(filename, "wb", &fp);
+ if (result != ISC_R_SUCCESS) {
+ isc_log_write(JOURNAL_COMMON_LOGARGS, ISC_LOG_ERROR,
+ "%s: create: %s", filename,
+ isc_result_totext(result));
+ return (ISC_R_UNEXPECTED);
+ }
+
+ if (downgrade) {
+ header = journal_header_ver1;
+ } else {
+ header = initial_journal_header;
+ }
+ header.index_size = index_size;
+ journal_header_encode(&header, &rawheader);
+
+ size = sizeof(journal_rawheader_t) +
+ index_size * sizeof(journal_rawpos_t);
+
+ mem = isc_mem_get(mctx, size);
+ memset(mem, 0, size);
+ memmove(mem, &rawheader, sizeof(rawheader));
+
+ result = isc_stdio_write(mem, 1, (size_t)size, fp, NULL);
+ if (result != ISC_R_SUCCESS) {
+ isc_log_write(JOURNAL_COMMON_LOGARGS, ISC_LOG_ERROR,
+ "%s: write: %s", filename,
+ isc_result_totext(result));
+ (void)isc_stdio_close(fp);
+ (void)isc_file_remove(filename);
+ isc_mem_put(mctx, mem, size);
+ return (ISC_R_UNEXPECTED);
+ }
+ isc_mem_put(mctx, mem, size);
+
+ result = isc_stdio_close(fp);
+ if (result != ISC_R_SUCCESS) {
+ isc_log_write(JOURNAL_COMMON_LOGARGS, ISC_LOG_ERROR,
+ "%s: close: %s", filename,
+ isc_result_totext(result));
+ (void)isc_file_remove(filename);
+ return (ISC_R_UNEXPECTED);
+ }
+
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+journal_open(isc_mem_t *mctx, const char *filename, bool writable, bool create,
+ bool downgrade, dns_journal_t **journalp) {
+ FILE *fp = NULL;
+ isc_result_t result;
+ journal_rawheader_t rawheader;
+ dns_journal_t *j;
+
+ REQUIRE(journalp != NULL && *journalp == NULL);
+
+ j = isc_mem_get(mctx, sizeof(*j));
+ *j = (dns_journal_t){ .state = JOURNAL_STATE_INVALID,
+ .filename = isc_mem_strdup(mctx, filename),
+ .xhdr_version = XHDR_VERSION2 };
+ isc_mem_attach(mctx, &j->mctx);
+
+ result = isc_stdio_open(j->filename, writable ? "rb+" : "rb", &fp);
+ if (result == ISC_R_FILENOTFOUND) {
+ if (create) {
+ isc_log_write(JOURNAL_COMMON_LOGARGS, ISC_LOG_DEBUG(1),
+ "journal file %s does not exist, "
+ "creating it",
+ j->filename);
+ CHECK(journal_file_create(mctx, downgrade, filename));
+ /*
+ * Retry.
+ */
+ result = isc_stdio_open(j->filename, "rb+", &fp);
+ } else {
+ FAIL(ISC_R_NOTFOUND);
+ }
+ }
+ if (result != ISC_R_SUCCESS) {
+ isc_log_write(JOURNAL_COMMON_LOGARGS, ISC_LOG_ERROR,
+ "%s: open: %s", j->filename,
+ isc_result_totext(result));
+ FAIL(ISC_R_UNEXPECTED);
+ }
+
+ j->fp = fp;
+
+ /*
+ * Set magic early so that seek/read can succeed.
+ */
+ j->magic = DNS_JOURNAL_MAGIC;
+
+ CHECK(journal_seek(j, 0));
+ CHECK(journal_read(j, &rawheader, sizeof(rawheader)));
+
+ if (memcmp(rawheader.h.format, journal_header_ver1.format,
+ sizeof(journal_header_ver1.format)) == 0)
+ {
+ /*
+ * The file header says it's the old format, but it
+ * still might have the new xhdr format because we
+ * forgot to change the format string when we introduced
+ * the new xhdr. When we first try to read it, we assume
+ * it uses the new xhdr format. If that fails, we'll be
+ * called a second time with compat set to true, in which
+ * case we can lower xhdr_version to 1 if we find a
+ * corrupt transaction.
+ */
+ j->header_ver1 = true;
+ } else if (memcmp(rawheader.h.format, initial_journal_header.format,
+ sizeof(initial_journal_header.format)) == 0)
+ {
+ /*
+ * File header says this is format version 2; all
+ * transactions have to match.
+ */
+ j->header_ver1 = false;
+ } else {
+ isc_log_write(JOURNAL_COMMON_LOGARGS, ISC_LOG_ERROR,
+ "%s: journal format not recognized", j->filename);
+ FAIL(ISC_R_UNEXPECTED);
+ }
+ journal_header_decode(&rawheader, &j->header);
+
+ /*
+ * If there is an index, read the raw index into a dynamically
+ * allocated buffer and then convert it into a cooked index.
+ */
+ if (j->header.index_size != 0) {
+ unsigned int i;
+ unsigned int rawbytes;
+ unsigned char *p;
+
+ rawbytes = j->header.index_size * sizeof(journal_rawpos_t);
+ j->rawindex = isc_mem_get(mctx, rawbytes);
+
+ CHECK(journal_read(j, j->rawindex, rawbytes));
+
+ j->index = isc_mem_get(mctx, j->header.index_size *
+ sizeof(journal_pos_t));
+
+ p = j->rawindex;
+ for (i = 0; i < j->header.index_size; i++) {
+ j->index[i].serial = decode_uint32(p);
+ p += 4;
+ j->index[i].offset = decode_uint32(p);
+ p += 4;
+ }
+ INSIST(p == j->rawindex + rawbytes);
+ }
+ j->offset = -1; /* Invalid, must seek explicitly. */
+
+ /*
+ * Initialize the iterator.
+ */
+ dns_name_init(&j->it.name, NULL);
+ dns_rdata_init(&j->it.rdata);
+
+ /*
+ * Set up empty initial buffers for unchecked and checked
+ * wire format RR data. They will be reallocated
+ * later.
+ */
+ isc_buffer_init(&j->it.source, NULL, 0);
+ isc_buffer_init(&j->it.target, NULL, 0);
+ dns_decompress_init(&j->it.dctx, -1, DNS_DECOMPRESS_NONE);
+
+ j->state = writable ? JOURNAL_STATE_WRITE : JOURNAL_STATE_READ;
+
+ *journalp = j;
+ return (ISC_R_SUCCESS);
+
+failure:
+ j->magic = 0;
+ if (j->rawindex != NULL) {
+ isc_mem_put(j->mctx, j->rawindex,
+ j->header.index_size * sizeof(journal_rawpos_t));
+ }
+ if (j->index != NULL) {
+ isc_mem_put(j->mctx, j->index,
+ j->header.index_size * sizeof(journal_pos_t));
+ }
+ isc_mem_free(j->mctx, j->filename);
+ if (j->fp != NULL) {
+ (void)isc_stdio_close(j->fp);
+ }
+ isc_mem_putanddetach(&j->mctx, j, sizeof(*j));
+ return (result);
+}
+
+isc_result_t
+dns_journal_open(isc_mem_t *mctx, const char *filename, unsigned int mode,
+ dns_journal_t **journalp) {
+ isc_result_t result;
+ size_t namelen;
+ char backup[1024];
+ bool writable, create;
+
+ create = ((mode & DNS_JOURNAL_CREATE) != 0);
+ writable = ((mode & (DNS_JOURNAL_WRITE | DNS_JOURNAL_CREATE)) != 0);
+
+ result = journal_open(mctx, filename, writable, create, false,
+ journalp);
+ if (result == ISC_R_NOTFOUND) {
+ namelen = strlen(filename);
+ if (namelen > 4U && strcmp(filename + namelen - 4, ".jnl") == 0)
+ {
+ namelen -= 4;
+ }
+
+ result = snprintf(backup, sizeof(backup), "%.*s.jbk",
+ (int)namelen, filename);
+ if (result >= sizeof(backup)) {
+ return (ISC_R_NOSPACE);
+ }
+ result = journal_open(mctx, backup, writable, writable, false,
+ journalp);
+ }
+ return (result);
+}
+
+/*
+ * A comparison function defining the sorting order for
+ * entries in the IXFR-style journal file.
+ *
+ * The IXFR format requires that deletions are sorted before
+ * additions, and within either one, SOA records are sorted
+ * before others.
+ *
+ * Also sort the non-SOA records by type as a courtesy to the
+ * server receiving the IXFR - it may help reduce the amount of
+ * rdataset merging it has to do.
+ */
+static int
+ixfr_order(const void *av, const void *bv) {
+ dns_difftuple_t const *const *ap = av;
+ dns_difftuple_t const *const *bp = bv;
+ dns_difftuple_t const *a = *ap;
+ dns_difftuple_t const *b = *bp;
+ int r;
+ int bop = 0, aop = 0;
+
+ switch (a->op) {
+ case DNS_DIFFOP_DEL:
+ case DNS_DIFFOP_DELRESIGN:
+ aop = 1;
+ break;
+ case DNS_DIFFOP_ADD:
+ case DNS_DIFFOP_ADDRESIGN:
+ aop = 0;
+ break;
+ default:
+ UNREACHABLE();
+ }
+
+ switch (b->op) {
+ case DNS_DIFFOP_DEL:
+ case DNS_DIFFOP_DELRESIGN:
+ bop = 1;
+ break;
+ case DNS_DIFFOP_ADD:
+ case DNS_DIFFOP_ADDRESIGN:
+ bop = 0;
+ break;
+ default:
+ UNREACHABLE();
+ }
+
+ r = bop - aop;
+ if (r != 0) {
+ return (r);
+ }
+
+ r = (b->rdata.type == dns_rdatatype_soa) -
+ (a->rdata.type == dns_rdatatype_soa);
+ if (r != 0) {
+ return (r);
+ }
+
+ r = (a->rdata.type - b->rdata.type);
+ return (r);
+}
+
+static isc_result_t
+maybe_fixup_xhdr(dns_journal_t *j, journal_xhdr_t *xhdr, uint32_t serial,
+ isc_offset_t offset) {
+ isc_result_t result = ISC_R_SUCCESS;
+
+ /*
+ * Handle mixture of version 1 and version 2
+ * transaction headers in a version 1 journal.
+ */
+ if ((xhdr->serial0 != serial ||
+ isc_serial_le(xhdr->serial1, xhdr->serial0)))
+ {
+ if (j->xhdr_version == XHDR_VERSION1 && xhdr->serial1 == serial)
+ {
+ isc_log_write(
+ JOURNAL_COMMON_LOGARGS, ISC_LOG_DEBUG(3),
+ "%s: XHDR_VERSION1 -> XHDR_VERSION2 at %u",
+ j->filename, serial);
+ j->xhdr_version = XHDR_VERSION2;
+ CHECK(journal_seek(j, offset));
+ CHECK(journal_read_xhdr(j, xhdr));
+ j->recovered = true;
+ } else if (j->xhdr_version == XHDR_VERSION2 &&
+ xhdr->count == serial)
+ {
+ isc_log_write(
+ JOURNAL_COMMON_LOGARGS, ISC_LOG_DEBUG(3),
+ "%s: XHDR_VERSION2 -> XHDR_VERSION1 at %u",
+ j->filename, serial);
+ j->xhdr_version = XHDR_VERSION1;
+ CHECK(journal_seek(j, offset));
+ CHECK(journal_read_xhdr(j, xhdr));
+ j->recovered = true;
+ }
+ }
+
+ /*
+ * Handle <size, serial0, serial1, 0> transaction header.
+ */
+ if (j->xhdr_version == XHDR_VERSION1) {
+ uint32_t value;
+
+ CHECK(journal_read(j, &value, sizeof(value)));
+ if (value != 0L) {
+ CHECK(journal_seek(j, offset + 12));
+ } else {
+ isc_log_write(JOURNAL_COMMON_LOGARGS, ISC_LOG_DEBUG(3),
+ "%s: XHDR_VERSION1 count zero at %u",
+ j->filename, serial);
+ j->xhdr_version = XHDR_VERSION2;
+ j->recovered = true;
+ }
+ } else if (j->xhdr_version == XHDR_VERSION2 && xhdr->count == serial &&
+ xhdr->serial1 == 0U &&
+ isc_serial_gt(xhdr->serial0, xhdr->count))
+ {
+ isc_log_write(JOURNAL_COMMON_LOGARGS, ISC_LOG_DEBUG(3),
+ "%s: XHDR_VERSION2 count zero at %u", j->filename,
+ serial);
+ xhdr->serial1 = xhdr->serial0;
+ xhdr->serial0 = xhdr->count;
+ xhdr->count = 0;
+ j->recovered = true;
+ }
+
+failure:
+ return (result);
+}
+
+/*
+ * Advance '*pos' to the next journal transaction.
+ *
+ * Requires:
+ * *pos refers to a valid journal transaction.
+ *
+ * Ensures:
+ * When ISC_R_SUCCESS is returned,
+ * *pos refers to the next journal transaction.
+ *
+ * Returns one of:
+ *
+ * ISC_R_SUCCESS
+ * ISC_R_NOMORE *pos pointed at the last transaction
+ * Other results due to file errors are possible.
+ */
+static isc_result_t
+journal_next(dns_journal_t *j, journal_pos_t *pos) {
+ isc_result_t result;
+ journal_xhdr_t xhdr;
+ size_t hdrsize;
+
+ REQUIRE(DNS_JOURNAL_VALID(j));
+
+ result = journal_seek(j, pos->offset);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+
+ if (pos->serial == j->header.end.serial) {
+ return (ISC_R_NOMORE);
+ }
+
+ /*
+ * Read the header of the current transaction.
+ * This will return ISC_R_NOMORE if we are at EOF.
+ */
+ result = journal_read_xhdr(j, &xhdr);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+
+ if (j->header_ver1) {
+ CHECK(maybe_fixup_xhdr(j, &xhdr, pos->serial, pos->offset));
+ }
+
+ /*
+ * Check serial number consistency.
+ */
+ if (xhdr.serial0 != pos->serial ||
+ isc_serial_le(xhdr.serial1, xhdr.serial0))
+ {
+ isc_log_write(JOURNAL_COMMON_LOGARGS, ISC_LOG_ERROR,
+ "%s: journal file corrupt: "
+ "expected serial %u, got %u",
+ j->filename, pos->serial, xhdr.serial0);
+ return (ISC_R_UNEXPECTED);
+ }
+
+ /*
+ * Check for offset wraparound.
+ */
+ hdrsize = (j->xhdr_version == XHDR_VERSION2)
+ ? sizeof(journal_rawxhdr_t)
+ : sizeof(journal_rawxhdr_ver1_t);
+
+ if ((isc_offset_t)(pos->offset + hdrsize + xhdr.size) < pos->offset) {
+ isc_log_write(JOURNAL_COMMON_LOGARGS, ISC_LOG_ERROR,
+ "%s: offset too large", j->filename);
+ return (ISC_R_UNEXPECTED);
+ }
+
+ pos->offset += hdrsize + xhdr.size;
+ pos->serial = xhdr.serial1;
+ return (ISC_R_SUCCESS);
+
+failure:
+ return (result);
+}
+
+/*
+ * If the index of the journal 'j' contains an entry "better"
+ * than '*best_guess', replace '*best_guess' with it.
+ *
+ * "Better" means having a serial number closer to 'serial'
+ * but not greater than 'serial'.
+ */
+static void
+index_find(dns_journal_t *j, uint32_t serial, journal_pos_t *best_guess) {
+ unsigned int i;
+ if (j->index == NULL) {
+ return;
+ }
+ for (i = 0; i < j->header.index_size; i++) {
+ if (POS_VALID(j->index[i]) &&
+ DNS_SERIAL_GE(serial, j->index[i].serial) &&
+ DNS_SERIAL_GT(j->index[i].serial, best_guess->serial))
+ {
+ *best_guess = j->index[i];
+ }
+ }
+}
+
+/*
+ * Add a new index entry. If there is no room, make room by removing
+ * the odd-numbered entries and compacting the others into the first
+ * half of the index. This decimates old index entries exponentially
+ * over time, so that the index always contains a much larger fraction
+ * of recent serial numbers than of old ones. This is deliberate -
+ * most index searches are for outgoing IXFR, and IXFR tends to request
+ * recent versions more often than old ones.
+ */
+static void
+index_add(dns_journal_t *j, journal_pos_t *pos) {
+ unsigned int i;
+
+ if (j->index == NULL) {
+ return;
+ }
+
+ /*
+ * Search for a vacant position.
+ */
+ for (i = 0; i < j->header.index_size; i++) {
+ if (!POS_VALID(j->index[i])) {
+ break;
+ }
+ }
+ if (i == j->header.index_size) {
+ unsigned int k = 0;
+ /*
+ * Found no vacant position. Make some room.
+ */
+ for (i = 0; i < j->header.index_size; i += 2) {
+ j->index[k++] = j->index[i];
+ }
+ i = k; /* 'i' identifies the first vacant position. */
+ while (k < j->header.index_size) {
+ POS_INVALIDATE(j->index[k]);
+ k++;
+ }
+ }
+ INSIST(i < j->header.index_size);
+ INSIST(!POS_VALID(j->index[i]));
+
+ /*
+ * Store the new index entry.
+ */
+ j->index[i] = *pos;
+}
+
+/*
+ * Invalidate any existing index entries that could become
+ * ambiguous when a new transaction with number 'serial' is added.
+ */
+static void
+index_invalidate(dns_journal_t *j, uint32_t serial) {
+ unsigned int i;
+ if (j->index == NULL) {
+ return;
+ }
+ for (i = 0; i < j->header.index_size; i++) {
+ if (!DNS_SERIAL_GT(serial, j->index[i].serial)) {
+ POS_INVALIDATE(j->index[i]);
+ }
+ }
+}
+
+/*
+ * Try to find a transaction with initial serial number 'serial'
+ * in the journal 'j'.
+ *
+ * If found, store its position at '*pos' and return ISC_R_SUCCESS.
+ *
+ * If 'serial' is current (= the ending serial number of the
+ * last transaction in the journal), set '*pos' to
+ * the position immediately following the last transaction and
+ * return ISC_R_SUCCESS.
+ *
+ * If 'serial' is within the range of addressable serial numbers
+ * covered by the journal but that particular serial number is missing
+ * (from the journal, not just from the index), return ISC_R_NOTFOUND.
+ *
+ * If 'serial' is outside the range of addressable serial numbers
+ * covered by the journal, return ISC_R_RANGE.
+ *
+ */
+static isc_result_t
+journal_find(dns_journal_t *j, uint32_t serial, journal_pos_t *pos) {
+ isc_result_t result;
+ journal_pos_t current_pos;
+
+ REQUIRE(DNS_JOURNAL_VALID(j));
+
+ if (DNS_SERIAL_GT(j->header.begin.serial, serial)) {
+ return (ISC_R_RANGE);
+ }
+ if (DNS_SERIAL_GT(serial, j->header.end.serial)) {
+ return (ISC_R_RANGE);
+ }
+ if (serial == j->header.end.serial) {
+ *pos = j->header.end;
+ return (ISC_R_SUCCESS);
+ }
+
+ current_pos = j->header.begin;
+ index_find(j, serial, &current_pos);
+
+ while (current_pos.serial != serial) {
+ if (DNS_SERIAL_GT(current_pos.serial, serial)) {
+ return (ISC_R_NOTFOUND);
+ }
+ result = journal_next(j, &current_pos);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+ }
+ *pos = current_pos;
+ return (ISC_R_SUCCESS);
+}
+
+isc_result_t
+dns_journal_begin_transaction(dns_journal_t *j) {
+ uint32_t offset;
+ isc_result_t result;
+
+ REQUIRE(DNS_JOURNAL_VALID(j));
+ REQUIRE(j->state == JOURNAL_STATE_WRITE ||
+ j->state == JOURNAL_STATE_INLINE);
+
+ /*
+ * Find the file offset where the new transaction should
+ * be written, and seek there.
+ */
+ if (JOURNAL_EMPTY(&j->header)) {
+ offset = sizeof(journal_rawheader_t) +
+ j->header.index_size * sizeof(journal_rawpos_t);
+ } else {
+ offset = j->header.end.offset;
+ }
+ j->x.pos[0].offset = offset;
+ j->x.pos[1].offset = offset; /* Initial value, will be incremented. */
+ j->x.n_soa = 0;
+
+ CHECK(journal_seek(j, offset));
+
+ /*
+ * Write a dummy transaction header of all zeroes to reserve
+ * space. It will be filled in when the transaction is
+ * finished.
+ */
+ CHECK(journal_write_xhdr(j, 0, 0, 0, 0));
+ j->x.pos[1].offset = j->offset;
+
+ j->state = JOURNAL_STATE_TRANSACTION;
+ result = ISC_R_SUCCESS;
+failure:
+ return (result);
+}
+
+isc_result_t
+dns_journal_writediff(dns_journal_t *j, dns_diff_t *diff) {
+ dns_difftuple_t *t;
+ isc_buffer_t buffer;
+ void *mem = NULL;
+ uint64_t size = 0;
+ uint32_t rrcount = 0;
+ isc_result_t result;
+ isc_region_t used;
+
+ REQUIRE(DNS_DIFF_VALID(diff));
+ REQUIRE(j->state == JOURNAL_STATE_TRANSACTION);
+
+ isc_log_write(JOURNAL_DEBUG_LOGARGS(3), "writing to journal");
+ (void)dns_diff_print(diff, NULL);
+
+ /*
+ * Pass 1: determine the buffer size needed, and
+ * keep track of SOA serial numbers.
+ */
+ for (t = ISC_LIST_HEAD(diff->tuples); t != NULL;
+ t = ISC_LIST_NEXT(t, link))
+ {
+ if (t->rdata.type == dns_rdatatype_soa) {
+ if (j->x.n_soa < 2) {
+ j->x.pos[j->x.n_soa].serial =
+ dns_soa_getserial(&t->rdata);
+ }
+ j->x.n_soa++;
+ }
+ size += sizeof(journal_rawrrhdr_t);
+ size += t->name.length; /* XXX should have access macro? */
+ size += 10;
+ size += t->rdata.length;
+ }
+
+ if (size >= DNS_JOURNAL_SIZE_MAX) {
+ isc_log_write(JOURNAL_COMMON_LOGARGS, ISC_LOG_ERROR,
+ "dns_journal_writediff: %s: journal entry "
+ "too big to be stored: %" PRIu64 " bytes",
+ j->filename, size);
+ return (ISC_R_NOSPACE);
+ }
+
+ mem = isc_mem_get(j->mctx, size);
+
+ isc_buffer_init(&buffer, mem, size);
+
+ /*
+ * Pass 2. Write RRs to buffer.
+ */
+ for (t = ISC_LIST_HEAD(diff->tuples); t != NULL;
+ t = ISC_LIST_NEXT(t, link))
+ {
+ /*
+ * Write the RR header.
+ */
+ isc_buffer_putuint32(&buffer,
+ t->name.length + 10 + t->rdata.length);
+ /*
+ * Write the owner name, RR header, and RR data.
+ */
+ isc_buffer_putmem(&buffer, t->name.ndata, t->name.length);
+ isc_buffer_putuint16(&buffer, t->rdata.type);
+ isc_buffer_putuint16(&buffer, t->rdata.rdclass);
+ isc_buffer_putuint32(&buffer, t->ttl);
+ INSIST(t->rdata.length < 65536);
+ isc_buffer_putuint16(&buffer, (uint16_t)t->rdata.length);
+ INSIST(isc_buffer_availablelength(&buffer) >= t->rdata.length);
+ isc_buffer_putmem(&buffer, t->rdata.data, t->rdata.length);
+
+ rrcount++;
+ }
+
+ isc_buffer_usedregion(&buffer, &used);
+ INSIST(used.length == size);
+
+ j->x.pos[1].offset += used.length;
+ j->x.n_rr = rrcount;
+
+ /*
+ * Write the buffer contents to the journal file.
+ */
+ CHECK(journal_write(j, used.base, used.length));
+
+ result = ISC_R_SUCCESS;
+
+failure:
+ if (mem != NULL) {
+ isc_mem_put(j->mctx, mem, size);
+ }
+ return (result);
+}
+
+isc_result_t
+dns_journal_commit(dns_journal_t *j) {
+ isc_result_t result;
+ journal_rawheader_t rawheader;
+ uint64_t total;
+
+ REQUIRE(DNS_JOURNAL_VALID(j));
+ REQUIRE(j->state == JOURNAL_STATE_TRANSACTION ||
+ j->state == JOURNAL_STATE_INLINE);
+
+ /*
+ * Just write out a updated header.
+ */
+ if (j->state == JOURNAL_STATE_INLINE) {
+ CHECK(journal_fsync(j));
+ journal_header_encode(&j->header, &rawheader);
+ CHECK(journal_seek(j, 0));
+ CHECK(journal_write(j, &rawheader, sizeof(rawheader)));
+ CHECK(journal_fsync(j));
+ j->state = JOURNAL_STATE_WRITE;
+ return (ISC_R_SUCCESS);
+ }
+
+ /*
+ * Perform some basic consistency checks.
+ */
+ if (j->x.n_soa != 2) {
+ isc_log_write(JOURNAL_COMMON_LOGARGS, ISC_LOG_ERROR,
+ "%s: malformed transaction: %d SOAs", j->filename,
+ j->x.n_soa);
+ return (ISC_R_UNEXPECTED);
+ }
+ if (!DNS_SERIAL_GT(j->x.pos[1].serial, j->x.pos[0].serial)) {
+ isc_log_write(JOURNAL_COMMON_LOGARGS, ISC_LOG_ERROR,
+ "%s: malformed transaction: serial number "
+ "did not increase",
+ j->filename);
+ return (ISC_R_UNEXPECTED);
+ }
+ if (!JOURNAL_EMPTY(&j->header)) {
+ if (j->x.pos[0].serial != j->header.end.serial) {
+ isc_log_write(JOURNAL_COMMON_LOGARGS, ISC_LOG_ERROR,
+ "malformed transaction: "
+ "%s last serial %u != "
+ "transaction first serial %u",
+ j->filename, j->header.end.serial,
+ j->x.pos[0].serial);
+ return (ISC_R_UNEXPECTED);
+ }
+ }
+
+ /*
+ * We currently don't support huge journal entries.
+ */
+ total = j->x.pos[1].offset - j->x.pos[0].offset;
+ if (total >= DNS_JOURNAL_SIZE_MAX) {
+ isc_log_write(JOURNAL_COMMON_LOGARGS, ISC_LOG_ERROR,
+ "transaction too big to be stored in journal: "
+ "%" PRIu64 "b (max is %" PRIu64 "b)",
+ total, (uint64_t)DNS_JOURNAL_SIZE_MAX);
+ return (ISC_R_UNEXPECTED);
+ }
+
+ /*
+ * Some old journal entries may become non-addressable
+ * when we increment the current serial number. Purge them
+ * by stepping header.begin forward to the first addressable
+ * transaction. Also purge them from the index.
+ */
+ if (!JOURNAL_EMPTY(&j->header)) {
+ while (!DNS_SERIAL_GT(j->x.pos[1].serial,
+ j->header.begin.serial))
+ {
+ CHECK(journal_next(j, &j->header.begin));
+ }
+ index_invalidate(j, j->x.pos[1].serial);
+ }
+#ifdef notyet
+ if (DNS_SERIAL_GT(last_dumped_serial, j->x.pos[1].serial)) {
+ force_dump(...);
+ }
+#endif /* ifdef notyet */
+
+ /*
+ * Commit the transaction data to stable storage.
+ */
+ CHECK(journal_fsync(j));
+
+ if (j->state == JOURNAL_STATE_TRANSACTION) {
+ isc_offset_t offset;
+ offset = (j->x.pos[1].offset - j->x.pos[0].offset) -
+ (j->header_ver1 ? sizeof(journal_rawxhdr_ver1_t)
+ : sizeof(journal_rawxhdr_t));
+ /*
+ * Update the transaction header.
+ */
+ CHECK(journal_seek(j, j->x.pos[0].offset));
+ CHECK(journal_write_xhdr(j, offset, j->x.n_rr,
+ j->x.pos[0].serial,
+ j->x.pos[1].serial));
+ }
+
+ /*
+ * Update the journal header.
+ */
+ if (JOURNAL_EMPTY(&j->header)) {
+ j->header.begin = j->x.pos[0];
+ }
+ j->header.end = j->x.pos[1];
+ journal_header_encode(&j->header, &rawheader);
+ CHECK(journal_seek(j, 0));
+ CHECK(journal_write(j, &rawheader, sizeof(rawheader)));
+
+ /*
+ * Update the index.
+ */
+ index_add(j, &j->x.pos[0]);
+
+ /*
+ * Convert the index into on-disk format and write
+ * it to disk.
+ */
+ CHECK(index_to_disk(j));
+
+ /*
+ * Commit the header to stable storage.
+ */
+ CHECK(journal_fsync(j));
+
+ /*
+ * We no longer have a transaction open.
+ */
+ j->state = JOURNAL_STATE_WRITE;
+
+ result = ISC_R_SUCCESS;
+
+failure:
+ return (result);
+}
+
+isc_result_t
+dns_journal_write_transaction(dns_journal_t *j, dns_diff_t *diff) {
+ isc_result_t result;
+
+ CHECK(dns_diff_sort(diff, ixfr_order));
+ CHECK(dns_journal_begin_transaction(j));
+ CHECK(dns_journal_writediff(j, diff));
+ CHECK(dns_journal_commit(j));
+ result = ISC_R_SUCCESS;
+failure:
+ return (result);
+}
+
+void
+dns_journal_destroy(dns_journal_t **journalp) {
+ dns_journal_t *j = NULL;
+
+ REQUIRE(journalp != NULL);
+ REQUIRE(DNS_JOURNAL_VALID(*journalp));
+
+ j = *journalp;
+ *journalp = NULL;
+
+ j->it.result = ISC_R_FAILURE;
+ dns_name_invalidate(&j->it.name);
+ dns_decompress_invalidate(&j->it.dctx);
+ if (j->rawindex != NULL) {
+ isc_mem_put(j->mctx, j->rawindex,
+ j->header.index_size * sizeof(journal_rawpos_t));
+ }
+ if (j->index != NULL) {
+ isc_mem_put(j->mctx, j->index,
+ j->header.index_size * sizeof(journal_pos_t));
+ }
+ if (j->it.target.base != NULL) {
+ isc_mem_put(j->mctx, j->it.target.base, j->it.target.length);
+ }
+ if (j->it.source.base != NULL) {
+ isc_mem_put(j->mctx, j->it.source.base, j->it.source.length);
+ }
+ if (j->filename != NULL) {
+ isc_mem_free(j->mctx, j->filename);
+ }
+ if (j->fp != NULL) {
+ (void)isc_stdio_close(j->fp);
+ }
+ j->magic = 0;
+ isc_mem_putanddetach(&j->mctx, j, sizeof(*j));
+}
+
+/*
+ * Roll the open journal 'j' into the database 'db'.
+ * A new database version will be created.
+ */
+
+/* XXX Share code with incoming IXFR? */
+
+isc_result_t
+dns_journal_rollforward(dns_journal_t *j, dns_db_t *db, unsigned int options) {
+ isc_buffer_t source; /* Transaction data from disk */
+ isc_buffer_t target; /* Ditto after _fromwire check */
+ uint32_t db_serial; /* Database SOA serial */
+ uint32_t end_serial; /* Last journal SOA serial */
+ isc_result_t result;
+ dns_dbversion_t *ver = NULL;
+ journal_pos_t pos;
+ dns_diff_t diff;
+ unsigned int n_soa = 0;
+ unsigned int n_put = 0;
+ dns_diffop_t op;
+
+ REQUIRE(DNS_JOURNAL_VALID(j));
+ REQUIRE(DNS_DB_VALID(db));
+
+ dns_diff_init(j->mctx, &diff);
+
+ /*
+ * Set up empty initial buffers for unchecked and checked
+ * wire format transaction data. They will be reallocated
+ * later.
+ */
+ isc_buffer_init(&source, NULL, 0);
+ isc_buffer_init(&target, NULL, 0);
+
+ /*
+ * Create the new database version.
+ */
+ CHECK(dns_db_newversion(db, &ver));
+
+ /*
+ * Get the current database SOA serial number.
+ */
+ CHECK(dns_db_getsoaserial(db, ver, &db_serial));
+
+ /*
+ * Locate a journal entry for the current database serial.
+ */
+ CHECK(journal_find(j, db_serial, &pos));
+
+ end_serial = dns_journal_last_serial(j);
+
+ /*
+ * If we're reading a version 1 file, scan all the transactions
+ * to see if the journal needs rewriting: if any outdated
+ * transaction headers are found, j->recovered will be set.
+ */
+ if (j->header_ver1) {
+ uint32_t start_serial = dns_journal_first_serial(j);
+
+ CHECK(dns_journal_iter_init(j, start_serial, db_serial, NULL));
+ for (result = dns_journal_first_rr(j); result == ISC_R_SUCCESS;
+ result = dns_journal_next_rr(j))
+ {
+ continue;
+ }
+ }
+
+ if (db_serial == end_serial) {
+ CHECK(DNS_R_UPTODATE);
+ }
+
+ CHECK(dns_journal_iter_init(j, db_serial, end_serial, NULL));
+ for (result = dns_journal_first_rr(j); result == ISC_R_SUCCESS;
+ result = dns_journal_next_rr(j))
+ {
+ dns_name_t *name = NULL;
+ dns_rdata_t *rdata = NULL;
+ dns_difftuple_t *tuple = NULL;
+ uint32_t ttl;
+
+ dns_journal_current_rr(j, &name, &ttl, &rdata);
+
+ if (rdata->type == dns_rdatatype_soa) {
+ n_soa++;
+ if (n_soa == 2) {
+ db_serial = j->it.current_serial;
+ }
+ }
+
+ if (n_soa == 3) {
+ n_soa = 1;
+ }
+ if (n_soa == 0) {
+ isc_log_write(JOURNAL_COMMON_LOGARGS, ISC_LOG_ERROR,
+ "%s: journal file corrupt: missing "
+ "initial SOA",
+ j->filename);
+ FAIL(ISC_R_UNEXPECTED);
+ }
+ if ((options & DNS_JOURNALOPT_RESIGN) != 0) {
+ op = (n_soa == 1) ? DNS_DIFFOP_DELRESIGN
+ : DNS_DIFFOP_ADDRESIGN;
+ } else {
+ op = (n_soa == 1) ? DNS_DIFFOP_DEL : DNS_DIFFOP_ADD;
+ }
+
+ CHECK(dns_difftuple_create(diff.mctx, op, name, ttl, rdata,
+ &tuple));
+ dns_diff_append(&diff, &tuple);
+
+ if (++n_put > 100) {
+ isc_log_write(JOURNAL_DEBUG_LOGARGS(3),
+ "%s: applying diff to database (%u)",
+ j->filename, db_serial);
+ (void)dns_diff_print(&diff, NULL);
+ CHECK(dns_diff_apply(&diff, db, ver));
+ dns_diff_clear(&diff);
+ n_put = 0;
+ }
+ }
+ if (result == ISC_R_NOMORE) {
+ result = ISC_R_SUCCESS;
+ }
+ CHECK(result);
+
+ if (n_put != 0) {
+ isc_log_write(JOURNAL_DEBUG_LOGARGS(3),
+ "%s: applying final diff to database (%u)",
+ j->filename, db_serial);
+ (void)dns_diff_print(&diff, NULL);
+ CHECK(dns_diff_apply(&diff, db, ver));
+ dns_diff_clear(&diff);
+ }
+
+failure:
+ if (ver != NULL) {
+ dns_db_closeversion(db, &ver,
+ result == ISC_R_SUCCESS ? true : false);
+ }
+
+ if (source.base != NULL) {
+ isc_mem_put(j->mctx, source.base, source.length);
+ }
+ if (target.base != NULL) {
+ isc_mem_put(j->mctx, target.base, target.length);
+ }
+
+ dns_diff_clear(&diff);
+
+ INSIST(ver == NULL);
+
+ return (result);
+}
+
+isc_result_t
+dns_journal_print(isc_mem_t *mctx, uint32_t flags, const char *filename,
+ FILE *file) {
+ dns_journal_t *j = NULL;
+ isc_buffer_t source; /* Transaction data from disk */
+ isc_buffer_t target; /* Ditto after _fromwire check */
+ uint32_t start_serial; /* Database SOA serial */
+ uint32_t end_serial; /* Last journal SOA serial */
+ isc_result_t result;
+ dns_diff_t diff;
+ unsigned int n_soa = 0;
+ unsigned int n_put = 0;
+ bool printxhdr = ((flags & DNS_JOURNAL_PRINTXHDR) != 0);
+
+ REQUIRE(filename != NULL);
+
+ result = dns_journal_open(mctx, filename, DNS_JOURNAL_READ, &j);
+ if (result == ISC_R_NOTFOUND) {
+ isc_log_write(JOURNAL_DEBUG_LOGARGS(3), "no journal file");
+ return (DNS_R_NOJOURNAL);
+ } else if (result != ISC_R_SUCCESS) {
+ isc_log_write(JOURNAL_COMMON_LOGARGS, ISC_LOG_ERROR,
+ "journal open failure: %s: %s",
+ isc_result_totext(result), filename);
+ return (result);
+ }
+
+ if (printxhdr) {
+ fprintf(file, "Journal format = %sHeader version = %d\n",
+ j->header.format + 1, j->header_ver1 ? 1 : 2);
+ fprintf(file, "Start serial = %u\n", j->header.begin.serial);
+ fprintf(file, "End serial = %u\n", j->header.end.serial);
+ fprintf(file, "Index (size = %u):\n", j->header.index_size);
+ for (uint32_t i = 0; i < j->header.index_size; i++) {
+ if (j->index[i].offset == 0) {
+ fputc('\n', file);
+ break;
+ }
+ fprintf(file, "%lld", (long long)j->index[i].offset);
+ fputc((i + 1) % 8 == 0 ? '\n' : ' ', file);
+ }
+ }
+ if (j->header.serialset) {
+ fprintf(file, "Source serial = %u\n", j->header.sourceserial);
+ }
+ dns_diff_init(j->mctx, &diff);
+
+ /*
+ * Set up empty initial buffers for unchecked and checked
+ * wire format transaction data. They will be reallocated
+ * later.
+ */
+ isc_buffer_init(&source, NULL, 0);
+ isc_buffer_init(&target, NULL, 0);
+
+ start_serial = dns_journal_first_serial(j);
+ end_serial = dns_journal_last_serial(j);
+
+ CHECK(dns_journal_iter_init(j, start_serial, end_serial, NULL));
+
+ for (result = dns_journal_first_rr(j); result == ISC_R_SUCCESS;
+ result = dns_journal_next_rr(j))
+ {
+ dns_name_t *name = NULL;
+ dns_rdata_t *rdata = NULL;
+ dns_difftuple_t *tuple = NULL;
+ static uint32_t i = 0;
+ bool print = false;
+ uint32_t ttl;
+
+ dns_journal_current_rr(j, &name, &ttl, &rdata);
+
+ if (rdata->type == dns_rdatatype_soa) {
+ n_soa++;
+ if (n_soa == 3) {
+ n_soa = 1;
+ }
+ if (n_soa == 1) {
+ print = printxhdr;
+ }
+ }
+ if (n_soa == 0) {
+ isc_log_write(JOURNAL_COMMON_LOGARGS, ISC_LOG_ERROR,
+ "%s: journal file corrupt: missing "
+ "initial SOA",
+ j->filename);
+ FAIL(ISC_R_UNEXPECTED);
+ }
+
+ if (print) {
+ fprintf(file,
+ "Transaction: version %d offset %lld size %u "
+ "rrcount %u start %u end %u\n",
+ j->xhdr_version, (long long)j->it.cpos.offset,
+ j->curxhdr.size, j->curxhdr.count,
+ j->curxhdr.serial0, j->curxhdr.serial1);
+ if (j->it.cpos.offset > j->index[i].offset) {
+ fprintf(file,
+ "ERROR: Offset mismatch, "
+ "expected %lld\n",
+ (long long)j->index[i].offset);
+ } else if (j->it.cpos.offset == j->index[i].offset) {
+ i++;
+ }
+ }
+ CHECK(dns_difftuple_create(
+ diff.mctx, n_soa == 1 ? DNS_DIFFOP_DEL : DNS_DIFFOP_ADD,
+ name, ttl, rdata, &tuple));
+ dns_diff_append(&diff, &tuple);
+
+ if (++n_put > 100 || printxhdr) {
+ result = dns_diff_print(&diff, file);
+ dns_diff_clear(&diff);
+ n_put = 0;
+ if (result != ISC_R_SUCCESS) {
+ break;
+ }
+ }
+ }
+ if (result == ISC_R_NOMORE) {
+ result = ISC_R_SUCCESS;
+ }
+ CHECK(result);
+
+ if (n_put != 0) {
+ result = dns_diff_print(&diff, file);
+ dns_diff_clear(&diff);
+ }
+ goto cleanup;
+
+failure:
+ isc_log_write(JOURNAL_COMMON_LOGARGS, ISC_LOG_ERROR,
+ "%s: cannot print: journal file corrupt", j->filename);
+
+cleanup:
+ if (source.base != NULL) {
+ isc_mem_put(j->mctx, source.base, source.length);
+ }
+ if (target.base != NULL) {
+ isc_mem_put(j->mctx, target.base, target.length);
+ }
+
+ dns_diff_clear(&diff);
+ dns_journal_destroy(&j);
+
+ return (result);
+}
+
+/**************************************************************************/
+/*
+ * Miscellaneous accessors.
+ */
+bool
+dns_journal_empty(dns_journal_t *j) {
+ return (JOURNAL_EMPTY(&j->header));
+}
+
+bool
+dns_journal_recovered(dns_journal_t *j) {
+ return (j->recovered);
+}
+
+uint32_t
+dns_journal_first_serial(dns_journal_t *j) {
+ return (j->header.begin.serial);
+}
+
+uint32_t
+dns_journal_last_serial(dns_journal_t *j) {
+ return (j->header.end.serial);
+}
+
+void
+dns_journal_set_sourceserial(dns_journal_t *j, uint32_t sourceserial) {
+ REQUIRE(j->state == JOURNAL_STATE_WRITE ||
+ j->state == JOURNAL_STATE_INLINE ||
+ j->state == JOURNAL_STATE_TRANSACTION);
+
+ j->header.sourceserial = sourceserial;
+ j->header.serialset = true;
+ if (j->state == JOURNAL_STATE_WRITE) {
+ j->state = JOURNAL_STATE_INLINE;
+ }
+}
+
+bool
+dns_journal_get_sourceserial(dns_journal_t *j, uint32_t *sourceserial) {
+ REQUIRE(sourceserial != NULL);
+
+ if (!j->header.serialset) {
+ return (false);
+ }
+ *sourceserial = j->header.sourceserial;
+ return (true);
+}
+
+/**************************************************************************/
+/*
+ * Iteration support.
+ *
+ * When serving an outgoing IXFR, we transmit a part the journal starting
+ * at the serial number in the IXFR request and ending at the serial
+ * number that is current when the IXFR request arrives. The ending
+ * serial number is not necessarily at the end of the journal:
+ * the journal may grow while the IXFR is in progress, but we stop
+ * when we reach the serial number that was current when the IXFR started.
+ */
+
+static isc_result_t
+read_one_rr(dns_journal_t *j);
+
+/*
+ * Make sure the buffer 'b' is has at least 'size' bytes
+ * allocated, and clear it.
+ *
+ * Requires:
+ * Either b->base is NULL, or it points to b->length bytes of memory
+ * previously allocated by isc_mem_get().
+ */
+
+static isc_result_t
+size_buffer(isc_mem_t *mctx, isc_buffer_t *b, unsigned size) {
+ if (b->length < size) {
+ void *mem = isc_mem_get(mctx, size);
+ if (mem == NULL) {
+ return (ISC_R_NOMEMORY);
+ }
+ if (b->base != NULL) {
+ isc_mem_put(mctx, b->base, b->length);
+ }
+ b->base = mem;
+ b->length = size;
+ }
+ isc_buffer_clear(b);
+ return (ISC_R_SUCCESS);
+}
+
+isc_result_t
+dns_journal_iter_init(dns_journal_t *j, uint32_t begin_serial,
+ uint32_t end_serial, size_t *xfrsizep) {
+ isc_result_t result;
+
+ CHECK(journal_find(j, begin_serial, &j->it.bpos));
+ INSIST(j->it.bpos.serial == begin_serial);
+
+ CHECK(journal_find(j, end_serial, &j->it.epos));
+ INSIST(j->it.epos.serial == end_serial);
+
+ if (xfrsizep != NULL) {
+ journal_pos_t pos = j->it.bpos;
+ journal_xhdr_t xhdr;
+ uint64_t size = 0;
+ uint32_t count = 0;
+
+ /*
+ * We already know the beginning and ending serial
+ * numbers are in the journal. Scan through them,
+ * adding up sizes and RR counts so we can calculate
+ * the IXFR size.
+ */
+ do {
+ CHECK(journal_seek(j, pos.offset));
+ CHECK(journal_read_xhdr(j, &xhdr));
+
+ if (j->header_ver1) {
+ CHECK(maybe_fixup_xhdr(j, &xhdr, pos.serial,
+ pos.offset));
+ }
+
+ /*
+ * Check that xhdr is consistent.
+ */
+ if (xhdr.serial0 != pos.serial ||
+ isc_serial_le(xhdr.serial1, xhdr.serial0))
+ {
+ CHECK(ISC_R_UNEXPECTED);
+ }
+
+ size += xhdr.size;
+ count += xhdr.count;
+
+ result = journal_next(j, &pos);
+ if (result == ISC_R_NOMORE) {
+ result = ISC_R_SUCCESS;
+ }
+ CHECK(result);
+ } while (pos.serial != end_serial);
+
+ /*
+ * For each RR, subtract the length of the RR header,
+ * as this would not be present in IXFR messages.
+ * (We don't need to worry about the transaction header
+ * because that was already excluded from xdr.size.)
+ */
+ *xfrsizep = size - (count * sizeof(journal_rawrrhdr_t));
+ }
+
+ result = ISC_R_SUCCESS;
+failure:
+ j->it.result = result;
+ return (j->it.result);
+}
+
+isc_result_t
+dns_journal_first_rr(dns_journal_t *j) {
+ isc_result_t result;
+
+ /*
+ * Seek to the beginning of the first transaction we are
+ * interested in.
+ */
+ CHECK(journal_seek(j, j->it.bpos.offset));
+ j->it.current_serial = j->it.bpos.serial;
+
+ j->it.xsize = 0; /* We have no transaction data yet... */
+ j->it.xpos = 0; /* ...and haven't used any of it. */
+
+ return (read_one_rr(j));
+
+failure:
+ return (result);
+}
+
+static isc_result_t
+read_one_rr(dns_journal_t *j) {
+ isc_result_t result;
+ dns_rdatatype_t rdtype;
+ dns_rdataclass_t rdclass;
+ unsigned int rdlen;
+ uint32_t ttl;
+ journal_xhdr_t xhdr;
+ journal_rrhdr_t rrhdr;
+ dns_journal_t save = *j;
+
+ if (j->offset > j->it.epos.offset) {
+ isc_log_write(JOURNAL_COMMON_LOGARGS, ISC_LOG_ERROR,
+ "%s: journal corrupt: possible integer overflow",
+ j->filename);
+ return (ISC_R_UNEXPECTED);
+ }
+ if (j->offset == j->it.epos.offset) {
+ return (ISC_R_NOMORE);
+ }
+ if (j->it.xpos == j->it.xsize) {
+ /*
+ * We are at a transaction boundary.
+ * Read another transaction header.
+ */
+ CHECK(journal_read_xhdr(j, &xhdr));
+ if (xhdr.size == 0) {
+ isc_log_write(JOURNAL_COMMON_LOGARGS, ISC_LOG_ERROR,
+ "%s: journal corrupt: empty transaction",
+ j->filename);
+ FAIL(ISC_R_UNEXPECTED);
+ }
+
+ if (j->header_ver1) {
+ CHECK(maybe_fixup_xhdr(j, &xhdr, j->it.current_serial,
+ save.offset));
+ }
+
+ if (xhdr.serial0 != j->it.current_serial ||
+ isc_serial_le(xhdr.serial1, xhdr.serial0))
+ {
+ isc_log_write(JOURNAL_COMMON_LOGARGS, ISC_LOG_ERROR,
+ "%s: journal file corrupt: "
+ "expected serial %u, got %u",
+ j->filename, j->it.current_serial,
+ xhdr.serial0);
+ FAIL(ISC_R_UNEXPECTED);
+ }
+
+ j->it.xsize = xhdr.size;
+ j->it.xpos = 0;
+ }
+ /*
+ * Read an RR.
+ */
+ CHECK(journal_read_rrhdr(j, &rrhdr));
+ /*
+ * Perform a sanity check on the journal RR size.
+ * The smallest possible RR has a 1-byte owner name
+ * and a 10-byte header. The largest possible
+ * RR has 65535 bytes of data, a header, and a maximum-
+ * size owner name, well below 70 k total.
+ */
+ if (rrhdr.size < 1 + 10 || rrhdr.size > 70000) {
+ isc_log_write(JOURNAL_COMMON_LOGARGS, ISC_LOG_ERROR,
+ "%s: journal corrupt: impossible RR size "
+ "(%d bytes)",
+ j->filename, rrhdr.size);
+ FAIL(ISC_R_UNEXPECTED);
+ }
+
+ CHECK(size_buffer(j->mctx, &j->it.source, rrhdr.size));
+ CHECK(journal_read(j, j->it.source.base, rrhdr.size));
+ isc_buffer_add(&j->it.source, rrhdr.size);
+
+ /*
+ * The target buffer is made the same size
+ * as the source buffer, with the assumption that when
+ * no compression in present, the output of dns_*_fromwire()
+ * is no larger than the input.
+ */
+ CHECK(size_buffer(j->mctx, &j->it.target, rrhdr.size));
+
+ /*
+ * Parse the owner name. We don't know where it
+ * ends yet, so we make the entire "remaining"
+ * part of the buffer "active".
+ */
+ isc_buffer_setactive(&j->it.source,
+ j->it.source.used - j->it.source.current);
+ CHECK(dns_name_fromwire(&j->it.name, &j->it.source, &j->it.dctx, 0,
+ &j->it.target));
+
+ /*
+ * Check that the RR header is there, and parse it.
+ */
+ if (isc_buffer_remaininglength(&j->it.source) < 10) {
+ FAIL(DNS_R_FORMERR);
+ }
+
+ rdtype = isc_buffer_getuint16(&j->it.source);
+ rdclass = isc_buffer_getuint16(&j->it.source);
+ ttl = isc_buffer_getuint32(&j->it.source);
+ rdlen = isc_buffer_getuint16(&j->it.source);
+
+ if (rdlen > DNS_RDATA_MAXLENGTH) {
+ isc_log_write(JOURNAL_COMMON_LOGARGS, ISC_LOG_ERROR,
+ "%s: journal corrupt: impossible rdlen "
+ "(%u bytes)",
+ j->filename, rdlen);
+ FAIL(ISC_R_FAILURE);
+ }
+
+ /*
+ * Parse the rdata.
+ */
+ if (isc_buffer_remaininglength(&j->it.source) != rdlen) {
+ FAIL(DNS_R_FORMERR);
+ }
+ isc_buffer_setactive(&j->it.source, rdlen);
+ dns_rdata_reset(&j->it.rdata);
+ CHECK(dns_rdata_fromwire(&j->it.rdata, rdclass, rdtype, &j->it.source,
+ &j->it.dctx, 0, &j->it.target));
+ j->it.ttl = ttl;
+
+ j->it.xpos += sizeof(journal_rawrrhdr_t) + rrhdr.size;
+ if (rdtype == dns_rdatatype_soa) {
+ /* XXX could do additional consistency checks here */
+ j->it.current_serial = dns_soa_getserial(&j->it.rdata);
+ }
+
+ result = ISC_R_SUCCESS;
+
+failure:
+ j->it.result = result;
+ return (result);
+}
+
+isc_result_t
+dns_journal_next_rr(dns_journal_t *j) {
+ j->it.result = read_one_rr(j);
+ return (j->it.result);
+}
+
+void
+dns_journal_current_rr(dns_journal_t *j, dns_name_t **name, uint32_t *ttl,
+ dns_rdata_t **rdata) {
+ REQUIRE(j->it.result == ISC_R_SUCCESS);
+ *name = &j->it.name;
+ *ttl = j->it.ttl;
+ *rdata = &j->it.rdata;
+}
+
+/**************************************************************************/
+/*
+ * Generating diffs from databases
+ */
+
+/*
+ * Construct a diff containing all the RRs at the current name of the
+ * database iterator 'dbit' in database 'db', version 'ver'.
+ * Set '*name' to the current name, and append the diff to 'diff'.
+ * All new tuples will have the operation 'op'.
+ *
+ * Requires: 'name' must have buffer large enough to hold the name.
+ * Typically, a dns_fixedname_t would be used.
+ */
+static isc_result_t
+get_name_diff(dns_db_t *db, dns_dbversion_t *ver, isc_stdtime_t now,
+ dns_dbiterator_t *dbit, dns_name_t *name, dns_diffop_t op,
+ dns_diff_t *diff) {
+ isc_result_t result;
+ dns_dbnode_t *node = NULL;
+ dns_rdatasetiter_t *rdsiter = NULL;
+ dns_difftuple_t *tuple = NULL;
+
+ result = dns_dbiterator_current(dbit, &node, name);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+
+ result = dns_db_allrdatasets(db, node, ver, 0, now, &rdsiter);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup_node;
+ }
+
+ for (result = dns_rdatasetiter_first(rdsiter); result == ISC_R_SUCCESS;
+ result = dns_rdatasetiter_next(rdsiter))
+ {
+ dns_rdataset_t rdataset;
+
+ dns_rdataset_init(&rdataset);
+ dns_rdatasetiter_current(rdsiter, &rdataset);
+
+ for (result = dns_rdataset_first(&rdataset);
+ result == ISC_R_SUCCESS;
+ result = dns_rdataset_next(&rdataset))
+ {
+ dns_rdata_t rdata = DNS_RDATA_INIT;
+ dns_rdataset_current(&rdataset, &rdata);
+ result = dns_difftuple_create(diff->mctx, op, name,
+ rdataset.ttl, &rdata,
+ &tuple);
+ if (result != ISC_R_SUCCESS) {
+ dns_rdataset_disassociate(&rdataset);
+ goto cleanup_iterator;
+ }
+ dns_diff_append(diff, &tuple);
+ }
+ dns_rdataset_disassociate(&rdataset);
+ if (result != ISC_R_NOMORE) {
+ goto cleanup_iterator;
+ }
+ }
+ if (result != ISC_R_NOMORE) {
+ goto cleanup_iterator;
+ }
+
+ result = ISC_R_SUCCESS;
+
+cleanup_iterator:
+ dns_rdatasetiter_destroy(&rdsiter);
+
+cleanup_node:
+ dns_db_detachnode(db, &node);
+
+ return (result);
+}
+
+/*
+ * Comparison function for use by dns_diff_subtract when sorting
+ * the diffs to be subtracted. The sort keys are the rdata type
+ * and the rdata itself. The owner name is ignored, because
+ * it is known to be the same for all tuples.
+ */
+static int
+rdata_order(const void *av, const void *bv) {
+ dns_difftuple_t const *const *ap = av;
+ dns_difftuple_t const *const *bp = bv;
+ dns_difftuple_t const *a = *ap;
+ dns_difftuple_t const *b = *bp;
+ int r;
+ r = (b->rdata.type - a->rdata.type);
+ if (r != 0) {
+ return (r);
+ }
+ r = dns_rdata_compare(&a->rdata, &b->rdata);
+ return (r);
+}
+
+static isc_result_t
+dns_diff_subtract(dns_diff_t diff[2], dns_diff_t *r) {
+ isc_result_t result;
+ dns_difftuple_t *p[2];
+ int i, t;
+ bool append;
+ dns_difftuplelist_t add, del;
+
+ CHECK(dns_diff_sort(&diff[0], rdata_order));
+ CHECK(dns_diff_sort(&diff[1], rdata_order));
+ ISC_LIST_INIT(add);
+ ISC_LIST_INIT(del);
+
+ for (;;) {
+ p[0] = ISC_LIST_HEAD(diff[0].tuples);
+ p[1] = ISC_LIST_HEAD(diff[1].tuples);
+ if (p[0] == NULL && p[1] == NULL) {
+ break;
+ }
+
+ for (i = 0; i < 2; i++) {
+ if (p[!i] == NULL) {
+ dns_difftuplelist_t *l = (i == 0) ? &add : &del;
+ ISC_LIST_UNLINK(diff[i].tuples, p[i], link);
+ ISC_LIST_APPEND(*l, p[i], link);
+ goto next;
+ }
+ }
+ t = rdata_order(&p[0], &p[1]);
+ if (t < 0) {
+ ISC_LIST_UNLINK(diff[0].tuples, p[0], link);
+ ISC_LIST_APPEND(add, p[0], link);
+ goto next;
+ }
+ if (t > 0) {
+ ISC_LIST_UNLINK(diff[1].tuples, p[1], link);
+ ISC_LIST_APPEND(del, p[1], link);
+ goto next;
+ }
+ INSIST(t == 0);
+ /*
+ * Identical RRs in both databases; skip them both
+ * if the ttl differs.
+ */
+ append = (p[0]->ttl != p[1]->ttl);
+ for (i = 0; i < 2; i++) {
+ ISC_LIST_UNLINK(diff[i].tuples, p[i], link);
+ if (append) {
+ dns_difftuplelist_t *l = (i == 0) ? &add : &del;
+ ISC_LIST_APPEND(*l, p[i], link);
+ } else {
+ dns_difftuple_free(&p[i]);
+ }
+ }
+ next:;
+ }
+ ISC_LIST_APPENDLIST(r->tuples, del, link);
+ ISC_LIST_APPENDLIST(r->tuples, add, link);
+ result = ISC_R_SUCCESS;
+failure:
+ return (result);
+}
+
+static isc_result_t
+diff_namespace(dns_db_t *dba, dns_dbversion_t *dbvera, dns_db_t *dbb,
+ dns_dbversion_t *dbverb, unsigned int options,
+ dns_diff_t *resultdiff) {
+ dns_db_t *db[2];
+ dns_dbversion_t *ver[2];
+ dns_dbiterator_t *dbit[2] = { NULL, NULL };
+ bool have[2] = { false, false };
+ dns_fixedname_t fixname[2];
+ isc_result_t result, itresult[2];
+ dns_diff_t diff[2];
+ int i, t;
+
+ db[0] = dba, db[1] = dbb;
+ ver[0] = dbvera, ver[1] = dbverb;
+
+ dns_diff_init(resultdiff->mctx, &diff[0]);
+ dns_diff_init(resultdiff->mctx, &diff[1]);
+
+ dns_fixedname_init(&fixname[0]);
+ dns_fixedname_init(&fixname[1]);
+
+ result = dns_db_createiterator(db[0], options, &dbit[0]);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+ result = dns_db_createiterator(db[1], options, &dbit[1]);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup_iterator;
+ }
+
+ itresult[0] = dns_dbiterator_first(dbit[0]);
+ itresult[1] = dns_dbiterator_first(dbit[1]);
+
+ for (;;) {
+ for (i = 0; i < 2; i++) {
+ if (!have[i] && itresult[i] == ISC_R_SUCCESS) {
+ CHECK(get_name_diff(
+ db[i], ver[i], 0, dbit[i],
+ dns_fixedname_name(&fixname[i]),
+ i == 0 ? DNS_DIFFOP_ADD
+ : DNS_DIFFOP_DEL,
+ &diff[i]));
+ itresult[i] = dns_dbiterator_next(dbit[i]);
+ have[i] = true;
+ }
+ }
+
+ if (!have[0] && !have[1]) {
+ INSIST(ISC_LIST_EMPTY(diff[0].tuples));
+ INSIST(ISC_LIST_EMPTY(diff[1].tuples));
+ break;
+ }
+
+ for (i = 0; i < 2; i++) {
+ if (!have[!i]) {
+ ISC_LIST_APPENDLIST(resultdiff->tuples,
+ diff[i].tuples, link);
+ INSIST(ISC_LIST_EMPTY(diff[i].tuples));
+ have[i] = false;
+ goto next;
+ }
+ }
+
+ t = dns_name_compare(dns_fixedname_name(&fixname[0]),
+ dns_fixedname_name(&fixname[1]));
+ if (t < 0) {
+ ISC_LIST_APPENDLIST(resultdiff->tuples, diff[0].tuples,
+ link);
+ INSIST(ISC_LIST_EMPTY(diff[0].tuples));
+ have[0] = false;
+ continue;
+ }
+ if (t > 0) {
+ ISC_LIST_APPENDLIST(resultdiff->tuples, diff[1].tuples,
+ link);
+ INSIST(ISC_LIST_EMPTY(diff[1].tuples));
+ have[1] = false;
+ continue;
+ }
+ INSIST(t == 0);
+ CHECK(dns_diff_subtract(diff, resultdiff));
+ INSIST(ISC_LIST_EMPTY(diff[0].tuples));
+ INSIST(ISC_LIST_EMPTY(diff[1].tuples));
+ have[0] = have[1] = false;
+ next:;
+ }
+ if (itresult[0] != ISC_R_NOMORE) {
+ FAIL(itresult[0]);
+ }
+ if (itresult[1] != ISC_R_NOMORE) {
+ FAIL(itresult[1]);
+ }
+
+ INSIST(ISC_LIST_EMPTY(diff[0].tuples));
+ INSIST(ISC_LIST_EMPTY(diff[1].tuples));
+
+failure:
+ dns_dbiterator_destroy(&dbit[1]);
+
+cleanup_iterator:
+ dns_dbiterator_destroy(&dbit[0]);
+ dns_diff_clear(&diff[0]);
+ dns_diff_clear(&diff[1]);
+ return (result);
+}
+
+/*
+ * Compare the databases 'dba' and 'dbb' and generate a journal
+ * entry containing the changes to make 'dba' from 'dbb' (note
+ * the order). This journal entry will consist of a single,
+ * possibly very large transaction.
+ */
+isc_result_t
+dns_db_diff(isc_mem_t *mctx, dns_db_t *dba, dns_dbversion_t *dbvera,
+ dns_db_t *dbb, dns_dbversion_t *dbverb, const char *filename) {
+ isc_result_t result;
+ dns_diff_t diff;
+
+ dns_diff_init(mctx, &diff);
+
+ result = dns_db_diffx(&diff, dba, dbvera, dbb, dbverb, filename);
+
+ dns_diff_clear(&diff);
+
+ return (result);
+}
+
+isc_result_t
+dns_db_diffx(dns_diff_t *diff, dns_db_t *dba, dns_dbversion_t *dbvera,
+ dns_db_t *dbb, dns_dbversion_t *dbverb, const char *filename) {
+ isc_result_t result;
+ dns_journal_t *journal = NULL;
+
+ if (filename != NULL) {
+ result = dns_journal_open(diff->mctx, filename,
+ DNS_JOURNAL_CREATE, &journal);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+ }
+
+ CHECK(diff_namespace(dba, dbvera, dbb, dbverb, DNS_DB_NONSEC3, diff));
+ CHECK(diff_namespace(dba, dbvera, dbb, dbverb, DNS_DB_NSEC3ONLY, diff));
+
+ if (journal != NULL) {
+ if (ISC_LIST_EMPTY(diff->tuples)) {
+ isc_log_write(JOURNAL_DEBUG_LOGARGS(3), "no changes");
+ } else {
+ CHECK(dns_journal_write_transaction(journal, diff));
+ }
+ }
+
+failure:
+ if (journal != NULL) {
+ dns_journal_destroy(&journal);
+ }
+ return (result);
+}
+
+static uint32_t
+rrcount(unsigned char *buf, unsigned int size) {
+ isc_buffer_t b;
+ uint32_t rrsize, count = 0;
+
+ isc_buffer_init(&b, buf, size);
+ isc_buffer_add(&b, size);
+ while (isc_buffer_remaininglength(&b) > 0) {
+ rrsize = isc_buffer_getuint32(&b);
+ INSIST(isc_buffer_remaininglength(&b) >= rrsize);
+ isc_buffer_forward(&b, rrsize);
+ count++;
+ }
+
+ return (count);
+}
+
+static bool
+check_delta(unsigned char *buf, size_t size) {
+ isc_buffer_t b;
+ uint32_t rrsize;
+
+ isc_buffer_init(&b, buf, size);
+ isc_buffer_add(&b, size);
+ while (isc_buffer_remaininglength(&b) > 0) {
+ if (isc_buffer_remaininglength(&b) < 4) {
+ return (false);
+ }
+ rrsize = isc_buffer_getuint32(&b);
+ /* "." + type + class + ttl + rdlen => 11U */
+ if (rrsize < 11U || isc_buffer_remaininglength(&b) < rrsize) {
+ return (false);
+ }
+ isc_buffer_forward(&b, rrsize);
+ }
+
+ return (true);
+}
+
+isc_result_t
+dns_journal_compact(isc_mem_t *mctx, char *filename, uint32_t serial,
+ uint32_t flags, uint32_t target_size) {
+ unsigned int i;
+ journal_pos_t best_guess;
+ journal_pos_t current_pos;
+ dns_journal_t *j1 = NULL;
+ dns_journal_t *j2 = NULL;
+ journal_rawheader_t rawheader;
+ unsigned int len;
+ size_t namelen;
+ unsigned char *buf = NULL;
+ unsigned int size = 0;
+ isc_result_t result;
+ unsigned int indexend;
+ char newname[PATH_MAX];
+ char backup[PATH_MAX];
+ bool is_backup = false;
+ bool rewrite = false;
+ bool downgrade = false;
+
+ REQUIRE(filename != NULL);
+
+ namelen = strlen(filename);
+ if (namelen > 4U && strcmp(filename + namelen - 4, ".jnl") == 0) {
+ namelen -= 4;
+ }
+
+ result = snprintf(newname, sizeof(newname), "%.*s.jnw", (int)namelen,
+ filename);
+ RUNTIME_CHECK(result < sizeof(newname));
+
+ result = snprintf(backup, sizeof(backup), "%.*s.jbk", (int)namelen,
+ filename);
+ RUNTIME_CHECK(result < sizeof(backup));
+
+ result = journal_open(mctx, filename, false, false, false, &j1);
+ if (result == ISC_R_NOTFOUND) {
+ is_backup = true;
+ result = journal_open(mctx, backup, false, false, false, &j1);
+ }
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+
+ /*
+ * Always perform a re-write when processing a version 1 journal.
+ */
+ rewrite = j1->header_ver1;
+
+ /*
+ * Check whether we need to rewrite the whole journal
+ * file (for example, to upversion it).
+ */
+ if ((flags & DNS_JOURNAL_COMPACTALL) != 0) {
+ if ((flags & DNS_JOURNAL_VERSION1) != 0) {
+ downgrade = true;
+ }
+ rewrite = true;
+ serial = dns_journal_first_serial(j1);
+ } else if (JOURNAL_EMPTY(&j1->header)) {
+ dns_journal_destroy(&j1);
+ return (ISC_R_SUCCESS);
+ }
+
+ if (DNS_SERIAL_GT(j1->header.begin.serial, serial) ||
+ DNS_SERIAL_GT(serial, j1->header.end.serial))
+ {
+ dns_journal_destroy(&j1);
+ return (ISC_R_RANGE);
+ }
+
+ /*
+ * Cope with very small target sizes.
+ */
+ indexend = sizeof(journal_rawheader_t) +
+ j1->header.index_size * sizeof(journal_rawpos_t);
+ if (target_size < DNS_JOURNAL_SIZE_MIN) {
+ target_size = DNS_JOURNAL_SIZE_MIN;
+ }
+ if (target_size < indexend * 2) {
+ target_size = target_size / 2 + indexend;
+ }
+
+ /*
+ * See if there is any work to do.
+ */
+ if (!rewrite && (uint32_t)j1->header.end.offset < target_size) {
+ dns_journal_destroy(&j1);
+ return (ISC_R_SUCCESS);
+ }
+
+ CHECK(journal_open(mctx, newname, true, true, downgrade, &j2));
+ CHECK(journal_seek(j2, indexend));
+
+ /*
+ * Remove overhead so space test below can succeed.
+ */
+ if (target_size >= indexend) {
+ target_size -= indexend;
+ }
+
+ /*
+ * Find if we can create enough free space.
+ */
+ best_guess = j1->header.begin;
+ for (i = 0; i < j1->header.index_size; i++) {
+ if (POS_VALID(j1->index[i]) &&
+ DNS_SERIAL_GE(serial, j1->index[i].serial) &&
+ ((uint32_t)(j1->header.end.offset - j1->index[i].offset) >=
+ target_size / 2) &&
+ j1->index[i].offset > best_guess.offset)
+ {
+ best_guess = j1->index[i];
+ }
+ }
+
+ current_pos = best_guess;
+ while (current_pos.serial != serial) {
+ CHECK(journal_next(j1, &current_pos));
+ if (current_pos.serial == j1->header.end.serial) {
+ break;
+ }
+
+ if (DNS_SERIAL_GE(serial, current_pos.serial) &&
+ ((uint32_t)(j1->header.end.offset - current_pos.offset) >=
+ (target_size / 2)) &&
+ current_pos.offset > best_guess.offset)
+ {
+ best_guess = current_pos;
+ } else {
+ break;
+ }
+ }
+
+ INSIST(best_guess.serial != j1->header.end.serial);
+ if (best_guess.serial != serial) {
+ CHECK(journal_next(j1, &best_guess));
+ serial = best_guess.serial;
+ }
+
+ /*
+ * We should now be roughly half target_size provided
+ * we did not reach 'serial'. If not we will just copy
+ * all uncommitted deltas regardless of the size.
+ */
+ len = j1->header.end.offset - best_guess.offset;
+ if (len != 0) {
+ CHECK(journal_seek(j1, best_guess.offset));
+
+ /* Prepare new header */
+ j2->header.begin.serial = best_guess.serial;
+ j2->header.begin.offset = indexend;
+ j2->header.sourceserial = j1->header.sourceserial;
+ j2->header.serialset = j1->header.serialset;
+ j2->header.end.serial = j1->header.end.serial;
+
+ /*
+ * Only use this method if we're rewriting the
+ * journal to fix outdated transaction headers;
+ * otherwise we'll copy the whole journal without
+ * parsing individual deltas below.
+ */
+ while (rewrite && len > 0) {
+ journal_xhdr_t xhdr;
+ isc_offset_t offset = j1->offset;
+ uint32_t count;
+
+ result = journal_read_xhdr(j1, &xhdr);
+ if (rewrite && result == ISC_R_NOMORE) {
+ break;
+ }
+ CHECK(result);
+
+ size = xhdr.size;
+ if (size > len) {
+ isc_log_write(JOURNAL_COMMON_LOGARGS,
+ ISC_LOG_ERROR,
+ "%s: journal file corrupt, "
+ "transaction too large",
+ j1->filename);
+ CHECK(ISC_R_FAILURE);
+ }
+ buf = isc_mem_get(mctx, size);
+ result = journal_read(j1, buf, size);
+
+ /*
+ * If we're repairing an outdated journal, the
+ * xhdr format may be wrong.
+ */
+ if (rewrite && (result != ISC_R_SUCCESS ||
+ !check_delta(buf, size)))
+ {
+ if (j1->xhdr_version == XHDR_VERSION2) {
+ /* XHDR_VERSION2 -> XHDR_VERSION1 */
+ j1->xhdr_version = XHDR_VERSION1;
+ CHECK(journal_seek(j1, offset));
+ CHECK(journal_read_xhdr(j1, &xhdr));
+ } else if (j1->xhdr_version == XHDR_VERSION1) {
+ /* XHDR_VERSION1 -> XHDR_VERSION2 */
+ j1->xhdr_version = XHDR_VERSION2;
+ CHECK(journal_seek(j1, offset));
+ CHECK(journal_read_xhdr(j1, &xhdr));
+ }
+
+ /* Check again */
+ isc_mem_put(mctx, buf, size);
+ size = xhdr.size;
+ if (size > len) {
+ isc_log_write(
+ JOURNAL_COMMON_LOGARGS,
+ ISC_LOG_ERROR,
+ "%s: journal file corrupt, "
+ "transaction too large",
+ j1->filename);
+ CHECK(ISC_R_FAILURE);
+ }
+ buf = isc_mem_get(mctx, size);
+ CHECK(journal_read(j1, buf, size));
+
+ if (!check_delta(buf, size)) {
+ CHECK(ISC_R_UNEXPECTED);
+ }
+ } else {
+ CHECK(result);
+ }
+
+ /*
+ * Recover from incorrectly written transaction header.
+ * The incorrect header was written as size, serial0,
+ * serial1, and 0. XHDR_VERSION2 is expecting size,
+ * count, serial0, and serial1.
+ */
+ if (j1->xhdr_version == XHDR_VERSION2 &&
+ xhdr.count == serial && xhdr.serial1 == 0U &&
+ isc_serial_gt(xhdr.serial0, xhdr.count))
+ {
+ xhdr.serial1 = xhdr.serial0;
+ xhdr.serial0 = xhdr.count;
+ xhdr.count = 0;
+ }
+
+ /*
+ * Check that xhdr is consistent.
+ */
+ if (xhdr.serial0 != serial ||
+ isc_serial_le(xhdr.serial1, xhdr.serial0))
+ {
+ CHECK(ISC_R_UNEXPECTED);
+ }
+
+ /*
+ * Extract record count from the transaction. This
+ * is needed when converting from XHDR_VERSION1 to
+ * XHDR_VERSION2, and when recovering from an
+ * incorrectly written XHDR_VERSION2.
+ */
+ count = rrcount(buf, size);
+ CHECK(journal_write_xhdr(j2, xhdr.size, count,
+ xhdr.serial0, xhdr.serial1));
+ CHECK(journal_write(j2, buf, size));
+
+ j2->header.end.offset = j2->offset;
+
+ serial = xhdr.serial1;
+
+ len = j1->header.end.offset - j1->offset;
+ isc_mem_put(mctx, buf, size);
+ }
+
+ /*
+ * If we're not rewriting transaction headers, we can use
+ * this faster method instead.
+ */
+ if (!rewrite) {
+ size = ISC_MIN(64 * 1024, len);
+ buf = isc_mem_get(mctx, size);
+ for (i = 0; i < len; i += size) {
+ unsigned int blob = ISC_MIN(size, len - i);
+ CHECK(journal_read(j1, buf, blob));
+ CHECK(journal_write(j2, buf, blob));
+ }
+
+ j2->header.end.offset = indexend + len;
+ }
+
+ CHECK(journal_fsync(j2));
+
+ /*
+ * Update the journal header.
+ */
+ journal_header_encode(&j2->header, &rawheader);
+ CHECK(journal_seek(j2, 0));
+ CHECK(journal_write(j2, &rawheader, sizeof(rawheader)));
+ CHECK(journal_fsync(j2));
+
+ /*
+ * Build new index.
+ */
+ current_pos = j2->header.begin;
+ while (current_pos.serial != j2->header.end.serial) {
+ index_add(j2, &current_pos);
+ CHECK(journal_next(j2, &current_pos));
+ }
+
+ /*
+ * Write index.
+ */
+ CHECK(index_to_disk(j2));
+ CHECK(journal_fsync(j2));
+
+ indexend = j2->header.end.offset;
+ POST(indexend);
+ }
+
+ /*
+ * Close both journals before trying to rename files.
+ */
+ dns_journal_destroy(&j1);
+ dns_journal_destroy(&j2);
+
+ /*
+ * With a UFS file system this should just succeed and be atomic.
+ * Any IXFR outs will just continue and the old journal will be
+ * removed on final close.
+ *
+ * With MSDOS / NTFS we need to do a two stage rename, triggered
+ * by EEXIST. (If any IXFR's are running in other threads, however,
+ * this will fail, and the journal will not be compacted. But
+ * if so, hopefully they'll be finished by the next time we
+ * compact.)
+ */
+ if (rename(newname, filename) == -1) {
+ if (errno == EEXIST && !is_backup) {
+ result = isc_file_remove(backup);
+ if (result != ISC_R_SUCCESS &&
+ result != ISC_R_FILENOTFOUND)
+ {
+ goto failure;
+ }
+ if (rename(filename, backup) == -1) {
+ goto maperrno;
+ }
+ if (rename(newname, filename) == -1) {
+ goto maperrno;
+ }
+ (void)isc_file_remove(backup);
+ } else {
+ maperrno:
+ result = ISC_R_FAILURE;
+ goto failure;
+ }
+ }
+
+ result = ISC_R_SUCCESS;
+
+failure:
+ (void)isc_file_remove(newname);
+ if (buf != NULL) {
+ isc_mem_put(mctx, buf, size);
+ }
+ if (j1 != NULL) {
+ dns_journal_destroy(&j1);
+ }
+ if (j2 != NULL) {
+ dns_journal_destroy(&j2);
+ }
+ return (result);
+}
+
+static isc_result_t
+index_to_disk(dns_journal_t *j) {
+ isc_result_t result = ISC_R_SUCCESS;
+
+ if (j->header.index_size != 0) {
+ unsigned int i;
+ unsigned char *p;
+ unsigned int rawbytes;
+
+ rawbytes = j->header.index_size * sizeof(journal_rawpos_t);
+
+ p = j->rawindex;
+ for (i = 0; i < j->header.index_size; i++) {
+ encode_uint32(j->index[i].serial, p);
+ p += 4;
+ encode_uint32(j->index[i].offset, p);
+ p += 4;
+ }
+ INSIST(p == j->rawindex + rawbytes);
+
+ CHECK(journal_seek(j, sizeof(journal_rawheader_t)));
+ CHECK(journal_write(j, j->rawindex, rawbytes));
+ }
+failure:
+ return (result);
+}
diff --git a/lib/dns/kasp.c b/lib/dns/kasp.c
new file mode 100644
index 0000000..d53abfb
--- /dev/null
+++ b/lib/dns/kasp.c
@@ -0,0 +1,515 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/*! \file */
+
+#include <string.h>
+
+#include <isc/assertions.h>
+#include <isc/buffer.h>
+#include <isc/file.h>
+#include <isc/hex.h>
+#include <isc/log.h>
+#include <isc/mem.h>
+#include <isc/util.h>
+
+#include <dns/kasp.h>
+#include <dns/keyvalues.h>
+#include <dns/log.h>
+
+/* Default TTLsig (maximum zone ttl) */
+#define DEFAULT_TTLSIG 604800 /* one week */
+
+isc_result_t
+dns_kasp_create(isc_mem_t *mctx, const char *name, dns_kasp_t **kaspp) {
+ dns_kasp_t *kasp;
+ dns_kasp_t k = {
+ .magic = DNS_KASP_MAGIC,
+ };
+
+ REQUIRE(name != NULL);
+ REQUIRE(kaspp != NULL && *kaspp == NULL);
+
+ kasp = isc_mem_get(mctx, sizeof(*kasp));
+ *kasp = k;
+
+ kasp->mctx = NULL;
+ isc_mem_attach(mctx, &kasp->mctx);
+ kasp->name = isc_mem_strdup(mctx, name);
+ isc_mutex_init(&kasp->lock);
+ isc_refcount_init(&kasp->references, 1);
+
+ ISC_LINK_INIT(kasp, link);
+ ISC_LIST_INIT(kasp->keys);
+
+ *kaspp = kasp;
+ return (ISC_R_SUCCESS);
+}
+
+void
+dns_kasp_attach(dns_kasp_t *source, dns_kasp_t **targetp) {
+ REQUIRE(DNS_KASP_VALID(source));
+ REQUIRE(targetp != NULL && *targetp == NULL);
+
+ isc_refcount_increment(&source->references);
+ *targetp = source;
+}
+
+static void
+destroy(dns_kasp_t *kasp) {
+ dns_kasp_key_t *key;
+ dns_kasp_key_t *key_next;
+
+ REQUIRE(!ISC_LINK_LINKED(kasp, link));
+
+ for (key = ISC_LIST_HEAD(kasp->keys); key != NULL; key = key_next) {
+ key_next = ISC_LIST_NEXT(key, link);
+ ISC_LIST_UNLINK(kasp->keys, key, link);
+ dns_kasp_key_destroy(key);
+ }
+ INSIST(ISC_LIST_EMPTY(kasp->keys));
+
+ isc_mutex_destroy(&kasp->lock);
+ isc_mem_free(kasp->mctx, kasp->name);
+ isc_mem_putanddetach(&kasp->mctx, kasp, sizeof(*kasp));
+}
+
+void
+dns_kasp_detach(dns_kasp_t **kaspp) {
+ REQUIRE(kaspp != NULL && DNS_KASP_VALID(*kaspp));
+
+ dns_kasp_t *kasp = *kaspp;
+ *kaspp = NULL;
+
+ if (isc_refcount_decrement(&kasp->references) == 1) {
+ destroy(kasp);
+ }
+}
+
+const char *
+dns_kasp_getname(dns_kasp_t *kasp) {
+ REQUIRE(DNS_KASP_VALID(kasp));
+
+ return (kasp->name);
+}
+
+void
+dns_kasp_freeze(dns_kasp_t *kasp) {
+ REQUIRE(DNS_KASP_VALID(kasp));
+ REQUIRE(!kasp->frozen);
+
+ kasp->frozen = true;
+}
+
+void
+dns_kasp_thaw(dns_kasp_t *kasp) {
+ REQUIRE(DNS_KASP_VALID(kasp));
+ REQUIRE(kasp->frozen);
+
+ kasp->frozen = false;
+}
+
+uint32_t
+dns_kasp_signdelay(dns_kasp_t *kasp) {
+ REQUIRE(DNS_KASP_VALID(kasp));
+ REQUIRE(kasp->frozen);
+
+ return (kasp->signatures_validity - kasp->signatures_refresh);
+}
+
+uint32_t
+dns_kasp_sigrefresh(dns_kasp_t *kasp) {
+ REQUIRE(DNS_KASP_VALID(kasp));
+ REQUIRE(kasp->frozen);
+
+ return (kasp->signatures_refresh);
+}
+
+void
+dns_kasp_setsigrefresh(dns_kasp_t *kasp, uint32_t value) {
+ REQUIRE(DNS_KASP_VALID(kasp));
+ REQUIRE(!kasp->frozen);
+
+ kasp->signatures_refresh = value;
+}
+
+uint32_t
+dns_kasp_sigvalidity(dns_kasp_t *kasp) {
+ REQUIRE(DNS_KASP_VALID(kasp));
+ REQUIRE(kasp->frozen);
+
+ return (kasp->signatures_validity);
+}
+
+void
+dns_kasp_setsigvalidity(dns_kasp_t *kasp, uint32_t value) {
+ REQUIRE(DNS_KASP_VALID(kasp));
+ REQUIRE(!kasp->frozen);
+
+ kasp->signatures_validity = value;
+}
+
+uint32_t
+dns_kasp_sigvalidity_dnskey(dns_kasp_t *kasp) {
+ REQUIRE(DNS_KASP_VALID(kasp));
+ REQUIRE(kasp->frozen);
+
+ return (kasp->signatures_validity_dnskey);
+}
+
+void
+dns_kasp_setsigvalidity_dnskey(dns_kasp_t *kasp, uint32_t value) {
+ REQUIRE(DNS_KASP_VALID(kasp));
+ REQUIRE(!kasp->frozen);
+
+ kasp->signatures_validity_dnskey = value;
+}
+
+dns_ttl_t
+dns_kasp_dnskeyttl(dns_kasp_t *kasp) {
+ REQUIRE(DNS_KASP_VALID(kasp));
+ REQUIRE(kasp->frozen);
+
+ return (kasp->dnskey_ttl);
+}
+
+void
+dns_kasp_setdnskeyttl(dns_kasp_t *kasp, dns_ttl_t ttl) {
+ REQUIRE(DNS_KASP_VALID(kasp));
+ REQUIRE(!kasp->frozen);
+
+ kasp->dnskey_ttl = ttl;
+}
+
+uint32_t
+dns_kasp_purgekeys(dns_kasp_t *kasp) {
+ REQUIRE(DNS_KASP_VALID(kasp));
+ REQUIRE(kasp->frozen);
+
+ return (kasp->purge_keys);
+}
+
+void
+dns_kasp_setpurgekeys(dns_kasp_t *kasp, uint32_t value) {
+ REQUIRE(DNS_KASP_VALID(kasp));
+ REQUIRE(!kasp->frozen);
+
+ kasp->purge_keys = value;
+}
+
+uint32_t
+dns_kasp_publishsafety(dns_kasp_t *kasp) {
+ REQUIRE(DNS_KASP_VALID(kasp));
+ REQUIRE(kasp->frozen);
+
+ return (kasp->publish_safety);
+}
+
+void
+dns_kasp_setpublishsafety(dns_kasp_t *kasp, uint32_t value) {
+ REQUIRE(DNS_KASP_VALID(kasp));
+ REQUIRE(!kasp->frozen);
+
+ kasp->publish_safety = value;
+}
+
+uint32_t
+dns_kasp_retiresafety(dns_kasp_t *kasp) {
+ REQUIRE(DNS_KASP_VALID(kasp));
+ REQUIRE(kasp->frozen);
+
+ return (kasp->retire_safety);
+}
+
+void
+dns_kasp_setretiresafety(dns_kasp_t *kasp, uint32_t value) {
+ REQUIRE(DNS_KASP_VALID(kasp));
+ REQUIRE(!kasp->frozen);
+
+ kasp->retire_safety = value;
+}
+
+dns_ttl_t
+dns_kasp_zonemaxttl(dns_kasp_t *kasp, bool fallback) {
+ REQUIRE(DNS_KASP_VALID(kasp));
+ REQUIRE(kasp->frozen);
+
+ if (kasp->zone_max_ttl == 0 && fallback) {
+ return (DEFAULT_TTLSIG);
+ }
+ return (kasp->zone_max_ttl);
+}
+
+void
+dns_kasp_setzonemaxttl(dns_kasp_t *kasp, dns_ttl_t ttl) {
+ REQUIRE(DNS_KASP_VALID(kasp));
+ REQUIRE(!kasp->frozen);
+
+ kasp->zone_max_ttl = ttl;
+}
+
+uint32_t
+dns_kasp_zonepropagationdelay(dns_kasp_t *kasp) {
+ REQUIRE(DNS_KASP_VALID(kasp));
+ REQUIRE(kasp->frozen);
+
+ return (kasp->zone_propagation_delay);
+}
+
+void
+dns_kasp_setzonepropagationdelay(dns_kasp_t *kasp, uint32_t value) {
+ REQUIRE(DNS_KASP_VALID(kasp));
+ REQUIRE(!kasp->frozen);
+
+ kasp->zone_propagation_delay = value;
+}
+
+dns_ttl_t
+dns_kasp_dsttl(dns_kasp_t *kasp) {
+ REQUIRE(DNS_KASP_VALID(kasp));
+ REQUIRE(kasp->frozen);
+
+ return (kasp->parent_ds_ttl);
+}
+
+void
+dns_kasp_setdsttl(dns_kasp_t *kasp, dns_ttl_t ttl) {
+ REQUIRE(DNS_KASP_VALID(kasp));
+ REQUIRE(!kasp->frozen);
+
+ kasp->parent_ds_ttl = ttl;
+}
+
+uint32_t
+dns_kasp_parentpropagationdelay(dns_kasp_t *kasp) {
+ REQUIRE(DNS_KASP_VALID(kasp));
+ REQUIRE(kasp->frozen);
+
+ return (kasp->parent_propagation_delay);
+}
+
+void
+dns_kasp_setparentpropagationdelay(dns_kasp_t *kasp, uint32_t value) {
+ REQUIRE(DNS_KASP_VALID(kasp));
+ REQUIRE(!kasp->frozen);
+
+ kasp->parent_propagation_delay = value;
+}
+
+isc_result_t
+dns_kasplist_find(dns_kasplist_t *list, const char *name, dns_kasp_t **kaspp) {
+ dns_kasp_t *kasp = NULL;
+
+ REQUIRE(kaspp != NULL && *kaspp == NULL);
+
+ if (list == NULL) {
+ return (ISC_R_NOTFOUND);
+ }
+
+ for (kasp = ISC_LIST_HEAD(*list); kasp != NULL;
+ kasp = ISC_LIST_NEXT(kasp, link))
+ {
+ if (strcmp(kasp->name, name) == 0) {
+ break;
+ }
+ }
+
+ if (kasp == NULL) {
+ return (ISC_R_NOTFOUND);
+ }
+
+ dns_kasp_attach(kasp, kaspp);
+ return (ISC_R_SUCCESS);
+}
+
+dns_kasp_keylist_t
+dns_kasp_keys(dns_kasp_t *kasp) {
+ REQUIRE(DNS_KASP_VALID(kasp));
+ REQUIRE(kasp->frozen);
+
+ return (kasp->keys);
+}
+
+bool
+dns_kasp_keylist_empty(dns_kasp_t *kasp) {
+ REQUIRE(DNS_KASP_VALID(kasp));
+
+ return (ISC_LIST_EMPTY(kasp->keys));
+}
+
+void
+dns_kasp_addkey(dns_kasp_t *kasp, dns_kasp_key_t *key) {
+ REQUIRE(DNS_KASP_VALID(kasp));
+ REQUIRE(!kasp->frozen);
+ REQUIRE(key != NULL);
+
+ ISC_LIST_APPEND(kasp->keys, key, link);
+}
+
+isc_result_t
+dns_kasp_key_create(dns_kasp_t *kasp, dns_kasp_key_t **keyp) {
+ dns_kasp_key_t *key;
+
+ REQUIRE(DNS_KASP_VALID(kasp));
+ REQUIRE(keyp != NULL && *keyp == NULL);
+
+ key = isc_mem_get(kasp->mctx, sizeof(*key));
+ key->mctx = NULL;
+ isc_mem_attach(kasp->mctx, &key->mctx);
+
+ ISC_LINK_INIT(key, link);
+
+ key->lifetime = 0;
+ key->algorithm = 0;
+ key->length = -1;
+ key->role = 0;
+ *keyp = key;
+ return (ISC_R_SUCCESS);
+}
+
+void
+dns_kasp_key_destroy(dns_kasp_key_t *key) {
+ REQUIRE(key != NULL);
+
+ isc_mem_putanddetach(&key->mctx, key, sizeof(*key));
+}
+
+uint32_t
+dns_kasp_key_algorithm(dns_kasp_key_t *key) {
+ REQUIRE(key != NULL);
+
+ return (key->algorithm);
+}
+
+unsigned int
+dns_kasp_key_size(dns_kasp_key_t *key) {
+ unsigned int size = 0;
+ unsigned int min = 0;
+
+ REQUIRE(key != NULL);
+
+ switch (key->algorithm) {
+ case DNS_KEYALG_RSASHA1:
+ case DNS_KEYALG_NSEC3RSASHA1:
+ case DNS_KEYALG_RSASHA256:
+ case DNS_KEYALG_RSASHA512:
+ min = (key->algorithm == DNS_KEYALG_RSASHA512) ? 1024 : 512;
+ if (key->length > -1) {
+ size = (unsigned int)key->length;
+ if (size < min) {
+ size = min;
+ }
+ if (size > 4096) {
+ size = 4096;
+ }
+ } else {
+ size = 2048;
+ }
+ break;
+ case DNS_KEYALG_ECDSA256:
+ size = 256;
+ break;
+ case DNS_KEYALG_ECDSA384:
+ size = 384;
+ break;
+ case DNS_KEYALG_ED25519:
+ size = 256;
+ break;
+ case DNS_KEYALG_ED448:
+ size = 456;
+ break;
+ default:
+ /* unsupported */
+ break;
+ }
+ return (size);
+}
+
+uint32_t
+dns_kasp_key_lifetime(dns_kasp_key_t *key) {
+ REQUIRE(key != NULL);
+
+ return (key->lifetime);
+}
+
+bool
+dns_kasp_key_ksk(dns_kasp_key_t *key) {
+ REQUIRE(key != NULL);
+
+ return (key->role & DNS_KASP_KEY_ROLE_KSK);
+}
+
+bool
+dns_kasp_key_zsk(dns_kasp_key_t *key) {
+ REQUIRE(key != NULL);
+
+ return (key->role & DNS_KASP_KEY_ROLE_ZSK);
+}
+
+uint8_t
+dns_kasp_nsec3iter(dns_kasp_t *kasp) {
+ REQUIRE(kasp != NULL);
+ REQUIRE(kasp->frozen);
+ REQUIRE(kasp->nsec3);
+
+ return (kasp->nsec3param.iterations);
+}
+
+uint8_t
+dns_kasp_nsec3flags(dns_kasp_t *kasp) {
+ REQUIRE(kasp != NULL);
+ REQUIRE(kasp->frozen);
+ REQUIRE(kasp->nsec3);
+
+ if (kasp->nsec3param.optout) {
+ return (0x01);
+ }
+ return (0x00);
+}
+
+uint8_t
+dns_kasp_nsec3saltlen(dns_kasp_t *kasp) {
+ REQUIRE(kasp != NULL);
+ REQUIRE(kasp->frozen);
+ REQUIRE(kasp->nsec3);
+
+ return (kasp->nsec3param.saltlen);
+}
+
+bool
+dns_kasp_nsec3(dns_kasp_t *kasp) {
+ REQUIRE(kasp != NULL);
+ REQUIRE(kasp->frozen);
+
+ return kasp->nsec3;
+}
+
+void
+dns_kasp_setnsec3(dns_kasp_t *kasp, bool nsec3) {
+ REQUIRE(kasp != NULL);
+ REQUIRE(!kasp->frozen);
+
+ kasp->nsec3 = nsec3;
+}
+
+void
+dns_kasp_setnsec3param(dns_kasp_t *kasp, uint8_t iter, bool optout,
+ uint8_t saltlen) {
+ REQUIRE(kasp != NULL);
+ REQUIRE(!kasp->frozen);
+ REQUIRE(kasp->nsec3);
+
+ kasp->nsec3param.iterations = iter;
+ kasp->nsec3param.optout = optout;
+ kasp->nsec3param.saltlen = saltlen;
+}
diff --git a/lib/dns/key.c b/lib/dns/key.c
new file mode 100644
index 0000000..7d4f378
--- /dev/null
+++ b/lib/dns/key.c
@@ -0,0 +1,192 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#include <inttypes.h>
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdlib.h>
+
+#include <isc/region.h>
+#include <isc/util.h>
+
+#include <dns/keyvalues.h>
+
+#include <dst/dst.h>
+
+#include "dst_internal.h"
+
+uint16_t
+dst_region_computeid(const isc_region_t *source) {
+ uint32_t ac;
+ const unsigned char *p;
+ int size;
+
+ REQUIRE(source != NULL);
+ REQUIRE(source->length >= 4);
+
+ p = source->base;
+ size = source->length;
+
+ for (ac = 0; size > 1; size -= 2, p += 2) {
+ ac += ((*p) << 8) + *(p + 1);
+ }
+
+ if (size > 0) {
+ ac += ((*p) << 8);
+ }
+ ac += (ac >> 16) & 0xffff;
+
+ return ((uint16_t)(ac & 0xffff));
+}
+
+uint16_t
+dst_region_computerid(const isc_region_t *source) {
+ uint32_t ac;
+ const unsigned char *p;
+ int size;
+
+ REQUIRE(source != NULL);
+ REQUIRE(source->length >= 4);
+
+ p = source->base;
+ size = source->length;
+
+ ac = ((*p) << 8) + *(p + 1);
+ ac |= DNS_KEYFLAG_REVOKE;
+ for (size -= 2, p += 2; size > 1; size -= 2, p += 2) {
+ ac += ((*p) << 8) + *(p + 1);
+ }
+
+ if (size > 0) {
+ ac += ((*p) << 8);
+ }
+ ac += (ac >> 16) & 0xffff;
+
+ return ((uint16_t)(ac & 0xffff));
+}
+
+dns_name_t *
+dst_key_name(const dst_key_t *key) {
+ REQUIRE(VALID_KEY(key));
+ return (key->key_name);
+}
+
+unsigned int
+dst_key_size(const dst_key_t *key) {
+ REQUIRE(VALID_KEY(key));
+ return (key->key_size);
+}
+
+unsigned int
+dst_key_proto(const dst_key_t *key) {
+ REQUIRE(VALID_KEY(key));
+ return (key->key_proto);
+}
+
+unsigned int
+dst_key_alg(const dst_key_t *key) {
+ REQUIRE(VALID_KEY(key));
+ return (key->key_alg);
+}
+
+uint32_t
+dst_key_flags(const dst_key_t *key) {
+ REQUIRE(VALID_KEY(key));
+ return (key->key_flags);
+}
+
+dns_keytag_t
+dst_key_id(const dst_key_t *key) {
+ REQUIRE(VALID_KEY(key));
+ return (key->key_id);
+}
+
+dns_keytag_t
+dst_key_rid(const dst_key_t *key) {
+ REQUIRE(VALID_KEY(key));
+ return (key->key_rid);
+}
+
+dns_rdataclass_t
+dst_key_class(const dst_key_t *key) {
+ REQUIRE(VALID_KEY(key));
+ return (key->key_class);
+}
+
+bool
+dst_key_iszonekey(const dst_key_t *key) {
+ REQUIRE(VALID_KEY(key));
+
+ if ((key->key_flags & DNS_KEYTYPE_NOAUTH) != 0) {
+ return (false);
+ }
+ if ((key->key_flags & DNS_KEYFLAG_OWNERMASK) != DNS_KEYOWNER_ZONE) {
+ return (false);
+ }
+ if (key->key_proto != DNS_KEYPROTO_DNSSEC &&
+ key->key_proto != DNS_KEYPROTO_ANY)
+ {
+ return (false);
+ }
+ return (true);
+}
+
+bool
+dst_key_isnullkey(const dst_key_t *key) {
+ REQUIRE(VALID_KEY(key));
+
+ if ((key->key_flags & DNS_KEYFLAG_TYPEMASK) != DNS_KEYTYPE_NOKEY) {
+ return (false);
+ }
+ if ((key->key_flags & DNS_KEYFLAG_OWNERMASK) != DNS_KEYOWNER_ZONE) {
+ return (false);
+ }
+ if (key->key_proto != DNS_KEYPROTO_DNSSEC &&
+ key->key_proto != DNS_KEYPROTO_ANY)
+ {
+ return (false);
+ }
+ return (true);
+}
+
+void
+dst_key_setbits(dst_key_t *key, uint16_t bits) {
+ unsigned int maxbits;
+ REQUIRE(VALID_KEY(key));
+ if (bits != 0) {
+ RUNTIME_CHECK(dst_key_sigsize(key, &maxbits) == ISC_R_SUCCESS);
+ maxbits *= 8;
+ REQUIRE(bits <= maxbits);
+ }
+ key->key_bits = bits;
+}
+
+uint16_t
+dst_key_getbits(const dst_key_t *key) {
+ REQUIRE(VALID_KEY(key));
+ return (key->key_bits);
+}
+
+void
+dst_key_setttl(dst_key_t *key, dns_ttl_t ttl) {
+ REQUIRE(VALID_KEY(key));
+ key->key_ttl = ttl;
+}
+
+dns_ttl_t
+dst_key_getttl(const dst_key_t *key) {
+ REQUIRE(VALID_KEY(key));
+ return (key->key_ttl);
+}
+
+/*! \file */
diff --git a/lib/dns/keydata.c b/lib/dns/keydata.c
new file mode 100644
index 0000000..c8e2d80
--- /dev/null
+++ b/lib/dns/keydata.c
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/*! \file */
+
+#include <inttypes.h>
+
+#include <isc/buffer.h>
+#include <isc/mem.h>
+#include <isc/string.h>
+#include <isc/util.h>
+
+#include <dns/keydata.h>
+#include <dns/rdata.h>
+#include <dns/rdatastruct.h>
+
+isc_result_t
+dns_keydata_todnskey(dns_rdata_keydata_t *keydata, dns_rdata_dnskey_t *dnskey,
+ isc_mem_t *mctx) {
+ REQUIRE(keydata != NULL && dnskey != NULL);
+
+ dnskey->common.rdtype = dns_rdatatype_dnskey;
+ dnskey->common.rdclass = keydata->common.rdclass;
+ dnskey->mctx = mctx;
+ dnskey->flags = keydata->flags;
+ dnskey->protocol = keydata->protocol;
+ dnskey->algorithm = keydata->algorithm;
+
+ dnskey->datalen = keydata->datalen;
+
+ if (mctx == NULL) {
+ dnskey->data = keydata->data;
+ } else {
+ dnskey->data = isc_mem_allocate(mctx, dnskey->datalen);
+ memmove(dnskey->data, keydata->data, dnskey->datalen);
+ }
+
+ return (ISC_R_SUCCESS);
+}
+
+isc_result_t
+dns_keydata_fromdnskey(dns_rdata_keydata_t *keydata, dns_rdata_dnskey_t *dnskey,
+ uint32_t refresh, uint32_t addhd, uint32_t removehd,
+ isc_mem_t *mctx) {
+ REQUIRE(keydata != NULL && dnskey != NULL);
+
+ keydata->common.rdtype = dns_rdatatype_keydata;
+ keydata->common.rdclass = dnskey->common.rdclass;
+ keydata->mctx = mctx;
+ keydata->refresh = refresh;
+ keydata->addhd = addhd;
+ keydata->removehd = removehd;
+ keydata->flags = dnskey->flags;
+ keydata->protocol = dnskey->protocol;
+ keydata->algorithm = dnskey->algorithm;
+
+ keydata->datalen = dnskey->datalen;
+ if (mctx == NULL) {
+ keydata->data = dnskey->data;
+ } else {
+ keydata->data = isc_mem_allocate(mctx, keydata->datalen);
+ memmove(keydata->data, dnskey->data, keydata->datalen);
+ }
+
+ return (ISC_R_SUCCESS);
+}
diff --git a/lib/dns/keymgr.c b/lib/dns/keymgr.c
new file mode 100644
index 0000000..106b376
--- /dev/null
+++ b/lib/dns/keymgr.c
@@ -0,0 +1,2647 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/*! \file */
+
+#include <inttypes.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#include <isc/buffer.h>
+#include <isc/dir.h>
+#include <isc/mem.h>
+#include <isc/print.h>
+#include <isc/result.h>
+#include <isc/string.h>
+#include <isc/util.h>
+
+#include <dns/dnssec.h>
+#include <dns/kasp.h>
+#include <dns/keymgr.h>
+#include <dns/keyvalues.h>
+#include <dns/log.h>
+
+#include <dst/dst.h>
+
+#define RETERR(x) \
+ do { \
+ result = (x); \
+ if (result != ISC_R_SUCCESS) \
+ goto failure; \
+ } while (0)
+
+/*
+ * Set key state to `target` state and change last changed
+ * to `time`, only if key state has not been set before.
+ */
+#define INITIALIZE_STATE(key, state, timing, target, time) \
+ do { \
+ dst_key_state_t s; \
+ char keystr[DST_KEY_FORMATSIZE]; \
+ if (dst_key_getstate((key), (state), &s) == ISC_R_NOTFOUND) { \
+ dst_key_setstate((key), (state), (target)); \
+ dst_key_settime((key), (timing), time); \
+ \
+ if (isc_log_wouldlog(dns_lctx, ISC_LOG_DEBUG(1))) { \
+ dst_key_format((key), keystr, sizeof(keystr)); \
+ isc_log_write( \
+ dns_lctx, DNS_LOGCATEGORY_DNSSEC, \
+ DNS_LOGMODULE_DNSSEC, \
+ ISC_LOG_DEBUG(3), \
+ "keymgr: DNSKEY %s (%s) initialize " \
+ "%s state to %s (policy %s)", \
+ keystr, keymgr_keyrole((key)), \
+ keystatetags[state], \
+ keystatestrings[target], \
+ dns_kasp_getname(kasp)); \
+ } \
+ } \
+ } while (0)
+
+/* Shorter keywords for better readability. */
+#define HIDDEN DST_KEY_STATE_HIDDEN
+#define RUMOURED DST_KEY_STATE_RUMOURED
+#define OMNIPRESENT DST_KEY_STATE_OMNIPRESENT
+#define UNRETENTIVE DST_KEY_STATE_UNRETENTIVE
+#define NA DST_KEY_STATE_NA
+
+/* Quickly get key state timing metadata. */
+#define NUM_KEYSTATES (DST_MAX_KEYSTATES)
+static int keystatetimes[NUM_KEYSTATES] = { DST_TIME_DNSKEY, DST_TIME_ZRRSIG,
+ DST_TIME_KRRSIG, DST_TIME_DS };
+/* Readable key state types and values. */
+static const char *keystatetags[NUM_KEYSTATES] = { "DNSKEY", "ZRRSIG", "KRRSIG",
+ "DS" };
+static const char *keystatestrings[4] = { "HIDDEN", "RUMOURED", "OMNIPRESENT",
+ "UNRETENTIVE" };
+
+/*
+ * Print key role.
+ *
+ */
+static const char *
+keymgr_keyrole(dst_key_t *key) {
+ bool ksk = false, zsk = false;
+ isc_result_t ret;
+ ret = dst_key_getbool(key, DST_BOOL_KSK, &ksk);
+ if (ret != ISC_R_SUCCESS) {
+ return ("UNKNOWN");
+ }
+ ret = dst_key_getbool(key, DST_BOOL_ZSK, &zsk);
+ if (ret != ISC_R_SUCCESS) {
+ return ("UNKNOWN");
+ }
+ if (ksk && zsk) {
+ return ("CSK");
+ } else if (ksk) {
+ return ("KSK");
+ } else if (zsk) {
+ return ("ZSK");
+ }
+ return ("NOSIGN");
+}
+
+/*
+ * Set the remove time on key given its retire time.
+ *
+ */
+static void
+keymgr_settime_remove(dns_dnsseckey_t *key, dns_kasp_t *kasp) {
+ isc_stdtime_t retire = 0, remove = 0, ksk_remove = 0, zsk_remove = 0;
+ bool zsk = false, ksk = false;
+ isc_result_t ret;
+
+ REQUIRE(key != NULL);
+ REQUIRE(key->key != NULL);
+
+ ret = dst_key_gettime(key->key, DST_TIME_INACTIVE, &retire);
+ if (ret != ISC_R_SUCCESS) {
+ return;
+ }
+
+ ret = dst_key_getbool(key->key, DST_BOOL_ZSK, &zsk);
+ if (ret == ISC_R_SUCCESS && zsk) {
+ dns_ttl_t ttlsig = dns_kasp_zonemaxttl(kasp, true);
+ /* ZSK: Iret = Dsgn + Dprp + TTLsig */
+ zsk_remove =
+ retire + ttlsig + dns_kasp_zonepropagationdelay(kasp) +
+ dns_kasp_retiresafety(kasp) + dns_kasp_signdelay(kasp);
+ }
+ ret = dst_key_getbool(key->key, DST_BOOL_KSK, &ksk);
+ if (ret == ISC_R_SUCCESS && ksk) {
+ /* KSK: Iret = DprpP + TTLds */
+ ksk_remove = retire + dns_kasp_dsttl(kasp) +
+ dns_kasp_parentpropagationdelay(kasp) +
+ dns_kasp_retiresafety(kasp);
+ }
+
+ remove = ISC_MAX(ksk_remove, zsk_remove);
+ dst_key_settime(key->key, DST_TIME_DELETE, remove);
+}
+
+/*
+ * Set the SyncPublish time (when the DS may be submitted to the parent).
+ *
+ */
+static void
+keymgr_settime_syncpublish(dns_dnsseckey_t *key, dns_kasp_t *kasp, bool first) {
+ isc_stdtime_t published, syncpublish;
+ bool ksk = false;
+ isc_result_t ret;
+
+ REQUIRE(key != NULL);
+ REQUIRE(key->key != NULL);
+
+ ret = dst_key_gettime(key->key, DST_TIME_PUBLISH, &published);
+ if (ret != ISC_R_SUCCESS) {
+ return;
+ }
+
+ ret = dst_key_getbool(key->key, DST_BOOL_KSK, &ksk);
+ if (ret != ISC_R_SUCCESS || !ksk) {
+ return;
+ }
+
+ syncpublish = published + dst_key_getttl(key->key) +
+ dns_kasp_zonepropagationdelay(kasp) +
+ dns_kasp_publishsafety(kasp);
+ if (first) {
+ /* Also need to wait until the signatures are omnipresent. */
+ isc_stdtime_t zrrsig_present;
+ dns_ttl_t ttlsig = dns_kasp_zonemaxttl(kasp, true);
+ zrrsig_present = published + ttlsig +
+ dns_kasp_zonepropagationdelay(kasp) +
+ dns_kasp_publishsafety(kasp);
+ if (zrrsig_present > syncpublish) {
+ syncpublish = zrrsig_present;
+ }
+ }
+ dst_key_settime(key->key, DST_TIME_SYNCPUBLISH, syncpublish);
+}
+
+/*
+ * Calculate prepublication time of a successor key of 'key'.
+ * This function can have side effects:
+ * 1. If there is no active time set, which would be super weird, set it now.
+ * 2. If there is no published time set, also super weird, set it now.
+ * 3. If there is no syncpublished time set, set it now.
+ * 4. If the lifetime is not set, it will be set now.
+ * 5. If there should be a retire time and it is not set, it will be set now.
+ * 6. The removed time is adjusted accordingly.
+ *
+ * This returns when the successor key needs to be published in the zone.
+ * A special value of 0 means there is no need for a successor.
+ *
+ */
+static isc_stdtime_t
+keymgr_prepublication_time(dns_dnsseckey_t *key, dns_kasp_t *kasp,
+ uint32_t lifetime, isc_stdtime_t now) {
+ isc_result_t ret;
+ isc_stdtime_t active, retire, pub, prepub;
+ bool zsk = false, ksk = false;
+
+ REQUIRE(key != NULL);
+ REQUIRE(key->key != NULL);
+
+ active = 0;
+ pub = 0;
+ retire = 0;
+
+ /*
+ * An active key must have publish and activate timing
+ * metadata.
+ */
+ ret = dst_key_gettime(key->key, DST_TIME_ACTIVATE, &active);
+ if (ret != ISC_R_SUCCESS) {
+ /* Super weird, but if it happens, set it to now. */
+ dst_key_settime(key->key, DST_TIME_ACTIVATE, now);
+ active = now;
+ }
+ ret = dst_key_gettime(key->key, DST_TIME_PUBLISH, &pub);
+ if (ret != ISC_R_SUCCESS) {
+ /* Super weird, but if it happens, set it to now. */
+ dst_key_settime(key->key, DST_TIME_PUBLISH, now);
+ pub = now;
+ }
+
+ /*
+ * Calculate prepublication time.
+ */
+ prepub = dst_key_getttl(key->key) + dns_kasp_publishsafety(kasp) +
+ dns_kasp_zonepropagationdelay(kasp);
+ ret = dst_key_getbool(key->key, DST_BOOL_KSK, &ksk);
+ if (ret == ISC_R_SUCCESS && ksk) {
+ isc_stdtime_t syncpub;
+
+ /*
+ * Set PublishCDS if not set.
+ */
+ ret = dst_key_gettime(key->key, DST_TIME_SYNCPUBLISH, &syncpub);
+ if (ret != ISC_R_SUCCESS) {
+ uint32_t tag;
+ isc_stdtime_t syncpub1, syncpub2;
+
+ syncpub1 = pub + prepub;
+ syncpub2 = 0;
+ ret = dst_key_getnum(key->key, DST_NUM_PREDECESSOR,
+ &tag);
+ if (ret != ISC_R_SUCCESS) {
+ /*
+ * No predecessor, wait for zone to be
+ * completely signed.
+ */
+ dns_ttl_t ttlsig = dns_kasp_zonemaxttl(kasp,
+ true);
+ syncpub2 = pub + ttlsig +
+ dns_kasp_publishsafety(kasp) +
+ dns_kasp_zonepropagationdelay(kasp);
+ }
+
+ syncpub = ISC_MAX(syncpub1, syncpub2);
+ dst_key_settime(key->key, DST_TIME_SYNCPUBLISH,
+ syncpub);
+ }
+ }
+
+ /*
+ * Not sure what to do when dst_key_getbool() fails here. Extending
+ * the prepublication time anyway is arguably the safest thing to do,
+ * so ignore the result code.
+ */
+ (void)dst_key_getbool(key->key, DST_BOOL_ZSK, &zsk);
+
+ ret = dst_key_gettime(key->key, DST_TIME_INACTIVE, &retire);
+ if (ret != ISC_R_SUCCESS) {
+ uint32_t klifetime = 0;
+
+ ret = dst_key_getnum(key->key, DST_NUM_LIFETIME, &klifetime);
+ if (ret != ISC_R_SUCCESS) {
+ dst_key_setnum(key->key, DST_NUM_LIFETIME, lifetime);
+ klifetime = lifetime;
+ }
+ if (klifetime == 0) {
+ /*
+ * No inactive time and no lifetime,
+ * so no need to start a rollover.
+ */
+ return (0);
+ }
+
+ retire = active + klifetime;
+ dst_key_settime(key->key, DST_TIME_INACTIVE, retire);
+ }
+
+ /*
+ * Update remove time.
+ */
+ keymgr_settime_remove(key, kasp);
+
+ /*
+ * Publish successor 'prepub' time before the 'retire' time of 'key'.
+ */
+ if (prepub > retire) {
+ /* We should have already prepublished the new key. */
+ return (now);
+ }
+ return (retire - prepub);
+}
+
+static void
+keymgr_key_retire(dns_dnsseckey_t *key, dns_kasp_t *kasp, isc_stdtime_t now) {
+ char keystr[DST_KEY_FORMATSIZE];
+ isc_result_t ret;
+ isc_stdtime_t retire;
+ dst_key_state_t s;
+ bool ksk = false, zsk = false;
+
+ REQUIRE(key != NULL);
+ REQUIRE(key->key != NULL);
+
+ /* This key wants to retire and hide in a corner. */
+ ret = dst_key_gettime(key->key, DST_TIME_INACTIVE, &retire);
+ if (ret != ISC_R_SUCCESS || (retire > now)) {
+ dst_key_settime(key->key, DST_TIME_INACTIVE, now);
+ }
+ dst_key_setstate(key->key, DST_KEY_GOAL, HIDDEN);
+ keymgr_settime_remove(key, kasp);
+
+ /* This key may not have key states set yet. Pretend as if they are
+ * in the OMNIPRESENT state.
+ */
+ if (dst_key_getstate(key->key, DST_KEY_DNSKEY, &s) != ISC_R_SUCCESS) {
+ dst_key_setstate(key->key, DST_KEY_DNSKEY, OMNIPRESENT);
+ dst_key_settime(key->key, DST_TIME_DNSKEY, now);
+ }
+
+ ret = dst_key_getbool(key->key, DST_BOOL_KSK, &ksk);
+ if (ret == ISC_R_SUCCESS && ksk) {
+ if (dst_key_getstate(key->key, DST_KEY_KRRSIG, &s) !=
+ ISC_R_SUCCESS)
+ {
+ dst_key_setstate(key->key, DST_KEY_KRRSIG, OMNIPRESENT);
+ dst_key_settime(key->key, DST_TIME_KRRSIG, now);
+ }
+ if (dst_key_getstate(key->key, DST_KEY_DS, &s) != ISC_R_SUCCESS)
+ {
+ dst_key_setstate(key->key, DST_KEY_DS, OMNIPRESENT);
+ dst_key_settime(key->key, DST_TIME_DS, now);
+ }
+ }
+ ret = dst_key_getbool(key->key, DST_BOOL_ZSK, &zsk);
+ if (ret == ISC_R_SUCCESS && zsk) {
+ if (dst_key_getstate(key->key, DST_KEY_ZRRSIG, &s) !=
+ ISC_R_SUCCESS)
+ {
+ dst_key_setstate(key->key, DST_KEY_ZRRSIG, OMNIPRESENT);
+ dst_key_settime(key->key, DST_TIME_ZRRSIG, now);
+ }
+ }
+
+ dst_key_format(key->key, keystr, sizeof(keystr));
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_DNSSEC, DNS_LOGMODULE_DNSSEC,
+ ISC_LOG_INFO, "keymgr: retire DNSKEY %s (%s)", keystr,
+ keymgr_keyrole(key->key));
+}
+
+/*
+ * Check if a dnsseckey matches kasp key configuration. A dnsseckey matches
+ * if it has the same algorithm and size, and if it has the same role as the
+ * kasp key configuration.
+ *
+ */
+static bool
+keymgr_dnsseckey_kaspkey_match(dns_dnsseckey_t *dkey, dns_kasp_key_t *kkey) {
+ dst_key_t *key;
+ isc_result_t ret;
+ bool role = false;
+
+ REQUIRE(dkey != NULL);
+ REQUIRE(kkey != NULL);
+
+ key = dkey->key;
+
+ /* Matching algorithms? */
+ if (dst_key_alg(key) != dns_kasp_key_algorithm(kkey)) {
+ return (false);
+ }
+ /* Matching length? */
+ if (dst_key_size(key) != dns_kasp_key_size(kkey)) {
+ return (false);
+ }
+ /* Matching role? */
+ ret = dst_key_getbool(key, DST_BOOL_KSK, &role);
+ if (ret != ISC_R_SUCCESS || role != dns_kasp_key_ksk(kkey)) {
+ return (false);
+ }
+ ret = dst_key_getbool(key, DST_BOOL_ZSK, &role);
+ if (ret != ISC_R_SUCCESS || role != dns_kasp_key_zsk(kkey)) {
+ return (false);
+ }
+
+ /* Found a match. */
+ return (true);
+}
+
+static bool
+keymgr_keyid_conflict(dst_key_t *newkey, dns_dnsseckeylist_t *keys) {
+ uint16_t id = dst_key_id(newkey);
+ uint32_t rid = dst_key_rid(newkey);
+ uint32_t alg = dst_key_alg(newkey);
+
+ for (dns_dnsseckey_t *dkey = ISC_LIST_HEAD(*keys); dkey != NULL;
+ dkey = ISC_LIST_NEXT(dkey, link))
+ {
+ if (dst_key_alg(dkey->key) != alg) {
+ continue;
+ }
+ if (dst_key_id(dkey->key) == id ||
+ dst_key_rid(dkey->key) == id ||
+ dst_key_id(dkey->key) == rid ||
+ dst_key_rid(dkey->key) == rid)
+ {
+ return (true);
+ }
+ }
+ return (false);
+}
+
+/*
+ * Create a new key for 'origin' given the kasp key configuration 'kkey'.
+ * This will check for key id collisions with keys in 'keylist'.
+ * The created key will be stored in 'dst_key'.
+ *
+ */
+static isc_result_t
+keymgr_createkey(dns_kasp_key_t *kkey, const dns_name_t *origin,
+ dns_rdataclass_t rdclass, isc_mem_t *mctx,
+ dns_dnsseckeylist_t *keylist, dns_dnsseckeylist_t *newkeys,
+ dst_key_t **dst_key) {
+ bool conflict = false;
+ int keyflags = DNS_KEYOWNER_ZONE;
+ isc_result_t result = ISC_R_SUCCESS;
+ dst_key_t *newkey = NULL;
+
+ do {
+ uint32_t algo = dns_kasp_key_algorithm(kkey);
+ int size = dns_kasp_key_size(kkey);
+
+ if (dns_kasp_key_ksk(kkey)) {
+ keyflags |= DNS_KEYFLAG_KSK;
+ }
+ RETERR(dst_key_generate(origin, algo, size, 0, keyflags,
+ DNS_KEYPROTO_DNSSEC, rdclass, mctx,
+ &newkey, NULL));
+
+ /* Key collision? */
+ conflict = keymgr_keyid_conflict(newkey, keylist);
+ if (!conflict) {
+ conflict = keymgr_keyid_conflict(newkey, newkeys);
+ }
+ if (conflict) {
+ /* Try again. */
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_DNSSEC,
+ DNS_LOGMODULE_DNSSEC, ISC_LOG_WARNING,
+ "keymgr: key collision id %d",
+ dst_key_id(newkey));
+ dst_key_free(&newkey);
+ }
+ } while (conflict);
+
+ INSIST(!conflict);
+ dst_key_setnum(newkey, DST_NUM_LIFETIME, dns_kasp_key_lifetime(kkey));
+ dst_key_setbool(newkey, DST_BOOL_KSK, dns_kasp_key_ksk(kkey));
+ dst_key_setbool(newkey, DST_BOOL_ZSK, dns_kasp_key_zsk(kkey));
+ *dst_key = newkey;
+ return (ISC_R_SUCCESS);
+
+failure:
+ return (result);
+}
+
+/*
+ * Return the desired state for this record 'type'. The desired state depends
+ * on whether the key wants to be active, or wants to retire. This implements
+ * the edges of our state machine:
+ *
+ * ----> OMNIPRESENT ----
+ * | |
+ * | \|/
+ *
+ * RUMOURED <----> UNRETENTIVE
+ *
+ * /|\ |
+ * | |
+ * ---- HIDDEN <----
+ *
+ * A key that wants to be active eventually wants to have its record types
+ * in the OMNIPRESENT state (that is, all resolvers that know about these
+ * type of records know about these records specifically).
+ *
+ * A key that wants to be retired eventually wants to have its record types
+ * in the HIDDEN state (that is, all resolvers that know about these type
+ * of records specifically don't know about these records).
+ *
+ */
+static dst_key_state_t
+keymgr_desiredstate(dns_dnsseckey_t *key, dst_key_state_t state) {
+ dst_key_state_t goal;
+
+ if (dst_key_getstate(key->key, DST_KEY_GOAL, &goal) != ISC_R_SUCCESS) {
+ /* No goal? No movement. */
+ return (state);
+ }
+
+ if (goal == HIDDEN) {
+ switch (state) {
+ case RUMOURED:
+ case OMNIPRESENT:
+ return (UNRETENTIVE);
+ case HIDDEN:
+ case UNRETENTIVE:
+ return (HIDDEN);
+ default:
+ return (state);
+ }
+ } else if (goal == OMNIPRESENT) {
+ switch (state) {
+ case RUMOURED:
+ case OMNIPRESENT:
+ return (OMNIPRESENT);
+ case HIDDEN:
+ case UNRETENTIVE:
+ return (RUMOURED);
+ default:
+ return (state);
+ }
+ }
+
+ /* Unknown goal. */
+ return (state);
+}
+
+/*
+ * Check if 'key' matches specific 'states'.
+ * A state in 'states' that is NA matches any state.
+ * A state in 'states' that is HIDDEN also matches if the state is not set.
+ * If 'next_state' is set (not NA), we are pretending as if record 'type' of
+ * 'subject' key already transitioned to the 'next state'.
+ *
+ */
+static bool
+keymgr_key_match_state(dst_key_t *key, dst_key_t *subject, int type,
+ dst_key_state_t next_state,
+ dst_key_state_t states[NUM_KEYSTATES]) {
+ REQUIRE(key != NULL);
+
+ for (int i = 0; i < NUM_KEYSTATES; i++) {
+ dst_key_state_t state;
+ if (states[i] == NA) {
+ continue;
+ }
+ if (next_state != NA && i == type &&
+ dst_key_id(key) == dst_key_id(subject))
+ {
+ /* Check next state rather than current state. */
+ state = next_state;
+ } else if (dst_key_getstate(key, i, &state) != ISC_R_SUCCESS) {
+ /* This is fine only if expected state is HIDDEN. */
+ if (states[i] != HIDDEN) {
+ return (false);
+ }
+ continue;
+ }
+ if (state != states[i]) {
+ return (false);
+ }
+ }
+ /* Match. */
+ return (true);
+}
+
+/*
+ * Key d directly depends on k if d is the direct predecessor of k.
+ */
+static bool
+keymgr_direct_dep(dst_key_t *d, dst_key_t *k) {
+ uint32_t s, p;
+
+ if (dst_key_getnum(d, DST_NUM_SUCCESSOR, &s) != ISC_R_SUCCESS) {
+ return (false);
+ }
+ if (dst_key_getnum(k, DST_NUM_PREDECESSOR, &p) != ISC_R_SUCCESS) {
+ return (false);
+ }
+ return (dst_key_id(d) == p && dst_key_id(k) == s);
+}
+
+/*
+ * Determine which key (if any) has a dependency on k.
+ */
+static bool
+keymgr_dep(dst_key_t *k, dns_dnsseckeylist_t *keyring, uint32_t *dep) {
+ for (dns_dnsseckey_t *d = ISC_LIST_HEAD(*keyring); d != NULL;
+ d = ISC_LIST_NEXT(d, link))
+ {
+ /*
+ * Check if k is a direct successor of d, e.g. d depends on k.
+ */
+ if (keymgr_direct_dep(d->key, k)) {
+ if (dep != NULL) {
+ *dep = dst_key_id(d->key);
+ }
+ return (true);
+ }
+ }
+ return (false);
+}
+
+/*
+ * Check if a 'z' is a successor of 'x'.
+ * This implements Equation(2) of "Flexible and Robust Key Rollover".
+ */
+static bool
+keymgr_key_is_successor(dst_key_t *x, dst_key_t *z, dst_key_t *key, int type,
+ dst_key_state_t next_state,
+ dns_dnsseckeylist_t *keyring) {
+ uint32_t dep_x;
+ uint32_t dep_z;
+
+ /*
+ * The successor relation requires that the predecessor key must not
+ * have any other keys relying on it. In other words, there must be
+ * nothing depending on x.
+ */
+ if (keymgr_dep(x, keyring, &dep_x)) {
+ return (false);
+ }
+
+ /*
+ * If there is no keys relying on key z, then z is not a successor.
+ */
+ if (!keymgr_dep(z, keyring, &dep_z)) {
+ return (false);
+ }
+
+ /*
+ * x depends on z, thus key z is a direct successor of key x.
+ */
+ if (dst_key_id(x) == dep_z) {
+ return (true);
+ }
+
+ /*
+ * It is possible to roll keys faster than the time required to finish
+ * the rollover procedure. For example, consider the keys x, y, z.
+ * Key x is currently published and is going to be replaced by y. The
+ * DNSKEY for x is removed from the zone and at the same moment the
+ * DNSKEY for y is introduced. Key y is a direct dependency for key x
+ * and is therefore the successor of x. However, before the new DNSKEY
+ * has been propagated, key z will replace key y. The DNSKEY for y is
+ * removed and moves into the same state as key x. Key y now directly
+ * depends on key z, and key z will be a new successor key for x.
+ */
+ dst_key_state_t zst[NUM_KEYSTATES] = { NA, NA, NA, NA };
+ for (int i = 0; i < NUM_KEYSTATES; i++) {
+ dst_key_state_t state;
+ if (dst_key_getstate(z, i, &state) != ISC_R_SUCCESS) {
+ continue;
+ }
+ zst[i] = state;
+ }
+
+ for (dns_dnsseckey_t *y = ISC_LIST_HEAD(*keyring); y != NULL;
+ y = ISC_LIST_NEXT(y, link))
+ {
+ if (dst_key_id(y->key) == dst_key_id(z)) {
+ continue;
+ }
+
+ if (dst_key_id(y->key) != dep_z) {
+ continue;
+ }
+ /*
+ * This is another key y, that depends on key z. It may be
+ * part of the successor relation if the key states match
+ * those of key z.
+ */
+
+ if (keymgr_key_match_state(y->key, key, type, next_state, zst))
+ {
+ /*
+ * If y is a successor of x, then z is also a
+ * successor of x.
+ */
+ return (keymgr_key_is_successor(x, y->key, key, type,
+ next_state, keyring));
+ }
+ }
+
+ return (false);
+}
+
+/*
+ * Check if a key exists in 'keyring' that matches 'states'.
+ *
+ * If 'match_algorithms', the key must also match the algorithm of 'key'.
+ * If 'next_state' is not NA, we are actually looking for a key as if
+ * 'key' already transitioned to the next state.
+ * If 'check_successor', we also want to make sure there is a successor
+ * relationship with the found key that matches 'states2'.
+ */
+static bool
+keymgr_key_exists_with_state(dns_dnsseckeylist_t *keyring, dns_dnsseckey_t *key,
+ int type, dst_key_state_t next_state,
+ dst_key_state_t states[NUM_KEYSTATES],
+ dst_key_state_t states2[NUM_KEYSTATES],
+ bool check_successor, bool match_algorithms) {
+ for (dns_dnsseckey_t *dkey = ISC_LIST_HEAD(*keyring); dkey != NULL;
+ dkey = ISC_LIST_NEXT(dkey, link))
+ {
+ if (match_algorithms &&
+ (dst_key_alg(dkey->key) != dst_key_alg(key->key)))
+ {
+ continue;
+ }
+
+ if (!keymgr_key_match_state(dkey->key, key->key, type,
+ next_state, states))
+ {
+ continue;
+ }
+
+ /* Found a match. */
+ if (!check_successor) {
+ return (true);
+ }
+
+ /*
+ * We have to make sure that the key we are checking, also
+ * has a successor relationship with another key.
+ */
+ for (dns_dnsseckey_t *skey = ISC_LIST_HEAD(*keyring);
+ skey != NULL; skey = ISC_LIST_NEXT(skey, link))
+ {
+ if (skey == dkey) {
+ continue;
+ }
+
+ if (!keymgr_key_match_state(skey->key, key->key, type,
+ next_state, states2))
+ {
+ continue;
+ }
+
+ /*
+ * Found a possible successor, check.
+ */
+ if (keymgr_key_is_successor(dkey->key, skey->key,
+ key->key, type, next_state,
+ keyring))
+ {
+ return (true);
+ }
+ }
+ }
+ /* No match. */
+ return (false);
+}
+
+/*
+ * Check if a key has a successor.
+ */
+static bool
+keymgr_key_has_successor(dns_dnsseckey_t *predecessor,
+ dns_dnsseckeylist_t *keyring) {
+ for (dns_dnsseckey_t *successor = ISC_LIST_HEAD(*keyring);
+ successor != NULL; successor = ISC_LIST_NEXT(successor, link))
+ {
+ if (keymgr_direct_dep(predecessor->key, successor->key)) {
+ return (true);
+ }
+ }
+ return (false);
+}
+
+/*
+ * Check if all keys have their DS hidden. If not, then there must be at
+ * least one key with an OMNIPRESENT DNSKEY.
+ *
+ * If 'next_state' is not NA, we are actually looking for a key as if
+ * 'key' already transitioned to the next state.
+ * If 'match_algorithms', only consider keys with same algorithm of 'key'.
+ *
+ */
+static bool
+keymgr_ds_hidden_or_chained(dns_dnsseckeylist_t *keyring, dns_dnsseckey_t *key,
+ int type, dst_key_state_t next_state,
+ bool match_algorithms, bool must_be_hidden) {
+ /* (3e) */
+ dst_key_state_t dnskey_chained[NUM_KEYSTATES] = { OMNIPRESENT, NA,
+ OMNIPRESENT, NA };
+ dst_key_state_t ds_hidden[NUM_KEYSTATES] = { NA, NA, NA, HIDDEN };
+ /* successor n/a */
+ dst_key_state_t na[NUM_KEYSTATES] = { NA, NA, NA, NA };
+
+ for (dns_dnsseckey_t *dkey = ISC_LIST_HEAD(*keyring); dkey != NULL;
+ dkey = ISC_LIST_NEXT(dkey, link))
+ {
+ if (match_algorithms &&
+ (dst_key_alg(dkey->key) != dst_key_alg(key->key)))
+ {
+ continue;
+ }
+
+ if (keymgr_key_match_state(dkey->key, key->key, type,
+ next_state, ds_hidden))
+ {
+ /* This key has its DS hidden. */
+ continue;
+ }
+
+ if (must_be_hidden) {
+ return (false);
+ }
+
+ /*
+ * This key does not have its DS hidden. There must be at
+ * least one key with the same algorithm that provides a
+ * chain of trust (can be this key).
+ */
+ if (keymgr_key_match_state(dkey->key, key->key, type,
+ next_state, dnskey_chained))
+ {
+ /* This DNSKEY and KRRSIG are OMNIPRESENT. */
+ continue;
+ }
+
+ /*
+ * Perhaps another key provides a chain of trust.
+ */
+ dnskey_chained[DST_KEY_DS] = OMNIPRESENT;
+ if (!keymgr_key_exists_with_state(keyring, key, type,
+ next_state, dnskey_chained,
+ na, false, match_algorithms))
+ {
+ /* There is no chain of trust. */
+ return (false);
+ }
+ }
+ /* All good. */
+ return (true);
+}
+
+/*
+ * Check if all keys have their DNSKEY hidden. If not, then there must be at
+ * least one key with an OMNIPRESENT ZRRSIG.
+ *
+ * If 'next_state' is not NA, we are actually looking for a key as if
+ * 'key' already transitioned to the next state.
+ * If 'match_algorithms', only consider keys with same algorithm of 'key'.
+ *
+ */
+static bool
+keymgr_dnskey_hidden_or_chained(dns_dnsseckeylist_t *keyring,
+ dns_dnsseckey_t *key, int type,
+ dst_key_state_t next_state,
+ bool match_algorithms) {
+ /* (3i) */
+ dst_key_state_t rrsig_chained[NUM_KEYSTATES] = { OMNIPRESENT,
+ OMNIPRESENT, NA, NA };
+ dst_key_state_t dnskey_hidden[NUM_KEYSTATES] = { HIDDEN, NA, NA, NA };
+ /* successor n/a */
+ dst_key_state_t na[NUM_KEYSTATES] = { NA, NA, NA, NA };
+
+ for (dns_dnsseckey_t *dkey = ISC_LIST_HEAD(*keyring); dkey != NULL;
+ dkey = ISC_LIST_NEXT(dkey, link))
+ {
+ if (match_algorithms &&
+ (dst_key_alg(dkey->key) != dst_key_alg(key->key)))
+ {
+ continue;
+ }
+
+ if (keymgr_key_match_state(dkey->key, key->key, type,
+ next_state, dnskey_hidden))
+ {
+ /* This key has its DNSKEY hidden. */
+ continue;
+ }
+
+ /*
+ * This key does not have its DNSKEY hidden. There must be at
+ * least one key with the same algorithm that has its RRSIG
+ * records OMNIPRESENT.
+ */
+ (void)dst_key_getstate(dkey->key, DST_KEY_DNSKEY,
+ &rrsig_chained[DST_KEY_DNSKEY]);
+ if (!keymgr_key_exists_with_state(keyring, key, type,
+ next_state, rrsig_chained, na,
+ false, match_algorithms))
+ {
+ /* There is no chain of trust. */
+ return (false);
+ }
+ }
+ /* All good. */
+ return (true);
+}
+
+/*
+ * Check for existence of DS.
+ *
+ */
+static bool
+keymgr_have_ds(dns_dnsseckeylist_t *keyring, dns_dnsseckey_t *key, int type,
+ dst_key_state_t next_state, bool secure_to_insecure) {
+ /* (3a) */
+ dst_key_state_t states[2][NUM_KEYSTATES] = {
+ /* DNSKEY, ZRRSIG, KRRSIG, DS */
+ { NA, NA, NA, OMNIPRESENT }, /* DS present */
+ { NA, NA, NA, RUMOURED } /* DS introducing */
+ };
+ /* successor n/a */
+ dst_key_state_t na[NUM_KEYSTATES] = { NA, NA, NA, NA };
+
+ /*
+ * Equation (3a):
+ * There is a key with the DS in either RUMOURD or OMNIPRESENT state.
+ */
+ return (keymgr_key_exists_with_state(keyring, key, type, next_state,
+ states[0], na, false, false) ||
+ keymgr_key_exists_with_state(keyring, key, type, next_state,
+ states[1], na, false, false) ||
+ (secure_to_insecure &&
+ keymgr_key_exists_with_state(keyring, key, type, next_state,
+ na, na, false, false)));
+}
+
+/*
+ * Check for existence of DNSKEY, or at least a good DNSKEY state.
+ * See equations what are good DNSKEY states.
+ *
+ */
+static bool
+keymgr_have_dnskey(dns_dnsseckeylist_t *keyring, dns_dnsseckey_t *key, int type,
+ dst_key_state_t next_state) {
+ dst_key_state_t states[9][NUM_KEYSTATES] = {
+ /* DNSKEY, ZRRSIG, KRRSIG, DS */
+ { OMNIPRESENT, NA, OMNIPRESENT, OMNIPRESENT }, /* (3b) */
+
+ { OMNIPRESENT, NA, OMNIPRESENT, UNRETENTIVE }, /* (3c)p */
+ { OMNIPRESENT, NA, OMNIPRESENT, RUMOURED }, /* (3c)s */
+
+ { UNRETENTIVE, NA, UNRETENTIVE, OMNIPRESENT }, /* (3d)p */
+ { OMNIPRESENT, NA, UNRETENTIVE, OMNIPRESENT }, /* (3d)p */
+ { UNRETENTIVE, NA, OMNIPRESENT, OMNIPRESENT }, /* (3d)p */
+ { RUMOURED, NA, RUMOURED, OMNIPRESENT }, /* (3d)s */
+ { OMNIPRESENT, NA, RUMOURED, OMNIPRESENT }, /* (3d)s */
+ { RUMOURED, NA, OMNIPRESENT, OMNIPRESENT }, /* (3d)s */
+ };
+ /* successor n/a */
+ dst_key_state_t na[NUM_KEYSTATES] = { NA, NA, NA, NA };
+
+ return (
+ /*
+ * Equation (3b):
+ * There is a key with the same algorithm with its DNSKEY,
+ * KRRSIG and DS records in OMNIPRESENT state.
+ */
+ keymgr_key_exists_with_state(keyring, key, type, next_state,
+ states[0], na, false, true) ||
+ /*
+ * Equation (3c):
+ * There are two or more keys with an OMNIPRESENT DNSKEY and
+ * the DS records get swapped. These keys must be in a
+ * successor relation.
+ */
+ keymgr_key_exists_with_state(keyring, key, type, next_state,
+ states[1], states[2], true,
+ true) ||
+ /*
+ * Equation (3d):
+ * There are two or more keys with an OMNIPRESENT DS and
+ * the DNSKEY records and its KRRSIG records get swapped.
+ * These keys must be in a successor relation. Since the
+ * state for DNSKEY and KRRSIG move independently, we have
+ * to check all combinations for DNSKEY and KRRSIG in
+ * OMNIPRESENT/UNRETENTIVE state for the predecessor, and
+ * OMNIPRESENT/RUMOURED state for the successor.
+ */
+ keymgr_key_exists_with_state(keyring, key, type, next_state,
+ states[3], states[6], true,
+ true) ||
+ keymgr_key_exists_with_state(keyring, key, type, next_state,
+ states[3], states[7], true,
+ true) ||
+ keymgr_key_exists_with_state(keyring, key, type, next_state,
+ states[3], states[8], true,
+ true) ||
+ keymgr_key_exists_with_state(keyring, key, type, next_state,
+ states[4], states[6], true,
+ true) ||
+ keymgr_key_exists_with_state(keyring, key, type, next_state,
+ states[4], states[7], true,
+ true) ||
+ keymgr_key_exists_with_state(keyring, key, type, next_state,
+ states[4], states[8], true,
+ true) ||
+ keymgr_key_exists_with_state(keyring, key, type, next_state,
+ states[5], states[6], true,
+ true) ||
+ keymgr_key_exists_with_state(keyring, key, type, next_state,
+ states[5], states[7], true,
+ true) ||
+ keymgr_key_exists_with_state(keyring, key, type, next_state,
+ states[5], states[8], true,
+ true) ||
+ /*
+ * Equation (3e):
+ * The key may be in any state as long as all keys have their
+ * DS HIDDEN, or when their DS is not HIDDEN, there must be a
+ * key with its DS in the same state and its DNSKEY omnipresent.
+ * In other words, if a DS record for the same algorithm is
+ * is still available to some validators, there must be a
+ * chain of trust for those validators.
+ */
+ keymgr_ds_hidden_or_chained(keyring, key, type, next_state,
+ true, false));
+}
+
+/*
+ * Check for existence of RRSIG (zsk), or a good RRSIG state.
+ * See equations what are good RRSIG states.
+ *
+ */
+static bool
+keymgr_have_rrsig(dns_dnsseckeylist_t *keyring, dns_dnsseckey_t *key, int type,
+ dst_key_state_t next_state) {
+ dst_key_state_t states[11][NUM_KEYSTATES] = {
+ /* DNSKEY, ZRRSIG, KRRSIG, DS */
+ { OMNIPRESENT, OMNIPRESENT, NA, NA }, /* (3f) */
+ { UNRETENTIVE, OMNIPRESENT, NA, NA }, /* (3g)p */
+ { RUMOURED, OMNIPRESENT, NA, NA }, /* (3g)s */
+ { OMNIPRESENT, UNRETENTIVE, NA, NA }, /* (3h)p */
+ { OMNIPRESENT, RUMOURED, NA, NA }, /* (3h)s */
+ };
+ /* successor n/a */
+ dst_key_state_t na[NUM_KEYSTATES] = { NA, NA, NA, NA };
+
+ return (
+ /*
+ * If all DS records are hidden than this rule can be ignored.
+ */
+ keymgr_ds_hidden_or_chained(keyring, key, type, next_state,
+ true, true) ||
+ /*
+ * Equation (3f):
+ * There is a key with the same algorithm with its DNSKEY and
+ * ZRRSIG records in OMNIPRESENT state.
+ */
+ keymgr_key_exists_with_state(keyring, key, type, next_state,
+ states[0], na, false, true) ||
+ /*
+ * Equation (3g):
+ * There are two or more keys with OMNIPRESENT ZRRSIG
+ * records and the DNSKEY records get swapped. These keys
+ * must be in a successor relation.
+ */
+ keymgr_key_exists_with_state(keyring, key, type, next_state,
+ states[1], states[2], true,
+ true) ||
+ /*
+ * Equation (3h):
+ * There are two or more keys with an OMNIPRESENT DNSKEY
+ * and the ZRRSIG records get swapped. These keys must be in
+ * a successor relation.
+ */
+ keymgr_key_exists_with_state(keyring, key, type, next_state,
+ states[3], states[4], true,
+ true) ||
+ /*
+ * Equation (3i):
+ * If no DNSKEYs are published, the state of the signatures is
+ * irrelevant. In case a DNSKEY is published however, there
+ * must be a path that can be validated from there.
+ */
+ keymgr_dnskey_hidden_or_chained(keyring, key, type, next_state,
+ true));
+}
+
+/*
+ * Check if a transition in the state machine is allowed by the policy.
+ * This means when we do rollovers, we want to follow the rules of the
+ * 1. Pre-publish rollover method (in case of a ZSK)
+ * - First introduce the DNSKEY record.
+ * - Only if the DNSKEY record is OMNIPRESENT, introduce ZRRSIG records.
+ *
+ * 2. Double-KSK rollover method (in case of a KSK)
+ * - First introduce the DNSKEY record, as well as the KRRSIG records.
+ * - Only if the DNSKEY record is OMNIPRESENT, suggest to introduce the DS.
+ */
+static bool
+keymgr_policy_approval(dns_dnsseckeylist_t *keyring, dns_dnsseckey_t *key,
+ int type, dst_key_state_t next) {
+ dst_key_state_t dnskeystate = HIDDEN;
+ dst_key_state_t ksk_present[NUM_KEYSTATES] = { OMNIPRESENT, NA,
+ OMNIPRESENT,
+ OMNIPRESENT };
+ dst_key_state_t ds_rumoured[NUM_KEYSTATES] = { OMNIPRESENT, NA,
+ OMNIPRESENT, RUMOURED };
+ dst_key_state_t ds_retired[NUM_KEYSTATES] = { OMNIPRESENT, NA,
+ OMNIPRESENT,
+ UNRETENTIVE };
+ dst_key_state_t ksk_rumoured[NUM_KEYSTATES] = { RUMOURED, NA, NA,
+ OMNIPRESENT };
+ dst_key_state_t ksk_retired[NUM_KEYSTATES] = { UNRETENTIVE, NA, NA,
+ OMNIPRESENT };
+ /* successor n/a */
+ dst_key_state_t na[NUM_KEYSTATES] = { NA, NA, NA, NA };
+
+ if (next != RUMOURED) {
+ /*
+ * Local policy only adds an extra barrier on transitions to
+ * the RUMOURED state.
+ */
+ return (true);
+ }
+
+ switch (type) {
+ case DST_KEY_DNSKEY:
+ /* No restrictions. */
+ return (true);
+ case DST_KEY_ZRRSIG:
+ /* Make sure the DNSKEY record is OMNIPRESENT. */
+ (void)dst_key_getstate(key->key, DST_KEY_DNSKEY, &dnskeystate);
+ if (dnskeystate == OMNIPRESENT) {
+ return (true);
+ }
+ /*
+ * Or are we introducing a new key for this algorithm? Because
+ * in that case allow publishing the RRSIG records before the
+ * DNSKEY.
+ */
+ return (!(keymgr_key_exists_with_state(keyring, key, type, next,
+ ksk_present, na, false,
+ true) ||
+ keymgr_key_exists_with_state(keyring, key, type, next,
+ ds_retired, ds_rumoured,
+ true, true) ||
+ keymgr_key_exists_with_state(
+ keyring, key, type, next, ksk_retired,
+ ksk_rumoured, true, true)));
+ case DST_KEY_KRRSIG:
+ /* Only introduce if the DNSKEY is also introduced. */
+ (void)dst_key_getstate(key->key, DST_KEY_DNSKEY, &dnskeystate);
+ return (dnskeystate != HIDDEN);
+ case DST_KEY_DS:
+ /* Make sure the DNSKEY record is OMNIPRESENT. */
+ (void)dst_key_getstate(key->key, DST_KEY_DNSKEY, &dnskeystate);
+ return (dnskeystate == OMNIPRESENT);
+ default:
+ return (false);
+ }
+}
+
+/*
+ * Check if a transition in the state machine is DNSSEC safe.
+ * This implements Equation(1) of "Flexible and Robust Key Rollover".
+ *
+ */
+static bool
+keymgr_transition_allowed(dns_dnsseckeylist_t *keyring, dns_dnsseckey_t *key,
+ int type, dst_key_state_t next_state,
+ bool secure_to_insecure) {
+ /* Debug logging. */
+ if (isc_log_wouldlog(dns_lctx, ISC_LOG_DEBUG(1))) {
+ bool rule1a, rule1b, rule2a, rule2b, rule3a, rule3b;
+ char keystr[DST_KEY_FORMATSIZE];
+ dst_key_format(key->key, keystr, sizeof(keystr));
+ rule1a = keymgr_have_ds(keyring, key, type, NA,
+ secure_to_insecure);
+ rule1b = keymgr_have_ds(keyring, key, type, next_state,
+ secure_to_insecure);
+ rule2a = keymgr_have_dnskey(keyring, key, type, NA);
+ rule2b = keymgr_have_dnskey(keyring, key, type, next_state);
+ rule3a = keymgr_have_rrsig(keyring, key, type, NA);
+ rule3b = keymgr_have_rrsig(keyring, key, type, next_state);
+ isc_log_write(
+ dns_lctx, DNS_LOGCATEGORY_DNSSEC, DNS_LOGMODULE_DNSSEC,
+ ISC_LOG_DEBUG(1),
+ "keymgr: dnssec evaluation of %s %s record %s: "
+ "rule1=(~%s or %s) rule2=(~%s or %s) "
+ "rule3=(~%s or %s)",
+ keymgr_keyrole(key->key), keystr, keystatetags[type],
+ rule1a ? "true" : "false", rule1b ? "true" : "false",
+ rule2a ? "true" : "false", rule2b ? "true" : "false",
+ rule3a ? "true" : "false", rule3b ? "true" : "false");
+ }
+
+ return (
+ /*
+ * Rule 1: There must be a DS at all times.
+ * First check the current situation: if the rule check fails,
+ * we allow the transition to attempt to move us out of the
+ * invalid state. If the rule check passes, also check if
+ * the next state is also still a valid situation.
+ */
+ (!keymgr_have_ds(keyring, key, type, NA, secure_to_insecure) ||
+ keymgr_have_ds(keyring, key, type, next_state,
+ secure_to_insecure)) &&
+ /*
+ * Rule 2: There must be a DNSKEY at all times. Again, first
+ * check the current situation, then assess the next state.
+ */
+ (!keymgr_have_dnskey(keyring, key, type, NA) ||
+ keymgr_have_dnskey(keyring, key, type, next_state)) &&
+ /*
+ * Rule 3: There must be RRSIG records at all times. Again,
+ * first check the current situation, then assess the next
+ * state.
+ */
+ (!keymgr_have_rrsig(keyring, key, type, NA) ||
+ keymgr_have_rrsig(keyring, key, type, next_state)));
+}
+
+/*
+ * Calculate the time when it is safe to do the next transition.
+ *
+ */
+static void
+keymgr_transition_time(dns_dnsseckey_t *key, int type,
+ dst_key_state_t next_state, dns_kasp_t *kasp,
+ isc_stdtime_t now, isc_stdtime_t *when) {
+ isc_result_t ret;
+ isc_stdtime_t lastchange, dstime, nexttime = now;
+ dns_ttl_t ttlsig = dns_kasp_zonemaxttl(kasp, true);
+
+ /*
+ * No need to wait if we move things into an uncertain state.
+ */
+ if (next_state == RUMOURED || next_state == UNRETENTIVE) {
+ *when = now;
+ return;
+ }
+
+ ret = dst_key_gettime(key->key, keystatetimes[type], &lastchange);
+ if (ret != ISC_R_SUCCESS) {
+ /* No last change, for safety purposes let's set it to now. */
+ dst_key_settime(key->key, keystatetimes[type], now);
+ lastchange = now;
+ }
+
+ switch (type) {
+ case DST_KEY_DNSKEY:
+ case DST_KEY_KRRSIG:
+ switch (next_state) {
+ case OMNIPRESENT:
+ /*
+ * RFC 7583: The publication interval (Ipub) is the
+ * amount of time that must elapse after the
+ * publication of a DNSKEY (plus RRSIG (KSK)) before
+ * it can be assumed that any resolvers that have the
+ * relevant RRset cached have a copy of the new
+ * information. This is the sum of the propagation
+ * delay (Dprp) and the DNSKEY TTL (TTLkey). This
+ * translates to zone-propagation-delay + dnskey-ttl.
+ * We will also add the publish-safety interval.
+ */
+ nexttime = lastchange + dst_key_getttl(key->key) +
+ dns_kasp_zonepropagationdelay(kasp) +
+ dns_kasp_publishsafety(kasp);
+ break;
+ case HIDDEN:
+ /*
+ * Same as OMNIPRESENT but without the publish-safety
+ * interval.
+ */
+ nexttime = lastchange + dst_key_getttl(key->key) +
+ dns_kasp_zonepropagationdelay(kasp);
+ break;
+ default:
+ nexttime = now;
+ break;
+ }
+ break;
+ case DST_KEY_ZRRSIG:
+ switch (next_state) {
+ case OMNIPRESENT:
+ case HIDDEN:
+ /*
+ * RFC 7583: The retire interval (Iret) is the amount
+ * of time that must elapse after a DNSKEY or
+ * associated data enters the retire state for any
+ * dependent information (RRSIG ZSK) to be purged from
+ * validating resolver caches. This is defined as:
+ *
+ * Iret = Dsgn + Dprp + TTLsig
+ *
+ * Where Dsgn is the Dsgn is the delay needed to
+ * ensure that all existing RRsets have been re-signed
+ * with the new key, Dprp is the propagation delay and
+ * TTLsig is the maximum TTL of all zone RRSIG
+ * records. This translates to:
+ *
+ * Dsgn + zone-propagation-delay + max-zone-ttl.
+ *
+ * We will also add the retire-safety interval.
+ */
+ nexttime = lastchange + ttlsig +
+ dns_kasp_zonepropagationdelay(kasp) +
+ dns_kasp_retiresafety(kasp);
+ /*
+ * Only add the sign delay Dsgn if there is an actual
+ * predecessor or successor key.
+ */
+ uint32_t tag;
+ ret = dst_key_getnum(key->key, DST_NUM_PREDECESSOR,
+ &tag);
+ if (ret != ISC_R_SUCCESS) {
+ ret = dst_key_getnum(key->key,
+ DST_NUM_SUCCESSOR, &tag);
+ }
+ if (ret == ISC_R_SUCCESS) {
+ nexttime += dns_kasp_signdelay(kasp);
+ }
+ break;
+ default:
+ nexttime = now;
+ break;
+ }
+ break;
+ case DST_KEY_DS:
+ switch (next_state) {
+ /*
+ * RFC 7583: The successor DS record is published in
+ * the parent zone and after the registration delay
+ * (Dreg), the time taken after the DS record has been
+ * submitted to the parent zone manager for it to be
+ * placed in the zone. Key N (the predecessor) must
+ * remain in the zone until any caches that contain a
+ * copy of the DS RRset have a copy containing the new
+ * DS record. This interval is the retire interval
+ * (Iret), given by:
+ *
+ * Iret = DprpP + TTLds
+ *
+ * This translates to:
+ *
+ * parent-propagation-delay + parent-ds-ttl.
+ *
+ * We will also add the retire-safety interval.
+ */
+ case OMNIPRESENT:
+ /* Make sure DS has been seen in the parent. */
+ ret = dst_key_gettime(key->key, DST_TIME_DSPUBLISH,
+ &dstime);
+ if (ret != ISC_R_SUCCESS || dstime > now) {
+ /* Not yet, try again in an hour. */
+ nexttime = now + 3600;
+ } else {
+ nexttime =
+ dstime + dns_kasp_dsttl(kasp) +
+ dns_kasp_parentpropagationdelay(kasp) +
+ dns_kasp_retiresafety(kasp);
+ }
+ break;
+ case HIDDEN:
+ /* Make sure DS has been withdrawn from the parent. */
+ ret = dst_key_gettime(key->key, DST_TIME_DSDELETE,
+ &dstime);
+ if (ret != ISC_R_SUCCESS || dstime > now) {
+ /* Not yet, try again in an hour. */
+ nexttime = now + 3600;
+ } else {
+ nexttime =
+ dstime + dns_kasp_dsttl(kasp) +
+ dns_kasp_parentpropagationdelay(kasp) +
+ dns_kasp_retiresafety(kasp);
+ }
+ break;
+ default:
+ nexttime = now;
+ break;
+ }
+ break;
+ default:
+ UNREACHABLE();
+ break;
+ }
+
+ *when = nexttime;
+}
+
+/*
+ * Update keys.
+ * This implements Algorithm (1) of "Flexible and Robust Key Rollover".
+ *
+ */
+static isc_result_t
+keymgr_update(dns_dnsseckeylist_t *keyring, dns_kasp_t *kasp, isc_stdtime_t now,
+ isc_stdtime_t *nexttime, bool secure_to_insecure) {
+ bool changed;
+
+ /* Repeat until nothing changed. */
+transition:
+ changed = false;
+
+ /* For all keys in the zone. */
+ for (dns_dnsseckey_t *dkey = ISC_LIST_HEAD(*keyring); dkey != NULL;
+ dkey = ISC_LIST_NEXT(dkey, link))
+ {
+ char keystr[DST_KEY_FORMATSIZE];
+ dst_key_format(dkey->key, keystr, sizeof(keystr));
+
+ /* For all records related to this key. */
+ for (int i = 0; i < NUM_KEYSTATES; i++) {
+ isc_result_t ret;
+ isc_stdtime_t when;
+ dst_key_state_t state, next_state;
+
+ ret = dst_key_getstate(dkey->key, i, &state);
+ if (ret == ISC_R_NOTFOUND) {
+ /*
+ * This record type is not applicable for this
+ * key, continue to the next record type.
+ */
+ continue;
+ }
+
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_DNSSEC,
+ DNS_LOGMODULE_DNSSEC, ISC_LOG_DEBUG(1),
+ "keymgr: examine %s %s type %s "
+ "in state %s",
+ keymgr_keyrole(dkey->key), keystr,
+ keystatetags[i], keystatestrings[state]);
+
+ /* Get the desired next state. */
+ next_state = keymgr_desiredstate(dkey, state);
+ if (state == next_state) {
+ /*
+ * This record is in a stable state.
+ * No change needed, continue with the next
+ * record type.
+ */
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_DNSSEC,
+ DNS_LOGMODULE_DNSSEC,
+ ISC_LOG_DEBUG(1),
+ "keymgr: %s %s type %s in "
+ "stable state %s",
+ keymgr_keyrole(dkey->key), keystr,
+ keystatetags[i],
+ keystatestrings[state]);
+ continue;
+ }
+
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_DNSSEC,
+ DNS_LOGMODULE_DNSSEC, ISC_LOG_DEBUG(1),
+ "keymgr: can we transition %s %s type %s "
+ "state %s to state %s?",
+ keymgr_keyrole(dkey->key), keystr,
+ keystatetags[i], keystatestrings[state],
+ keystatestrings[next_state]);
+
+ /* Is the transition allowed according to policy? */
+ if (!keymgr_policy_approval(keyring, dkey, i,
+ next_state))
+ {
+ /* No, please respect rollover methods. */
+ isc_log_write(
+ dns_lctx, DNS_LOGCATEGORY_DNSSEC,
+ DNS_LOGMODULE_DNSSEC, ISC_LOG_DEBUG(1),
+ "keymgr: policy says no to %s %s type "
+ "%s "
+ "state %s to state %s",
+ keymgr_keyrole(dkey->key), keystr,
+ keystatetags[i], keystatestrings[state],
+ keystatestrings[next_state]);
+
+ continue;
+ }
+
+ /* Is the transition DNSSEC safe? */
+ if (!keymgr_transition_allowed(keyring, dkey, i,
+ next_state,
+ secure_to_insecure))
+ {
+ /* No, this would make the zone bogus. */
+ isc_log_write(
+ dns_lctx, DNS_LOGCATEGORY_DNSSEC,
+ DNS_LOGMODULE_DNSSEC, ISC_LOG_DEBUG(1),
+ "keymgr: dnssec says no to %s %s type "
+ "%s "
+ "state %s to state %s",
+ keymgr_keyrole(dkey->key), keystr,
+ keystatetags[i], keystatestrings[state],
+ keystatestrings[next_state]);
+ continue;
+ }
+
+ /* Is it time to make the transition? */
+ when = now;
+ keymgr_transition_time(dkey, i, next_state, kasp, now,
+ &when);
+ if (when > now) {
+ /* Not yet. */
+ isc_log_write(
+ dns_lctx, DNS_LOGCATEGORY_DNSSEC,
+ DNS_LOGMODULE_DNSSEC, ISC_LOG_DEBUG(1),
+ "keymgr: time says no to %s %s type %s "
+ "state %s to state %s (wait %u "
+ "seconds)",
+ keymgr_keyrole(dkey->key), keystr,
+ keystatetags[i], keystatestrings[state],
+ keystatestrings[next_state],
+ when - now);
+ if (*nexttime == 0 || *nexttime > when) {
+ *nexttime = when;
+ }
+ continue;
+ }
+
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_DNSSEC,
+ DNS_LOGMODULE_DNSSEC, ISC_LOG_DEBUG(1),
+ "keymgr: transition %s %s type %s "
+ "state %s to state %s!",
+ keymgr_keyrole(dkey->key), keystr,
+ keystatetags[i], keystatestrings[state],
+ keystatestrings[next_state]);
+
+ /* It is safe to make the transition. */
+ dst_key_setstate(dkey->key, i, next_state);
+ dst_key_settime(dkey->key, keystatetimes[i], now);
+ INSIST(dst_key_ismodified(dkey->key));
+ changed = true;
+ }
+ }
+
+ /* We changed something, continue processing. */
+ if (changed) {
+ goto transition;
+ }
+
+ return (ISC_R_SUCCESS);
+}
+
+/*
+ * See if this key needs to be initialized with properties. A key created
+ * and derived from a dnssec-policy will have the required metadata available,
+ * otherwise these may be missing and need to be initialized. The key states
+ * will be initialized according to existing timing metadata.
+ *
+ */
+static void
+keymgr_key_init(dns_dnsseckey_t *key, dns_kasp_t *kasp, isc_stdtime_t now,
+ bool csk) {
+ bool ksk, zsk;
+ isc_result_t ret;
+ isc_stdtime_t active = 0, pub = 0, syncpub = 0, retire = 0, remove = 0;
+ dst_key_state_t dnskey_state = HIDDEN;
+ dst_key_state_t ds_state = HIDDEN;
+ dst_key_state_t zrrsig_state = HIDDEN;
+ dst_key_state_t goal_state = HIDDEN;
+
+ REQUIRE(key != NULL);
+ REQUIRE(key->key != NULL);
+
+ /* Initialize role. */
+ ret = dst_key_getbool(key->key, DST_BOOL_KSK, &ksk);
+ if (ret != ISC_R_SUCCESS) {
+ ksk = ((dst_key_flags(key->key) & DNS_KEYFLAG_KSK) != 0);
+ dst_key_setbool(key->key, DST_BOOL_KSK, (ksk || csk));
+ }
+ ret = dst_key_getbool(key->key, DST_BOOL_ZSK, &zsk);
+ if (ret != ISC_R_SUCCESS) {
+ zsk = ((dst_key_flags(key->key) & DNS_KEYFLAG_KSK) == 0);
+ dst_key_setbool(key->key, DST_BOOL_ZSK, (zsk || csk));
+ }
+
+ /* Get time metadata. */
+ ret = dst_key_gettime(key->key, DST_TIME_ACTIVATE, &active);
+ if (active <= now && ret == ISC_R_SUCCESS) {
+ dns_ttl_t ttlsig = dns_kasp_zonemaxttl(kasp, true);
+ ttlsig += dns_kasp_zonepropagationdelay(kasp);
+ if ((active + ttlsig) <= now) {
+ zrrsig_state = OMNIPRESENT;
+ } else {
+ zrrsig_state = RUMOURED;
+ }
+ goal_state = OMNIPRESENT;
+ }
+ ret = dst_key_gettime(key->key, DST_TIME_PUBLISH, &pub);
+ if (pub <= now && ret == ISC_R_SUCCESS) {
+ dns_ttl_t key_ttl = dst_key_getttl(key->key);
+ key_ttl += dns_kasp_zonepropagationdelay(kasp);
+ if ((pub + key_ttl) <= now) {
+ dnskey_state = OMNIPRESENT;
+ } else {
+ dnskey_state = RUMOURED;
+ }
+ goal_state = OMNIPRESENT;
+ }
+ ret = dst_key_gettime(key->key, DST_TIME_SYNCPUBLISH, &syncpub);
+ if (syncpub <= now && ret == ISC_R_SUCCESS) {
+ dns_ttl_t ds_ttl = dns_kasp_dsttl(kasp);
+ ds_ttl += dns_kasp_parentpropagationdelay(kasp);
+ if ((syncpub + ds_ttl) <= now) {
+ ds_state = OMNIPRESENT;
+ } else {
+ ds_state = RUMOURED;
+ }
+ goal_state = OMNIPRESENT;
+ }
+ ret = dst_key_gettime(key->key, DST_TIME_INACTIVE, &retire);
+ if (retire <= now && ret == ISC_R_SUCCESS) {
+ dns_ttl_t ttlsig = dns_kasp_zonemaxttl(kasp, true);
+ ttlsig += dns_kasp_zonepropagationdelay(kasp);
+ if ((retire + ttlsig) <= now) {
+ zrrsig_state = HIDDEN;
+ } else {
+ zrrsig_state = UNRETENTIVE;
+ }
+ ds_state = UNRETENTIVE;
+ goal_state = HIDDEN;
+ }
+ ret = dst_key_gettime(key->key, DST_TIME_DELETE, &remove);
+ if (remove <= now && ret == ISC_R_SUCCESS) {
+ dns_ttl_t key_ttl = dst_key_getttl(key->key);
+ key_ttl += dns_kasp_zonepropagationdelay(kasp);
+ if ((remove + key_ttl) <= now) {
+ dnskey_state = HIDDEN;
+ } else {
+ dnskey_state = UNRETENTIVE;
+ }
+ zrrsig_state = HIDDEN;
+ ds_state = HIDDEN;
+ goal_state = HIDDEN;
+ }
+
+ /* Set goal if not already set. */
+ if (dst_key_getstate(key->key, DST_KEY_GOAL, &goal_state) !=
+ ISC_R_SUCCESS)
+ {
+ dst_key_setstate(key->key, DST_KEY_GOAL, goal_state);
+ }
+
+ /* Set key states for all keys that do not have them. */
+ INITIALIZE_STATE(key->key, DST_KEY_DNSKEY, DST_TIME_DNSKEY,
+ dnskey_state, now);
+ if (ksk || csk) {
+ INITIALIZE_STATE(key->key, DST_KEY_KRRSIG, DST_TIME_KRRSIG,
+ dnskey_state, now);
+ INITIALIZE_STATE(key->key, DST_KEY_DS, DST_TIME_DS, ds_state,
+ now);
+ }
+ if (zsk || csk) {
+ INITIALIZE_STATE(key->key, DST_KEY_ZRRSIG, DST_TIME_ZRRSIG,
+ zrrsig_state, now);
+ }
+}
+
+static isc_result_t
+keymgr_key_rollover(dns_kasp_key_t *kaspkey, dns_dnsseckey_t *active_key,
+ dns_dnsseckeylist_t *keyring, dns_dnsseckeylist_t *newkeys,
+ const dns_name_t *origin, dns_rdataclass_t rdclass,
+ dns_kasp_t *kasp, uint32_t lifetime, bool rollover,
+ isc_stdtime_t now, isc_stdtime_t *nexttime,
+ isc_mem_t *mctx) {
+ char keystr[DST_KEY_FORMATSIZE];
+ isc_stdtime_t retire = 0, active = 0, prepub = 0;
+ dns_dnsseckey_t *new_key = NULL;
+ dns_dnsseckey_t *candidate = NULL;
+ dst_key_t *dst_key = NULL;
+
+ /* Do we need to create a successor for the active key? */
+ if (active_key != NULL) {
+ if (isc_log_wouldlog(dns_lctx, ISC_LOG_DEBUG(1))) {
+ dst_key_format(active_key->key, keystr, sizeof(keystr));
+ isc_log_write(
+ dns_lctx, DNS_LOGCATEGORY_DNSSEC,
+ DNS_LOGMODULE_DNSSEC, ISC_LOG_DEBUG(1),
+ "keymgr: DNSKEY %s (%s) is active in policy %s",
+ keystr, keymgr_keyrole(active_key->key),
+ dns_kasp_getname(kasp));
+ }
+
+ /*
+ * Calculate when the successor needs to be published
+ * in the zone.
+ */
+ prepub = keymgr_prepublication_time(active_key, kasp, lifetime,
+ now);
+ if (prepub > now) {
+ if (isc_log_wouldlog(dns_lctx, ISC_LOG_DEBUG(1))) {
+ dst_key_format(active_key->key, keystr,
+ sizeof(keystr));
+ isc_log_write(
+ dns_lctx, DNS_LOGCATEGORY_DNSSEC,
+ DNS_LOGMODULE_DNSSEC, ISC_LOG_DEBUG(1),
+ "keymgr: new successor needed for "
+ "DNSKEY %s (%s) (policy %s) in %u "
+ "seconds",
+ keystr, keymgr_keyrole(active_key->key),
+ dns_kasp_getname(kasp), (prepub - now));
+ }
+ }
+ if (prepub == 0 || prepub > now) {
+ /* No need to start rollover now. */
+ if (*nexttime == 0 || prepub < *nexttime) {
+ *nexttime = prepub;
+ }
+ return (ISC_R_SUCCESS);
+ }
+
+ if (keymgr_key_has_successor(active_key, keyring)) {
+ /* Key already has successor. */
+ if (isc_log_wouldlog(dns_lctx, ISC_LOG_DEBUG(1))) {
+ dst_key_format(active_key->key, keystr,
+ sizeof(keystr));
+ isc_log_write(
+ dns_lctx, DNS_LOGCATEGORY_DNSSEC,
+ DNS_LOGMODULE_DNSSEC, ISC_LOG_DEBUG(1),
+ "keymgr: key DNSKEY %s (%s) (policy "
+ "%s) already has successor",
+ keystr, keymgr_keyrole(active_key->key),
+ dns_kasp_getname(kasp));
+ }
+ return (ISC_R_SUCCESS);
+ }
+
+ if (isc_log_wouldlog(dns_lctx, ISC_LOG_DEBUG(1))) {
+ dst_key_format(active_key->key, keystr, sizeof(keystr));
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_DNSSEC,
+ DNS_LOGMODULE_DNSSEC, ISC_LOG_DEBUG(1),
+ "keymgr: need successor for DNSKEY %s "
+ "(%s) (policy %s)",
+ keystr, keymgr_keyrole(active_key->key),
+ dns_kasp_getname(kasp));
+ }
+
+ /*
+ * If rollover is not allowed, warn.
+ */
+ if (!rollover) {
+ dst_key_format(active_key->key, keystr, sizeof(keystr));
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_DNSSEC,
+ DNS_LOGMODULE_DNSSEC, ISC_LOG_WARNING,
+ "keymgr: DNSKEY %s (%s) is offline in "
+ "policy %s, cannot start rollover",
+ keystr, keymgr_keyrole(active_key->key),
+ dns_kasp_getname(kasp));
+ return (ISC_R_SUCCESS);
+ }
+ } else if (isc_log_wouldlog(dns_lctx, ISC_LOG_DEBUG(1))) {
+ char namestr[DNS_NAME_FORMATSIZE];
+ dns_name_format(origin, namestr, sizeof(namestr));
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_DNSSEC,
+ DNS_LOGMODULE_DNSSEC, ISC_LOG_DEBUG(1),
+ "keymgr: no active key found for %s (policy %s)",
+ namestr, dns_kasp_getname(kasp));
+ }
+
+ /* It is time to do key rollover, we need a new key. */
+
+ /*
+ * Check if there is a key available in pool because keys
+ * may have been pregenerated with dnssec-keygen.
+ */
+ for (candidate = ISC_LIST_HEAD(*keyring); candidate != NULL;
+ candidate = ISC_LIST_NEXT(candidate, link))
+ {
+ if (keymgr_dnsseckey_kaspkey_match(candidate, kaspkey) &&
+ dst_key_is_unused(candidate->key))
+ {
+ /* Found a candidate in keyring. */
+ break;
+ }
+ }
+
+ if (candidate == NULL) {
+ /* No key available in keyring, create a new one. */
+ bool csk = (dns_kasp_key_ksk(kaspkey) &&
+ dns_kasp_key_zsk(kaspkey));
+
+ isc_result_t result = keymgr_createkey(kaspkey, origin, rdclass,
+ mctx, keyring, newkeys,
+ &dst_key);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+ dst_key_setttl(dst_key, dns_kasp_dnskeyttl(kasp));
+ dst_key_settime(dst_key, DST_TIME_CREATED, now);
+ result = dns_dnsseckey_create(mctx, &dst_key, &new_key);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+ keymgr_key_init(new_key, kasp, now, csk);
+ } else {
+ new_key = candidate;
+ }
+ dst_key_setnum(new_key->key, DST_NUM_LIFETIME, lifetime);
+
+ /* Got a key. */
+ if (active_key == NULL) {
+ /*
+ * If there is no active key found yet for this kasp
+ * key configuration, immediately make this key active.
+ */
+ dst_key_settime(new_key->key, DST_TIME_PUBLISH, now);
+ dst_key_settime(new_key->key, DST_TIME_ACTIVATE, now);
+ keymgr_settime_syncpublish(new_key, kasp, true);
+ active = now;
+ } else {
+ /*
+ * This is a successor. Mark the relationship.
+ */
+ isc_stdtime_t created;
+ (void)dst_key_gettime(new_key->key, DST_TIME_CREATED, &created);
+
+ dst_key_setnum(new_key->key, DST_NUM_PREDECESSOR,
+ dst_key_id(active_key->key));
+ dst_key_setnum(active_key->key, DST_NUM_SUCCESSOR,
+ dst_key_id(new_key->key));
+ (void)dst_key_gettime(active_key->key, DST_TIME_INACTIVE,
+ &retire);
+ active = retire;
+
+ /*
+ * If prepublication time and/or retire time are
+ * in the past (before the new key was created), use
+ * creation time as published and active time,
+ * effectively immediately making the key active.
+ */
+ if (prepub < created) {
+ active += (created - prepub);
+ prepub = created;
+ }
+ if (active < created) {
+ active = created;
+ }
+ dst_key_settime(new_key->key, DST_TIME_PUBLISH, prepub);
+ dst_key_settime(new_key->key, DST_TIME_ACTIVATE, active);
+ keymgr_settime_syncpublish(new_key, kasp, false);
+
+ /*
+ * Retire predecessor.
+ */
+ dst_key_setstate(active_key->key, DST_KEY_GOAL, HIDDEN);
+ }
+
+ /* This key wants to be present. */
+ dst_key_setstate(new_key->key, DST_KEY_GOAL, OMNIPRESENT);
+
+ /* Do we need to set retire time? */
+ if (lifetime > 0) {
+ dst_key_settime(new_key->key, DST_TIME_INACTIVE,
+ (active + lifetime));
+ keymgr_settime_remove(new_key, kasp);
+ }
+
+ /* Append dnsseckey to list of new keys. */
+ dns_dnssec_get_hints(new_key, now);
+ new_key->source = dns_keysource_repository;
+ INSIST(!new_key->legacy);
+ if (candidate == NULL) {
+ ISC_LIST_APPEND(*newkeys, new_key, link);
+ }
+
+ /* Logging. */
+ dst_key_format(new_key->key, keystr, sizeof(keystr));
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_DNSSEC, DNS_LOGMODULE_DNSSEC,
+ ISC_LOG_INFO, "keymgr: DNSKEY %s (%s) %s for policy %s",
+ keystr, keymgr_keyrole(new_key->key),
+ (candidate != NULL) ? "selected" : "created",
+ dns_kasp_getname(kasp));
+ return (ISC_R_SUCCESS);
+}
+
+static bool
+keymgr_key_may_be_purged(dst_key_t *key, uint32_t after, isc_stdtime_t now) {
+ bool ksk = false;
+ bool zsk = false;
+ dst_key_state_t hidden[NUM_KEYSTATES] = { HIDDEN, NA, NA, NA };
+ isc_stdtime_t lastchange = 0;
+
+ char keystr[DST_KEY_FORMATSIZE];
+ dst_key_format(key, keystr, sizeof(keystr));
+
+ /* If 'purge-keys' is disabled, always retain keys. */
+ if (after == 0) {
+ return (false);
+ }
+
+ /* Don't purge keys with goal OMNIPRESENT */
+ if (dst_key_goal(key) == OMNIPRESENT) {
+ return (false);
+ }
+
+ /* Don't purge unused keys. */
+ if (dst_key_is_unused(key)) {
+ return (false);
+ }
+
+ /* If this key is completely HIDDEN it may be purged. */
+ (void)dst_key_getbool(key, DST_BOOL_KSK, &ksk);
+ (void)dst_key_getbool(key, DST_BOOL_ZSK, &zsk);
+ if (ksk) {
+ hidden[DST_KEY_KRRSIG] = HIDDEN;
+ hidden[DST_KEY_DS] = HIDDEN;
+ }
+ if (zsk) {
+ hidden[DST_KEY_ZRRSIG] = HIDDEN;
+ }
+ if (!keymgr_key_match_state(key, key, 0, NA, hidden)) {
+ return (false);
+ }
+
+ /*
+ * Check 'purge-keys' interval. If the interval has passed since
+ * the last key change, it may be purged.
+ */
+ for (int i = 0; i < NUM_KEYSTATES; i++) {
+ isc_stdtime_t change = 0;
+ (void)dst_key_gettime(key, keystatetimes[i], &change);
+ if (change > lastchange) {
+ lastchange = change;
+ }
+ }
+
+ return ((lastchange + after) < now);
+}
+
+static void
+keymgr_purge_keyfile(dst_key_t *key, const char *dir, int type) {
+ isc_result_t ret;
+ isc_buffer_t fileb;
+ char filename[NAME_MAX];
+
+ /*
+ * Make the filename.
+ */
+ isc_buffer_init(&fileb, filename, sizeof(filename));
+ ret = dst_key_buildfilename(key, type, dir, &fileb);
+ if (ret != ISC_R_SUCCESS) {
+ char keystr[DST_KEY_FORMATSIZE];
+ dst_key_format(key, keystr, sizeof(keystr));
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_DNSSEC,
+ DNS_LOGMODULE_DNSSEC, ISC_LOG_WARNING,
+ "keymgr: failed to purge DNSKEY %s (%s): cannot "
+ "build filename (%s)",
+ keystr, keymgr_keyrole(key),
+ isc_result_totext(ret));
+ return;
+ }
+
+ if (unlink(filename) < 0) {
+ char keystr[DST_KEY_FORMATSIZE];
+ dst_key_format(key, keystr, sizeof(keystr));
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_DNSSEC,
+ DNS_LOGMODULE_DNSSEC, ISC_LOG_WARNING,
+ "keymgr: failed to purge DNSKEY %s (%s): unlink "
+ "'%s' failed",
+ keystr, keymgr_keyrole(key), filename);
+ }
+}
+
+/*
+ * Examine 'keys' and match 'kasp' policy.
+ *
+ */
+isc_result_t
+dns_keymgr_run(const dns_name_t *origin, dns_rdataclass_t rdclass,
+ const char *directory, isc_mem_t *mctx,
+ dns_dnsseckeylist_t *keyring, dns_dnsseckeylist_t *dnskeys,
+ dns_kasp_t *kasp, isc_stdtime_t now, isc_stdtime_t *nexttime) {
+ isc_result_t result = ISC_R_SUCCESS;
+ dns_dnsseckeylist_t newkeys;
+ dns_kasp_key_t *kkey;
+ dns_dnsseckey_t *newkey = NULL;
+ isc_dir_t dir;
+ bool dir_open = false;
+ bool secure_to_insecure = false;
+ int numkeys = 0;
+ int options = (DST_TYPE_PRIVATE | DST_TYPE_PUBLIC | DST_TYPE_STATE);
+ char keystr[DST_KEY_FORMATSIZE];
+
+ REQUIRE(DNS_KASP_VALID(kasp));
+ REQUIRE(keyring != NULL);
+
+ ISC_LIST_INIT(newkeys);
+
+ isc_dir_init(&dir);
+ if (directory == NULL) {
+ directory = ".";
+ }
+
+ RETERR(isc_dir_open(&dir, directory));
+ dir_open = true;
+
+ *nexttime = 0;
+
+ /* Debug logging: what keys are available in the keyring? */
+ if (isc_log_wouldlog(dns_lctx, ISC_LOG_DEBUG(1))) {
+ if (ISC_LIST_EMPTY(*keyring)) {
+ char namebuf[DNS_NAME_FORMATSIZE];
+ dns_name_format(origin, namebuf, sizeof(namebuf));
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_DNSSEC,
+ DNS_LOGMODULE_DNSSEC, ISC_LOG_DEBUG(1),
+ "keymgr: keyring empty (zone %s policy "
+ "%s)",
+ namebuf, dns_kasp_getname(kasp));
+ }
+
+ for (dns_dnsseckey_t *dkey = ISC_LIST_HEAD(*keyring);
+ dkey != NULL; dkey = ISC_LIST_NEXT(dkey, link))
+ {
+ dst_key_format(dkey->key, keystr, sizeof(keystr));
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_DNSSEC,
+ DNS_LOGMODULE_DNSSEC, ISC_LOG_DEBUG(1),
+ "keymgr: keyring: %s (policy %s)", keystr,
+ dns_kasp_getname(kasp));
+ }
+ for (dns_dnsseckey_t *dkey = ISC_LIST_HEAD(*dnskeys);
+ dkey != NULL; dkey = ISC_LIST_NEXT(dkey, link))
+ {
+ dst_key_format(dkey->key, keystr, sizeof(keystr));
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_DNSSEC,
+ DNS_LOGMODULE_DNSSEC, ISC_LOG_DEBUG(1),
+ "keymgr: dnskeys: %s (policy %s)", keystr,
+ dns_kasp_getname(kasp));
+ }
+ }
+
+ for (dns_dnsseckey_t *dkey = ISC_LIST_HEAD(*dnskeys); dkey != NULL;
+ dkey = ISC_LIST_NEXT(dkey, link))
+ {
+ numkeys++;
+ }
+
+ /* Do we need to remove keys? */
+ for (dns_dnsseckey_t *dkey = ISC_LIST_HEAD(*keyring); dkey != NULL;
+ dkey = ISC_LIST_NEXT(dkey, link))
+ {
+ bool found_match = false;
+
+ keymgr_key_init(dkey, kasp, now, (numkeys == 1));
+
+ for (kkey = ISC_LIST_HEAD(dns_kasp_keys(kasp)); kkey != NULL;
+ kkey = ISC_LIST_NEXT(kkey, link))
+ {
+ if (keymgr_dnsseckey_kaspkey_match(dkey, kkey)) {
+ found_match = true;
+ break;
+ }
+ }
+
+ /* No match, so retire unwanted retire key. */
+ if (!found_match) {
+ keymgr_key_retire(dkey, kasp, now);
+ }
+
+ /* Check purge-keys interval. */
+ if (keymgr_key_may_be_purged(dkey->key,
+ dns_kasp_purgekeys(kasp), now))
+ {
+ dst_key_format(dkey->key, keystr, sizeof(keystr));
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_DNSSEC,
+ DNS_LOGMODULE_DNSSEC, ISC_LOG_INFO,
+ "keymgr: purge DNSKEY %s (%s) according "
+ "to policy %s",
+ keystr, keymgr_keyrole(dkey->key),
+ dns_kasp_getname(kasp));
+
+ keymgr_purge_keyfile(dkey->key, directory,
+ DST_TYPE_PUBLIC);
+ keymgr_purge_keyfile(dkey->key, directory,
+ DST_TYPE_PRIVATE);
+ keymgr_purge_keyfile(dkey->key, directory,
+ DST_TYPE_STATE);
+
+ dkey->purge = true;
+ }
+ }
+
+ /* Create keys according to the policy, if come in short. */
+ for (kkey = ISC_LIST_HEAD(dns_kasp_keys(kasp)); kkey != NULL;
+ kkey = ISC_LIST_NEXT(kkey, link))
+ {
+ uint32_t lifetime = dns_kasp_key_lifetime(kkey);
+ dns_dnsseckey_t *active_key = NULL;
+ bool rollover_allowed = true;
+
+ /* Do we have keys available for this kasp key? */
+ for (dns_dnsseckey_t *dkey = ISC_LIST_HEAD(*keyring);
+ dkey != NULL; dkey = ISC_LIST_NEXT(dkey, link))
+ {
+ if (keymgr_dnsseckey_kaspkey_match(dkey, kkey)) {
+ /* Found a match. */
+ dst_key_format(dkey->key, keystr,
+ sizeof(keystr));
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_DNSSEC,
+ DNS_LOGMODULE_DNSSEC,
+ ISC_LOG_DEBUG(1),
+ "keymgr: DNSKEY %s (%s) matches "
+ "policy %s",
+ keystr, keymgr_keyrole(dkey->key),
+ dns_kasp_getname(kasp));
+
+ /* Initialize lifetime if not set. */
+ uint32_t l;
+ if (dst_key_getnum(dkey->key, DST_NUM_LIFETIME,
+ &l) != ISC_R_SUCCESS)
+ {
+ dst_key_setnum(dkey->key,
+ DST_NUM_LIFETIME,
+ lifetime);
+ }
+
+ if (active_key) {
+ /* We already have an active key that
+ * matches the kasp policy.
+ */
+ if (!dst_key_is_unused(dkey->key) &&
+ (dst_key_goal(dkey->key) ==
+ OMNIPRESENT) &&
+ !keymgr_dep(dkey->key, keyring,
+ NULL) &&
+ !keymgr_dep(active_key->key,
+ keyring, NULL))
+ {
+ /*
+ * Multiple signing keys match
+ * the kasp key configuration.
+ * Retire excess keys in use.
+ */
+ keymgr_key_retire(dkey, kasp,
+ now);
+ }
+ continue;
+ }
+
+ /*
+ * Save the matched key only if it is active
+ * or desires to be active.
+ */
+ if (dst_key_goal(dkey->key) == OMNIPRESENT ||
+ dst_key_is_active(dkey->key, now))
+ {
+ active_key = dkey;
+ }
+ }
+ }
+
+ if (active_key == NULL) {
+ /*
+ * We didn't found an active key, perhaps the .private
+ * key file is offline. If so, we don't want to create
+ * a successor key. Check if we have an appropriate
+ * state file.
+ */
+ for (dns_dnsseckey_t *dnskey = ISC_LIST_HEAD(*dnskeys);
+ dnskey != NULL;
+ dnskey = ISC_LIST_NEXT(dnskey, link))
+ {
+ if (keymgr_dnsseckey_kaspkey_match(dnskey,
+ kkey))
+ {
+ /* Found a match. */
+ dst_key_format(dnskey->key, keystr,
+ sizeof(keystr));
+ isc_log_write(
+ dns_lctx,
+ DNS_LOGCATEGORY_DNSSEC,
+ DNS_LOGMODULE_DNSSEC,
+ ISC_LOG_DEBUG(1),
+ "keymgr: DNSKEY %s (%s) "
+ "offline, policy %s",
+ keystr,
+ keymgr_keyrole(dnskey->key),
+ dns_kasp_getname(kasp));
+ rollover_allowed = false;
+ active_key = dnskey;
+ break;
+ }
+ }
+ }
+
+ /* See if this key requires a rollover. */
+ RETERR(keymgr_key_rollover(
+ kkey, active_key, keyring, &newkeys, origin, rdclass,
+ kasp, lifetime, rollover_allowed, now, nexttime, mctx));
+ }
+
+ /* Walked all kasp key configurations. Append new keys. */
+ if (!ISC_LIST_EMPTY(newkeys)) {
+ ISC_LIST_APPENDLIST(*keyring, newkeys, link);
+ }
+
+ /*
+ * If the policy has an empty key list, this means the zone is going
+ * back to unsigned.
+ */
+ secure_to_insecure = dns_kasp_keylist_empty(kasp);
+
+ /* Read to update key states. */
+ keymgr_update(keyring, kasp, now, nexttime, secure_to_insecure);
+
+ /* Store key states and update hints. */
+ for (dns_dnsseckey_t *dkey = ISC_LIST_HEAD(*keyring); dkey != NULL;
+ dkey = ISC_LIST_NEXT(dkey, link))
+ {
+ if (dst_key_ismodified(dkey->key) && !dkey->purge) {
+ dns_dnssec_get_hints(dkey, now);
+ RETERR(dst_key_tofile(dkey->key, options, directory));
+ dst_key_setmodified(dkey->key, false);
+ }
+ }
+
+ result = ISC_R_SUCCESS;
+
+failure:
+ if (dir_open) {
+ isc_dir_close(&dir);
+ }
+
+ if (result != ISC_R_SUCCESS) {
+ while ((newkey = ISC_LIST_HEAD(newkeys)) != NULL) {
+ ISC_LIST_UNLINK(newkeys, newkey, link);
+ INSIST(newkey->key != NULL);
+ dst_key_free(&newkey->key);
+ dns_dnsseckey_destroy(mctx, &newkey);
+ }
+ }
+
+ if (isc_log_wouldlog(dns_lctx, ISC_LOG_DEBUG(3))) {
+ char namebuf[DNS_NAME_FORMATSIZE];
+ dns_name_format(origin, namebuf, sizeof(namebuf));
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_DNSSEC,
+ DNS_LOGMODULE_DNSSEC, ISC_LOG_DEBUG(3),
+ "keymgr: %s done", namebuf);
+ }
+ return (result);
+}
+
+static isc_result_t
+keymgr_checkds(dns_kasp_t *kasp, dns_dnsseckeylist_t *keyring,
+ const char *directory, isc_stdtime_t now, isc_stdtime_t when,
+ bool dspublish, dns_keytag_t id, unsigned int alg,
+ bool check_id) {
+ int options = (DST_TYPE_PRIVATE | DST_TYPE_PUBLIC | DST_TYPE_STATE);
+ isc_dir_t dir;
+ isc_result_t result;
+ dns_dnsseckey_t *ksk_key = NULL;
+
+ REQUIRE(DNS_KASP_VALID(kasp));
+ REQUIRE(keyring != NULL);
+
+ for (dns_dnsseckey_t *dkey = ISC_LIST_HEAD(*keyring); dkey != NULL;
+ dkey = ISC_LIST_NEXT(dkey, link))
+ {
+ isc_result_t ret;
+ bool ksk = false;
+
+ ret = dst_key_getbool(dkey->key, DST_BOOL_KSK, &ksk);
+ if (ret == ISC_R_SUCCESS && ksk) {
+ if (check_id && dst_key_id(dkey->key) != id) {
+ continue;
+ }
+ if (alg > 0 && dst_key_alg(dkey->key) != alg) {
+ continue;
+ }
+
+ if (ksk_key != NULL) {
+ /*
+ * Only checkds for one key at a time.
+ */
+ return (DNS_R_TOOMANYKEYS);
+ }
+
+ ksk_key = dkey;
+ }
+ }
+
+ if (ksk_key == NULL) {
+ return (DNS_R_NOKEYMATCH);
+ }
+
+ if (dspublish) {
+ dst_key_state_t s;
+ dst_key_settime(ksk_key->key, DST_TIME_DSPUBLISH, when);
+ result = dst_key_getstate(ksk_key->key, DST_KEY_DS, &s);
+ if (result != ISC_R_SUCCESS || s != RUMOURED) {
+ dst_key_setstate(ksk_key->key, DST_KEY_DS, RUMOURED);
+ }
+ } else {
+ dst_key_state_t s;
+ dst_key_settime(ksk_key->key, DST_TIME_DSDELETE, when);
+ result = dst_key_getstate(ksk_key->key, DST_KEY_DS, &s);
+ if (result != ISC_R_SUCCESS || s != UNRETENTIVE) {
+ dst_key_setstate(ksk_key->key, DST_KEY_DS, UNRETENTIVE);
+ }
+ }
+
+ if (isc_log_wouldlog(dns_lctx, ISC_LOG_NOTICE)) {
+ char keystr[DST_KEY_FORMATSIZE];
+ char timestr[26]; /* Minimal buf as per ctime_r() spec. */
+
+ dst_key_format(ksk_key->key, keystr, sizeof(keystr));
+ isc_stdtime_tostring(when, timestr, sizeof(timestr));
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_DNSSEC,
+ DNS_LOGMODULE_DNSSEC, ISC_LOG_NOTICE,
+ "keymgr: checkds DS for key %s seen %s at %s",
+ keystr, dspublish ? "published" : "withdrawn",
+ timestr);
+ }
+
+ /* Store key state and update hints. */
+ isc_dir_init(&dir);
+ if (directory == NULL) {
+ directory = ".";
+ }
+ result = isc_dir_open(&dir, directory);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+
+ dns_dnssec_get_hints(ksk_key, now);
+ result = dst_key_tofile(ksk_key->key, options, directory);
+ if (result == ISC_R_SUCCESS) {
+ dst_key_setmodified(ksk_key->key, false);
+ }
+ isc_dir_close(&dir);
+
+ return (result);
+}
+
+isc_result_t
+dns_keymgr_checkds(dns_kasp_t *kasp, dns_dnsseckeylist_t *keyring,
+ const char *directory, isc_stdtime_t now, isc_stdtime_t when,
+ bool dspublish) {
+ return (keymgr_checkds(kasp, keyring, directory, now, when, dspublish,
+ 0, 0, false));
+}
+
+isc_result_t
+dns_keymgr_checkds_id(dns_kasp_t *kasp, dns_dnsseckeylist_t *keyring,
+ const char *directory, isc_stdtime_t now,
+ isc_stdtime_t when, bool dspublish, dns_keytag_t id,
+ unsigned int alg) {
+ return (keymgr_checkds(kasp, keyring, directory, now, when, dspublish,
+ id, alg, true));
+}
+
+static void
+keytime_status(dst_key_t *key, isc_stdtime_t now, isc_buffer_t *buf,
+ const char *pre, int ks, int kt) {
+ char timestr[26]; /* Minimal buf as per ctime_r() spec. */
+ isc_result_t ret;
+ isc_stdtime_t when = 0;
+ dst_key_state_t state = NA;
+
+ isc_buffer_printf(buf, "%s", pre);
+ (void)dst_key_getstate(key, ks, &state);
+ ret = dst_key_gettime(key, kt, &when);
+ if (state == RUMOURED || state == OMNIPRESENT) {
+ isc_buffer_printf(buf, "yes - since ");
+ } else if (now < when) {
+ isc_buffer_printf(buf, "no - scheduled ");
+ } else {
+ isc_buffer_printf(buf, "no\n");
+ return;
+ }
+ if (ret == ISC_R_SUCCESS) {
+ isc_stdtime_tostring(when, timestr, sizeof(timestr));
+ isc_buffer_printf(buf, "%s\n", timestr);
+ }
+}
+
+static void
+rollover_status(dns_dnsseckey_t *dkey, dns_kasp_t *kasp, isc_stdtime_t now,
+ isc_buffer_t *buf, bool zsk) {
+ char timestr[26]; /* Minimal buf as per ctime_r() spec. */
+ isc_result_t ret = ISC_R_SUCCESS;
+ isc_stdtime_t active_time = 0;
+ dst_key_state_t state = NA, goal = NA;
+ int rrsig, active, retire;
+ dst_key_t *key = dkey->key;
+
+ if (zsk) {
+ rrsig = DST_KEY_ZRRSIG;
+ active = DST_TIME_ACTIVATE;
+ retire = DST_TIME_INACTIVE;
+ } else {
+ rrsig = DST_KEY_KRRSIG;
+ active = DST_TIME_PUBLISH;
+ retire = DST_TIME_DELETE;
+ }
+
+ isc_buffer_printf(buf, "\n");
+
+ (void)dst_key_getstate(key, DST_KEY_GOAL, &goal);
+ (void)dst_key_getstate(key, rrsig, &state);
+ (void)dst_key_gettime(key, active, &active_time);
+ if (active_time == 0) {
+ // only interested in keys that were once active.
+ return;
+ }
+
+ if (goal == HIDDEN && (state == UNRETENTIVE || state == HIDDEN)) {
+ isc_stdtime_t remove_time = 0;
+ // is the key removed yet?
+ state = NA;
+ (void)dst_key_getstate(key, DST_KEY_DNSKEY, &state);
+ if (state == RUMOURED || state == OMNIPRESENT) {
+ ret = dst_key_gettime(key, DST_TIME_DELETE,
+ &remove_time);
+ if (ret == ISC_R_SUCCESS) {
+ isc_buffer_printf(buf, " Key is retired, will "
+ "be removed on ");
+ isc_stdtime_tostring(remove_time, timestr,
+ sizeof(timestr));
+ isc_buffer_printf(buf, "%s", timestr);
+ }
+ } else {
+ isc_buffer_printf(
+ buf, " Key has been removed from the zone");
+ }
+ } else {
+ isc_stdtime_t retire_time = 0;
+ uint32_t lifetime = 0;
+ (void)dst_key_getnum(key, DST_NUM_LIFETIME, &lifetime);
+ ret = dst_key_gettime(key, retire, &retire_time);
+ if (ret == ISC_R_SUCCESS) {
+ if (now < retire_time) {
+ if (goal == OMNIPRESENT) {
+ isc_buffer_printf(buf,
+ " Next rollover "
+ "scheduled on ");
+ retire_time = keymgr_prepublication_time(
+ dkey, kasp, lifetime, now);
+ } else {
+ isc_buffer_printf(
+ buf, " Key will retire on ");
+ }
+ } else {
+ isc_buffer_printf(buf,
+ " Rollover is due since ");
+ }
+ isc_stdtime_tostring(retire_time, timestr,
+ sizeof(timestr));
+ isc_buffer_printf(buf, "%s", timestr);
+ } else {
+ isc_buffer_printf(buf, " No rollover scheduled");
+ }
+ }
+ isc_buffer_printf(buf, "\n");
+}
+
+static void
+keystate_status(dst_key_t *key, isc_buffer_t *buf, const char *pre, int ks) {
+ dst_key_state_t state = NA;
+
+ (void)dst_key_getstate(key, ks, &state);
+ switch (state) {
+ case HIDDEN:
+ isc_buffer_printf(buf, " - %shidden\n", pre);
+ break;
+ case RUMOURED:
+ isc_buffer_printf(buf, " - %srumoured\n", pre);
+ break;
+ case OMNIPRESENT:
+ isc_buffer_printf(buf, " - %somnipresent\n", pre);
+ break;
+ case UNRETENTIVE:
+ isc_buffer_printf(buf, " - %sunretentive\n", pre);
+ break;
+ case NA:
+ default:
+ /* print nothing */
+ break;
+ }
+}
+
+void
+dns_keymgr_status(dns_kasp_t *kasp, dns_dnsseckeylist_t *keyring,
+ isc_stdtime_t now, char *out, size_t out_len) {
+ isc_buffer_t buf;
+ char timestr[26]; /* Minimal buf as per ctime_r() spec. */
+
+ REQUIRE(DNS_KASP_VALID(kasp));
+ REQUIRE(keyring != NULL);
+ REQUIRE(out != NULL);
+
+ isc_buffer_init(&buf, out, out_len);
+
+ // policy name
+ isc_buffer_printf(&buf, "dnssec-policy: %s\n", dns_kasp_getname(kasp));
+ isc_buffer_printf(&buf, "current time: ");
+ isc_stdtime_tostring(now, timestr, sizeof(timestr));
+ isc_buffer_printf(&buf, "%s\n", timestr);
+
+ for (dns_dnsseckey_t *dkey = ISC_LIST_HEAD(*keyring); dkey != NULL;
+ dkey = ISC_LIST_NEXT(dkey, link))
+ {
+ char algstr[DNS_NAME_FORMATSIZE];
+ bool ksk = false, zsk = false;
+ isc_result_t ret;
+
+ if (dst_key_is_unused(dkey->key)) {
+ continue;
+ }
+
+ // key data
+ dns_secalg_format((dns_secalg_t)dst_key_alg(dkey->key), algstr,
+ sizeof(algstr));
+ isc_buffer_printf(&buf, "\nkey: %d (%s), %s\n",
+ dst_key_id(dkey->key), algstr,
+ keymgr_keyrole(dkey->key));
+
+ // publish status
+ keytime_status(dkey->key, now, &buf,
+ " published: ", DST_KEY_DNSKEY,
+ DST_TIME_PUBLISH);
+
+ // signing status
+ ret = dst_key_getbool(dkey->key, DST_BOOL_KSK, &ksk);
+ if (ret == ISC_R_SUCCESS && ksk) {
+ keytime_status(dkey->key, now, &buf,
+ " key signing: ", DST_KEY_KRRSIG,
+ DST_TIME_PUBLISH);
+ }
+ ret = dst_key_getbool(dkey->key, DST_BOOL_ZSK, &zsk);
+ if (ret == ISC_R_SUCCESS && zsk) {
+ keytime_status(dkey->key, now, &buf,
+ " zone signing: ", DST_KEY_ZRRSIG,
+ DST_TIME_ACTIVATE);
+ }
+
+ // rollover status
+ rollover_status(dkey, kasp, now, &buf, zsk);
+
+ // key states
+ keystate_status(dkey->key, &buf,
+ "goal: ", DST_KEY_GOAL);
+ keystate_status(dkey->key, &buf,
+ "dnskey: ", DST_KEY_DNSKEY);
+ keystate_status(dkey->key, &buf,
+ "ds: ", DST_KEY_DS);
+ keystate_status(dkey->key, &buf,
+ "zone rrsig: ", DST_KEY_ZRRSIG);
+ keystate_status(dkey->key, &buf,
+ "key rrsig: ", DST_KEY_KRRSIG);
+ }
+}
+
+isc_result_t
+dns_keymgr_rollover(dns_kasp_t *kasp, dns_dnsseckeylist_t *keyring,
+ const char *directory, isc_stdtime_t now,
+ isc_stdtime_t when, dns_keytag_t id,
+ unsigned int algorithm) {
+ int options = (DST_TYPE_PRIVATE | DST_TYPE_PUBLIC | DST_TYPE_STATE);
+ isc_dir_t dir;
+ isc_result_t result;
+ dns_dnsseckey_t *key = NULL;
+ isc_stdtime_t active, retire, prepub;
+
+ REQUIRE(DNS_KASP_VALID(kasp));
+ REQUIRE(keyring != NULL);
+
+ for (dns_dnsseckey_t *dkey = ISC_LIST_HEAD(*keyring); dkey != NULL;
+ dkey = ISC_LIST_NEXT(dkey, link))
+ {
+ if (dst_key_id(dkey->key) != id) {
+ continue;
+ }
+ if (algorithm > 0 && dst_key_alg(dkey->key) != algorithm) {
+ continue;
+ }
+ if (key != NULL) {
+ /*
+ * Only rollover for one key at a time.
+ */
+ return (DNS_R_TOOMANYKEYS);
+ }
+ key = dkey;
+ }
+
+ if (key == NULL) {
+ return (DNS_R_NOKEYMATCH);
+ }
+
+ result = dst_key_gettime(key->key, DST_TIME_ACTIVATE, &active);
+ if (result != ISC_R_SUCCESS || active > now) {
+ return (DNS_R_KEYNOTACTIVE);
+ }
+
+ result = dst_key_gettime(key->key, DST_TIME_INACTIVE, &retire);
+ if (result != ISC_R_SUCCESS) {
+ /**
+ * Default to as if this key was not scheduled to
+ * become retired, as if it had unlimited lifetime.
+ */
+ retire = 0;
+ }
+
+ /**
+ * Usually when is set to now, which is before the scheduled
+ * prepublication time, meaning we reduce the lifetime of the
+ * key. But in some cases, the lifetime can also be extended.
+ * We accept it, but we can return an error here if that
+ * turns out to be unintuitive behavior.
+ */
+ prepub = dst_key_getttl(key->key) + dns_kasp_publishsafety(kasp) +
+ dns_kasp_zonepropagationdelay(kasp);
+ retire = when + prepub;
+
+ dst_key_settime(key->key, DST_TIME_INACTIVE, retire);
+ dst_key_setnum(key->key, DST_NUM_LIFETIME, (retire - active));
+
+ /* Store key state and update hints. */
+ isc_dir_init(&dir);
+ if (directory == NULL) {
+ directory = ".";
+ }
+ result = isc_dir_open(&dir, directory);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+
+ dns_dnssec_get_hints(key, now);
+ result = dst_key_tofile(key->key, options, directory);
+ if (result == ISC_R_SUCCESS) {
+ dst_key_setmodified(key->key, false);
+ }
+ isc_dir_close(&dir);
+
+ return (result);
+}
diff --git a/lib/dns/keytable.c b/lib/dns/keytable.c
new file mode 100644
index 0000000..8634281
--- /dev/null
+++ b/lib/dns/keytable.c
@@ -0,0 +1,956 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/*! \file */
+
+#include <stdbool.h>
+
+#include <isc/mem.h>
+#include <isc/print.h>
+#include <isc/refcount.h>
+#include <isc/result.h>
+#include <isc/rwlock.h>
+#include <isc/string.h> /* Required for HP/UX (and others?) */
+#include <isc/util.h>
+
+#include <dns/dnssec.h>
+#include <dns/fixedname.h>
+#include <dns/keytable.h>
+#include <dns/rbt.h>
+#include <dns/rdata.h>
+#include <dns/rdatalist.h>
+#include <dns/rdataset.h>
+#include <dns/rdatastruct.h>
+
+#define KEYTABLE_MAGIC ISC_MAGIC('K', 'T', 'b', 'l')
+#define VALID_KEYTABLE(kt) ISC_MAGIC_VALID(kt, KEYTABLE_MAGIC)
+
+#define KEYNODE_MAGIC ISC_MAGIC('K', 'N', 'o', 'd')
+#define VALID_KEYNODE(kn) ISC_MAGIC_VALID(kn, KEYNODE_MAGIC)
+
+struct dns_keytable {
+ /* Unlocked. */
+ unsigned int magic;
+ isc_mem_t *mctx;
+ isc_refcount_t references;
+ isc_rwlock_t rwlock;
+ /* Locked by rwlock. */
+ dns_rbt_t *table;
+};
+
+struct dns_keynode {
+ unsigned int magic;
+ isc_mem_t *mctx;
+ isc_refcount_t refcount;
+ isc_rwlock_t rwlock;
+ dns_rdatalist_t *dslist;
+ dns_rdataset_t dsset;
+ bool managed;
+ bool initial;
+};
+
+static dns_keynode_t *
+new_keynode(dns_rdata_ds_t *ds, dns_keytable_t *keytable, bool managed,
+ bool initial);
+
+static void
+keynode_disassociate(dns_rdataset_t *rdataset);
+static isc_result_t
+keynode_first(dns_rdataset_t *rdataset);
+static isc_result_t
+keynode_next(dns_rdataset_t *rdataset);
+static void
+keynode_current(dns_rdataset_t *rdataset, dns_rdata_t *rdata);
+static void
+keynode_clone(dns_rdataset_t *source, dns_rdataset_t *target);
+
+static dns_rdatasetmethods_t methods = {
+ keynode_disassociate,
+ keynode_first,
+ keynode_next,
+ keynode_current,
+ keynode_clone,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL, /* settrust */
+ NULL, /* expire */
+ NULL, /* clearprefetch */
+ NULL,
+ NULL,
+ NULL /* addglue */
+};
+
+static void
+keynode_attach(dns_keynode_t *source, dns_keynode_t **target) {
+ REQUIRE(VALID_KEYNODE(source));
+ isc_refcount_increment(&source->refcount);
+ *target = source;
+}
+
+static void
+keynode_detach(isc_mem_t *mctx, dns_keynode_t **keynodep) {
+ REQUIRE(keynodep != NULL && VALID_KEYNODE(*keynodep));
+ dns_keynode_t *knode = *keynodep;
+ *keynodep = NULL;
+
+ if (isc_refcount_decrement(&knode->refcount) == 1) {
+ dns_rdata_t *rdata = NULL;
+ isc_refcount_destroy(&knode->refcount);
+ isc_rwlock_destroy(&knode->rwlock);
+ if (knode->dslist != NULL) {
+ for (rdata = ISC_LIST_HEAD(knode->dslist->rdata);
+ rdata != NULL;
+ rdata = ISC_LIST_HEAD(knode->dslist->rdata))
+ {
+ ISC_LIST_UNLINK(knode->dslist->rdata, rdata,
+ link);
+ isc_mem_put(mctx, rdata->data,
+ DNS_DS_BUFFERSIZE);
+ isc_mem_put(mctx, rdata, sizeof(*rdata));
+ }
+
+ isc_mem_put(mctx, knode->dslist,
+ sizeof(*knode->dslist));
+ knode->dslist = NULL;
+ }
+ isc_mem_putanddetach(&knode->mctx, knode,
+ sizeof(dns_keynode_t));
+ }
+}
+
+static void
+free_keynode(void *node, void *arg) {
+ dns_keynode_t *keynode = node;
+ isc_mem_t *mctx = arg;
+
+ keynode_detach(mctx, &keynode);
+}
+
+isc_result_t
+dns_keytable_create(isc_mem_t *mctx, dns_keytable_t **keytablep) {
+ dns_keytable_t *keytable;
+ isc_result_t result;
+
+ /*
+ * Create a keytable.
+ */
+
+ REQUIRE(keytablep != NULL && *keytablep == NULL);
+
+ keytable = isc_mem_get(mctx, sizeof(*keytable));
+
+ keytable->table = NULL;
+ result = dns_rbt_create(mctx, free_keynode, mctx, &keytable->table);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup_keytable;
+ }
+
+ isc_rwlock_init(&keytable->rwlock, 0, 0);
+ isc_refcount_init(&keytable->references, 1);
+
+ keytable->mctx = NULL;
+ isc_mem_attach(mctx, &keytable->mctx);
+ keytable->magic = KEYTABLE_MAGIC;
+ *keytablep = keytable;
+
+ return (ISC_R_SUCCESS);
+
+cleanup_keytable:
+ isc_mem_putanddetach(&mctx, keytable, sizeof(*keytable));
+
+ return (result);
+}
+
+void
+dns_keytable_attach(dns_keytable_t *source, dns_keytable_t **targetp) {
+ REQUIRE(VALID_KEYTABLE(source));
+ REQUIRE(targetp != NULL && *targetp == NULL);
+
+ isc_refcount_increment(&source->references);
+
+ *targetp = source;
+}
+
+void
+dns_keytable_detach(dns_keytable_t **keytablep) {
+ REQUIRE(keytablep != NULL && VALID_KEYTABLE(*keytablep));
+ dns_keytable_t *keytable = *keytablep;
+ *keytablep = NULL;
+
+ if (isc_refcount_decrement(&keytable->references) == 1) {
+ isc_refcount_destroy(&keytable->references);
+ dns_rbt_destroy(&keytable->table);
+ isc_rwlock_destroy(&keytable->rwlock);
+ keytable->magic = 0;
+ isc_mem_putanddetach(&keytable->mctx, keytable,
+ sizeof(*keytable));
+ }
+}
+
+static void
+add_ds(dns_keynode_t *knode, dns_rdata_ds_t *ds, isc_mem_t *mctx) {
+ isc_result_t result;
+ dns_rdata_t *dsrdata = NULL, *rdata = NULL;
+ void *data = NULL;
+ bool exists = false;
+ isc_buffer_t b;
+
+ dsrdata = isc_mem_get(mctx, sizeof(*dsrdata));
+ dns_rdata_init(dsrdata);
+
+ data = isc_mem_get(mctx, DNS_DS_BUFFERSIZE);
+ isc_buffer_init(&b, data, DNS_DS_BUFFERSIZE);
+
+ result = dns_rdata_fromstruct(dsrdata, dns_rdataclass_in,
+ dns_rdatatype_ds, ds, &b);
+ RUNTIME_CHECK(result == ISC_R_SUCCESS);
+
+ RWLOCK(&knode->rwlock, isc_rwlocktype_write);
+
+ if (knode->dslist == NULL) {
+ knode->dslist = isc_mem_get(mctx, sizeof(*knode->dslist));
+ dns_rdatalist_init(knode->dslist);
+ knode->dslist->rdclass = dns_rdataclass_in;
+ knode->dslist->type = dns_rdatatype_ds;
+
+ INSIST(knode->dsset.methods == NULL);
+ knode->dsset.methods = &methods;
+ knode->dsset.rdclass = knode->dslist->rdclass;
+ knode->dsset.type = knode->dslist->type;
+ knode->dsset.covers = knode->dslist->covers;
+ knode->dsset.ttl = knode->dslist->ttl;
+ knode->dsset.private1 = knode;
+ knode->dsset.private2 = NULL;
+ knode->dsset.private3 = NULL;
+ knode->dsset.privateuint4 = 0;
+ knode->dsset.private5 = NULL;
+ knode->dsset.trust = dns_trust_ultimate;
+ }
+
+ for (rdata = ISC_LIST_HEAD(knode->dslist->rdata); rdata != NULL;
+ rdata = ISC_LIST_NEXT(rdata, link))
+ {
+ if (dns_rdata_compare(rdata, dsrdata) == 0) {
+ exists = true;
+ break;
+ }
+ }
+
+ if (exists) {
+ isc_mem_put(mctx, dsrdata->data, DNS_DS_BUFFERSIZE);
+ isc_mem_put(mctx, dsrdata, sizeof(*dsrdata));
+ } else {
+ ISC_LIST_APPEND(knode->dslist->rdata, dsrdata, link);
+ }
+
+ RWUNLOCK(&knode->rwlock, isc_rwlocktype_write);
+}
+
+static isc_result_t
+delete_ds(dns_keytable_t *keytable, dns_rbtnode_t *node, dns_rdata_ds_t *ds) {
+ dns_keynode_t *knode = node->data;
+ isc_result_t result;
+ dns_rdata_t dsrdata = DNS_RDATA_INIT;
+ dns_rdata_t *rdata = NULL;
+ unsigned char data[DNS_DS_BUFFERSIZE];
+ bool found = false;
+ isc_buffer_t b;
+
+ RWLOCK(&knode->rwlock, isc_rwlocktype_read);
+ if (knode->dslist == NULL) {
+ RWUNLOCK(&knode->rwlock, isc_rwlocktype_read);
+ return (ISC_R_SUCCESS);
+ }
+
+ isc_buffer_init(&b, data, DNS_DS_BUFFERSIZE);
+
+ result = dns_rdata_fromstruct(&dsrdata, dns_rdataclass_in,
+ dns_rdatatype_ds, ds, &b);
+ if (result != ISC_R_SUCCESS) {
+ RWUNLOCK(&knode->rwlock, isc_rwlocktype_write);
+ return (result);
+ }
+
+ for (rdata = ISC_LIST_HEAD(knode->dslist->rdata); rdata != NULL;
+ rdata = ISC_LIST_NEXT(rdata, link))
+ {
+ if (dns_rdata_compare(rdata, &dsrdata) == 0) {
+ found = true;
+ break;
+ }
+ }
+
+ if (!found) {
+ RWUNLOCK(&knode->rwlock, isc_rwlocktype_read);
+ /*
+ * The keyname must have matched or we wouldn't be here,
+ * so we use DNS_R_PARTIALMATCH instead of ISC_R_NOTFOUND.
+ */
+ return (DNS_R_PARTIALMATCH);
+ }
+
+ /*
+ * Replace knode with a new instance without the DS.
+ */
+ node->data = new_keynode(NULL, keytable, knode->managed,
+ knode->initial);
+ for (rdata = ISC_LIST_HEAD(knode->dslist->rdata); rdata != NULL;
+ rdata = ISC_LIST_NEXT(rdata, link))
+ {
+ if (dns_rdata_compare(rdata, &dsrdata) != 0) {
+ dns_rdata_ds_t ds0;
+ result = dns_rdata_tostruct(rdata, &ds0, NULL);
+ RUNTIME_CHECK(result == ISC_R_SUCCESS);
+ add_ds(node->data, &ds0, keytable->mctx);
+ }
+ }
+ RWUNLOCK(&knode->rwlock, isc_rwlocktype_read);
+
+ keynode_detach(keytable->mctx, &knode);
+
+ return (ISC_R_SUCCESS);
+}
+
+/*%
+ * Create a keynode for "ds" (or a null key node if "ds" is NULL), set
+ * "managed" and "initial" as requested and attach the keynode to
+ * to "node" in "keytable".
+ */
+static dns_keynode_t *
+new_keynode(dns_rdata_ds_t *ds, dns_keytable_t *keytable, bool managed,
+ bool initial) {
+ dns_keynode_t *knode = NULL;
+
+ REQUIRE(VALID_KEYTABLE(keytable));
+ REQUIRE(!initial || managed);
+
+ knode = isc_mem_get(keytable->mctx, sizeof(dns_keynode_t));
+ *knode = (dns_keynode_t){ .magic = KEYNODE_MAGIC };
+
+ dns_rdataset_init(&knode->dsset);
+ isc_refcount_init(&knode->refcount, 1);
+ isc_rwlock_init(&knode->rwlock, 0, 0);
+
+ /*
+ * If a DS was supplied, initialize an rdatalist.
+ */
+ if (ds != NULL) {
+ add_ds(knode, ds, keytable->mctx);
+ }
+
+ isc_mem_attach(keytable->mctx, &knode->mctx);
+ knode->managed = managed;
+ knode->initial = initial;
+
+ return (knode);
+}
+
+/*%
+ * Add key trust anchor "ds" at "keyname" in "keytable". If an anchor
+ * already exists at the requested name does not contain "ds", update it.
+ * If "ds" is NULL, add a null key to indicate that "keyname" should be
+ * treated as a secure domain without supplying key data which would allow
+ * the domain to be validated.
+ */
+static isc_result_t
+insert(dns_keytable_t *keytable, bool managed, bool initial,
+ const dns_name_t *keyname, dns_rdata_ds_t *ds,
+ dns_keytable_callback_t callback, void *callback_arg) {
+ dns_rbtnode_t *node = NULL;
+ isc_result_t result;
+
+ REQUIRE(VALID_KEYTABLE(keytable));
+
+ RWLOCK(&keytable->rwlock, isc_rwlocktype_write);
+
+ result = dns_rbt_addnode(keytable->table, keyname, &node);
+ if (result == ISC_R_SUCCESS) {
+ /*
+ * There was no node for "keyname" in "keytable" yet, so one
+ * was created. Create a new key node for the supplied
+ * trust anchor (or a null key node if "ds" is NULL)
+ * and attach it to the created node.
+ */
+ node->data = new_keynode(ds, keytable, managed, initial);
+ if (callback != NULL) {
+ (*callback)(keyname, callback_arg);
+ }
+ } else if (result == ISC_R_EXISTS) {
+ /*
+ * A node already exists for "keyname" in "keytable".
+ */
+ if (ds != NULL) {
+ dns_keynode_t *knode = node->data;
+ if (knode == NULL) {
+ node->data = new_keynode(ds, keytable, managed,
+ initial);
+ if (callback != NULL) {
+ (*callback)(keyname, callback_arg);
+ }
+ } else {
+ add_ds(knode, ds, keytable->mctx);
+ }
+ }
+ result = ISC_R_SUCCESS;
+ }
+
+ RWUNLOCK(&keytable->rwlock, isc_rwlocktype_write);
+
+ return (result);
+}
+
+isc_result_t
+dns_keytable_add(dns_keytable_t *keytable, bool managed, bool initial,
+ dns_name_t *name, dns_rdata_ds_t *ds,
+ dns_keytable_callback_t callback, void *callback_arg) {
+ REQUIRE(ds != NULL);
+ REQUIRE(!initial || managed);
+
+ return (insert(keytable, managed, initial, name, ds, callback,
+ callback_arg));
+}
+
+isc_result_t
+dns_keytable_marksecure(dns_keytable_t *keytable, const dns_name_t *name) {
+ return (insert(keytable, true, false, name, NULL, NULL, NULL));
+}
+
+isc_result_t
+dns_keytable_delete(dns_keytable_t *keytable, const dns_name_t *keyname,
+ dns_keytable_callback_t callback, void *callback_arg) {
+ isc_result_t result;
+ dns_rbtnode_t *node = NULL;
+
+ REQUIRE(VALID_KEYTABLE(keytable));
+ REQUIRE(keyname != NULL);
+
+ RWLOCK(&keytable->rwlock, isc_rwlocktype_write);
+ result = dns_rbt_findnode(keytable->table, keyname, NULL, &node, NULL,
+ DNS_RBTFIND_NOOPTIONS, NULL, NULL);
+ if (result == ISC_R_SUCCESS) {
+ if (node->data != NULL) {
+ result = dns_rbt_deletenode(keytable->table, node,
+ false);
+ if (callback != NULL) {
+ (*callback)(keyname, callback_arg);
+ }
+ } else {
+ result = ISC_R_NOTFOUND;
+ }
+ } else if (result == DNS_R_PARTIALMATCH) {
+ result = ISC_R_NOTFOUND;
+ }
+ RWUNLOCK(&keytable->rwlock, isc_rwlocktype_write);
+
+ return (result);
+}
+
+isc_result_t
+dns_keytable_deletekey(dns_keytable_t *keytable, const dns_name_t *keyname,
+ dns_rdata_dnskey_t *dnskey) {
+ isc_result_t result;
+ dns_rbtnode_t *node = NULL;
+ dns_keynode_t *knode = NULL;
+ dns_rdata_t rdata = DNS_RDATA_INIT;
+ unsigned char data[4096], digest[DNS_DS_BUFFERSIZE];
+ dns_rdata_ds_t ds;
+ isc_buffer_t b;
+
+ REQUIRE(VALID_KEYTABLE(keytable));
+ REQUIRE(dnskey != NULL);
+
+ RWLOCK(&keytable->rwlock, isc_rwlocktype_write);
+ result = dns_rbt_findnode(keytable->table, keyname, NULL, &node, NULL,
+ DNS_RBTFIND_NOOPTIONS, NULL, NULL);
+
+ if (result == DNS_R_PARTIALMATCH) {
+ result = ISC_R_NOTFOUND;
+ }
+ if (result != ISC_R_SUCCESS) {
+ goto finish;
+ }
+
+ if (node->data == NULL) {
+ result = ISC_R_NOTFOUND;
+ goto finish;
+ }
+
+ knode = node->data;
+
+ RWLOCK(&knode->rwlock, isc_rwlocktype_read);
+ if (knode->dslist == NULL) {
+ RWUNLOCK(&knode->rwlock, isc_rwlocktype_read);
+ result = DNS_R_PARTIALMATCH;
+ goto finish;
+ }
+ RWUNLOCK(&knode->rwlock, isc_rwlocktype_read);
+
+ isc_buffer_init(&b, data, sizeof(data));
+ result = dns_rdata_fromstruct(&rdata, dnskey->common.rdclass,
+ dns_rdatatype_dnskey, dnskey, &b);
+ if (result != ISC_R_SUCCESS) {
+ goto finish;
+ }
+
+ result = dns_ds_fromkeyrdata(keyname, &rdata, DNS_DSDIGEST_SHA256,
+ digest, &ds);
+ if (result != ISC_R_SUCCESS) {
+ goto finish;
+ }
+
+ result = delete_ds(keytable, node, &ds);
+
+finish:
+ RWUNLOCK(&keytable->rwlock, isc_rwlocktype_write);
+ return (result);
+}
+
+isc_result_t
+dns_keytable_find(dns_keytable_t *keytable, const dns_name_t *keyname,
+ dns_keynode_t **keynodep) {
+ isc_result_t result;
+ dns_rbtnode_t *node = NULL;
+
+ REQUIRE(VALID_KEYTABLE(keytable));
+ REQUIRE(keyname != NULL);
+ REQUIRE(keynodep != NULL && *keynodep == NULL);
+
+ RWLOCK(&keytable->rwlock, isc_rwlocktype_read);
+ result = dns_rbt_findnode(keytable->table, keyname, NULL, &node, NULL,
+ DNS_RBTFIND_NOOPTIONS, NULL, NULL);
+ if (result == ISC_R_SUCCESS) {
+ if (node->data != NULL) {
+ keynode_attach(node->data, keynodep);
+ } else {
+ result = ISC_R_NOTFOUND;
+ }
+ } else if (result == DNS_R_PARTIALMATCH) {
+ result = ISC_R_NOTFOUND;
+ }
+ RWUNLOCK(&keytable->rwlock, isc_rwlocktype_read);
+
+ return (result);
+}
+
+isc_result_t
+dns_keytable_finddeepestmatch(dns_keytable_t *keytable, const dns_name_t *name,
+ dns_name_t *foundname) {
+ isc_result_t result;
+ void *data;
+
+ /*
+ * Search for the deepest match in 'keytable'.
+ */
+
+ REQUIRE(VALID_KEYTABLE(keytable));
+ REQUIRE(dns_name_isabsolute(name));
+ REQUIRE(foundname != NULL);
+
+ RWLOCK(&keytable->rwlock, isc_rwlocktype_read);
+
+ data = NULL;
+ result = dns_rbt_findname(keytable->table, name, 0, foundname, &data);
+
+ if (result == ISC_R_SUCCESS || result == DNS_R_PARTIALMATCH) {
+ result = ISC_R_SUCCESS;
+ }
+
+ RWUNLOCK(&keytable->rwlock, isc_rwlocktype_read);
+
+ return (result);
+}
+
+void
+dns_keytable_detachkeynode(dns_keytable_t *keytable, dns_keynode_t **keynodep) {
+ /*
+ * Give back a keynode found via dns_keytable_findkeynode().
+ */
+
+ REQUIRE(VALID_KEYTABLE(keytable));
+ REQUIRE(keynodep != NULL && VALID_KEYNODE(*keynodep));
+
+ keynode_detach(keytable->mctx, keynodep);
+}
+
+isc_result_t
+dns_keytable_issecuredomain(dns_keytable_t *keytable, const dns_name_t *name,
+ dns_name_t *foundname, bool *wantdnssecp) {
+ isc_result_t result;
+ dns_rbtnode_t *node = NULL;
+
+ /*
+ * Is 'name' at or beneath a trusted key?
+ */
+
+ REQUIRE(VALID_KEYTABLE(keytable));
+ REQUIRE(dns_name_isabsolute(name));
+ REQUIRE(wantdnssecp != NULL);
+
+ RWLOCK(&keytable->rwlock, isc_rwlocktype_read);
+
+ result = dns_rbt_findnode(keytable->table, name, foundname, &node, NULL,
+ DNS_RBTFIND_NOOPTIONS, NULL, NULL);
+ if (result == ISC_R_SUCCESS || result == DNS_R_PARTIALMATCH) {
+ INSIST(node->data != NULL);
+ *wantdnssecp = true;
+ result = ISC_R_SUCCESS;
+ } else if (result == ISC_R_NOTFOUND) {
+ *wantdnssecp = false;
+ result = ISC_R_SUCCESS;
+ }
+
+ RWUNLOCK(&keytable->rwlock, isc_rwlocktype_read);
+
+ return (result);
+}
+
+static isc_result_t
+putstr(isc_buffer_t **b, const char *str) {
+ isc_result_t result;
+
+ result = isc_buffer_reserve(b, strlen(str));
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+
+ isc_buffer_putstr(*b, str);
+ return (ISC_R_SUCCESS);
+}
+
+isc_result_t
+dns_keytable_dump(dns_keytable_t *keytable, FILE *fp) {
+ isc_result_t result;
+ isc_buffer_t *text = NULL;
+
+ REQUIRE(VALID_KEYTABLE(keytable));
+ REQUIRE(fp != NULL);
+
+ isc_buffer_allocate(keytable->mctx, &text, 4096);
+
+ result = dns_keytable_totext(keytable, &text);
+
+ if (isc_buffer_usedlength(text) != 0) {
+ (void)putstr(&text, "\n");
+ } else if (result == ISC_R_SUCCESS) {
+ (void)putstr(&text, "none");
+ } else {
+ (void)putstr(&text, "could not dump key table: ");
+ (void)putstr(&text, isc_result_totext(result));
+ }
+
+ fprintf(fp, "%.*s", (int)isc_buffer_usedlength(text),
+ (char *)isc_buffer_base(text));
+
+ isc_buffer_free(&text);
+ return (result);
+}
+
+static isc_result_t
+keynode_dslist_totext(dns_name_t *name, dns_keynode_t *keynode,
+ isc_buffer_t **text) {
+ isc_result_t result;
+ char namebuf[DNS_NAME_FORMATSIZE];
+ char obuf[DNS_NAME_FORMATSIZE + 200];
+ dns_rdataset_t dsset;
+
+ dns_name_format(name, namebuf, sizeof(namebuf));
+
+ dns_rdataset_init(&dsset);
+ if (!dns_keynode_dsset(keynode, &dsset)) {
+ return (ISC_R_SUCCESS);
+ }
+
+ for (result = dns_rdataset_first(&dsset); result == ISC_R_SUCCESS;
+ result = dns_rdataset_next(&dsset))
+ {
+ char algbuf[DNS_SECALG_FORMATSIZE];
+ dns_rdata_t rdata = DNS_RDATA_INIT;
+ dns_rdata_ds_t ds;
+
+ dns_rdataset_current(&dsset, &rdata);
+ result = dns_rdata_tostruct(&rdata, &ds, NULL);
+ RUNTIME_CHECK(result == ISC_R_SUCCESS);
+
+ dns_secalg_format(ds.algorithm, algbuf, sizeof(algbuf));
+
+ RWLOCK(&keynode->rwlock, isc_rwlocktype_read);
+ snprintf(obuf, sizeof(obuf), "%s/%s/%d ; %s%s\n", namebuf,
+ algbuf, ds.key_tag,
+ keynode->initial ? "initializing " : "",
+ keynode->managed ? "managed" : "static");
+ RWUNLOCK(&keynode->rwlock, isc_rwlocktype_read);
+
+ result = putstr(text, obuf);
+ if (result != ISC_R_SUCCESS) {
+ dns_rdataset_disassociate(&dsset);
+ return (result);
+ }
+ }
+ dns_rdataset_disassociate(&dsset);
+
+ return (ISC_R_SUCCESS);
+}
+
+isc_result_t
+dns_keytable_totext(dns_keytable_t *keytable, isc_buffer_t **text) {
+ isc_result_t result;
+ dns_keynode_t *knode;
+ dns_rbtnode_t *node;
+ dns_rbtnodechain_t chain;
+ dns_name_t *foundname, *origin, *fullname;
+ dns_fixedname_t fixedfoundname, fixedorigin, fixedfullname;
+
+ REQUIRE(VALID_KEYTABLE(keytable));
+ REQUIRE(text != NULL && *text != NULL);
+
+ origin = dns_fixedname_initname(&fixedorigin);
+ fullname = dns_fixedname_initname(&fixedfullname);
+ foundname = dns_fixedname_initname(&fixedfoundname);
+
+ RWLOCK(&keytable->rwlock, isc_rwlocktype_read);
+ dns_rbtnodechain_init(&chain);
+ result = dns_rbtnodechain_first(&chain, keytable->table, NULL, NULL);
+ if (result != ISC_R_SUCCESS && result != DNS_R_NEWORIGIN) {
+ if (result == ISC_R_NOTFOUND) {
+ result = ISC_R_SUCCESS;
+ }
+ goto cleanup;
+ }
+ for (;;) {
+ dns_rbtnodechain_current(&chain, foundname, origin, &node);
+
+ knode = node->data;
+ if (knode != NULL && knode->dslist != NULL) {
+ result = dns_name_concatenate(foundname, origin,
+ fullname, NULL);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+
+ result = keynode_dslist_totext(fullname, knode, text);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+ }
+
+ result = dns_rbtnodechain_next(&chain, NULL, NULL);
+ if (result != ISC_R_SUCCESS && result != DNS_R_NEWORIGIN) {
+ if (result == ISC_R_NOMORE) {
+ result = ISC_R_SUCCESS;
+ }
+ break;
+ }
+ }
+
+cleanup:
+ dns_rbtnodechain_invalidate(&chain);
+ RWUNLOCK(&keytable->rwlock, isc_rwlocktype_read);
+ return (result);
+}
+
+isc_result_t
+dns_keytable_forall(dns_keytable_t *keytable,
+ void (*func)(dns_keytable_t *, dns_keynode_t *,
+ dns_name_t *, void *),
+ void *arg) {
+ isc_result_t result;
+ dns_rbtnode_t *node;
+ dns_rbtnodechain_t chain;
+ dns_fixedname_t fixedfoundname, fixedorigin, fixedfullname;
+ dns_name_t *foundname, *origin, *fullname;
+
+ REQUIRE(VALID_KEYTABLE(keytable));
+
+ origin = dns_fixedname_initname(&fixedorigin);
+ fullname = dns_fixedname_initname(&fixedfullname);
+ foundname = dns_fixedname_initname(&fixedfoundname);
+
+ RWLOCK(&keytable->rwlock, isc_rwlocktype_read);
+ dns_rbtnodechain_init(&chain);
+ result = dns_rbtnodechain_first(&chain, keytable->table, NULL, NULL);
+ if (result != ISC_R_SUCCESS && result != DNS_R_NEWORIGIN) {
+ if (result == ISC_R_NOTFOUND) {
+ result = ISC_R_SUCCESS;
+ }
+ goto cleanup;
+ }
+
+ for (;;) {
+ dns_rbtnodechain_current(&chain, foundname, origin, &node);
+ if (node->data != NULL) {
+ result = dns_name_concatenate(foundname, origin,
+ fullname, NULL);
+ RUNTIME_CHECK(result == ISC_R_SUCCESS);
+ (*func)(keytable, node->data, fullname, arg);
+ }
+ result = dns_rbtnodechain_next(&chain, NULL, NULL);
+ if (result != ISC_R_SUCCESS && result != DNS_R_NEWORIGIN) {
+ if (result == ISC_R_NOMORE) {
+ result = ISC_R_SUCCESS;
+ }
+ break;
+ }
+ }
+
+cleanup:
+ dns_rbtnodechain_invalidate(&chain);
+ RWUNLOCK(&keytable->rwlock, isc_rwlocktype_read);
+ return (result);
+}
+
+bool
+dns_keynode_dsset(dns_keynode_t *keynode, dns_rdataset_t *rdataset) {
+ bool result;
+ REQUIRE(VALID_KEYNODE(keynode));
+ REQUIRE(rdataset == NULL || DNS_RDATASET_VALID(rdataset));
+
+ RWLOCK(&keynode->rwlock, isc_rwlocktype_read);
+ if (keynode->dslist != NULL) {
+ if (rdataset != NULL) {
+ keynode_clone(&keynode->dsset, rdataset);
+ }
+ result = true;
+ } else {
+ result = false;
+ }
+ RWUNLOCK(&keynode->rwlock, isc_rwlocktype_read);
+ return (result);
+}
+
+bool
+dns_keynode_managed(dns_keynode_t *keynode) {
+ bool managed;
+
+ REQUIRE(VALID_KEYNODE(keynode));
+
+ RWLOCK(&keynode->rwlock, isc_rwlocktype_read);
+ managed = keynode->managed;
+ RWUNLOCK(&keynode->rwlock, isc_rwlocktype_read);
+
+ return (managed);
+}
+
+bool
+dns_keynode_initial(dns_keynode_t *keynode) {
+ bool initial;
+
+ REQUIRE(VALID_KEYNODE(keynode));
+
+ RWLOCK(&keynode->rwlock, isc_rwlocktype_read);
+ initial = keynode->initial;
+ RWUNLOCK(&keynode->rwlock, isc_rwlocktype_read);
+
+ return (initial);
+}
+
+void
+dns_keynode_trust(dns_keynode_t *keynode) {
+ REQUIRE(VALID_KEYNODE(keynode));
+
+ RWLOCK(&keynode->rwlock, isc_rwlocktype_write);
+ keynode->initial = false;
+ RWUNLOCK(&keynode->rwlock, isc_rwlocktype_write);
+}
+
+static void
+keynode_disassociate(dns_rdataset_t *rdataset) {
+ dns_keynode_t *keynode;
+
+ REQUIRE(rdataset != NULL);
+ REQUIRE(rdataset->methods == &methods);
+
+ rdataset->methods = NULL;
+ keynode = rdataset->private1;
+ rdataset->private1 = NULL;
+
+ keynode_detach(keynode->mctx, &keynode);
+}
+
+static isc_result_t
+keynode_first(dns_rdataset_t *rdataset) {
+ dns_keynode_t *keynode;
+
+ REQUIRE(rdataset != NULL);
+ REQUIRE(rdataset->methods == &methods);
+
+ keynode = rdataset->private1;
+ RWLOCK(&keynode->rwlock, isc_rwlocktype_read);
+ rdataset->private2 = ISC_LIST_HEAD(keynode->dslist->rdata);
+ RWUNLOCK(&keynode->rwlock, isc_rwlocktype_read);
+
+ if (rdataset->private2 == NULL) {
+ return (ISC_R_NOMORE);
+ }
+
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+keynode_next(dns_rdataset_t *rdataset) {
+ dns_keynode_t *keynode;
+ dns_rdata_t *rdata;
+
+ REQUIRE(rdataset != NULL);
+ REQUIRE(rdataset->methods == &methods);
+
+ rdata = rdataset->private2;
+ if (rdata == NULL) {
+ return (ISC_R_NOMORE);
+ }
+
+ keynode = rdataset->private1;
+ RWLOCK(&keynode->rwlock, isc_rwlocktype_read);
+ rdataset->private2 = ISC_LIST_NEXT(rdata, link);
+ RWUNLOCK(&keynode->rwlock, isc_rwlocktype_read);
+
+ if (rdataset->private2 == NULL) {
+ return (ISC_R_NOMORE);
+ }
+
+ return (ISC_R_SUCCESS);
+}
+
+static void
+keynode_current(dns_rdataset_t *rdataset, dns_rdata_t *rdata) {
+ dns_rdata_t *list_rdata;
+
+ REQUIRE(rdataset != NULL);
+ REQUIRE(rdataset->methods == &methods);
+
+ list_rdata = rdataset->private2;
+ INSIST(list_rdata != NULL);
+
+ dns_rdata_clone(list_rdata, rdata);
+}
+
+static void
+keynode_clone(dns_rdataset_t *source, dns_rdataset_t *target) {
+ dns_keynode_t *keynode;
+
+ REQUIRE(source != NULL);
+ REQUIRE(target != NULL);
+ REQUIRE(source->methods == &methods);
+
+ keynode = source->private1;
+ isc_refcount_increment(&keynode->refcount);
+
+ *target = *source;
+
+ /*
+ * Reset iterator state.
+ */
+ target->private2 = NULL;
+}
diff --git a/lib/dns/log.c b/lib/dns/log.c
new file mode 100644
index 0000000..a8bf01d
--- /dev/null
+++ b/lib/dns/log.c
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/*! \file */
+
+#include <isc/util.h>
+
+#include <dns/log.h>
+
+/*%
+ * When adding a new category, be sure to add the appropriate
+ * \#define to <dns/log.h>.
+ */
+isc_logcategory_t dns_categories[] = {
+ { "notify", 0 }, { "database", 0 }, { "security", 0 },
+ { "_placeholder", 0 }, { "dnssec", 0 }, { "resolver", 0 },
+ { "xfer-in", 0 }, { "xfer-out", 0 }, { "dispatch", 0 },
+ { "lame-servers", 0 }, { "delegation-only", 0 }, { "edns-disabled", 0 },
+ { "rpz", 0 }, { "rate-limit", 0 }, { "cname", 0 },
+ { "spill", 0 }, { "dnstap", 0 }, { "zoneload", 0 },
+ { "nsid", 0 }, { "rpz-passthru", 0 }, { NULL, 0 }
+};
+
+/*%
+ * When adding a new module, be sure to add the appropriate
+ * \#define to <dns/log.h>.
+ */
+isc_logmodule_t dns_modules[] = {
+ { "dns/db", 0 }, { "dns/rbtdb", 0 },
+ { "dns/rbt", 0 }, { "dns/rdata", 0 },
+ { "dns/master", 0 }, { "dns/message", 0 },
+ { "dns/cache", 0 }, { "dns/config", 0 },
+ { "dns/resolver", 0 }, { "dns/zone", 0 },
+ { "dns/journal", 0 }, { "dns/adb", 0 },
+ { "dns/xfrin", 0 }, { "dns/xfrout", 0 },
+ { "dns/acl", 0 }, { "dns/validator", 0 },
+ { "dns/dispatch", 0 }, { "dns/request", 0 },
+ { "dns/masterdump", 0 }, { "dns/tsig", 0 },
+ { "dns/tkey", 0 }, { "dns/sdb", 0 },
+ { "dns/diff", 0 }, { "dns/hints", 0 },
+ { "dns/unused1", 0 }, { "dns/dlz", 0 },
+ { "dns/dnssec", 0 }, { "dns/crypto", 0 },
+ { "dns/packets", 0 }, { "dns/nta", 0 },
+ { "dns/dyndb", 0 }, { "dns/dnstap", 0 },
+ { "dns/ssu", 0 }, { NULL, 0 }
+};
+
+isc_log_t *dns_lctx = NULL;
+
+void
+dns_log_init(isc_log_t *lctx) {
+ REQUIRE(lctx != NULL);
+
+ isc_log_registercategories(lctx, dns_categories);
+ isc_log_registermodules(lctx, dns_modules);
+}
+
+void
+dns_log_setcontext(isc_log_t *lctx) {
+ dns_lctx = lctx;
+}
diff --git a/lib/dns/lookup.c b/lib/dns/lookup.c
new file mode 100644
index 0000000..d370c19
--- /dev/null
+++ b/lib/dns/lookup.c
@@ -0,0 +1,445 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/*! \file */
+
+#include <stdbool.h>
+
+#include <isc/mem.h>
+#include <isc/netaddr.h>
+#include <isc/result.h>
+#include <isc/string.h> /* Required for HP/UX (and others?) */
+#include <isc/task.h>
+#include <isc/util.h>
+
+#include <dns/db.h>
+#include <dns/events.h>
+#include <dns/lookup.h>
+#include <dns/rdata.h>
+#include <dns/rdataset.h>
+#include <dns/rdatastruct.h>
+#include <dns/resolver.h>
+#include <dns/view.h>
+
+struct dns_lookup {
+ /* Unlocked. */
+ unsigned int magic;
+ isc_mem_t *mctx;
+ isc_mutex_t lock;
+ dns_rdatatype_t type;
+ dns_fixedname_t name;
+ /* Locked by lock. */
+ unsigned int options;
+ isc_task_t *task;
+ dns_view_t *view;
+ dns_lookupevent_t *event;
+ dns_fetch_t *fetch;
+ unsigned int restarts;
+ bool canceled;
+ dns_rdataset_t rdataset;
+ dns_rdataset_t sigrdataset;
+};
+
+#define LOOKUP_MAGIC ISC_MAGIC('l', 'o', 'o', 'k')
+#define VALID_LOOKUP(l) ISC_MAGIC_VALID((l), LOOKUP_MAGIC)
+
+#define MAX_RESTARTS 16
+
+static void
+lookup_find(dns_lookup_t *lookup, dns_fetchevent_t *event);
+
+static void
+fetch_done(isc_task_t *task, isc_event_t *event) {
+ dns_lookup_t *lookup = event->ev_arg;
+ dns_fetchevent_t *fevent;
+
+ UNUSED(task);
+ REQUIRE(event->ev_type == DNS_EVENT_FETCHDONE);
+ REQUIRE(VALID_LOOKUP(lookup));
+ REQUIRE(lookup->task == task);
+ fevent = (dns_fetchevent_t *)event;
+ REQUIRE(fevent->fetch == lookup->fetch);
+
+ lookup_find(lookup, fevent);
+}
+
+static isc_result_t
+start_fetch(dns_lookup_t *lookup) {
+ isc_result_t result;
+
+ /*
+ * The caller must be holding the lookup's lock.
+ */
+
+ REQUIRE(lookup->fetch == NULL);
+
+ result = dns_resolver_createfetch(
+ lookup->view->resolver, dns_fixedname_name(&lookup->name),
+ lookup->type, NULL, NULL, NULL, NULL, 0, 0, 0, NULL,
+ lookup->task, fetch_done, lookup, &lookup->rdataset,
+ &lookup->sigrdataset, &lookup->fetch);
+
+ return (result);
+}
+
+static void
+build_event(dns_lookup_t *lookup) {
+ dns_name_t *name = NULL;
+ dns_rdataset_t *rdataset = NULL;
+ dns_rdataset_t *sigrdataset = NULL;
+
+ name = isc_mem_get(lookup->mctx, sizeof(dns_name_t));
+ dns_name_init(name, NULL);
+ dns_name_dup(dns_fixedname_name(&lookup->name), lookup->mctx, name);
+
+ if (dns_rdataset_isassociated(&lookup->rdataset)) {
+ rdataset = isc_mem_get(lookup->mctx, sizeof(dns_rdataset_t));
+ dns_rdataset_init(rdataset);
+ dns_rdataset_clone(&lookup->rdataset, rdataset);
+ }
+
+ if (dns_rdataset_isassociated(&lookup->sigrdataset)) {
+ sigrdataset = isc_mem_get(lookup->mctx, sizeof(dns_rdataset_t));
+ dns_rdataset_init(sigrdataset);
+ dns_rdataset_clone(&lookup->sigrdataset, sigrdataset);
+ }
+
+ lookup->event->name = name;
+ lookup->event->rdataset = rdataset;
+ lookup->event->sigrdataset = sigrdataset;
+}
+
+static isc_result_t
+view_find(dns_lookup_t *lookup, dns_name_t *foundname) {
+ isc_result_t result;
+ dns_name_t *name = dns_fixedname_name(&lookup->name);
+ dns_rdatatype_t type;
+
+ if (lookup->type == dns_rdatatype_rrsig) {
+ type = dns_rdatatype_any;
+ } else {
+ type = lookup->type;
+ }
+
+ result = dns_view_find(lookup->view, name, type, 0, 0, false, false,
+ &lookup->event->db, &lookup->event->node,
+ foundname, &lookup->rdataset,
+ &lookup->sigrdataset);
+ return (result);
+}
+
+static void
+lookup_find(dns_lookup_t *lookup, dns_fetchevent_t *event) {
+ isc_result_t result = ISC_R_SUCCESS;
+ bool want_restart;
+ bool send_event;
+ dns_name_t *name = NULL, *fname = NULL, *prefix = NULL;
+ dns_fixedname_t foundname, fixed;
+ dns_rdata_t rdata = DNS_RDATA_INIT;
+ unsigned int nlabels;
+ int order;
+ dns_namereln_t namereln;
+ dns_rdata_cname_t cname;
+ dns_rdata_dname_t dname;
+
+ REQUIRE(VALID_LOOKUP(lookup));
+
+ LOCK(&lookup->lock);
+
+ name = dns_fixedname_name(&lookup->name);
+
+ do {
+ lookup->restarts++;
+ want_restart = false;
+ send_event = true;
+
+ if (event == NULL && !lookup->canceled) {
+ fname = dns_fixedname_initname(&foundname);
+ INSIST(!dns_rdataset_isassociated(&lookup->rdataset));
+ INSIST(!dns_rdataset_isassociated(
+ &lookup->sigrdataset));
+ /*
+ * If we have restarted then clear the old node.
+ */
+ if (lookup->event->node != NULL) {
+ INSIST(lookup->event->db != NULL);
+ dns_db_detachnode(lookup->event->db,
+ &lookup->event->node);
+ }
+ if (lookup->event->db != NULL) {
+ dns_db_detach(&lookup->event->db);
+ }
+ result = view_find(lookup, fname);
+ if (result == ISC_R_NOTFOUND) {
+ /*
+ * We don't know anything about the name.
+ * Launch a fetch.
+ */
+ if (lookup->event->node != NULL) {
+ INSIST(lookup->event->db != NULL);
+ dns_db_detachnode(lookup->event->db,
+ &lookup->event->node);
+ }
+ if (lookup->event->db != NULL) {
+ dns_db_detach(&lookup->event->db);
+ }
+ result = start_fetch(lookup);
+ if (result == ISC_R_SUCCESS) {
+ send_event = false;
+ }
+ goto done;
+ }
+ } else if (event != NULL) {
+ result = event->result;
+ fname = event->foundname;
+ dns_resolver_destroyfetch(&lookup->fetch);
+ INSIST(event->rdataset == &lookup->rdataset);
+ INSIST(event->sigrdataset == &lookup->sigrdataset);
+ }
+
+ /*
+ * If we've been canceled, forget about the result.
+ */
+ if (lookup->canceled) {
+ result = ISC_R_CANCELED;
+ }
+
+ switch (result) {
+ case ISC_R_SUCCESS:
+ build_event(lookup);
+ if (event == NULL) {
+ break;
+ }
+ if (event->db != NULL) {
+ dns_db_attach(event->db, &lookup->event->db);
+ }
+ if (event->node != NULL) {
+ dns_db_attachnode(lookup->event->db,
+ event->node,
+ &lookup->event->node);
+ }
+ break;
+ case DNS_R_CNAME:
+ /*
+ * Copy the CNAME's target into the lookup's
+ * query name and start over.
+ */
+ result = dns_rdataset_first(&lookup->rdataset);
+ if (result != ISC_R_SUCCESS) {
+ break;
+ }
+ dns_rdataset_current(&lookup->rdataset, &rdata);
+ result = dns_rdata_tostruct(&rdata, &cname, NULL);
+ dns_rdata_reset(&rdata);
+ if (result != ISC_R_SUCCESS) {
+ break;
+ }
+ dns_name_copy(&cname.cname, name);
+ dns_rdata_freestruct(&cname);
+ want_restart = true;
+ send_event = false;
+ break;
+ case DNS_R_DNAME:
+ namereln = dns_name_fullcompare(name, fname, &order,
+ &nlabels);
+ INSIST(namereln == dns_namereln_subdomain);
+ /*
+ * Get the target name of the DNAME.
+ */
+ result = dns_rdataset_first(&lookup->rdataset);
+ if (result != ISC_R_SUCCESS) {
+ break;
+ }
+ dns_rdataset_current(&lookup->rdataset, &rdata);
+ result = dns_rdata_tostruct(&rdata, &dname, NULL);
+ dns_rdata_reset(&rdata);
+ if (result != ISC_R_SUCCESS) {
+ break;
+ }
+ /*
+ * Construct the new query name and start over.
+ */
+ prefix = dns_fixedname_initname(&fixed);
+ dns_name_split(name, nlabels, prefix, NULL);
+ result = dns_name_concatenate(prefix, &dname.dname,
+ name, NULL);
+ dns_rdata_freestruct(&dname);
+ if (result == ISC_R_SUCCESS) {
+ want_restart = true;
+ send_event = false;
+ }
+ break;
+ default:
+ send_event = true;
+ }
+
+ if (dns_rdataset_isassociated(&lookup->rdataset)) {
+ dns_rdataset_disassociate(&lookup->rdataset);
+ }
+ if (dns_rdataset_isassociated(&lookup->sigrdataset)) {
+ dns_rdataset_disassociate(&lookup->sigrdataset);
+ }
+
+ done:
+ if (event != NULL) {
+ if (event->node != NULL) {
+ dns_db_detachnode(event->db, &event->node);
+ }
+ if (event->db != NULL) {
+ dns_db_detach(&event->db);
+ }
+ isc_event_free(ISC_EVENT_PTR(&event));
+ }
+
+ /*
+ * Limit the number of restarts.
+ */
+ if (want_restart && lookup->restarts == MAX_RESTARTS) {
+ want_restart = false;
+ result = ISC_R_QUOTA;
+ send_event = true;
+ }
+ } while (want_restart);
+
+ if (send_event) {
+ lookup->event->result = result;
+ lookup->event->ev_sender = lookup;
+ isc_task_sendanddetach(&lookup->task,
+ (isc_event_t **)&lookup->event);
+ dns_view_detach(&lookup->view);
+ }
+
+ UNLOCK(&lookup->lock);
+}
+
+static void
+levent_destroy(isc_event_t *event) {
+ dns_lookupevent_t *levent;
+ isc_mem_t *mctx;
+
+ REQUIRE(event->ev_type == DNS_EVENT_LOOKUPDONE);
+ mctx = event->ev_destroy_arg;
+ levent = (dns_lookupevent_t *)event;
+
+ if (levent->name != NULL) {
+ if (dns_name_dynamic(levent->name)) {
+ dns_name_free(levent->name, mctx);
+ }
+ isc_mem_put(mctx, levent->name, sizeof(dns_name_t));
+ }
+ if (levent->rdataset != NULL) {
+ dns_rdataset_disassociate(levent->rdataset);
+ isc_mem_put(mctx, levent->rdataset, sizeof(dns_rdataset_t));
+ }
+ if (levent->sigrdataset != NULL) {
+ dns_rdataset_disassociate(levent->sigrdataset);
+ isc_mem_put(mctx, levent->sigrdataset, sizeof(dns_rdataset_t));
+ }
+ if (levent->node != NULL) {
+ dns_db_detachnode(levent->db, &levent->node);
+ }
+ if (levent->db != NULL) {
+ dns_db_detach(&levent->db);
+ }
+ isc_mem_put(mctx, event, event->ev_size);
+}
+
+isc_result_t
+dns_lookup_create(isc_mem_t *mctx, const dns_name_t *name, dns_rdatatype_t type,
+ dns_view_t *view, unsigned int options, isc_task_t *task,
+ isc_taskaction_t action, void *arg, dns_lookup_t **lookupp) {
+ dns_lookup_t *lookup;
+ isc_event_t *ievent;
+
+ lookup = isc_mem_get(mctx, sizeof(*lookup));
+ lookup->mctx = NULL;
+ isc_mem_attach(mctx, &lookup->mctx);
+ lookup->options = options;
+
+ ievent = isc_event_allocate(mctx, lookup, DNS_EVENT_LOOKUPDONE, action,
+ arg, sizeof(*lookup->event));
+ lookup->event = (dns_lookupevent_t *)ievent;
+ lookup->event->ev_destroy = levent_destroy;
+ lookup->event->ev_destroy_arg = mctx;
+ lookup->event->result = ISC_R_FAILURE;
+ lookup->event->name = NULL;
+ lookup->event->rdataset = NULL;
+ lookup->event->sigrdataset = NULL;
+ lookup->event->db = NULL;
+ lookup->event->node = NULL;
+
+ lookup->task = NULL;
+ isc_task_attach(task, &lookup->task);
+
+ isc_mutex_init(&lookup->lock);
+
+ dns_fixedname_init(&lookup->name);
+
+ dns_name_copy(name, dns_fixedname_name(&lookup->name));
+
+ lookup->type = type;
+ lookup->view = NULL;
+ dns_view_attach(view, &lookup->view);
+ lookup->fetch = NULL;
+ lookup->restarts = 0;
+ lookup->canceled = false;
+ dns_rdataset_init(&lookup->rdataset);
+ dns_rdataset_init(&lookup->sigrdataset);
+ lookup->magic = LOOKUP_MAGIC;
+
+ *lookupp = lookup;
+
+ lookup_find(lookup, NULL);
+
+ return (ISC_R_SUCCESS);
+}
+
+void
+dns_lookup_cancel(dns_lookup_t *lookup) {
+ REQUIRE(VALID_LOOKUP(lookup));
+
+ LOCK(&lookup->lock);
+
+ if (!lookup->canceled) {
+ lookup->canceled = true;
+ if (lookup->fetch != NULL) {
+ INSIST(lookup->view != NULL);
+ dns_resolver_cancelfetch(lookup->fetch);
+ }
+ }
+
+ UNLOCK(&lookup->lock);
+}
+
+void
+dns_lookup_destroy(dns_lookup_t **lookupp) {
+ dns_lookup_t *lookup;
+
+ REQUIRE(lookupp != NULL);
+ lookup = *lookupp;
+ *lookupp = NULL;
+ REQUIRE(VALID_LOOKUP(lookup));
+ REQUIRE(lookup->event == NULL);
+ REQUIRE(lookup->task == NULL);
+ REQUIRE(lookup->view == NULL);
+ if (dns_rdataset_isassociated(&lookup->rdataset)) {
+ dns_rdataset_disassociate(&lookup->rdataset);
+ }
+ if (dns_rdataset_isassociated(&lookup->sigrdataset)) {
+ dns_rdataset_disassociate(&lookup->sigrdataset);
+ }
+
+ isc_mutex_destroy(&lookup->lock);
+ lookup->magic = 0;
+ isc_mem_putanddetach(&lookup->mctx, lookup, sizeof(*lookup));
+}
diff --git a/lib/dns/master.c b/lib/dns/master.c
new file mode 100644
index 0000000..97b9343
--- /dev/null
+++ b/lib/dns/master.c
@@ -0,0 +1,3200 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/*! \file */
+
+#include <inttypes.h>
+#include <stdbool.h>
+
+#include <isc/atomic.h>
+#include <isc/event.h>
+#include <isc/lex.h>
+#include <isc/magic.h>
+#include <isc/mem.h>
+#include <isc/print.h>
+#include <isc/refcount.h>
+#include <isc/result.h>
+#include <isc/serial.h>
+#include <isc/stdio.h>
+#include <isc/stdtime.h>
+#include <isc/string.h>
+#include <isc/task.h>
+#include <isc/util.h>
+
+#include <dns/callbacks.h>
+#include <dns/events.h>
+#include <dns/fixedname.h>
+#include <dns/master.h>
+#include <dns/name.h>
+#include <dns/rdata.h>
+#include <dns/rdataclass.h>
+#include <dns/rdatalist.h>
+#include <dns/rdataset.h>
+#include <dns/rdatastruct.h>
+#include <dns/rdatatype.h>
+#include <dns/soa.h>
+#include <dns/time.h>
+#include <dns/ttl.h>
+
+/*!
+ * Grow the number of dns_rdatalist_t (#RDLSZ) and dns_rdata_t (#RDSZ)
+ * structures by these sizes when we need to.
+ *
+ */
+/*% RDLSZ reflects the number of different types with the same name expected. */
+#define RDLSZ 32
+/*%
+ * RDSZ reflects the number of rdata expected at a give name that can fit into
+ * 64k.
+ */
+#define RDSZ 512
+
+#define NBUFS 4
+#define MAXWIRESZ 255
+
+/*%
+ * Target buffer size and minimum target size.
+ * MINTSIZ must be big enough to hold the largest rdata record.
+ * \brief
+ * TSIZ >= MINTSIZ
+ */
+#define TSIZ (128 * 1024)
+/*%
+ * max message size - header - root - type - class - ttl - rdlen
+ */
+#define MINTSIZ DNS_RDATA_MAXLENGTH
+/*%
+ * Size for tokens in the presentation format,
+ * The largest tokens are the base64 blocks in KEY and CERT records,
+ * Largest key allowed is about 1372 bytes but
+ * there is no fixed upper bound on CERT records.
+ * 2K is too small for some X.509s, 8K is overkill.
+ */
+#define TOKENSIZ (8 * 1024)
+
+/*%
+ * Buffers sizes for $GENERATE.
+ */
+#define DNS_MASTER_LHS 2048
+#define DNS_MASTER_RHS MINTSIZ
+
+#define CHECKNAMESFAIL(x) (((x)&DNS_MASTER_CHECKNAMESFAIL) != 0)
+
+typedef ISC_LIST(dns_rdatalist_t) rdatalist_head_t;
+
+typedef struct dns_incctx dns_incctx_t;
+
+/*%
+ * Master file load state.
+ */
+
+struct dns_loadctx {
+ unsigned int magic;
+ isc_mem_t *mctx;
+ dns_masterformat_t format;
+
+ dns_rdatacallbacks_t *callbacks;
+ isc_task_t *task;
+ dns_loaddonefunc_t done;
+ void *done_arg;
+
+ /* Common methods */
+ isc_result_t (*openfile)(dns_loadctx_t *lctx, const char *filename);
+ isc_result_t (*load)(dns_loadctx_t *lctx);
+
+ /* Members used by all formats */
+ uint32_t maxttl;
+
+ /* Members specific to the text format: */
+ isc_lex_t *lex;
+ bool keep_lex;
+ unsigned int options;
+ bool ttl_known;
+ bool default_ttl_known;
+ bool warn_1035;
+ bool warn_tcr;
+ bool warn_sigexpired;
+ bool seen_include;
+ uint32_t ttl;
+ uint32_t default_ttl;
+ dns_rdataclass_t zclass;
+ dns_fixedname_t fixed_top;
+ dns_name_t *top; /*%< top of zone */
+
+ /* Members specific to the raw format: */
+ FILE *f;
+ bool first;
+ dns_masterrawheader_t header;
+
+ /* Which fixed buffers we are using? */
+ unsigned int loop_cnt; /*% records per quantum,
+ * 0 => all. */
+ isc_result_t result;
+
+ /* Atomic */
+ isc_refcount_t references;
+ atomic_bool canceled;
+
+ /* locked by lock */
+ dns_incctx_t *inc;
+ uint32_t resign;
+ isc_stdtime_t now;
+
+ dns_masterincludecb_t include_cb;
+ void *include_arg;
+};
+
+struct dns_incctx {
+ dns_incctx_t *parent;
+ dns_name_t *origin;
+ dns_name_t *current;
+ dns_name_t *glue;
+ dns_fixedname_t fixed[NBUFS]; /* working buffers */
+ unsigned int in_use[NBUFS]; /* covert to bitmap? */
+ int glue_in_use;
+ int current_in_use;
+ int origin_in_use;
+ bool origin_changed;
+ bool drop;
+ unsigned int glue_line;
+ unsigned int current_line;
+};
+
+#define DNS_LCTX_MAGIC ISC_MAGIC('L', 'c', 't', 'x')
+#define DNS_LCTX_VALID(lctx) ISC_MAGIC_VALID(lctx, DNS_LCTX_MAGIC)
+
+#define DNS_AS_STR(t) ((t).value.as_textregion.base)
+
+static isc_result_t
+openfile_text(dns_loadctx_t *lctx, const char *master_file);
+
+static isc_result_t
+load_text(dns_loadctx_t *lctx);
+
+static isc_result_t
+openfile_raw(dns_loadctx_t *lctx, const char *master_file);
+
+static isc_result_t
+load_raw(dns_loadctx_t *lctx);
+
+static isc_result_t
+pushfile(const char *master_file, dns_name_t *origin, dns_loadctx_t *lctx);
+
+static isc_result_t
+commit(dns_rdatacallbacks_t *, dns_loadctx_t *, rdatalist_head_t *,
+ dns_name_t *, const char *, unsigned int);
+
+static bool
+is_glue(rdatalist_head_t *, dns_name_t *);
+
+static dns_rdatalist_t *
+grow_rdatalist(int, dns_rdatalist_t *, int, rdatalist_head_t *,
+ rdatalist_head_t *, isc_mem_t *mctx);
+
+static dns_rdata_t *
+grow_rdata(int, dns_rdata_t *, int, rdatalist_head_t *, rdatalist_head_t *,
+ isc_mem_t *);
+
+static void
+load_quantum(isc_task_t *task, isc_event_t *event);
+
+static isc_result_t
+task_send(dns_loadctx_t *lctx);
+
+static void
+loadctx_destroy(dns_loadctx_t *lctx);
+
+#define GETTOKENERR(lexer, options, token, eol, err) \
+ do { \
+ result = gettoken(lexer, options, token, eol, callbacks); \
+ switch (result) { \
+ case ISC_R_SUCCESS: \
+ break; \
+ case ISC_R_UNEXPECTED: \
+ goto insist_and_cleanup; \
+ default: \
+ if (MANYERRS(lctx, result)) { \
+ SETRESULT(lctx, result); \
+ LOGIT(result); \
+ read_till_eol = true; \
+ err goto next_line; \
+ } else \
+ goto log_and_cleanup; \
+ } \
+ if ((token)->type == isc_tokentype_special) { \
+ result = DNS_R_SYNTAX; \
+ if (MANYERRS(lctx, result)) { \
+ SETRESULT(lctx, result); \
+ LOGIT(result); \
+ read_till_eol = true; \
+ goto next_line; \
+ } else \
+ goto log_and_cleanup; \
+ } \
+ } while (0)
+#define GETTOKEN(lexer, options, token, eol) \
+ GETTOKENERR(lexer, options, token, eol, {})
+
+#define COMMITALL \
+ do { \
+ result = commit(callbacks, lctx, &current_list, ictx->current, \
+ source, ictx->current_line); \
+ if (MANYERRS(lctx, result)) { \
+ SETRESULT(lctx, result); \
+ } else if (result != ISC_R_SUCCESS) \
+ goto insist_and_cleanup; \
+ result = commit(callbacks, lctx, &glue_list, ictx->glue, \
+ source, ictx->glue_line); \
+ if (MANYERRS(lctx, result)) { \
+ SETRESULT(lctx, result); \
+ } else if (result != ISC_R_SUCCESS) \
+ goto insist_and_cleanup; \
+ rdcount = 0; \
+ rdlcount = 0; \
+ isc_buffer_init(&target, target_mem, target_size); \
+ rdcount_save = rdcount; \
+ rdlcount_save = rdlcount; \
+ } while (0)
+
+#define WARNUNEXPECTEDEOF(lexer) \
+ do { \
+ if (isc_lex_isfile(lexer)) \
+ (*callbacks->warn)(callbacks, \
+ "%s: file does not end with " \
+ "newline", \
+ source); \
+ } while (0)
+
+#define EXPECTEOL \
+ do { \
+ GETTOKEN(lctx->lex, 0, &token, true); \
+ if (token.type != isc_tokentype_eol) { \
+ isc_lex_ungettoken(lctx->lex, &token); \
+ result = DNS_R_EXTRATOKEN; \
+ if (MANYERRS(lctx, result)) { \
+ SETRESULT(lctx, result); \
+ LOGIT(result); \
+ read_till_eol = true; \
+ break; \
+ } else if (result != ISC_R_SUCCESS) \
+ goto log_and_cleanup; \
+ } \
+ } while (0)
+
+#define MANYERRS(lctx, result) \
+ ((result != ISC_R_SUCCESS) && (result != ISC_R_IOERROR) && \
+ ((lctx)->options & DNS_MASTER_MANYERRORS) != 0)
+
+#define SETRESULT(lctx, r) \
+ do { \
+ if ((lctx)->result == ISC_R_SUCCESS) \
+ (lctx)->result = r; \
+ } while (0)
+
+#define LOGITFILE(result, filename) \
+ if (result == ISC_R_INVALIDFILE || result == ISC_R_FILENOTFOUND || \
+ result == ISC_R_IOERROR || result == ISC_R_TOOMANYOPENFILES || \
+ result == ISC_R_NOPERM) \
+ (*callbacks->error)(callbacks, "%s: %s:%lu: %s: %s", \
+ "dns_master_load", source, line, filename, \
+ isc_result_totext(result)); \
+ else \
+ LOGIT(result)
+
+#define LOGIT(result) \
+ if (result == ISC_R_NOMEMORY) \
+ (*callbacks->error)(callbacks, "dns_master_load: %s", \
+ isc_result_totext(result)); \
+ else \
+ (*callbacks->error)(callbacks, "%s: %s:%lu: %s", \
+ "dns_master_load", source, line, \
+ isc_result_totext(result))
+
+static unsigned char in_addr_arpa_data[] = "\007IN-ADDR\004ARPA";
+static unsigned char in_addr_arpa_offsets[] = { 0, 8, 13 };
+static dns_name_t const in_addr_arpa =
+ DNS_NAME_INITABSOLUTE(in_addr_arpa_data, in_addr_arpa_offsets);
+
+static unsigned char ip6_int_data[] = "\003IP6\003INT";
+static unsigned char ip6_int_offsets[] = { 0, 4, 8 };
+static dns_name_t const ip6_int = DNS_NAME_INITABSOLUTE(ip6_int_data,
+ ip6_int_offsets);
+
+static unsigned char ip6_arpa_data[] = "\003IP6\004ARPA";
+static unsigned char ip6_arpa_offsets[] = { 0, 4, 9 };
+static dns_name_t const ip6_arpa = DNS_NAME_INITABSOLUTE(ip6_arpa_data,
+ ip6_arpa_offsets);
+
+static bool
+dns_master_isprimary(dns_loadctx_t *lctx) {
+ return ((lctx->options & DNS_MASTER_ZONE) != 0 &&
+ (lctx->options & DNS_MASTER_SECONDARY) == 0 &&
+ (lctx->options & DNS_MASTER_KEY) == 0);
+}
+
+static isc_result_t
+gettoken(isc_lex_t *lex, unsigned int options, isc_token_t *token, bool eol,
+ dns_rdatacallbacks_t *callbacks) {
+ isc_result_t result;
+
+ options |= ISC_LEXOPT_EOL | ISC_LEXOPT_EOF | ISC_LEXOPT_DNSMULTILINE |
+ ISC_LEXOPT_ESCAPE;
+ result = isc_lex_gettoken(lex, options, token);
+ if (result != ISC_R_SUCCESS) {
+ switch (result) {
+ case ISC_R_NOMEMORY:
+ return (ISC_R_NOMEMORY);
+ default:
+ (*callbacks->error)(callbacks,
+ "dns_master_load: %s:%lu:"
+ " isc_lex_gettoken() failed: %s",
+ isc_lex_getsourcename(lex),
+ isc_lex_getsourceline(lex),
+ isc_result_totext(result));
+ return (result);
+ }
+ /*NOTREACHED*/
+ }
+ if (eol != true) {
+ if (token->type == isc_tokentype_eol ||
+ token->type == isc_tokentype_eof)
+ {
+ {
+ unsigned long int line;
+ const char *what;
+ const char *file;
+ file = isc_lex_getsourcename(lex);
+ line = isc_lex_getsourceline(lex);
+ if (token->type == isc_tokentype_eol) {
+ line--;
+ what = "line";
+ } else {
+ what = "file";
+ }
+ (*callbacks->error)(callbacks,
+ "dns_master_load: %s:%lu: "
+ "unexpected end of %s",
+ file, line, what);
+ return (ISC_R_UNEXPECTEDEND);
+ }
+ }
+ }
+ return (ISC_R_SUCCESS);
+}
+
+void
+dns_loadctx_attach(dns_loadctx_t *source, dns_loadctx_t **target) {
+ REQUIRE(target != NULL && *target == NULL);
+ REQUIRE(DNS_LCTX_VALID(source));
+
+ isc_refcount_increment(&source->references);
+
+ *target = source;
+}
+
+void
+dns_loadctx_detach(dns_loadctx_t **lctxp) {
+ dns_loadctx_t *lctx;
+
+ REQUIRE(lctxp != NULL);
+ lctx = *lctxp;
+ *lctxp = NULL;
+ REQUIRE(DNS_LCTX_VALID(lctx));
+
+ if (isc_refcount_decrement(&lctx->references) == 1) {
+ loadctx_destroy(lctx);
+ }
+}
+
+static void
+incctx_destroy(isc_mem_t *mctx, dns_incctx_t *ictx) {
+ dns_incctx_t *parent;
+
+again:
+ parent = ictx->parent;
+ ictx->parent = NULL;
+
+ isc_mem_put(mctx, ictx, sizeof(*ictx));
+
+ if (parent != NULL) {
+ ictx = parent;
+ goto again;
+ }
+}
+
+static void
+loadctx_destroy(dns_loadctx_t *lctx) {
+ REQUIRE(DNS_LCTX_VALID(lctx));
+
+ isc_refcount_destroy(&lctx->references);
+
+ lctx->magic = 0;
+ if (lctx->inc != NULL) {
+ incctx_destroy(lctx->mctx, lctx->inc);
+ }
+
+ if (lctx->f != NULL) {
+ isc_result_t result = isc_stdio_close(lctx->f);
+ if (result != ISC_R_SUCCESS) {
+ UNEXPECTED_ERROR("isc_stdio_close() failed: %s",
+ isc_result_totext(result));
+ }
+ }
+
+ /* isc_lex_destroy() will close all open streams */
+ if (lctx->lex != NULL && !lctx->keep_lex) {
+ isc_lex_destroy(&lctx->lex);
+ }
+
+ if (lctx->task != NULL) {
+ isc_task_detach(&lctx->task);
+ }
+
+ isc_mem_putanddetach(&lctx->mctx, lctx, sizeof(*lctx));
+}
+
+static isc_result_t
+incctx_create(isc_mem_t *mctx, dns_name_t *origin, dns_incctx_t **ictxp) {
+ dns_incctx_t *ictx;
+ isc_region_t r;
+ int i;
+
+ ictx = isc_mem_get(mctx, sizeof(*ictx));
+
+ for (i = 0; i < NBUFS; i++) {
+ dns_fixedname_init(&ictx->fixed[i]);
+ ictx->in_use[i] = false;
+ }
+
+ ictx->origin_in_use = 0;
+ ictx->origin = dns_fixedname_name(&ictx->fixed[ictx->origin_in_use]);
+ ictx->in_use[ictx->origin_in_use] = true;
+ dns_name_toregion(origin, &r);
+ dns_name_fromregion(ictx->origin, &r);
+
+ ictx->glue = NULL;
+ ictx->current = NULL;
+ ictx->glue_in_use = -1;
+ ictx->current_in_use = -1;
+ ictx->parent = NULL;
+ ictx->drop = false;
+ ictx->glue_line = 0;
+ ictx->current_line = 0;
+ ictx->origin_changed = true;
+
+ *ictxp = ictx;
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+loadctx_create(dns_masterformat_t format, isc_mem_t *mctx, unsigned int options,
+ uint32_t resign, dns_name_t *top, dns_rdataclass_t zclass,
+ dns_name_t *origin, dns_rdatacallbacks_t *callbacks,
+ isc_task_t *task, dns_loaddonefunc_t done, void *done_arg,
+ dns_masterincludecb_t include_cb, void *include_arg,
+ isc_lex_t *lex, dns_loadctx_t **lctxp) {
+ dns_loadctx_t *lctx;
+ isc_result_t result;
+ isc_region_t r;
+ isc_lexspecials_t specials;
+
+ REQUIRE(lctxp != NULL && *lctxp == NULL);
+ REQUIRE(callbacks != NULL);
+ REQUIRE(callbacks->add != NULL);
+ REQUIRE(callbacks->error != NULL);
+ REQUIRE(callbacks->warn != NULL);
+ REQUIRE(mctx != NULL);
+ REQUIRE(dns_name_isabsolute(top));
+ REQUIRE(dns_name_isabsolute(origin));
+ REQUIRE((task == NULL && done == NULL) ||
+ (task != NULL && done != NULL));
+
+ lctx = isc_mem_get(mctx, sizeof(*lctx));
+
+ lctx->inc = NULL;
+ result = incctx_create(mctx, origin, &lctx->inc);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup_ctx;
+ }
+
+ lctx->maxttl = 0;
+
+ lctx->format = format;
+ switch (format) {
+ case dns_masterformat_text:
+ lctx->openfile = openfile_text;
+ lctx->load = load_text;
+ break;
+ case dns_masterformat_raw:
+ lctx->openfile = openfile_raw;
+ lctx->load = load_raw;
+ break;
+ default:
+ UNREACHABLE();
+ }
+
+ if (lex != NULL) {
+ lctx->lex = lex;
+ lctx->keep_lex = true;
+ } else {
+ lctx->lex = NULL;
+ result = isc_lex_create(mctx, TOKENSIZ, &lctx->lex);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup_inc;
+ }
+ lctx->keep_lex = false;
+ /*
+ * If specials change update dns_test_rdatafromstring()
+ * in lib/dns/tests/dnstest.c.
+ */
+ memset(specials, 0, sizeof(specials));
+ specials[0] = 1;
+ specials['('] = 1;
+ specials[')'] = 1;
+ specials['"'] = 1;
+ isc_lex_setspecials(lctx->lex, specials);
+ isc_lex_setcomments(lctx->lex, ISC_LEXCOMMENT_DNSMASTERFILE);
+ }
+
+ lctx->ttl_known = ((options & DNS_MASTER_NOTTL) != 0);
+ lctx->ttl = 0;
+ lctx->default_ttl_known = lctx->ttl_known;
+ lctx->default_ttl = 0;
+ lctx->warn_1035 = true; /* XXX Argument? */
+ lctx->warn_tcr = true; /* XXX Argument? */
+ lctx->warn_sigexpired = true; /* XXX Argument? */
+ lctx->options = options;
+ lctx->seen_include = false;
+ lctx->zclass = zclass;
+ lctx->resign = resign;
+ lctx->result = ISC_R_SUCCESS;
+ lctx->include_cb = include_cb;
+ lctx->include_arg = include_arg;
+ isc_stdtime_get(&lctx->now);
+
+ lctx->top = dns_fixedname_initname(&lctx->fixed_top);
+ dns_name_toregion(top, &r);
+ dns_name_fromregion(lctx->top, &r);
+
+ lctx->f = NULL;
+ lctx->first = true;
+ dns_master_initrawheader(&lctx->header);
+
+ lctx->loop_cnt = (done != NULL) ? 100 : 0;
+ lctx->callbacks = callbacks;
+ lctx->task = NULL;
+ if (task != NULL) {
+ isc_task_attach(task, &lctx->task);
+ }
+ lctx->done = done;
+ lctx->done_arg = done_arg;
+ atomic_init(&lctx->canceled, false);
+ lctx->mctx = NULL;
+ isc_mem_attach(mctx, &lctx->mctx);
+
+ isc_refcount_init(&lctx->references, 1); /* Implicit attach. */
+
+ lctx->magic = DNS_LCTX_MAGIC;
+ *lctxp = lctx;
+ return (ISC_R_SUCCESS);
+
+cleanup_inc:
+ incctx_destroy(mctx, lctx->inc);
+cleanup_ctx:
+ isc_mem_put(mctx, lctx, sizeof(*lctx));
+ return (result);
+}
+
+static const char *hex = "0123456789abcdef0123456789ABCDEF";
+
+/*%
+ * Convert value into a nibble sequence from least significant to most
+ * significant nibble. Zero fill upper most significant nibbles if
+ * required to make the width.
+ *
+ * Returns the number of characters that should have been written without
+ * counting the terminating NUL.
+ */
+static unsigned int
+nibbles(char *numbuf, size_t length, unsigned int width, char mode, int value) {
+ unsigned int count = 0;
+
+ /*
+ * This reserve space for the NUL string terminator.
+ */
+ if (length > 0U) {
+ *numbuf = '\0';
+ length--;
+ }
+ do {
+ char val = hex[(value & 0x0f) + ((mode == 'n') ? 0 : 16)];
+ value >>= 4;
+ if (length > 0U) {
+ *numbuf++ = val;
+ *numbuf = '\0';
+ length--;
+ }
+ if (width > 0) {
+ width--;
+ }
+ count++;
+ /*
+ * If width is non zero then we need to add a label separator.
+ * If value is non zero then we need to add another label and
+ * that requires a label separator.
+ */
+ if (width > 0 || value != 0) {
+ if (length > 0U) {
+ *numbuf++ = '.';
+ *numbuf = '\0';
+ length--;
+ }
+ if (width > 0) {
+ width--;
+ }
+ count++;
+ }
+ } while (value != 0 || width > 0);
+ return (count);
+}
+
+static isc_result_t
+genname(char *name, int it, char *buffer, size_t length) {
+ char fmt[sizeof("%04000000000d")];
+ char numbuf[128];
+ char *cp;
+ char mode[2] = { 0 };
+ char brace[2] = { 0 };
+ char comma1[2] = { 0 };
+ char comma2[2] = { 0 };
+ int delta = 0;
+ isc_textregion_t r;
+ unsigned int n;
+ unsigned int width;
+ bool nibblemode;
+
+ r.base = buffer;
+ r.length = (unsigned int)length;
+
+ while (*name != '\0') {
+ if (*name == '$') {
+ name++;
+ if (*name == '$') {
+ if (r.length == 0) {
+ return (ISC_R_NOSPACE);
+ }
+ r.base[0] = *name++;
+ isc_textregion_consume(&r, 1);
+ continue;
+ }
+ nibblemode = false;
+ strlcpy(fmt, "%d", sizeof(fmt));
+ /* Get format specifier. */
+ if (*name == '{') {
+ n = sscanf(name,
+ "{%d%1[,}]%u%1[,}]%1[doxXnN]%1[}]",
+ &delta, comma1, &width, comma2, mode,
+ brace);
+ if (n < 2 || n > 6) {
+ return (DNS_R_SYNTAX);
+ }
+ if (comma1[0] == '}') {
+ /* %{delta} */
+ } else if (comma1[0] == ',' && comma2[0] == '}')
+ {
+ /* %{delta,width} */
+ n = snprintf(fmt, sizeof(fmt), "%%0%ud",
+ width);
+ } else if (comma1[0] == ',' &&
+ comma2[0] == ',' && mode[0] != 0 &&
+ brace[0] == '}')
+ {
+ /* %{delta,width,format} */
+ if (mode[0] == 'n' || mode[0] == 'N') {
+ nibblemode = true;
+ }
+ n = snprintf(fmt, sizeof(fmt),
+ "%%0%u%c", width, mode[0]);
+ } else {
+ return (DNS_R_SYNTAX);
+ }
+ if (n >= sizeof(fmt)) {
+ return (ISC_R_NOSPACE);
+ }
+ /* Skip past closing brace. */
+ while (*name != '\0' && *name++ != '}') {
+ continue;
+ }
+ }
+ /*
+ * 'it' is >= 0 so we don't need to check for
+ * underflow.
+ */
+ if ((it > 0 && delta > INT_MAX - it)) {
+ return (ISC_R_RANGE);
+ }
+ if (nibblemode) {
+ n = nibbles(numbuf, sizeof(numbuf), width,
+ mode[0], it + delta);
+ } else {
+ n = snprintf(numbuf, sizeof(numbuf), fmt,
+ it + delta);
+ }
+ if (n >= sizeof(numbuf)) {
+ return (ISC_R_NOSPACE);
+ }
+ cp = numbuf;
+ while (*cp != '\0') {
+ if (r.length == 0) {
+ return (ISC_R_NOSPACE);
+ }
+ r.base[0] = *cp++;
+ isc_textregion_consume(&r, 1);
+ }
+ } else if (*name == '\\') {
+ if (r.length == 0) {
+ return (ISC_R_NOSPACE);
+ }
+ r.base[0] = *name++;
+ isc_textregion_consume(&r, 1);
+ if (*name == '\0') {
+ continue;
+ }
+ if (r.length == 0) {
+ return (ISC_R_NOSPACE);
+ }
+ r.base[0] = *name++;
+ isc_textregion_consume(&r, 1);
+ } else {
+ if (r.length == 0) {
+ return (ISC_R_NOSPACE);
+ }
+ r.base[0] = *name++;
+ isc_textregion_consume(&r, 1);
+ }
+ }
+ if (r.length == 0) {
+ return (ISC_R_NOSPACE);
+ }
+ r.base[0] = '\0';
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+generate(dns_loadctx_t *lctx, char *range, char *lhs, char *gtype, char *rhs,
+ const char *source, unsigned int line) {
+ char *target_mem = NULL;
+ char *lhsbuf = NULL;
+ char *rhsbuf = NULL;
+ dns_fixedname_t ownerfixed;
+ dns_name_t *owner;
+ dns_rdata_t rdata = DNS_RDATA_INIT;
+ dns_rdatacallbacks_t *callbacks;
+ dns_rdatalist_t rdatalist;
+ dns_rdatatype_t type;
+ rdatalist_head_t head;
+ int target_size = MINTSIZ; /* only one rdata at a time */
+ isc_buffer_t buffer;
+ isc_buffer_t target;
+ isc_result_t result;
+ isc_textregion_t r;
+ int n, start, stop, step = 0;
+ unsigned int i;
+ dns_incctx_t *ictx;
+ char dummy[2];
+
+ ictx = lctx->inc;
+ callbacks = lctx->callbacks;
+ owner = dns_fixedname_initname(&ownerfixed);
+ ISC_LIST_INIT(head);
+
+ target_mem = isc_mem_get(lctx->mctx, target_size);
+ rhsbuf = isc_mem_get(lctx->mctx, DNS_MASTER_RHS);
+ lhsbuf = isc_mem_get(lctx->mctx, DNS_MASTER_LHS);
+ if (target_mem == NULL || rhsbuf == NULL || lhsbuf == NULL) {
+ result = ISC_R_NOMEMORY;
+ goto error_cleanup;
+ }
+ isc_buffer_init(&target, target_mem, target_size);
+
+ n = sscanf(range, "%d-%d%1[/]%d", &start, &stop, dummy, &step);
+ if ((n != 2 && n != 4) || (start < 0) || (stop < 0) ||
+ (n == 4 && step < 1) || (stop < start))
+ {
+ (*callbacks->error)(callbacks, "%s: %s:%lu: invalid range '%s'",
+ "$GENERATE", source, line, range);
+ result = DNS_R_SYNTAX;
+ goto insist_cleanup;
+ }
+ if (n == 2) {
+ step = 1;
+ }
+
+ /*
+ * Get type.
+ */
+ r.base = gtype;
+ r.length = strlen(gtype);
+ result = dns_rdatatype_fromtext(&type, &r);
+ if (result != ISC_R_SUCCESS) {
+ (*callbacks->error)(callbacks,
+ "%s: %s:%lu: unknown RR type '%s'",
+ "$GENERATE", source, line, gtype);
+ goto insist_cleanup;
+ }
+
+ /*
+ * RFC2930: TKEY and TSIG are not allowed to be loaded
+ * from master files.
+ */
+ if (dns_master_isprimary(lctx) && dns_rdatatype_ismeta(type)) {
+ (*callbacks->error)(callbacks, "%s: %s:%lu: meta RR type '%s'",
+ "$GENERATE", source, line, gtype);
+ result = DNS_R_METATYPE;
+ goto insist_cleanup;
+ }
+
+ for (i = start; i <= (unsigned int)stop; i += step) {
+ result = genname(lhs, i, lhsbuf, DNS_MASTER_LHS);
+ if (result != ISC_R_SUCCESS) {
+ goto error_cleanup;
+ }
+ result = genname(rhs, i, rhsbuf, DNS_MASTER_RHS);
+ if (result != ISC_R_SUCCESS) {
+ goto error_cleanup;
+ }
+
+ isc_buffer_init(&buffer, lhsbuf, strlen(lhsbuf));
+ isc_buffer_add(&buffer, strlen(lhsbuf));
+ isc_buffer_setactive(&buffer, strlen(lhsbuf));
+ result = dns_name_fromtext(owner, &buffer, ictx->origin, 0,
+ NULL);
+ if (result != ISC_R_SUCCESS) {
+ goto error_cleanup;
+ }
+
+ if (dns_master_isprimary(lctx) &&
+ !dns_name_issubdomain(owner, lctx->top))
+ {
+ char namebuf[DNS_NAME_FORMATSIZE];
+ dns_name_format(owner, namebuf, sizeof(namebuf));
+ /*
+ * Ignore out-of-zone data.
+ */
+ (*callbacks->warn)(callbacks,
+ "%s:%lu: "
+ "ignoring out-of-zone data (%s)",
+ source, line, namebuf);
+ continue;
+ }
+
+ isc_buffer_init(&buffer, rhsbuf, strlen(rhsbuf));
+ isc_buffer_add(&buffer, strlen(rhsbuf));
+ isc_buffer_setactive(&buffer, strlen(rhsbuf));
+
+ result = isc_lex_openbuffer(lctx->lex, &buffer);
+ if (result != ISC_R_SUCCESS) {
+ goto error_cleanup;
+ }
+
+ isc_buffer_init(&target, target_mem, target_size);
+ result = dns_rdata_fromtext(&rdata, lctx->zclass, type,
+ lctx->lex, ictx->origin, 0,
+ lctx->mctx, &target, callbacks);
+ RUNTIME_CHECK(isc_lex_close(lctx->lex) == ISC_R_SUCCESS);
+ if (result != ISC_R_SUCCESS) {
+ goto error_cleanup;
+ }
+
+ dns_rdatalist_init(&rdatalist);
+ rdatalist.type = type;
+ rdatalist.rdclass = lctx->zclass;
+ rdatalist.ttl = lctx->ttl;
+ ISC_LIST_PREPEND(head, &rdatalist, link);
+ ISC_LIST_APPEND(rdatalist.rdata, &rdata, link);
+ result = commit(callbacks, lctx, &head, owner, source, line);
+ ISC_LIST_UNLINK(rdatalist.rdata, &rdata, link);
+ if (result != ISC_R_SUCCESS) {
+ goto error_cleanup;
+ }
+ dns_rdata_reset(&rdata);
+ }
+ result = ISC_R_SUCCESS;
+ goto cleanup;
+
+error_cleanup:
+ if (result == ISC_R_NOMEMORY) {
+ (*callbacks->error)(callbacks, "$GENERATE: %s",
+ isc_result_totext(result));
+ } else {
+ (*callbacks->error)(callbacks, "$GENERATE: %s:%lu: %s", source,
+ line, isc_result_totext(result));
+ }
+
+insist_cleanup:
+ INSIST(result != ISC_R_SUCCESS);
+
+cleanup:
+ if (target_mem != NULL) {
+ isc_mem_put(lctx->mctx, target_mem, target_size);
+ }
+ if (lhsbuf != NULL) {
+ isc_mem_put(lctx->mctx, lhsbuf, DNS_MASTER_LHS);
+ }
+ if (rhsbuf != NULL) {
+ isc_mem_put(lctx->mctx, rhsbuf, DNS_MASTER_RHS);
+ }
+ return (result);
+}
+
+static void
+limit_ttl(dns_rdatacallbacks_t *callbacks, const char *source,
+ unsigned int line, uint32_t *ttlp) {
+ if (*ttlp > 0x7fffffffUL) {
+ (callbacks->warn)(callbacks,
+ "%s: %s:%lu: "
+ "$TTL %lu > MAXTTL, "
+ "setting $TTL to 0",
+ "dns_master_load", source, line, *ttlp);
+ *ttlp = 0;
+ }
+}
+
+static isc_result_t
+check_ns(dns_loadctx_t *lctx, isc_token_t *token, const char *source,
+ unsigned long line) {
+ char *tmp = NULL;
+ isc_result_t result = ISC_R_SUCCESS;
+ void (*callback)(struct dns_rdatacallbacks *, const char *, ...);
+
+ if ((lctx->options & DNS_MASTER_FATALNS) != 0) {
+ callback = lctx->callbacks->error;
+ } else {
+ callback = lctx->callbacks->warn;
+ }
+
+ if (token->type == isc_tokentype_string) {
+ struct in_addr addr;
+ struct in6_addr addr6;
+
+ tmp = isc_mem_strdup(lctx->mctx, DNS_AS_STR(*token));
+ /*
+ * Catch both "1.2.3.4" and "1.2.3.4."
+ */
+ if (tmp[strlen(tmp) - 1] == '.') {
+ tmp[strlen(tmp) - 1] = '\0';
+ }
+ if (inet_pton(AF_INET, tmp, &addr) == 1 ||
+ inet_pton(AF_INET6, tmp, &addr6) == 1)
+ {
+ result = DNS_R_NSISADDRESS;
+ }
+ }
+ if (result != ISC_R_SUCCESS) {
+ (*callback)(lctx->callbacks,
+ "%s:%lu: NS record '%s' "
+ "appears to be an address",
+ source, line, DNS_AS_STR(*token));
+ }
+ if (tmp != NULL) {
+ isc_mem_free(lctx->mctx, tmp);
+ }
+ return (result);
+}
+
+static void
+check_wildcard(dns_incctx_t *ictx, const char *source, unsigned long line,
+ dns_rdatacallbacks_t *callbacks) {
+ dns_name_t *name;
+
+ name = (ictx->glue != NULL) ? ictx->glue : ictx->current;
+ if (dns_name_internalwildcard(name)) {
+ char namebuf[DNS_NAME_FORMATSIZE];
+
+ dns_name_format(name, namebuf, sizeof(namebuf));
+ (*callbacks->warn)(callbacks,
+ "%s:%lu: warning: ownername "
+ "'%s' contains an non-terminal wildcard",
+ source, line, namebuf);
+ }
+}
+
+static isc_result_t
+openfile_text(dns_loadctx_t *lctx, const char *master_file) {
+ return (isc_lex_openfile(lctx->lex, master_file));
+}
+
+static int
+find_free_name(dns_incctx_t *incctx) {
+ int i;
+
+ for (i = 0; i < (NBUFS - 1); i++) {
+ if (!incctx->in_use[i]) {
+ break;
+ }
+ }
+ INSIST(!incctx->in_use[i]);
+ return (i);
+}
+
+static isc_result_t
+load_text(dns_loadctx_t *lctx) {
+ dns_rdataclass_t rdclass;
+ dns_rdatatype_t type, covers;
+ uint32_t ttl_offset = 0;
+ dns_name_t *new_name;
+ bool current_has_delegation = false;
+ bool done = false;
+ bool finish_origin = false;
+ bool finish_include = false;
+ bool read_till_eol = false;
+ bool initialws;
+ char *include_file = NULL;
+ isc_token_t token;
+ isc_result_t result = ISC_R_UNEXPECTED;
+ rdatalist_head_t glue_list;
+ rdatalist_head_t current_list;
+ dns_rdatalist_t *this;
+ dns_rdatalist_t *rdatalist = NULL;
+ dns_rdatalist_t *new_rdatalist;
+ int rdlcount = 0;
+ int rdlcount_save = 0;
+ int rdatalist_size = 0;
+ isc_buffer_t buffer;
+ isc_buffer_t target;
+ isc_buffer_t target_ft;
+ isc_buffer_t target_save;
+ dns_rdata_t *rdata = NULL;
+ dns_rdata_t *new_rdata;
+ int rdcount = 0;
+ int rdcount_save = 0;
+ int rdata_size = 0;
+ unsigned char *target_mem = NULL;
+ int target_size = TSIZ;
+ int new_in_use;
+ unsigned int loop_cnt = 0;
+ isc_mem_t *mctx;
+ dns_rdatacallbacks_t *callbacks;
+ dns_incctx_t *ictx;
+ char *range = NULL;
+ char *lhs = NULL;
+ char *gtype = NULL;
+ char *rhs = NULL;
+ const char *source;
+ unsigned long line = 0;
+ bool explicit_ttl;
+ char classname1[DNS_RDATACLASS_FORMATSIZE];
+ char classname2[DNS_RDATACLASS_FORMATSIZE];
+ unsigned int options = 0;
+
+ REQUIRE(DNS_LCTX_VALID(lctx));
+ callbacks = lctx->callbacks;
+ mctx = lctx->mctx;
+ ictx = lctx->inc;
+
+ ISC_LIST_INIT(glue_list);
+ ISC_LIST_INIT(current_list);
+
+ /*
+ * Allocate target_size of buffer space. This is greater than twice
+ * the maximum individual RR data size.
+ */
+ target_mem = isc_mem_get(mctx, target_size);
+ isc_buffer_init(&target, target_mem, target_size);
+ target_save = target;
+
+ if ((lctx->options & DNS_MASTER_CHECKNAMES) != 0) {
+ options |= DNS_RDATA_CHECKNAMES;
+ }
+ if ((lctx->options & DNS_MASTER_CHECKNAMESFAIL) != 0) {
+ options |= DNS_RDATA_CHECKNAMESFAIL;
+ }
+ if ((lctx->options & DNS_MASTER_CHECKMX) != 0) {
+ options |= DNS_RDATA_CHECKMX;
+ }
+ if ((lctx->options & DNS_MASTER_CHECKMXFAIL) != 0) {
+ options |= DNS_RDATA_CHECKMXFAIL;
+ }
+ source = isc_lex_getsourcename(lctx->lex);
+ do {
+ initialws = false;
+ line = isc_lex_getsourceline(lctx->lex);
+ GETTOKEN(lctx->lex, ISC_LEXOPT_INITIALWS | ISC_LEXOPT_QSTRING,
+ &token, true);
+ line = isc_lex_getsourceline(lctx->lex);
+
+ if (token.type == isc_tokentype_eof) {
+ if (read_till_eol) {
+ WARNUNEXPECTEDEOF(lctx->lex);
+ }
+ /* Pop the include stack? */
+ if (ictx->parent != NULL) {
+ COMMITALL;
+ lctx->inc = ictx->parent;
+ ictx->parent = NULL;
+ incctx_destroy(lctx->mctx, ictx);
+ RUNTIME_CHECK(isc_lex_close(lctx->lex) ==
+ ISC_R_SUCCESS);
+ line = isc_lex_getsourceline(lctx->lex);
+ POST(line);
+ source = isc_lex_getsourcename(lctx->lex);
+ ictx = lctx->inc;
+ continue;
+ }
+ done = true;
+ continue;
+ }
+
+ if (token.type == isc_tokentype_eol) {
+ read_till_eol = false;
+ continue; /* blank line */
+ }
+
+ if (read_till_eol) {
+ continue;
+ }
+
+ if (token.type == isc_tokentype_initialws) {
+ /*
+ * Still working on the same name.
+ */
+ initialws = true;
+ } else if (token.type == isc_tokentype_string ||
+ token.type == isc_tokentype_qstring)
+ {
+ /*
+ * "$" Support.
+ *
+ * "$ORIGIN" and "$INCLUDE" can both take domain names.
+ * The processing of "$ORIGIN" and "$INCLUDE" extends
+ * across the normal domain name processing.
+ */
+
+ if (strcasecmp(DNS_AS_STR(token), "$ORIGIN") == 0) {
+ GETTOKEN(lctx->lex, 0, &token, false);
+ finish_origin = true;
+ } else if (strcasecmp(DNS_AS_STR(token), "$TTL") == 0) {
+ GETTOKENERR(lctx->lex, 0, &token, false,
+ lctx->ttl = 0;
+ lctx->default_ttl_known = true;);
+ result = dns_ttl_fromtext(
+ &token.value.as_textregion, &lctx->ttl);
+ if (MANYERRS(lctx, result)) {
+ SETRESULT(lctx, result);
+ lctx->ttl = 0;
+ } else if (result != ISC_R_SUCCESS) {
+ goto insist_and_cleanup;
+ }
+ limit_ttl(callbacks, source, line, &lctx->ttl);
+ lctx->default_ttl = lctx->ttl;
+ lctx->default_ttl_known = true;
+ EXPECTEOL;
+ continue;
+ } else if (strcasecmp(DNS_AS_STR(token), "$INCLUDE") ==
+ 0)
+ {
+ COMMITALL;
+ if ((lctx->options & DNS_MASTER_NOINCLUDE) != 0)
+ {
+ (callbacks->error)(callbacks,
+ "%s: %s:%lu: "
+ "$INCLUDE not "
+ "allowed",
+ "dns_master_load",
+ source, line);
+ result = DNS_R_REFUSED;
+ goto insist_and_cleanup;
+ }
+ if (ttl_offset != 0) {
+ (callbacks->error)(callbacks,
+ "%s: %s:%lu: "
+ "$INCLUDE "
+ "may not be used "
+ "with $DATE",
+ "dns_master_load",
+ source, line);
+ result = DNS_R_SYNTAX;
+ goto insist_and_cleanup;
+ }
+ GETTOKEN(lctx->lex, ISC_LEXOPT_QSTRING, &token,
+ false);
+ if (include_file != NULL) {
+ isc_mem_free(mctx, include_file);
+ }
+ include_file =
+ isc_mem_strdup(mctx, DNS_AS_STR(token));
+ GETTOKEN(lctx->lex, 0, &token, true);
+
+ if (token.type == isc_tokentype_eol ||
+ token.type == isc_tokentype_eof)
+ {
+ if (token.type == isc_tokentype_eof) {
+ WARNUNEXPECTEDEOF(lctx->lex);
+ }
+ /*
+ * No origin field.
+ */
+ result = pushfile(include_file,
+ ictx->origin, lctx);
+ if (MANYERRS(lctx, result)) {
+ SETRESULT(lctx, result);
+ LOGITFILE(result, include_file);
+ continue;
+ } else if (result != ISC_R_SUCCESS) {
+ LOGITFILE(result, include_file);
+ goto insist_and_cleanup;
+ }
+ ictx = lctx->inc;
+ source = isc_lex_getsourcename(
+ lctx->lex);
+ line = isc_lex_getsourceline(lctx->lex);
+ POST(line);
+ continue;
+ }
+ /*
+ * There is an origin field. Fall through
+ * to domain name processing code and do
+ * the actual inclusion later.
+ */
+ finish_include = true;
+ } else if (strcasecmp(DNS_AS_STR(token), "$DATE") == 0)
+ {
+ int64_t dump_time64;
+ isc_stdtime_t dump_time, current_time;
+ GETTOKEN(lctx->lex, 0, &token, false);
+ isc_stdtime_get(&current_time);
+ result = dns_time64_fromtext(DNS_AS_STR(token),
+ &dump_time64);
+ if (MANYERRS(lctx, result)) {
+ SETRESULT(lctx, result);
+ LOGIT(result);
+ dump_time64 = 0;
+ } else if (result != ISC_R_SUCCESS) {
+ goto log_and_cleanup;
+ }
+ dump_time = (isc_stdtime_t)dump_time64;
+ if (dump_time != dump_time64) {
+ UNEXPECTED_ERROR("%s: %s:%lu: $DATE "
+ "outside epoch",
+ "dns_master_load",
+ source, line);
+ result = ISC_R_UNEXPECTED;
+ goto insist_and_cleanup;
+ }
+ if (dump_time > current_time) {
+ UNEXPECTED_ERROR("%s: %s:%lu: "
+ "$DATE in future, "
+ "using current date",
+ "dns_master_load",
+ source, line);
+ dump_time = current_time;
+ }
+ ttl_offset = current_time - dump_time;
+ EXPECTEOL;
+ continue;
+ } else if (strcasecmp(DNS_AS_STR(token), "$GENERATE") ==
+ 0)
+ {
+ /*
+ * Lazy cleanup.
+ */
+ if (range != NULL) {
+ isc_mem_free(mctx, range);
+ }
+ if (lhs != NULL) {
+ isc_mem_free(mctx, lhs);
+ }
+ if (gtype != NULL) {
+ isc_mem_free(mctx, gtype);
+ }
+ if (rhs != NULL) {
+ isc_mem_free(mctx, rhs);
+ }
+ range = lhs = gtype = rhs = NULL;
+ /* RANGE */
+ GETTOKEN(lctx->lex, 0, &token, false);
+ range = isc_mem_strdup(mctx, DNS_AS_STR(token));
+ /* LHS */
+ GETTOKEN(lctx->lex, 0, &token, false);
+ lhs = isc_mem_strdup(mctx, DNS_AS_STR(token));
+ rdclass = 0;
+ explicit_ttl = false;
+ /* CLASS? */
+ GETTOKEN(lctx->lex, 0, &token, false);
+ if (dns_rdataclass_fromtext(
+ &rdclass,
+ &token.value.as_textregion) ==
+ ISC_R_SUCCESS)
+ {
+ GETTOKEN(lctx->lex, 0, &token, false);
+ }
+ /* TTL? */
+ if (dns_ttl_fromtext(&token.value.as_textregion,
+ &lctx->ttl) ==
+ ISC_R_SUCCESS)
+ {
+ limit_ttl(callbacks, source, line,
+ &lctx->ttl);
+ lctx->ttl_known = true;
+ explicit_ttl = true;
+ GETTOKEN(lctx->lex, 0, &token, false);
+ }
+ /* CLASS? */
+ if (rdclass == 0 &&
+ dns_rdataclass_fromtext(
+ &rdclass,
+ &token.value.as_textregion) ==
+ ISC_R_SUCCESS)
+ {
+ GETTOKEN(lctx->lex, 0, &token, false);
+ }
+ /* TYPE */
+ gtype = isc_mem_strdup(mctx, DNS_AS_STR(token));
+ /* RHS */
+ GETTOKEN(lctx->lex, ISC_LEXOPT_QSTRING, &token,
+ false);
+ rhs = isc_mem_strdup(mctx, DNS_AS_STR(token));
+ if (!lctx->ttl_known &&
+ !lctx->default_ttl_known)
+ {
+ (*callbacks->error)(callbacks,
+ "%s: %s:%lu: no "
+ "TTL specified",
+ "dns_master_load",
+ source, line);
+ result = DNS_R_NOTTL;
+ if (MANYERRS(lctx, result)) {
+ SETRESULT(lctx, result);
+ lctx->ttl = 0;
+ } else {
+ goto insist_and_cleanup;
+ }
+ } else if (!explicit_ttl &&
+ lctx->default_ttl_known)
+ {
+ lctx->ttl = lctx->default_ttl;
+ }
+ /*
+ * If the class specified does not match the
+ * zone's class print out a error message and
+ * exit.
+ */
+ if (rdclass != 0 && rdclass != lctx->zclass) {
+ goto bad_class;
+ }
+ result = generate(lctx, range, lhs, gtype, rhs,
+ source, line);
+ if (MANYERRS(lctx, result)) {
+ SETRESULT(lctx, result);
+ } else if (result != ISC_R_SUCCESS) {
+ goto insist_and_cleanup;
+ }
+ EXPECTEOL;
+ continue;
+ } else if (strncasecmp(DNS_AS_STR(token), "$", 1) == 0)
+ {
+ (callbacks->error)(callbacks,
+ "%s: %s:%lu: "
+ "unknown $ directive '%s'",
+ "dns_master_load", source,
+ line, DNS_AS_STR(token));
+ result = DNS_R_SYNTAX;
+ if (MANYERRS(lctx, result)) {
+ SETRESULT(lctx, result);
+ } else {
+ goto insist_and_cleanup;
+ }
+ }
+
+ /*
+ * Normal processing resumes.
+ */
+ new_in_use = find_free_name(ictx);
+ new_name = dns_fixedname_initname(
+ &ictx->fixed[new_in_use]);
+ isc_buffer_init(&buffer, token.value.as_region.base,
+ token.value.as_region.length);
+ isc_buffer_add(&buffer, token.value.as_region.length);
+ isc_buffer_setactive(&buffer,
+ token.value.as_region.length);
+ result = dns_name_fromtext(new_name, &buffer,
+ ictx->origin, 0, NULL);
+ if (MANYERRS(lctx, result)) {
+ SETRESULT(lctx, result);
+ LOGIT(result);
+ read_till_eol = true;
+ continue;
+ } else if (result != ISC_R_SUCCESS) {
+ goto log_and_cleanup;
+ }
+
+ /*
+ * Finish $ORIGIN / $INCLUDE processing if required.
+ */
+ if (finish_origin) {
+ if (ictx->origin_in_use != -1) {
+ ictx->in_use[ictx->origin_in_use] =
+ false;
+ }
+ ictx->origin_in_use = new_in_use;
+ ictx->in_use[ictx->origin_in_use] = true;
+ ictx->origin = new_name;
+ ictx->origin_changed = true;
+ finish_origin = false;
+ EXPECTEOL;
+ continue;
+ }
+ if (finish_include) {
+ finish_include = false;
+ EXPECTEOL;
+ result = pushfile(include_file, new_name, lctx);
+ if (MANYERRS(lctx, result)) {
+ SETRESULT(lctx, result);
+ LOGITFILE(result, include_file);
+ continue;
+ } else if (result != ISC_R_SUCCESS) {
+ LOGITFILE(result, include_file);
+ goto insist_and_cleanup;
+ }
+ ictx = lctx->inc;
+ ictx->origin_changed = true;
+ source = isc_lex_getsourcename(lctx->lex);
+ line = isc_lex_getsourceline(lctx->lex);
+ POST(line);
+ continue;
+ }
+
+ /*
+ * "$" Processing Finished
+ */
+
+ /*
+ * If we are processing glue and the new name does
+ * not match the current glue name, commit the glue
+ * and pop stacks leaving us in 'normal' processing
+ * state. Linked lists are undone by commit().
+ */
+ if (ictx->glue != NULL &&
+ !dns_name_caseequal(ictx->glue, new_name))
+ {
+ result = commit(callbacks, lctx, &glue_list,
+ ictx->glue, source,
+ ictx->glue_line);
+ if (MANYERRS(lctx, result)) {
+ SETRESULT(lctx, result);
+ } else if (result != ISC_R_SUCCESS) {
+ goto insist_and_cleanup;
+ }
+ if (ictx->glue_in_use != -1) {
+ ictx->in_use[ictx->glue_in_use] = false;
+ }
+ ictx->glue_in_use = -1;
+ ictx->glue = NULL;
+ rdcount = rdcount_save;
+ rdlcount = rdlcount_save;
+ target = target_save;
+ }
+
+ /*
+ * If we are in 'normal' processing state and the new
+ * name does not match the current name, see if the
+ * new name is for glue and treat it as such,
+ * otherwise we have a new name so commit what we
+ * have.
+ */
+ if ((ictx->glue == NULL) &&
+ (ictx->current == NULL ||
+ !dns_name_caseequal(ictx->current, new_name)))
+ {
+ if (current_has_delegation &&
+ is_glue(&current_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,
+ &current_list,
+ ictx->current, source,
+ ictx->current_line);
+ if (MANYERRS(lctx, result)) {
+ SETRESULT(lctx, result);
+ } else if (result != ISC_R_SUCCESS) {
+ goto insist_and_cleanup;
+ }
+ rdcount = 0;
+ rdlcount = 0;
+ if (ictx->current_in_use != -1) {
+ ictx->in_use
+ [ictx->current_in_use] =
+ false;
+ }
+ ictx->current_in_use = new_in_use;
+ ictx->in_use[ictx->current_in_use] =
+ true;
+ ictx->current = new_name;
+ current_has_delegation = false;
+ isc_buffer_init(&target, target_mem,
+ target_size);
+ }
+ /*
+ * Check for internal wildcards.
+ */
+ if ((lctx->options &
+ DNS_MASTER_CHECKWILDCARD) != 0)
+ {
+ check_wildcard(ictx, source, line,
+ callbacks);
+ }
+ }
+ if (dns_master_isprimary(lctx) &&
+ !dns_name_issubdomain(new_name, lctx->top))
+ {
+ char namebuf[DNS_NAME_FORMATSIZE];
+ dns_name_format(new_name, namebuf,
+ sizeof(namebuf));
+ /*
+ * Ignore out-of-zone data.
+ */
+ (*callbacks->warn)(callbacks,
+ "%s:%lu: "
+ "ignoring out-of-zone data "
+ "(%s)",
+ source, line, namebuf);
+ ictx->drop = true;
+ } else {
+ ictx->drop = false;
+ }
+ } else {
+ UNEXPECTED_ERROR("%s:%lu: isc_lex_gettoken() returned "
+ "unexpected token type (%d)",
+ source, line, token.type);
+ result = ISC_R_UNEXPECTED;
+ if (MANYERRS(lctx, result)) {
+ SETRESULT(lctx, result);
+ LOGIT(result);
+ continue;
+ } else {
+ goto insist_and_cleanup;
+ }
+ }
+
+ /*
+ * Find TTL, class and type. Both TTL and class are optional
+ * and may occur in any order if they exist. TTL and class
+ * come before type which must exist.
+ *
+ * [<TTL>] [<class>] <type> <RDATA>
+ * [<class>] [<TTL>] <type> <RDATA>
+ */
+
+ type = 0;
+ rdclass = 0;
+
+ GETTOKEN(lctx->lex, 0, &token, initialws);
+
+ if (initialws) {
+ if (token.type == isc_tokentype_eol) {
+ read_till_eol = false;
+ continue; /* blank line */
+ }
+
+ if (token.type == isc_tokentype_eof) {
+ WARNUNEXPECTEDEOF(lctx->lex);
+ read_till_eol = false;
+ isc_lex_ungettoken(lctx->lex, &token);
+ continue;
+ }
+
+ if (ictx->current == NULL) {
+ (*callbacks->error)(callbacks,
+ "%s:%lu: no current owner "
+ "name",
+ source, line);
+ result = DNS_R_NOOWNER;
+ if (MANYERRS(lctx, result)) {
+ SETRESULT(lctx, result);
+ read_till_eol = true;
+ continue;
+ } else {
+ goto insist_and_cleanup;
+ }
+ }
+
+ if (ictx->origin_changed) {
+ char cbuf[DNS_NAME_FORMATSIZE];
+ char obuf[DNS_NAME_FORMATSIZE];
+ dns_name_format(ictx->current, cbuf,
+ sizeof(cbuf));
+ dns_name_format(ictx->origin, obuf,
+ sizeof(obuf));
+ (*callbacks->warn)(callbacks,
+ "%s:%lu: record with "
+ "inherited "
+ "owner (%s) immediately "
+ "after "
+ "$ORIGIN (%s)",
+ source, line, cbuf, obuf);
+ }
+ }
+
+ ictx->origin_changed = false;
+
+ if (dns_rdataclass_fromtext(&rdclass,
+ &token.value.as_textregion) ==
+ ISC_R_SUCCESS)
+ {
+ GETTOKEN(lctx->lex, 0, &token, false);
+ }
+
+ explicit_ttl = false;
+ result = dns_ttl_fromtext(&token.value.as_textregion,
+ &lctx->ttl);
+ if (result == ISC_R_SUCCESS) {
+ limit_ttl(callbacks, source, line, &lctx->ttl);
+ explicit_ttl = true;
+ lctx->ttl_known = true;
+ GETTOKEN(lctx->lex, 0, &token, false);
+ }
+
+ if (token.type != isc_tokentype_string) {
+ UNEXPECTED_ERROR("isc_lex_gettoken() returned "
+ "unexpected token type");
+ result = ISC_R_UNEXPECTED;
+ if (MANYERRS(lctx, result)) {
+ SETRESULT(lctx, result);
+ read_till_eol = true;
+ continue;
+ } else {
+ goto insist_and_cleanup;
+ }
+ }
+
+ if (rdclass == 0 &&
+ dns_rdataclass_fromtext(&rdclass,
+ &token.value.as_textregion) ==
+ ISC_R_SUCCESS)
+ {
+ GETTOKEN(lctx->lex, 0, &token, false);
+ }
+
+ if (token.type != isc_tokentype_string) {
+ UNEXPECTED_ERROR("isc_lex_gettoken() returned "
+ "unexpected token type");
+ result = ISC_R_UNEXPECTED;
+ if (MANYERRS(lctx, result)) {
+ SETRESULT(lctx, result);
+ read_till_eol = true;
+ continue;
+ } else {
+ goto insist_and_cleanup;
+ }
+ }
+
+ result = dns_rdatatype_fromtext(&type,
+ &token.value.as_textregion);
+ if (result != ISC_R_SUCCESS) {
+ (*callbacks->warn)(
+ callbacks, "%s:%lu: unknown RR type '%.*s'",
+ source, line, token.value.as_textregion.length,
+ token.value.as_textregion.base);
+ if (MANYERRS(lctx, result)) {
+ SETRESULT(lctx, result);
+ read_till_eol = true;
+ continue;
+ } else if (result != ISC_R_SUCCESS) {
+ goto insist_and_cleanup;
+ }
+ }
+
+ /*
+ * If the class specified does not match the zone's class
+ * print out a error message and exit.
+ */
+ if (rdclass != 0 && rdclass != lctx->zclass) {
+ bad_class:
+
+ dns_rdataclass_format(rdclass, classname1,
+ sizeof(classname1));
+ dns_rdataclass_format(lctx->zclass, classname2,
+ sizeof(classname2));
+ (*callbacks->error)(callbacks,
+ "%s:%lu: class '%s' != "
+ "zone class '%s'",
+ source, line, classname1,
+ classname2);
+ result = DNS_R_BADCLASS;
+ if (MANYERRS(lctx, result)) {
+ SETRESULT(lctx, result);
+ read_till_eol = true;
+ continue;
+ } else {
+ goto insist_and_cleanup;
+ }
+ }
+
+ if (type == dns_rdatatype_ns && ictx->glue == NULL) {
+ current_has_delegation = true;
+ }
+
+ /*
+ * RFC1123: MD and MF are not allowed to be loaded from
+ * master files.
+ */
+ if (dns_master_isprimary(lctx) &&
+ (type == dns_rdatatype_md || type == dns_rdatatype_mf))
+ {
+ char typebuf[DNS_RDATATYPE_FORMATSIZE];
+
+ result = DNS_R_OBSOLETE;
+
+ dns_rdatatype_format(type, typebuf, sizeof(typebuf));
+ (*callbacks->error)(callbacks, "%s:%lu: %s '%s': %s",
+ source, line, "type", typebuf,
+ isc_result_totext(result));
+ if (MANYERRS(lctx, result)) {
+ SETRESULT(lctx, result);
+ } else {
+ goto insist_and_cleanup;
+ }
+ }
+
+ /*
+ * RFC2930: TKEY and TSIG are not allowed to be loaded
+ * from master files.
+ */
+ if (dns_master_isprimary(lctx) && dns_rdatatype_ismeta(type)) {
+ char typebuf[DNS_RDATATYPE_FORMATSIZE];
+
+ result = DNS_R_METATYPE;
+
+ dns_rdatatype_format(type, typebuf, sizeof(typebuf));
+ (*callbacks->error)(callbacks, "%s:%lu: %s '%s': %s",
+ source, line, "type", typebuf,
+ isc_result_totext(result));
+ if (MANYERRS(lctx, result)) {
+ SETRESULT(lctx, result);
+ } else {
+ goto insist_and_cleanup;
+ }
+ }
+
+ /*
+ * Find a rdata structure.
+ */
+ if (rdcount == rdata_size) {
+ new_rdata = grow_rdata(rdata_size + RDSZ, rdata,
+ rdata_size, &current_list,
+ &glue_list, mctx);
+ if (new_rdata == NULL) {
+ result = ISC_R_NOMEMORY;
+ goto log_and_cleanup;
+ }
+ rdata_size += RDSZ;
+ rdata = new_rdata;
+ }
+
+ /*
+ * Peek at the NS record.
+ */
+ if (type == dns_rdatatype_ns &&
+ lctx->zclass == dns_rdataclass_in &&
+ (lctx->options & DNS_MASTER_CHECKNS) != 0)
+ {
+ GETTOKEN(lctx->lex, 0, &token, false);
+ result = check_ns(lctx, &token, source, line);
+ isc_lex_ungettoken(lctx->lex, &token);
+ if ((lctx->options & DNS_MASTER_FATALNS) != 0) {
+ if (MANYERRS(lctx, result)) {
+ SETRESULT(lctx, result);
+ } else if (result != ISC_R_SUCCESS) {
+ goto insist_and_cleanup;
+ }
+ }
+ }
+
+ /*
+ * Check owner name.
+ */
+ options &= ~DNS_RDATA_CHECKREVERSE;
+ if ((lctx->options & DNS_MASTER_CHECKNAMES) != 0) {
+ bool ok;
+ dns_name_t *name;
+
+ name = (ictx->glue != NULL) ? ictx->glue
+ : ictx->current;
+ ok = dns_rdata_checkowner(name, lctx->zclass, type,
+ true);
+ if (!ok) {
+ char namebuf[DNS_NAME_FORMATSIZE];
+ const char *desc;
+ dns_name_format(name, namebuf, sizeof(namebuf));
+ result = DNS_R_BADOWNERNAME;
+ desc = isc_result_totext(result);
+ if (CHECKNAMESFAIL(lctx->options) ||
+ type == dns_rdatatype_nsec3)
+ {
+ (*callbacks->error)(
+ callbacks, "%s:%lu: %s: %s",
+ source, line, namebuf, desc);
+ if (MANYERRS(lctx, result)) {
+ SETRESULT(lctx, result);
+ } else {
+ goto cleanup;
+ }
+ } else {
+ (*callbacks->warn)(
+ callbacks, "%s:%lu: %s: %s",
+ source, line, namebuf, desc);
+ }
+ }
+ if (type == dns_rdatatype_ptr &&
+ !dns_name_isdnssd(name) &&
+ (dns_name_issubdomain(name, &in_addr_arpa) ||
+ dns_name_issubdomain(name, &ip6_arpa) ||
+ dns_name_issubdomain(name, &ip6_int)))
+ {
+ options |= DNS_RDATA_CHECKREVERSE;
+ }
+ }
+
+ /*
+ * Read rdata contents.
+ */
+ dns_rdata_init(&rdata[rdcount]);
+ target_ft = target;
+ result = dns_rdata_fromtext(&rdata[rdcount], lctx->zclass, type,
+ lctx->lex, ictx->origin, options,
+ lctx->mctx, &target, callbacks);
+ if (MANYERRS(lctx, result)) {
+ SETRESULT(lctx, result);
+ continue;
+ } else if (result != ISC_R_SUCCESS) {
+ goto insist_and_cleanup;
+ }
+
+ if (ictx->drop) {
+ target = target_ft;
+ continue;
+ }
+
+ if (type == dns_rdatatype_soa &&
+ (lctx->options & DNS_MASTER_ZONE) != 0 &&
+ !dns_name_equal(ictx->current, lctx->top))
+ {
+ char namebuf[DNS_NAME_FORMATSIZE];
+ dns_name_format(ictx->current, namebuf,
+ sizeof(namebuf));
+ (*callbacks->error)(callbacks,
+ "%s:%lu: SOA "
+ "record not at top of zone (%s)",
+ source, line, namebuf);
+ result = DNS_R_NOTZONETOP;
+ if (MANYERRS(lctx, result)) {
+ SETRESULT(lctx, result);
+ read_till_eol = true;
+ target = target_ft;
+ continue;
+ } else {
+ goto insist_and_cleanup;
+ }
+ }
+
+ if (dns_rdatatype_atparent(type) &&
+ dns_master_isprimary(lctx) &&
+ dns_name_equal(ictx->current, lctx->top))
+ {
+ char namebuf[DNS_NAME_FORMATSIZE];
+ char typebuf[DNS_RDATATYPE_FORMATSIZE];
+
+ dns_name_format(ictx->current, namebuf,
+ sizeof(namebuf));
+ dns_rdatatype_format(type, typebuf, sizeof(typebuf));
+ (*callbacks->error)(
+ callbacks,
+ "%s:%lu: %s record at top of zone (%s)", source,
+ line, typebuf, namebuf);
+ result = DNS_R_ATZONETOP;
+ if (MANYERRS(lctx, result)) {
+ SETRESULT(lctx, result);
+ target = target_ft;
+ continue;
+ } else {
+ goto insist_and_cleanup;
+ }
+ }
+
+ if (type == dns_rdatatype_rrsig || type == dns_rdatatype_sig) {
+ covers = dns_rdata_covers(&rdata[rdcount]);
+ } else {
+ covers = 0;
+ }
+
+ if (!lctx->ttl_known && !lctx->default_ttl_known) {
+ if (type == dns_rdatatype_soa) {
+ (*callbacks->warn)(callbacks,
+ "%s:%lu: no TTL specified; "
+ "using SOA MINTTL instead",
+ source, line);
+ lctx->ttl = dns_soa_getminimum(&rdata[rdcount]);
+ limit_ttl(callbacks, source, line, &lctx->ttl);
+ lctx->default_ttl = lctx->ttl;
+ lctx->default_ttl_known = true;
+ } else if ((lctx->options & DNS_MASTER_HINT) != 0) {
+ /*
+ * Zero TTL's are fine for hints.
+ */
+ lctx->ttl = 0;
+ lctx->default_ttl = lctx->ttl;
+ lctx->default_ttl_known = true;
+ } else {
+ (*callbacks->warn)(callbacks,
+ "%s:%lu: no TTL specified; "
+ "zone rejected",
+ source, line);
+ result = DNS_R_NOTTL;
+ if (MANYERRS(lctx, result)) {
+ SETRESULT(lctx, result);
+ lctx->ttl = 0;
+ } else {
+ goto insist_and_cleanup;
+ }
+ }
+ } else if (!explicit_ttl && lctx->default_ttl_known) {
+ lctx->ttl = lctx->default_ttl;
+ } else if (!explicit_ttl && lctx->warn_1035) {
+ (*callbacks->warn)(callbacks,
+ "%s:%lu: "
+ "using RFC1035 TTL semantics",
+ source, line);
+ lctx->warn_1035 = false;
+ }
+
+ if (type == dns_rdatatype_rrsig && lctx->warn_sigexpired) {
+ dns_rdata_rrsig_t sig;
+ result = dns_rdata_tostruct(&rdata[rdcount], &sig,
+ NULL);
+ RUNTIME_CHECK(result == ISC_R_SUCCESS);
+ if (isc_serial_lt(sig.timeexpire, lctx->now)) {
+ (*callbacks->warn)(callbacks,
+ "%s:%lu: "
+ "signature has expired",
+ source, line);
+ lctx->warn_sigexpired = false;
+ }
+ }
+
+ if ((type == dns_rdatatype_sig || type == dns_rdatatype_nxt) &&
+ lctx->warn_tcr && dns_master_isprimary(lctx))
+ {
+ (*callbacks->warn)(callbacks,
+ "%s:%lu: old style DNSSEC "
+ " zone detected",
+ source, line);
+ lctx->warn_tcr = false;
+ }
+
+ if ((lctx->options & DNS_MASTER_AGETTL) != 0) {
+ /*
+ * Adjust the TTL for $DATE. If the RR has
+ * already expired, set its TTL to 0. This
+ * should be okay even if the TTL stretching
+ * feature is not in effect, because it will
+ * just be quickly expired by the cache, and the
+ * way this was written before the patch it
+ * could potentially add 0 TTLs anyway.
+ */
+ if (lctx->ttl < ttl_offset) {
+ lctx->ttl = 0;
+ } else {
+ lctx->ttl -= ttl_offset;
+ }
+ }
+
+ /*
+ * Find type in rdatalist.
+ * If it does not exist create new one and prepend to list
+ * as this will minimise list traversal.
+ */
+ if (ictx->glue != NULL) {
+ this = ISC_LIST_HEAD(glue_list);
+ } else {
+ this = ISC_LIST_HEAD(current_list);
+ }
+
+ while (this != NULL) {
+ if (this->type == type && this->covers == covers) {
+ break;
+ }
+ this = ISC_LIST_NEXT(this, link);
+ }
+
+ if (this == NULL) {
+ if (rdlcount == rdatalist_size) {
+ new_rdatalist = grow_rdatalist(
+ rdatalist_size + RDLSZ, rdatalist,
+ rdatalist_size, &current_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, &current_list, ictx->current, source,
+ ictx->current_line);
+ if (MANYERRS(lctx, result)) {
+ SETRESULT(lctx, result);
+ } else if (result != ISC_R_SUCCESS) {
+ goto insist_and_cleanup;
+ }
+ result = commit(callbacks, lctx, &glue_list, ictx->glue, source,
+ ictx->glue_line);
+ if (MANYERRS(lctx, result)) {
+ SETRESULT(lctx, result);
+ } else if (result != ISC_R_SUCCESS) {
+ goto insist_and_cleanup;
+ }
+
+ if (!done) {
+ INSIST(lctx->done != NULL && lctx->task != NULL);
+ result = DNS_R_CONTINUE;
+ } else if (result == ISC_R_SUCCESS && lctx->result != ISC_R_SUCCESS) {
+ result = lctx->result;
+ } else if (result == ISC_R_SUCCESS && lctx->seen_include) {
+ result = DNS_R_SEENINCLUDE;
+ }
+ goto cleanup;
+
+log_and_cleanup:
+ LOGIT(result);
+
+insist_and_cleanup:
+ INSIST(result != ISC_R_SUCCESS);
+
+cleanup:
+ while ((this = ISC_LIST_HEAD(current_list)) != NULL) {
+ ISC_LIST_UNLINK(current_list, this, link);
+ }
+ while ((this = ISC_LIST_HEAD(glue_list)) != NULL) {
+ ISC_LIST_UNLINK(glue_list, this, link);
+ }
+ if (rdatalist != NULL) {
+ isc_mem_put(mctx, rdatalist,
+ rdatalist_size * sizeof(*rdatalist));
+ }
+ if (rdata != NULL) {
+ isc_mem_put(mctx, rdata, rdata_size * sizeof(*rdata));
+ }
+ if (target_mem != NULL) {
+ isc_mem_put(mctx, target_mem, target_size);
+ }
+ if (include_file != NULL) {
+ isc_mem_free(mctx, include_file);
+ }
+ if (range != NULL) {
+ isc_mem_free(mctx, range);
+ }
+ if (lhs != NULL) {
+ isc_mem_free(mctx, lhs);
+ }
+ if (gtype != NULL) {
+ isc_mem_free(mctx, gtype);
+ }
+ if (rhs != NULL) {
+ isc_mem_free(mctx, rhs);
+ }
+ return (result);
+}
+
+static isc_result_t
+pushfile(const char *master_file, dns_name_t *origin, dns_loadctx_t *lctx) {
+ isc_result_t result;
+ dns_incctx_t *ictx;
+ dns_incctx_t *newctx = NULL;
+ isc_region_t r;
+
+ REQUIRE(master_file != NULL);
+ REQUIRE(DNS_LCTX_VALID(lctx));
+
+ ictx = lctx->inc;
+ lctx->seen_include = true;
+
+ result = incctx_create(lctx->mctx, origin, &newctx);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+
+ /*
+ * Push origin_changed.
+ */
+ newctx->origin_changed = ictx->origin_changed;
+
+ /* Set current domain. */
+ if (ictx->glue != NULL || ictx->current != NULL) {
+ newctx->current_in_use = find_free_name(newctx);
+ newctx->current = dns_fixedname_name(
+ &newctx->fixed[newctx->current_in_use]);
+ newctx->in_use[newctx->current_in_use] = true;
+ dns_name_toregion(
+ (ictx->glue != NULL) ? ictx->glue : ictx->current, &r);
+ dns_name_fromregion(newctx->current, &r);
+ newctx->drop = ictx->drop;
+ }
+
+ result = (lctx->openfile)(lctx, master_file);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+ newctx->parent = ictx;
+ lctx->inc = newctx;
+
+ if (lctx->include_cb != NULL) {
+ lctx->include_cb(master_file, lctx->include_arg);
+ }
+ return (ISC_R_SUCCESS);
+
+cleanup:
+ incctx_destroy(lctx->mctx, newctx);
+ return (result);
+}
+
+/*
+ * Fill/check exists buffer with 'len' bytes. Track remaining bytes to be
+ * read when incrementally filling the buffer.
+ */
+static isc_result_t
+read_and_check(bool do_read, isc_buffer_t *buffer, size_t len, FILE *f,
+ uint32_t *totallen) {
+ isc_result_t result;
+
+ REQUIRE(totallen != NULL);
+
+ if (do_read) {
+ INSIST(isc_buffer_availablelength(buffer) >= len);
+ result = isc_stdio_read(isc_buffer_used(buffer), 1, len, f,
+ NULL);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+ isc_buffer_add(buffer, (unsigned int)len);
+ if (*totallen < len) {
+ return (ISC_R_RANGE);
+ }
+ *totallen -= (uint32_t)len;
+ } else if (isc_buffer_remaininglength(buffer) < len) {
+ return (ISC_R_RANGE);
+ }
+
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+load_header(dns_loadctx_t *lctx) {
+ isc_result_t result = ISC_R_SUCCESS;
+ dns_masterrawheader_t header;
+ dns_rdatacallbacks_t *callbacks;
+ size_t commonlen = sizeof(header.format) + sizeof(header.version);
+ size_t remainder;
+ unsigned char data[sizeof(header)];
+ isc_buffer_t target;
+
+ REQUIRE(DNS_LCTX_VALID(lctx));
+
+ if (lctx->format != dns_masterformat_raw) {
+ return (ISC_R_NOTIMPLEMENTED);
+ }
+
+ callbacks = lctx->callbacks;
+ dns_master_initrawheader(&header);
+
+ INSIST(commonlen <= sizeof(header));
+ isc_buffer_init(&target, data, sizeof(data));
+
+ result = isc_stdio_read(data, 1, commonlen, lctx->f, NULL);
+ if (result != ISC_R_SUCCESS) {
+ UNEXPECTED_ERROR("isc_stdio_read failed: %s",
+ isc_result_totext(result));
+ return (result);
+ }
+
+ isc_buffer_add(&target, (unsigned int)commonlen);
+ header.format = isc_buffer_getuint32(&target);
+ if (header.format != lctx->format) {
+ (*callbacks->error)(callbacks,
+ "dns_master_load: "
+ "file format mismatch (not raw)");
+ return (ISC_R_NOTIMPLEMENTED);
+ }
+
+ header.version = isc_buffer_getuint32(&target);
+
+ switch (header.version) {
+ case 0:
+ remainder = sizeof(header.dumptime);
+ break;
+ case DNS_RAWFORMAT_VERSION:
+ remainder = sizeof(header) - commonlen;
+ break;
+ default:
+ (*callbacks->error)(callbacks, "dns_master_load: "
+ "unsupported file format "
+ "version");
+ return (ISC_R_NOTIMPLEMENTED);
+ }
+
+ result = isc_stdio_read(data + commonlen, 1, remainder, lctx->f, NULL);
+ if (result != ISC_R_SUCCESS) {
+ UNEXPECTED_ERROR("isc_stdio_read failed: %s",
+ isc_result_totext(result));
+ return (result);
+ }
+
+ isc_buffer_add(&target, (unsigned int)remainder);
+ header.dumptime = isc_buffer_getuint32(&target);
+ if (header.version == DNS_RAWFORMAT_VERSION) {
+ header.flags = isc_buffer_getuint32(&target);
+ header.sourceserial = isc_buffer_getuint32(&target);
+ header.lastxfrin = isc_buffer_getuint32(&target);
+ }
+
+ lctx->first = false;
+ lctx->header = header;
+
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+openfile_raw(dns_loadctx_t *lctx, const char *master_file) {
+ isc_result_t result;
+
+ result = isc_stdio_open(master_file, "rb", &lctx->f);
+ if (result != ISC_R_SUCCESS && result != ISC_R_FILENOTFOUND) {
+ UNEXPECTED_ERROR("isc_stdio_open() failed: %s",
+ isc_result_totext(result));
+ }
+
+ return (result);
+}
+
+static isc_result_t
+load_raw(dns_loadctx_t *lctx) {
+ isc_result_t result = ISC_R_SUCCESS;
+ bool done = false;
+ unsigned int loop_cnt = 0;
+ dns_rdatacallbacks_t *callbacks;
+ unsigned char namebuf[DNS_NAME_MAXWIRE];
+ dns_fixedname_t fixed;
+ dns_name_t *name;
+ rdatalist_head_t head, dummy;
+ dns_rdatalist_t rdatalist;
+ isc_mem_t *mctx = lctx->mctx;
+ dns_rdata_t *rdata = NULL;
+ unsigned int rdata_size = 0;
+ int target_size = TSIZ;
+ isc_buffer_t target, buf;
+ unsigned char *target_mem = NULL;
+ dns_decompress_t dctx;
+
+ callbacks = lctx->callbacks;
+ dns_decompress_init(&dctx, -1, DNS_DECOMPRESS_NONE);
+
+ if (lctx->first) {
+ result = load_header(lctx);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+ }
+
+ ISC_LIST_INIT(head);
+ ISC_LIST_INIT(dummy);
+
+ /*
+ * Allocate target_size of buffer space. This is greater than twice
+ * the maximum individual RR data size.
+ */
+ target_mem = isc_mem_get(mctx, target_size);
+ isc_buffer_init(&target, target_mem, target_size);
+
+ name = dns_fixedname_initname(&fixed);
+
+ /*
+ * In the following loop, we regard any error fatal regardless of
+ * whether "MANYERRORS" is set in the context option. This is because
+ * normal errors should already have been checked at creation time.
+ * Besides, it is very unlikely that we can recover from an error
+ * in this format, and so trying to continue parsing erroneous data
+ * does not really make sense.
+ */
+ for (loop_cnt = 0; (lctx->loop_cnt == 0 || loop_cnt < lctx->loop_cnt);
+ loop_cnt++)
+ {
+ unsigned int i, rdcount;
+ uint16_t namelen;
+ uint32_t totallen;
+ size_t minlen, readlen;
+ bool sequential_read = false;
+
+ /* Read the data length */
+ isc_buffer_clear(&target);
+ INSIST(isc_buffer_availablelength(&target) >= sizeof(totallen));
+ result = isc_stdio_read(target.base, 1, sizeof(totallen),
+ lctx->f, NULL);
+ if (result == ISC_R_EOF) {
+ result = ISC_R_SUCCESS;
+ done = true;
+ break;
+ }
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+ isc_buffer_add(&target, sizeof(totallen));
+ totallen = isc_buffer_getuint32(&target);
+
+ /*
+ * Validation: the input data must at least contain the common
+ * header.
+ */
+ minlen = sizeof(totallen) + sizeof(uint16_t) +
+ sizeof(uint16_t) + sizeof(uint16_t) +
+ sizeof(uint32_t) + sizeof(uint32_t);
+ if (totallen < minlen) {
+ result = ISC_R_RANGE;
+ goto cleanup;
+ }
+ totallen -= sizeof(totallen);
+
+ isc_buffer_clear(&target);
+ if (totallen > isc_buffer_availablelength(&target)) {
+ /*
+ * The default buffer size should typically be large
+ * enough to store the entire RRset. We could try to
+ * allocate enough space if this is not the case, but
+ * it might cause a hazardous result when "totallen"
+ * is forged. Thus, we'd rather take an inefficient
+ * but robust approach in this atypical case: read
+ * data step by step, and commit partial data when
+ * necessary. Note that the buffer must be large
+ * enough to store the "header part", owner name, and
+ * at least one rdata (however large it is).
+ */
+ sequential_read = true;
+ readlen = minlen - sizeof(totallen);
+ } else {
+ /*
+ * Typical case. We can read the whole RRset at once
+ * with the default buffer.
+ */
+ readlen = totallen;
+ }
+ result = isc_stdio_read(target.base, 1, readlen, lctx->f, NULL);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+ isc_buffer_add(&target, (unsigned int)readlen);
+ totallen -= (uint32_t)readlen;
+
+ /* Construct RRset headers */
+ dns_rdatalist_init(&rdatalist);
+ rdatalist.rdclass = isc_buffer_getuint16(&target);
+ if (lctx->zclass != rdatalist.rdclass) {
+ result = DNS_R_BADCLASS;
+ goto cleanup;
+ }
+ rdatalist.type = isc_buffer_getuint16(&target);
+ rdatalist.covers = isc_buffer_getuint16(&target);
+ rdatalist.ttl = isc_buffer_getuint32(&target);
+ rdcount = isc_buffer_getuint32(&target);
+ if (rdcount == 0 || rdcount > 0xffff) {
+ result = ISC_R_RANGE;
+ goto cleanup;
+ }
+ INSIST(isc_buffer_consumedlength(&target) <= readlen);
+
+ /* Owner name: length followed by name */
+ result = read_and_check(sequential_read, &target,
+ sizeof(namelen), lctx->f, &totallen);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+ namelen = isc_buffer_getuint16(&target);
+ if (namelen > sizeof(namebuf)) {
+ result = ISC_R_RANGE;
+ goto cleanup;
+ }
+
+ result = read_and_check(sequential_read, &target, namelen,
+ lctx->f, &totallen);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+
+ isc_buffer_setactive(&target, (unsigned int)namelen);
+ result = dns_name_fromwire(name, &target, &dctx, 0, NULL);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+
+ if ((lctx->options & DNS_MASTER_CHECKTTL) != 0 &&
+ rdatalist.ttl > lctx->maxttl)
+ {
+ (callbacks->error)(callbacks,
+ "dns_master_load: "
+ "TTL %d exceeds configured "
+ "max-zone-ttl %d",
+ rdatalist.ttl, lctx->maxttl);
+ result = ISC_R_RANGE;
+ goto cleanup;
+ }
+
+ /* Rdata contents. */
+ if (rdcount > rdata_size) {
+ dns_rdata_t *new_rdata = NULL;
+
+ new_rdata = grow_rdata(rdcount + RDSZ, rdata,
+ rdata_size, &head, &dummy, mctx);
+ if (new_rdata == NULL) {
+ result = ISC_R_NOMEMORY;
+ goto cleanup;
+ }
+ rdata_size = rdcount + RDSZ;
+ rdata = new_rdata;
+ }
+
+ continue_read:
+ for (i = 0; i < rdcount; i++) {
+ uint16_t rdlen;
+
+ dns_rdata_init(&rdata[i]);
+
+ if (sequential_read &&
+ isc_buffer_availablelength(&target) < MINTSIZ)
+ {
+ unsigned int j;
+
+ INSIST(i > 0); /* detect an infinite loop */
+
+ /* Partial Commit. */
+ ISC_LIST_APPEND(head, &rdatalist, link);
+ result = commit(callbacks, lctx, &head, name,
+ NULL, 0);
+ for (j = 0; j < i; j++) {
+ ISC_LIST_UNLINK(rdatalist.rdata,
+ &rdata[j], link);
+ dns_rdata_reset(&rdata[j]);
+ }
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+
+ /* Rewind the buffer and continue */
+ isc_buffer_clear(&target);
+
+ rdcount -= i;
+
+ goto continue_read;
+ }
+
+ /* rdata length */
+ result = read_and_check(sequential_read, &target,
+ sizeof(rdlen), lctx->f,
+ &totallen);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+ rdlen = isc_buffer_getuint16(&target);
+
+ /* rdata */
+ result = read_and_check(sequential_read, &target, rdlen,
+ lctx->f, &totallen);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+ isc_buffer_setactive(&target, (unsigned int)rdlen);
+ /*
+ * It is safe to have the source active region and
+ * the target available region be the same if
+ * decompression is disabled (see dctx above) and we
+ * are not downcasing names (options == 0).
+ */
+ isc_buffer_init(&buf, isc_buffer_current(&target),
+ (unsigned int)rdlen);
+ result = dns_rdata_fromwire(
+ &rdata[i], rdatalist.rdclass, rdatalist.type,
+ &target, &dctx, 0, &buf);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+ ISC_LIST_APPEND(rdatalist.rdata, &rdata[i], link);
+ }
+
+ /*
+ * Sanity check. Still having remaining space is not
+ * necessarily critical, but it very likely indicates broken
+ * or malformed data.
+ */
+ if (isc_buffer_remaininglength(&target) != 0 || totallen != 0) {
+ result = ISC_R_RANGE;
+ goto cleanup;
+ }
+
+ ISC_LIST_APPEND(head, &rdatalist, link);
+
+ /* Commit this RRset. rdatalist will be unlinked. */
+ result = commit(callbacks, lctx, &head, name, NULL, 0);
+
+ for (i = 0; i < rdcount; i++) {
+ ISC_LIST_UNLINK(rdatalist.rdata, &rdata[i], link);
+ dns_rdata_reset(&rdata[i]);
+ }
+
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+ }
+
+ if (!done) {
+ INSIST(lctx->done != NULL && lctx->task != NULL);
+ result = DNS_R_CONTINUE;
+ } else if (result == ISC_R_SUCCESS && lctx->result != ISC_R_SUCCESS) {
+ result = lctx->result;
+ }
+
+ if (result == ISC_R_SUCCESS && callbacks->rawdata != NULL) {
+ (*callbacks->rawdata)(callbacks->zone, &lctx->header);
+ }
+
+cleanup:
+ if (rdata != NULL) {
+ isc_mem_put(mctx, rdata, rdata_size * sizeof(*rdata));
+ }
+ if (target_mem != NULL) {
+ isc_mem_put(mctx, target_mem, target_size);
+ }
+ if (result != ISC_R_SUCCESS && result != DNS_R_CONTINUE) {
+ (*callbacks->error)(callbacks, "dns_master_load: %s",
+ isc_result_totext(result));
+ }
+
+ return (result);
+}
+
+isc_result_t
+dns_master_loadfile(const char *master_file, dns_name_t *top,
+ dns_name_t *origin, dns_rdataclass_t zclass,
+ unsigned int options, uint32_t resign,
+ dns_rdatacallbacks_t *callbacks,
+ dns_masterincludecb_t include_cb, void *include_arg,
+ isc_mem_t *mctx, dns_masterformat_t format,
+ dns_ttl_t maxttl) {
+ dns_loadctx_t *lctx = NULL;
+ isc_result_t result;
+
+ result = loadctx_create(format, mctx, options, resign, top, zclass,
+ origin, callbacks, NULL, NULL, NULL, include_cb,
+ include_arg, NULL, &lctx);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+
+ lctx->maxttl = maxttl;
+
+ result = (lctx->openfile)(lctx, master_file);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+
+ result = (lctx->load)(lctx);
+ INSIST(result != DNS_R_CONTINUE);
+
+cleanup:
+ dns_loadctx_detach(&lctx);
+ return (result);
+}
+
+isc_result_t
+dns_master_loadfileinc(const char *master_file, dns_name_t *top,
+ dns_name_t *origin, dns_rdataclass_t zclass,
+ unsigned int options, uint32_t resign,
+ dns_rdatacallbacks_t *callbacks, isc_task_t *task,
+ dns_loaddonefunc_t done, void *done_arg,
+ dns_loadctx_t **lctxp, dns_masterincludecb_t include_cb,
+ void *include_arg, isc_mem_t *mctx,
+ dns_masterformat_t format, uint32_t maxttl) {
+ dns_loadctx_t *lctx = NULL;
+ isc_result_t result;
+
+ REQUIRE(task != NULL);
+ REQUIRE(done != NULL);
+
+ result = loadctx_create(format, mctx, options, resign, top, zclass,
+ origin, callbacks, task, done, done_arg,
+ include_cb, include_arg, NULL, &lctx);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+
+ lctx->maxttl = maxttl;
+
+ result = (lctx->openfile)(lctx, master_file);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+
+ result = task_send(lctx);
+ if (result == ISC_R_SUCCESS) {
+ dns_loadctx_attach(lctx, lctxp);
+ return (DNS_R_CONTINUE);
+ }
+
+cleanup:
+ dns_loadctx_detach(&lctx);
+ return (result);
+}
+
+isc_result_t
+dns_master_loadstream(FILE *stream, dns_name_t *top, dns_name_t *origin,
+ dns_rdataclass_t zclass, unsigned int options,
+ dns_rdatacallbacks_t *callbacks, isc_mem_t *mctx) {
+ isc_result_t result;
+ dns_loadctx_t *lctx = NULL;
+
+ REQUIRE(stream != NULL);
+
+ result = loadctx_create(dns_masterformat_text, mctx, options, 0, top,
+ zclass, origin, callbacks, NULL, NULL, NULL,
+ NULL, NULL, NULL, &lctx);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+
+ result = isc_lex_openstream(lctx->lex, stream);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+
+ result = (lctx->load)(lctx);
+ INSIST(result != DNS_R_CONTINUE);
+
+cleanup:
+ if (lctx != NULL) {
+ dns_loadctx_detach(&lctx);
+ }
+ return (result);
+}
+
+isc_result_t
+dns_master_loadstreaminc(FILE *stream, dns_name_t *top, dns_name_t *origin,
+ dns_rdataclass_t zclass, unsigned int options,
+ dns_rdatacallbacks_t *callbacks, isc_task_t *task,
+ dns_loaddonefunc_t done, void *done_arg,
+ dns_loadctx_t **lctxp, isc_mem_t *mctx) {
+ isc_result_t result;
+ dns_loadctx_t *lctx = NULL;
+
+ REQUIRE(stream != NULL);
+ REQUIRE(task != NULL);
+ REQUIRE(done != NULL);
+
+ result = loadctx_create(dns_masterformat_text, mctx, options, 0, top,
+ zclass, origin, callbacks, task, done, done_arg,
+ NULL, NULL, NULL, &lctx);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+
+ result = isc_lex_openstream(lctx->lex, stream);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+
+ result = task_send(lctx);
+ if (result == ISC_R_SUCCESS) {
+ dns_loadctx_attach(lctx, lctxp);
+ return (DNS_R_CONTINUE);
+ }
+
+cleanup:
+ if (lctx != NULL) {
+ dns_loadctx_detach(&lctx);
+ }
+ return (result);
+}
+
+isc_result_t
+dns_master_loadbuffer(isc_buffer_t *buffer, dns_name_t *top, dns_name_t *origin,
+ dns_rdataclass_t zclass, unsigned int options,
+ dns_rdatacallbacks_t *callbacks, isc_mem_t *mctx) {
+ isc_result_t result;
+ dns_loadctx_t *lctx = NULL;
+
+ REQUIRE(buffer != NULL);
+
+ result = loadctx_create(dns_masterformat_text, mctx, options, 0, top,
+ zclass, origin, callbacks, NULL, NULL, NULL,
+ NULL, NULL, NULL, &lctx);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+
+ result = isc_lex_openbuffer(lctx->lex, buffer);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+
+ result = (lctx->load)(lctx);
+ INSIST(result != DNS_R_CONTINUE);
+
+cleanup:
+ dns_loadctx_detach(&lctx);
+ return (result);
+}
+
+isc_result_t
+dns_master_loadbufferinc(isc_buffer_t *buffer, dns_name_t *top,
+ dns_name_t *origin, dns_rdataclass_t zclass,
+ unsigned int options, dns_rdatacallbacks_t *callbacks,
+ isc_task_t *task, dns_loaddonefunc_t done,
+ void *done_arg, dns_loadctx_t **lctxp,
+ isc_mem_t *mctx) {
+ isc_result_t result;
+ dns_loadctx_t *lctx = NULL;
+
+ REQUIRE(buffer != NULL);
+ REQUIRE(task != NULL);
+ REQUIRE(done != NULL);
+
+ result = loadctx_create(dns_masterformat_text, mctx, options, 0, top,
+ zclass, origin, callbacks, task, done, done_arg,
+ NULL, NULL, NULL, &lctx);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+
+ result = isc_lex_openbuffer(lctx->lex, buffer);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+
+ result = task_send(lctx);
+ if (result == ISC_R_SUCCESS) {
+ dns_loadctx_attach(lctx, lctxp);
+ return (DNS_R_CONTINUE);
+ }
+
+cleanup:
+ dns_loadctx_detach(&lctx);
+ return (result);
+}
+
+isc_result_t
+dns_master_loadlexer(isc_lex_t *lex, dns_name_t *top, dns_name_t *origin,
+ dns_rdataclass_t zclass, unsigned int options,
+ dns_rdatacallbacks_t *callbacks, isc_mem_t *mctx) {
+ isc_result_t result;
+ dns_loadctx_t *lctx = NULL;
+
+ REQUIRE(lex != NULL);
+
+ result = loadctx_create(dns_masterformat_text, mctx, options, 0, top,
+ zclass, origin, callbacks, NULL, NULL, NULL,
+ NULL, NULL, lex, &lctx);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+
+ result = (lctx->load)(lctx);
+ INSIST(result != DNS_R_CONTINUE);
+
+ dns_loadctx_detach(&lctx);
+ return (result);
+}
+
+isc_result_t
+dns_master_loadlexerinc(isc_lex_t *lex, dns_name_t *top, dns_name_t *origin,
+ dns_rdataclass_t zclass, unsigned int options,
+ dns_rdatacallbacks_t *callbacks, isc_task_t *task,
+ dns_loaddonefunc_t done, void *done_arg,
+ dns_loadctx_t **lctxp, isc_mem_t *mctx) {
+ isc_result_t result;
+ dns_loadctx_t *lctx = NULL;
+
+ REQUIRE(lex != NULL);
+ REQUIRE(task != NULL);
+ REQUIRE(done != NULL);
+
+ result = loadctx_create(dns_masterformat_text, mctx, options, 0, top,
+ zclass, origin, callbacks, task, done, done_arg,
+ NULL, NULL, lex, &lctx);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+
+ result = task_send(lctx);
+ if (result == ISC_R_SUCCESS) {
+ dns_loadctx_attach(lctx, lctxp);
+ return (DNS_R_CONTINUE);
+ }
+
+ dns_loadctx_detach(&lctx);
+ return (result);
+}
+
+/*
+ * Grow the slab of dns_rdatalist_t structures.
+ * Re-link glue and current list.
+ */
+static dns_rdatalist_t *
+grow_rdatalist(int new_len, dns_rdatalist_t *oldlist, int old_len,
+ rdatalist_head_t *current, rdatalist_head_t *glue,
+ isc_mem_t *mctx) {
+ dns_rdatalist_t *newlist;
+ int rdlcount = 0;
+ ISC_LIST(dns_rdatalist_t) save;
+ dns_rdatalist_t *this;
+
+ newlist = isc_mem_get(mctx, new_len * sizeof(*newlist));
+ if (newlist == NULL) {
+ return (NULL);
+ }
+
+ ISC_LIST_INIT(save);
+ while ((this = ISC_LIST_HEAD(*current)) != NULL) {
+ ISC_LIST_UNLINK(*current, this, link);
+ ISC_LIST_APPEND(save, this, link);
+ }
+ while ((this = ISC_LIST_HEAD(save)) != NULL) {
+ ISC_LIST_UNLINK(save, this, link);
+ INSIST(rdlcount < new_len);
+ newlist[rdlcount] = *this;
+ ISC_LIST_APPEND(*current, &newlist[rdlcount], link);
+ rdlcount++;
+ }
+
+ ISC_LIST_INIT(save);
+ while ((this = ISC_LIST_HEAD(*glue)) != NULL) {
+ ISC_LIST_UNLINK(*glue, this, link);
+ ISC_LIST_APPEND(save, this, link);
+ }
+ while ((this = ISC_LIST_HEAD(save)) != NULL) {
+ ISC_LIST_UNLINK(save, this, link);
+ INSIST(rdlcount < new_len);
+ newlist[rdlcount] = *this;
+ ISC_LIST_APPEND(*glue, &newlist[rdlcount], link);
+ rdlcount++;
+ }
+
+ INSIST(rdlcount == old_len);
+ if (oldlist != NULL) {
+ isc_mem_put(mctx, oldlist, old_len * sizeof(*oldlist));
+ }
+ return (newlist);
+}
+
+/*
+ * Grow the slab of rdata structs.
+ * Re-link the current and glue chains.
+ */
+static dns_rdata_t *
+grow_rdata(int new_len, dns_rdata_t *oldlist, int old_len,
+ rdatalist_head_t *current, rdatalist_head_t *glue, isc_mem_t *mctx) {
+ dns_rdata_t *newlist;
+ int rdcount = 0;
+ ISC_LIST(dns_rdata_t) save;
+ dns_rdatalist_t *this;
+ dns_rdata_t *rdata;
+
+ newlist = isc_mem_get(mctx, new_len * sizeof(*newlist));
+ if (newlist == NULL) {
+ return (NULL);
+ }
+ memset(newlist, 0, new_len * sizeof(*newlist));
+
+ /*
+ * Copy current relinking.
+ */
+ this = ISC_LIST_HEAD(*current);
+ while (this != NULL) {
+ ISC_LIST_INIT(save);
+ while ((rdata = ISC_LIST_HEAD(this->rdata)) != NULL) {
+ ISC_LIST_UNLINK(this->rdata, rdata, link);
+ ISC_LIST_APPEND(save, rdata, link);
+ }
+ while ((rdata = ISC_LIST_HEAD(save)) != NULL) {
+ ISC_LIST_UNLINK(save, rdata, link);
+ INSIST(rdcount < new_len);
+ newlist[rdcount] = *rdata;
+ ISC_LIST_APPEND(this->rdata, &newlist[rdcount], link);
+ rdcount++;
+ }
+ this = ISC_LIST_NEXT(this, link);
+ }
+
+ /*
+ * Copy glue relinking.
+ */
+ this = ISC_LIST_HEAD(*glue);
+ while (this != NULL) {
+ ISC_LIST_INIT(save);
+ while ((rdata = ISC_LIST_HEAD(this->rdata)) != NULL) {
+ ISC_LIST_UNLINK(this->rdata, rdata, link);
+ ISC_LIST_APPEND(save, rdata, link);
+ }
+ while ((rdata = ISC_LIST_HEAD(save)) != NULL) {
+ ISC_LIST_UNLINK(save, rdata, link);
+ INSIST(rdcount < new_len);
+ newlist[rdcount] = *rdata;
+ ISC_LIST_APPEND(this->rdata, &newlist[rdcount], link);
+ rdcount++;
+ }
+ this = ISC_LIST_NEXT(this, link);
+ }
+ INSIST(rdcount == old_len || rdcount == 0);
+ if (oldlist != NULL) {
+ isc_mem_put(mctx, oldlist, old_len * sizeof(*oldlist));
+ }
+ return (newlist);
+}
+
+static uint32_t
+resign_fromlist(dns_rdatalist_t *this, dns_loadctx_t *lctx) {
+ dns_rdata_t *rdata;
+ dns_rdata_rrsig_t sig;
+ uint32_t when;
+
+ rdata = ISC_LIST_HEAD(this->rdata);
+ INSIST(rdata != NULL);
+ (void)dns_rdata_tostruct(rdata, &sig, NULL);
+ if (isc_serial_gt(sig.timesigned, lctx->now)) {
+ when = lctx->now;
+ } else {
+ when = sig.timeexpire - lctx->resign;
+ }
+
+ rdata = ISC_LIST_NEXT(rdata, link);
+ while (rdata != NULL) {
+ (void)dns_rdata_tostruct(rdata, &sig, NULL);
+ if (isc_serial_gt(sig.timesigned, lctx->now)) {
+ when = lctx->now;
+ } else if (sig.timeexpire - lctx->resign < when) {
+ when = sig.timeexpire - lctx->resign;
+ }
+ rdata = ISC_LIST_NEXT(rdata, link);
+ }
+ return (when);
+}
+
+/*
+ * Convert each element from a rdatalist_t to rdataset then call commit.
+ * Unlink each element as we go.
+ */
+
+static isc_result_t
+commit(dns_rdatacallbacks_t *callbacks, dns_loadctx_t *lctx,
+ rdatalist_head_t *head, dns_name_t *owner, const char *source,
+ unsigned int line) {
+ dns_rdatalist_t *this;
+ dns_rdataset_t dataset;
+ isc_result_t result;
+ char namebuf[DNS_NAME_FORMATSIZE];
+ void (*error)(struct dns_rdatacallbacks *, const char *, ...);
+
+ this = ISC_LIST_HEAD(*head);
+ error = callbacks->error;
+
+ if (this == NULL) {
+ return (ISC_R_SUCCESS);
+ }
+ do {
+ dns_rdataset_init(&dataset);
+ RUNTIME_CHECK(dns_rdatalist_tordataset(this, &dataset) ==
+ ISC_R_SUCCESS);
+ dataset.trust = dns_trust_ultimate;
+ /*
+ * If this is a secure dynamic zone set the re-signing time.
+ */
+ if (dataset.type == dns_rdatatype_rrsig &&
+ (lctx->options & DNS_MASTER_RESIGN) != 0)
+ {
+ dataset.attributes |= DNS_RDATASETATTR_RESIGN;
+ dataset.resign = resign_fromlist(this, lctx);
+ }
+ result = ((*callbacks->add)(callbacks->add_private, owner,
+ &dataset));
+ if (result == ISC_R_NOMEMORY) {
+ (*error)(callbacks, "dns_master_load: %s",
+ isc_result_totext(result));
+ } else if (result != ISC_R_SUCCESS) {
+ dns_name_format(owner, namebuf, sizeof(namebuf));
+ if (source != NULL) {
+ (*error)(callbacks, "%s: %s:%lu: %s: %s",
+ "dns_master_load", source, line,
+ namebuf, isc_result_totext(result));
+ } else {
+ (*error)(callbacks, "%s: %s: %s",
+ "dns_master_load", namebuf,
+ isc_result_totext(result));
+ }
+ }
+ if (MANYERRS(lctx, result)) {
+ SETRESULT(lctx, result);
+ } else if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+ ISC_LIST_UNLINK(*head, this, link);
+ this = ISC_LIST_HEAD(*head);
+ } while (this != NULL);
+ return (ISC_R_SUCCESS);
+}
+
+/*
+ * Returns true if one of the NS rdata's contains 'owner'.
+ */
+
+static bool
+is_glue(rdatalist_head_t *head, dns_name_t *owner) {
+ dns_rdatalist_t *this;
+ dns_rdata_t *rdata;
+ isc_region_t region;
+ dns_name_t name;
+
+ /*
+ * Find NS rrset.
+ */
+ this = ISC_LIST_HEAD(*head);
+ while (this != NULL) {
+ if (this->type == dns_rdatatype_ns) {
+ break;
+ }
+ this = ISC_LIST_NEXT(this, link);
+ }
+ if (this == NULL) {
+ return (false);
+ }
+
+ rdata = ISC_LIST_HEAD(this->rdata);
+ while (rdata != NULL) {
+ dns_name_init(&name, NULL);
+ dns_rdata_toregion(rdata, &region);
+ dns_name_fromregion(&name, &region);
+ if (dns_name_equal(&name, owner)) {
+ return (true);
+ }
+ rdata = ISC_LIST_NEXT(rdata, link);
+ }
+ return (false);
+}
+
+static void
+load_quantum(isc_task_t *task, isc_event_t *event) {
+ isc_result_t result;
+ dns_loadctx_t *lctx;
+
+ REQUIRE(event != NULL);
+ lctx = event->ev_arg;
+ REQUIRE(DNS_LCTX_VALID(lctx));
+
+ if (atomic_load_acquire(&lctx->canceled)) {
+ result = ISC_R_CANCELED;
+ } else {
+ result = (lctx->load)(lctx);
+ }
+ if (result == DNS_R_CONTINUE) {
+ event->ev_arg = lctx;
+ isc_task_send(task, &event);
+ } else {
+ (lctx->done)(lctx->done_arg, result);
+ isc_event_free(&event);
+ dns_loadctx_detach(&lctx);
+ }
+}
+
+static isc_result_t
+task_send(dns_loadctx_t *lctx) {
+ isc_event_t *event;
+
+ event = isc_event_allocate(lctx->mctx, NULL, DNS_EVENT_MASTERQUANTUM,
+ load_quantum, lctx, sizeof(*event));
+ isc_task_send(lctx->task, &event);
+ return (ISC_R_SUCCESS);
+}
+
+void
+dns_loadctx_cancel(dns_loadctx_t *lctx) {
+ REQUIRE(DNS_LCTX_VALID(lctx));
+
+ atomic_store_release(&lctx->canceled, true);
+}
+
+void
+dns_master_initrawheader(dns_masterrawheader_t *header) {
+ memset(header, 0, sizeof(dns_masterrawheader_t));
+}
diff --git a/lib/dns/masterdump.c b/lib/dns/masterdump.c
new file mode 100644
index 0000000..4946d0d
--- /dev/null
+++ b/lib/dns/masterdump.c
@@ -0,0 +1,2094 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/*! \file */
+
+#include <inttypes.h>
+#include <stdbool.h>
+#include <stdlib.h>
+
+#include <isc/atomic.h>
+#include <isc/buffer.h>
+#include <isc/event.h>
+#include <isc/file.h>
+#include <isc/magic.h>
+#include <isc/mem.h>
+#include <isc/print.h>
+#include <isc/refcount.h>
+#include <isc/result.h>
+#include <isc/stdio.h>
+#include <isc/string.h>
+#include <isc/task.h>
+#include <isc/time.h>
+#include <isc/types.h>
+#include <isc/util.h>
+
+#include <dns/db.h>
+#include <dns/dbiterator.h>
+#include <dns/events.h>
+#include <dns/fixedname.h>
+#include <dns/log.h>
+#include <dns/master.h>
+#include <dns/masterdump.h>
+#include <dns/ncache.h>
+#include <dns/rdata.h>
+#include <dns/rdataclass.h>
+#include <dns/rdataset.h>
+#include <dns/rdatasetiter.h>
+#include <dns/rdatatype.h>
+#include <dns/time.h>
+#include <dns/ttl.h>
+
+#define DNS_DCTX_MAGIC ISC_MAGIC('D', 'c', 't', 'x')
+#define DNS_DCTX_VALID(d) ISC_MAGIC_VALID(d, DNS_DCTX_MAGIC)
+
+#define RETERR(x) \
+ do { \
+ isc_result_t _r = (x); \
+ if (_r != ISC_R_SUCCESS) \
+ return ((_r)); \
+ } while (0)
+
+#define CHECK(x) \
+ do { \
+ if ((x) != ISC_R_SUCCESS) \
+ goto cleanup; \
+ } while (0)
+
+struct dns_master_style {
+ dns_masterstyle_flags_t flags; /* DNS_STYLEFLAG_* */
+ unsigned int ttl_column;
+ unsigned int class_column;
+ unsigned int type_column;
+ unsigned int rdata_column;
+ unsigned int line_length;
+ unsigned int tab_width;
+ unsigned int split_width;
+};
+
+/*%
+ * The maximum length of the newline+indentation that is output
+ * when inserting a line break in an RR. This effectively puts an
+ * upper limits on the value of "rdata_column", because if it is
+ * very large, the tabs and spaces needed to reach it will not fit.
+ */
+#define DNS_TOTEXT_LINEBREAK_MAXLEN 100
+
+/*% Does the rdataset 'r' contain a stale answer? */
+#define STALE(r) (((r)->attributes & DNS_RDATASETATTR_STALE) != 0)
+/*% Does the rdataset 'r' contain an expired answer? */
+#define ANCIENT(r) (((r)->attributes & DNS_RDATASETATTR_ANCIENT) != 0)
+
+/*%
+ * Context structure for a masterfile dump in progress.
+ */
+typedef struct dns_totext_ctx {
+ dns_master_style_t style;
+ bool class_printed;
+ char *linebreak;
+ char linebreak_buf[DNS_TOTEXT_LINEBREAK_MAXLEN];
+ dns_name_t *origin;
+ dns_name_t *neworigin;
+ dns_fixedname_t origin_fixname;
+ uint32_t current_ttl;
+ bool current_ttl_valid;
+ dns_ttl_t serve_stale_ttl;
+ dns_indent_t indent;
+} dns_totext_ctx_t;
+
+const dns_master_style_t dns_master_style_keyzone = {
+ DNS_STYLEFLAG_OMIT_OWNER | DNS_STYLEFLAG_OMIT_CLASS |
+ DNS_STYLEFLAG_REL_OWNER | DNS_STYLEFLAG_REL_DATA |
+ DNS_STYLEFLAG_OMIT_TTL | DNS_STYLEFLAG_TTL |
+ DNS_STYLEFLAG_COMMENT | DNS_STYLEFLAG_RRCOMMENT |
+ DNS_STYLEFLAG_MULTILINE | DNS_STYLEFLAG_KEYDATA,
+ 24,
+ 24,
+ 24,
+ 32,
+ 80,
+ 8,
+ UINT_MAX
+};
+
+const dns_master_style_t dns_master_style_default = {
+ DNS_STYLEFLAG_OMIT_OWNER | DNS_STYLEFLAG_OMIT_CLASS |
+ DNS_STYLEFLAG_REL_OWNER | DNS_STYLEFLAG_REL_DATA |
+ DNS_STYLEFLAG_OMIT_TTL | DNS_STYLEFLAG_TTL |
+ DNS_STYLEFLAG_COMMENT | DNS_STYLEFLAG_RRCOMMENT |
+ DNS_STYLEFLAG_MULTILINE,
+ 24,
+ 24,
+ 24,
+ 32,
+ 80,
+ 8,
+ UINT_MAX
+};
+
+const dns_master_style_t dns_master_style_full = {
+ DNS_STYLEFLAG_COMMENT | DNS_STYLEFLAG_RESIGN,
+ 46,
+ 46,
+ 46,
+ 64,
+ 120,
+ 8,
+ UINT_MAX
+};
+
+const dns_master_style_t dns_master_style_explicitttl = {
+ DNS_STYLEFLAG_OMIT_OWNER | DNS_STYLEFLAG_OMIT_CLASS |
+ DNS_STYLEFLAG_REL_OWNER | DNS_STYLEFLAG_REL_DATA |
+ DNS_STYLEFLAG_COMMENT | DNS_STYLEFLAG_RRCOMMENT |
+ DNS_STYLEFLAG_MULTILINE,
+ 24,
+ 32,
+ 32,
+ 40,
+ 80,
+ 8,
+ UINT_MAX
+};
+
+const dns_master_style_t dns_master_style_cache = {
+ DNS_STYLEFLAG_OMIT_OWNER | DNS_STYLEFLAG_OMIT_CLASS |
+ DNS_STYLEFLAG_MULTILINE | DNS_STYLEFLAG_RRCOMMENT |
+ DNS_STYLEFLAG_TRUST | DNS_STYLEFLAG_NCACHE,
+ 24,
+ 32,
+ 32,
+ 40,
+ 80,
+ 8,
+ UINT_MAX
+};
+
+const dns_master_style_t dns_master_style_cache_with_expired = {
+ DNS_STYLEFLAG_OMIT_OWNER | DNS_STYLEFLAG_OMIT_CLASS |
+ DNS_STYLEFLAG_MULTILINE | DNS_STYLEFLAG_RRCOMMENT |
+ DNS_STYLEFLAG_TRUST | DNS_STYLEFLAG_NCACHE |
+ DNS_STYLEFLAG_EXPIRED,
+ 24,
+ 32,
+ 32,
+ 40,
+ 80,
+ 8,
+ UINT_MAX
+};
+
+const dns_master_style_t dns_master_style_simple = { 0, 24, 32, 32,
+ 40, 80, 8, UINT_MAX };
+
+/*%
+ * A style suitable for dns_rdataset_totext().
+ */
+const dns_master_style_t dns_master_style_debug = {
+ DNS_STYLEFLAG_REL_OWNER, 24, 32, 40, 48, 80, 8, UINT_MAX
+};
+
+/*%
+ * Similar, but indented (i.e., prepended with indentctx.string).
+ */
+const dns_master_style_t dns_master_style_indent = {
+ DNS_STYLEFLAG_REL_OWNER | DNS_STYLEFLAG_INDENT,
+ 24,
+ 32,
+ 40,
+ 48,
+ 80,
+ 8,
+ UINT_MAX
+};
+
+/*%
+ * Similar, but with each line commented out.
+ */
+const dns_master_style_t dns_master_style_comment = {
+ DNS_STYLEFLAG_REL_OWNER | DNS_STYLEFLAG_MULTILINE |
+ DNS_STYLEFLAG_RRCOMMENT | DNS_STYLEFLAG_COMMENTDATA,
+ 24,
+ 32,
+ 40,
+ 48,
+ 80,
+ 8,
+ UINT_MAX
+};
+
+/*%
+ * YAML style
+ */
+const dns_master_style_t dns_master_style_yaml = {
+ DNS_STYLEFLAG_YAML | DNS_STYLEFLAG_REL_OWNER | DNS_STYLEFLAG_INDENT,
+ 24,
+ 32,
+ 40,
+ 48,
+ 80,
+ 8,
+ UINT_MAX
+};
+
+#define N_SPACES 10
+static char spaces[N_SPACES + 1] = " ";
+
+#define N_TABS 10
+static char tabs[N_TABS + 1] = "\t\t\t\t\t\t\t\t\t\t";
+
+struct dns_dumpctx {
+ unsigned int magic;
+ isc_mem_t *mctx;
+ isc_mutex_t lock;
+ isc_refcount_t references;
+ atomic_bool canceled;
+ bool do_date;
+ isc_stdtime_t now;
+ FILE *f;
+ dns_db_t *db;
+ dns_dbversion_t *version;
+ dns_dbiterator_t *dbiter;
+ dns_totext_ctx_t tctx;
+ isc_task_t *task;
+ dns_dumpdonefunc_t done;
+ void *done_arg;
+ /* dns_master_dumpasync() */
+ isc_result_t result;
+ char *file;
+ char *tmpfile;
+ dns_masterformat_t format;
+ dns_masterrawheader_t header;
+ isc_result_t (*dumpsets)(isc_mem_t *mctx, const dns_name_t *name,
+ dns_rdatasetiter_t *rdsiter,
+ dns_totext_ctx_t *ctx, isc_buffer_t *buffer,
+ FILE *f);
+};
+
+#define NXDOMAIN(x) (((x)->attributes & DNS_RDATASETATTR_NXDOMAIN) != 0)
+
+static const dns_indent_t default_indent = { "\t", 1 };
+static const dns_indent_t default_yamlindent = { " ", 1 };
+
+/*%
+ * Output tabs and spaces to go from column '*current' to
+ * column 'to', and update '*current' to reflect the new
+ * current column.
+ */
+static isc_result_t
+indent(unsigned int *current, unsigned int to, int tabwidth,
+ isc_buffer_t *target) {
+ isc_region_t r;
+ unsigned char *p;
+ unsigned int from;
+ int ntabs, nspaces, t;
+
+ from = *current;
+
+ if (to < from + 1) {
+ to = from + 1;
+ }
+
+ ntabs = to / tabwidth - from / tabwidth;
+ if (ntabs < 0) {
+ ntabs = 0;
+ }
+
+ if (ntabs > 0) {
+ isc_buffer_availableregion(target, &r);
+ if (r.length < (unsigned)ntabs) {
+ return (ISC_R_NOSPACE);
+ }
+ p = r.base;
+
+ t = ntabs;
+ while (t) {
+ int n = t;
+ if (n > N_TABS) {
+ n = N_TABS;
+ }
+ memmove(p, tabs, n);
+ p += n;
+ t -= n;
+ }
+ isc_buffer_add(target, ntabs);
+ from = (to / tabwidth) * tabwidth;
+ }
+
+ nspaces = to - from;
+ INSIST(nspaces >= 0);
+
+ isc_buffer_availableregion(target, &r);
+ if (r.length < (unsigned)nspaces) {
+ return (ISC_R_NOSPACE);
+ }
+ p = r.base;
+
+ t = nspaces;
+ while (t) {
+ int n = t;
+ if (n > N_SPACES) {
+ n = N_SPACES;
+ }
+ memmove(p, spaces, n);
+ p += n;
+ t -= n;
+ }
+ isc_buffer_add(target, nspaces);
+
+ *current = to;
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+totext_ctx_init(const dns_master_style_t *style, const dns_indent_t *indentctx,
+ dns_totext_ctx_t *ctx) {
+ isc_result_t result;
+
+ REQUIRE(style->tab_width != 0);
+
+ if (indentctx == NULL) {
+ if ((style->flags & DNS_STYLEFLAG_YAML) != 0) {
+ indentctx = &default_yamlindent;
+ } else {
+ indentctx = &default_indent;
+ }
+ }
+
+ ctx->style = *style;
+ ctx->class_printed = false;
+
+ dns_fixedname_init(&ctx->origin_fixname);
+
+ /*
+ * Set up the line break string if needed.
+ */
+ if ((ctx->style.flags & DNS_STYLEFLAG_MULTILINE) != 0) {
+ isc_buffer_t buf;
+ isc_region_t r;
+ unsigned int col = 0;
+
+ isc_buffer_init(&buf, ctx->linebreak_buf,
+ sizeof(ctx->linebreak_buf));
+
+ isc_buffer_availableregion(&buf, &r);
+ if (r.length < 1) {
+ return (DNS_R_TEXTTOOLONG);
+ }
+ r.base[0] = '\n';
+ isc_buffer_add(&buf, 1);
+
+ if ((ctx->style.flags & DNS_STYLEFLAG_INDENT) != 0 ||
+ (ctx->style.flags & DNS_STYLEFLAG_YAML) != 0)
+ {
+ unsigned int i, len = strlen(indentctx->string);
+ for (i = 0; i < indentctx->count; i++) {
+ if (isc_buffer_availablelength(&buf) < len) {
+ return (DNS_R_TEXTTOOLONG);
+ }
+ isc_buffer_putstr(&buf, indentctx->string);
+ }
+ }
+
+ if ((ctx->style.flags & DNS_STYLEFLAG_COMMENTDATA) != 0) {
+ isc_buffer_availableregion(&buf, &r);
+ if (r.length < 1) {
+ return (DNS_R_TEXTTOOLONG);
+ }
+ r.base[0] = ';';
+ isc_buffer_add(&buf, 1);
+ }
+
+ result = indent(&col, ctx->style.rdata_column,
+ ctx->style.tab_width, &buf);
+ /*
+ * Do not return ISC_R_NOSPACE if the line break string
+ * buffer is too small, because that would just make
+ * dump_rdataset() retry indefinitely with ever
+ * bigger target buffers. That's a different buffer,
+ * so it won't help. Use DNS_R_TEXTTOOLONG as a substitute.
+ */
+ if (result == ISC_R_NOSPACE) {
+ return (DNS_R_TEXTTOOLONG);
+ }
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+
+ isc_buffer_availableregion(&buf, &r);
+ if (r.length < 1) {
+ return (DNS_R_TEXTTOOLONG);
+ }
+ r.base[0] = '\0';
+ isc_buffer_add(&buf, 1);
+ ctx->linebreak = ctx->linebreak_buf;
+ } else {
+ ctx->linebreak = NULL;
+ }
+
+ ctx->origin = NULL;
+ ctx->neworigin = NULL;
+ ctx->current_ttl = 0;
+ ctx->current_ttl_valid = false;
+ ctx->serve_stale_ttl = 0;
+ ctx->indent = *indentctx;
+
+ return (ISC_R_SUCCESS);
+}
+
+#define INDENT_TO(col) \
+ do { \
+ if ((ctx->style.flags & DNS_STYLEFLAG_YAML) != 0) { \
+ if ((result = str_totext(" ", target)) != \
+ ISC_R_SUCCESS) \
+ return ((result)); \
+ } else if ((result = indent(&column, ctx->style.col, \
+ ctx->style.tab_width, target)) != \
+ ISC_R_SUCCESS) \
+ return ((result)); \
+ } while (0)
+
+static isc_result_t
+str_totext(const char *source, isc_buffer_t *target) {
+ unsigned int l;
+ isc_region_t region;
+
+ isc_buffer_availableregion(target, &region);
+ l = strlen(source);
+
+ if (l > region.length) {
+ return (ISC_R_NOSPACE);
+ }
+
+ memmove(region.base, source, l);
+ isc_buffer_add(target, l);
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+ncache_summary(dns_rdataset_t *rdataset, bool omit_final_dot,
+ dns_totext_ctx_t *ctx, isc_buffer_t *target) {
+ isc_result_t result = ISC_R_SUCCESS;
+ dns_rdataset_t rds;
+ dns_name_t name;
+
+ dns_rdataset_init(&rds);
+ dns_name_init(&name, NULL);
+
+ do {
+ dns_ncache_current(rdataset, &name, &rds);
+ for (result = dns_rdataset_first(&rds); result == ISC_R_SUCCESS;
+ result = dns_rdataset_next(&rds))
+ {
+ if ((ctx->style.flags & DNS_STYLEFLAG_INDENT) != 0 ||
+ (ctx->style.flags & DNS_STYLEFLAG_YAML) != 0)
+ {
+ unsigned int i;
+ for (i = 0; i < ctx->indent.count; i++) {
+ CHECK(str_totext(ctx->indent.string,
+ target));
+ }
+ }
+
+ if ((ctx->style.flags & DNS_STYLEFLAG_YAML) != 0) {
+ CHECK(str_totext("- ", target));
+ } else {
+ CHECK(str_totext("; ", target));
+ }
+
+ CHECK(dns_name_totext(&name, omit_final_dot, target));
+ CHECK(str_totext(" ", target));
+ CHECK(dns_rdatatype_totext(rds.type, target));
+ if (rds.type == dns_rdatatype_rrsig) {
+ CHECK(str_totext(" ", target));
+ CHECK(dns_rdatatype_totext(rds.covers, target));
+ CHECK(str_totext(" ...\n", target));
+ } else {
+ dns_rdata_t rdata = DNS_RDATA_INIT;
+ dns_rdataset_current(&rds, &rdata);
+ CHECK(str_totext(" ", target));
+ CHECK(dns_rdata_tofmttext(&rdata, dns_rootname,
+ 0, 0, 0, " ",
+ target));
+ CHECK(str_totext("\n", target));
+ }
+ }
+ dns_rdataset_disassociate(&rds);
+ result = dns_rdataset_next(rdataset);
+ } while (result == ISC_R_SUCCESS);
+
+ if (result == ISC_R_NOMORE) {
+ result = ISC_R_SUCCESS;
+ }
+cleanup:
+ if (dns_rdataset_isassociated(&rds)) {
+ dns_rdataset_disassociate(&rds);
+ }
+
+ return (result);
+}
+
+/*
+ * Convert 'rdataset' to master file text format according to 'ctx',
+ * storing the result in 'target'. If 'owner_name' is NULL, it
+ * is omitted; otherwise 'owner_name' must be valid and have at least
+ * one label.
+ */
+
+static isc_result_t
+rdataset_totext(dns_rdataset_t *rdataset, const dns_name_t *owner_name,
+ dns_totext_ctx_t *ctx, bool omit_final_dot,
+ isc_buffer_t *target) {
+ isc_result_t result;
+ unsigned int column;
+ bool first = true;
+ uint32_t current_ttl;
+ bool current_ttl_valid;
+ dns_rdatatype_t type;
+ unsigned int type_start;
+ dns_fixedname_t fixed;
+ dns_name_t *name = NULL;
+ unsigned int i;
+
+ REQUIRE(DNS_RDATASET_VALID(rdataset));
+
+ rdataset->attributes |= DNS_RDATASETATTR_LOADORDER;
+ result = dns_rdataset_first(rdataset);
+
+ current_ttl = ctx->current_ttl;
+ current_ttl_valid = ctx->current_ttl_valid;
+
+ if (owner_name != NULL) {
+ name = dns_fixedname_initname(&fixed);
+ dns_name_copy(owner_name, name);
+ dns_rdataset_getownercase(rdataset, name);
+ }
+
+ while (result == ISC_R_SUCCESS) {
+ column = 0;
+
+ /*
+ * Indent?
+ */
+ if ((ctx->style.flags & DNS_STYLEFLAG_INDENT) != 0 ||
+ (ctx->style.flags & DNS_STYLEFLAG_YAML) != 0)
+ {
+ for (i = 0; i < ctx->indent.count; i++) {
+ RETERR(str_totext(ctx->indent.string, target));
+ }
+ }
+
+ /*
+ * YAML or comment prefix?
+ */
+ if ((ctx->style.flags & DNS_STYLEFLAG_YAML) != 0) {
+ RETERR(str_totext("- ", target));
+ } else if ((ctx->style.flags & DNS_STYLEFLAG_COMMENTDATA) != 0)
+ {
+ RETERR(str_totext(";", target));
+ }
+
+ /*
+ * Owner name.
+ */
+ if (name != NULL &&
+ !((ctx->style.flags & DNS_STYLEFLAG_OMIT_OWNER) != 0 &&
+ !first))
+ {
+ unsigned int name_start = target->used;
+ RETERR(dns_name_totext(name, omit_final_dot, target));
+ column += target->used - name_start;
+ }
+
+ /*
+ * TTL.
+ */
+ if ((ctx->style.flags & DNS_STYLEFLAG_NO_TTL) == 0 &&
+ !((ctx->style.flags & DNS_STYLEFLAG_OMIT_TTL) != 0 &&
+ current_ttl_valid && rdataset->ttl == current_ttl))
+ {
+ char ttlbuf[64];
+ isc_region_t r;
+ unsigned int length;
+
+ INDENT_TO(ttl_column);
+ if ((ctx->style.flags & DNS_STYLEFLAG_TTL_UNITS) != 0) {
+ length = target->used;
+ result = dns_ttl_totext(rdataset->ttl, false,
+ false, target);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+ column += target->used - length;
+ } else {
+ length = snprintf(ttlbuf, sizeof(ttlbuf), "%u",
+ rdataset->ttl);
+ INSIST(length <= sizeof(ttlbuf));
+ isc_buffer_availableregion(target, &r);
+ if (r.length < length) {
+ return (ISC_R_NOSPACE);
+ }
+ memmove(r.base, ttlbuf, length);
+ isc_buffer_add(target, length);
+ column += length;
+ }
+
+ /*
+ * If the $TTL directive is not in use, the TTL we
+ * just printed becomes the default for subsequent RRs.
+ */
+ if ((ctx->style.flags & DNS_STYLEFLAG_TTL) == 0) {
+ current_ttl = rdataset->ttl;
+ current_ttl_valid = true;
+ }
+ }
+
+ /*
+ * Class.
+ */
+ if ((ctx->style.flags & DNS_STYLEFLAG_NO_CLASS) == 0 &&
+ ((ctx->style.flags & DNS_STYLEFLAG_OMIT_CLASS) == 0 ||
+ !ctx->class_printed))
+ {
+ unsigned int class_start;
+ INDENT_TO(class_column);
+ class_start = target->used;
+ if ((ctx->style.flags & DNS_STYLEFLAG_UNKNOWNFORMAT) !=
+ 0)
+ {
+ result = dns_rdataclass_tounknowntext(
+ rdataset->rdclass, target);
+ } else {
+ result = dns_rdataclass_totext(
+ rdataset->rdclass, target);
+ }
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+ column += (target->used - class_start);
+ }
+
+ /*
+ * Type.
+ */
+
+ if ((rdataset->attributes & DNS_RDATASETATTR_NEGATIVE) != 0) {
+ type = rdataset->covers;
+ } else {
+ type = rdataset->type;
+ }
+
+ INDENT_TO(type_column);
+ type_start = target->used;
+ if ((rdataset->attributes & DNS_RDATASETATTR_NEGATIVE) != 0) {
+ RETERR(str_totext("\\-", target));
+ }
+ switch (type) {
+ case dns_rdatatype_keydata:
+#define KEYDATA "KEYDATA"
+ if ((ctx->style.flags & DNS_STYLEFLAG_KEYDATA) != 0) {
+ if (isc_buffer_availablelength(target) <
+ (sizeof(KEYDATA) - 1))
+ {
+ return (ISC_R_NOSPACE);
+ }
+ isc_buffer_putstr(target, KEYDATA);
+ break;
+ }
+ FALLTHROUGH;
+ default:
+ if ((ctx->style.flags & DNS_STYLEFLAG_UNKNOWNFORMAT) !=
+ 0)
+ {
+ result = dns_rdatatype_tounknowntext(type,
+ target);
+ } else {
+ result = dns_rdatatype_totext(type, target);
+ }
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+ }
+ column += (target->used - type_start);
+
+ /*
+ * Rdata.
+ */
+ INDENT_TO(rdata_column);
+ if ((rdataset->attributes & DNS_RDATASETATTR_NEGATIVE) != 0) {
+ if (NXDOMAIN(rdataset)) {
+ RETERR(str_totext(";-$NXDOMAIN\n", target));
+ } else {
+ RETERR(str_totext(";-$NXRRSET\n", target));
+ }
+ /*
+ * Print a summary of the cached records which make
+ * up the negative response.
+ */
+ RETERR(ncache_summary(rdataset, omit_final_dot, ctx,
+ target));
+ break;
+ } else {
+ dns_rdata_t rdata = DNS_RDATA_INIT;
+ isc_region_t r;
+
+ dns_rdataset_current(rdataset, &rdata);
+
+ RETERR(dns_rdata_tofmttext(
+ &rdata, ctx->origin, ctx->style.flags,
+ ctx->style.line_length -
+ ctx->style.rdata_column,
+ ctx->style.split_width, ctx->linebreak,
+ target));
+
+ isc_buffer_availableregion(target, &r);
+ if (r.length < 1) {
+ return (ISC_R_NOSPACE);
+ }
+ r.base[0] = '\n';
+ isc_buffer_add(target, 1);
+ }
+
+ first = false;
+ result = dns_rdataset_next(rdataset);
+ }
+
+ if (result != ISC_R_NOMORE) {
+ return (result);
+ }
+
+ /*
+ * Update the ctx state to reflect what we just printed.
+ * This is done last, only when we are sure we will return
+ * success, because this function may be called multiple
+ * times with increasing buffer sizes until it succeeds,
+ * and failed attempts must not update the state prematurely.
+ */
+ ctx->class_printed = true;
+ ctx->current_ttl = current_ttl;
+ ctx->current_ttl_valid = current_ttl_valid;
+
+ return (ISC_R_SUCCESS);
+}
+
+/*
+ * Print the name, type, and class of an empty rdataset,
+ * such as those used to represent the question section
+ * of a DNS message.
+ */
+static isc_result_t
+question_totext(dns_rdataset_t *rdataset, const dns_name_t *owner_name,
+ dns_totext_ctx_t *ctx, bool omit_final_dot,
+ isc_buffer_t *target) {
+ unsigned int column;
+ isc_result_t result;
+ isc_region_t r;
+
+ REQUIRE(DNS_RDATASET_VALID(rdataset));
+ result = dns_rdataset_first(rdataset);
+ REQUIRE(result == ISC_R_NOMORE);
+
+ column = 0;
+
+ /* Owner name */
+ {
+ unsigned int name_start = target->used;
+ RETERR(dns_name_totext(owner_name, omit_final_dot, target));
+ column += target->used - name_start;
+ }
+
+ /* Class */
+ {
+ unsigned int class_start;
+ INDENT_TO(class_column);
+ class_start = target->used;
+ if ((ctx->style.flags & DNS_STYLEFLAG_UNKNOWNFORMAT) != 0) {
+ result = dns_rdataclass_tounknowntext(rdataset->rdclass,
+ target);
+ } else {
+ result = dns_rdataclass_totext(rdataset->rdclass,
+ target);
+ }
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+ column += (target->used - class_start);
+ }
+
+ /* Type */
+ {
+ unsigned int type_start;
+ INDENT_TO(type_column);
+ type_start = target->used;
+ if ((ctx->style.flags & DNS_STYLEFLAG_UNKNOWNFORMAT) != 0) {
+ result = dns_rdatatype_tounknowntext(rdataset->type,
+ target);
+ } else {
+ result = dns_rdatatype_totext(rdataset->type, target);
+ }
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+ column += (target->used - type_start);
+ }
+
+ isc_buffer_availableregion(target, &r);
+ if (r.length < 1) {
+ return (ISC_R_NOSPACE);
+ }
+ r.base[0] = '\n';
+ isc_buffer_add(target, 1);
+
+ return (ISC_R_SUCCESS);
+}
+
+isc_result_t
+dns_rdataset_totext(dns_rdataset_t *rdataset, const dns_name_t *owner_name,
+ bool omit_final_dot, bool question, isc_buffer_t *target) {
+ dns_totext_ctx_t ctx;
+ isc_result_t result;
+ result = totext_ctx_init(&dns_master_style_debug, NULL, &ctx);
+ if (result != ISC_R_SUCCESS) {
+ UNEXPECTED_ERROR("could not set master file style");
+ return (ISC_R_UNEXPECTED);
+ }
+
+ /*
+ * The caller might want to give us an empty owner
+ * name (e.g. if they are outputting into a master
+ * file and this rdataset has the same name as the
+ * previous one.)
+ */
+ if (dns_name_countlabels(owner_name) == 0) {
+ owner_name = NULL;
+ }
+
+ if (question) {
+ return (question_totext(rdataset, owner_name, &ctx,
+ omit_final_dot, target));
+ } else {
+ return (rdataset_totext(rdataset, owner_name, &ctx,
+ omit_final_dot, target));
+ }
+}
+
+isc_result_t
+dns_master_rdatasettotext(const dns_name_t *owner_name,
+ dns_rdataset_t *rdataset,
+ const dns_master_style_t *style, dns_indent_t *indent,
+ isc_buffer_t *target) {
+ dns_totext_ctx_t ctx;
+ isc_result_t result;
+ result = totext_ctx_init(style, indent, &ctx);
+ if (result != ISC_R_SUCCESS) {
+ UNEXPECTED_ERROR("could not set master file style");
+ return (ISC_R_UNEXPECTED);
+ }
+
+ return (rdataset_totext(rdataset, owner_name, &ctx, false, target));
+}
+
+isc_result_t
+dns_master_questiontotext(const dns_name_t *owner_name,
+ dns_rdataset_t *rdataset,
+ const dns_master_style_t *style,
+ isc_buffer_t *target) {
+ dns_totext_ctx_t ctx;
+ isc_result_t result;
+ result = totext_ctx_init(style, NULL, &ctx);
+ if (result != ISC_R_SUCCESS) {
+ UNEXPECTED_ERROR("could not set master file style");
+ return (ISC_R_UNEXPECTED);
+ }
+
+ return (question_totext(rdataset, owner_name, &ctx, false, target));
+}
+
+/*
+ * Print an rdataset. 'buffer' is a scratch buffer, which must have been
+ * dynamically allocated by the caller. It must be large enough to
+ * hold the result from dns_ttl_totext(). If more than that is needed,
+ * the buffer will be grown automatically.
+ */
+
+static isc_result_t
+dump_rdataset(isc_mem_t *mctx, const dns_name_t *name, dns_rdataset_t *rdataset,
+ dns_totext_ctx_t *ctx, isc_buffer_t *buffer, FILE *f) {
+ isc_region_t r;
+ isc_result_t result;
+
+ REQUIRE(buffer->length > 0);
+
+ /*
+ * Output a $TTL directive if needed.
+ */
+
+ if ((ctx->style.flags & DNS_STYLEFLAG_TTL) != 0) {
+ if (!ctx->current_ttl_valid ||
+ ctx->current_ttl != rdataset->ttl)
+ {
+ if ((ctx->style.flags & DNS_STYLEFLAG_COMMENT) != 0) {
+ isc_buffer_clear(buffer);
+ result = dns_ttl_totext(rdataset->ttl, true,
+ true, buffer);
+ INSIST(result == ISC_R_SUCCESS);
+ isc_buffer_usedregion(buffer, &r);
+ fprintf(f, "$TTL %u\t; %.*s\n", rdataset->ttl,
+ (int)r.length, (char *)r.base);
+ } else {
+ fprintf(f, "$TTL %u\n", rdataset->ttl);
+ }
+ ctx->current_ttl = rdataset->ttl;
+ ctx->current_ttl_valid = true;
+ }
+ }
+
+ isc_buffer_clear(buffer);
+
+ /*
+ * Generate the text representation of the rdataset into
+ * the buffer. If the buffer is too small, grow it.
+ */
+ for (;;) {
+ int newlength;
+ void *newmem;
+ result = rdataset_totext(rdataset, name, ctx, false, buffer);
+ if (result != ISC_R_NOSPACE) {
+ break;
+ }
+
+ newlength = buffer->length * 2;
+ newmem = isc_mem_get(mctx, newlength);
+ isc_mem_put(mctx, buffer->base, buffer->length);
+ isc_buffer_init(buffer, newmem, newlength);
+ }
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+
+ /*
+ * Write the buffer contents to the master file.
+ */
+ isc_buffer_usedregion(buffer, &r);
+ result = isc_stdio_write(r.base, 1, (size_t)r.length, f, NULL);
+
+ if (result != ISC_R_SUCCESS) {
+ UNEXPECTED_ERROR("master file write failed: %s",
+ isc_result_totext(result));
+ return (result);
+ }
+
+ return (ISC_R_SUCCESS);
+}
+
+/*
+ * Define the order in which rdatasets should be printed in zone
+ * files. We will print SOA and NS records before others, SIGs
+ * immediately following the things they sign, and order everything
+ * else by RR number. This is all just for aesthetics and
+ * compatibility with buggy software that expects the SOA to be first;
+ * the DNS specifications allow any order.
+ */
+
+static int
+dump_order(const dns_rdataset_t *rds) {
+ int t;
+ int sig;
+ if (rds->type == dns_rdatatype_rrsig) {
+ t = rds->covers;
+ sig = 1;
+ } else {
+ t = rds->type;
+ sig = 0;
+ }
+ switch (t) {
+ case dns_rdatatype_soa:
+ t = 0;
+ break;
+ case dns_rdatatype_ns:
+ t = 1;
+ break;
+ default:
+ t += 2;
+ break;
+ }
+ return ((t << 1) + sig);
+}
+
+static int
+dump_order_compare(const void *a, const void *b) {
+ return (dump_order(*((const dns_rdataset_t *const *)a)) -
+ dump_order(*((const dns_rdataset_t *const *)b)));
+}
+
+/*
+ * Dump all the rdatasets of a domain name to a master file. We make
+ * a "best effort" attempt to sort the RRsets in a nice order, but if
+ * there are more than MAXSORT RRsets, we punt and only sort them in
+ * groups of MAXSORT. This is not expected to ever happen in practice
+ * since much less than 64 RR types have been registered with the
+ * IANA, so far, and the output will be correct (though not
+ * aesthetically pleasing) even if it does happen.
+ */
+
+#define MAXSORT 64
+
+static isc_result_t
+dump_rdatasets_text(isc_mem_t *mctx, const dns_name_t *name,
+ dns_rdatasetiter_t *rdsiter, dns_totext_ctx_t *ctx,
+ isc_buffer_t *buffer, FILE *f) {
+ isc_result_t itresult, dumpresult;
+ isc_region_t r;
+ dns_rdataset_t rdatasets[MAXSORT];
+ dns_rdataset_t *sorted[MAXSORT];
+ int i, n;
+
+ itresult = dns_rdatasetiter_first(rdsiter);
+ dumpresult = ISC_R_SUCCESS;
+
+ if (itresult == ISC_R_SUCCESS && ctx->neworigin != NULL) {
+ isc_buffer_clear(buffer);
+ itresult = dns_name_totext(ctx->neworigin, false, buffer);
+ RUNTIME_CHECK(itresult == ISC_R_SUCCESS);
+ isc_buffer_usedregion(buffer, &r);
+ fprintf(f, "$ORIGIN %.*s\n", (int)r.length, (char *)r.base);
+ ctx->neworigin = NULL;
+ }
+
+again:
+ for (i = 0; itresult == ISC_R_SUCCESS && i < MAXSORT;
+ itresult = dns_rdatasetiter_next(rdsiter), i++)
+ {
+ dns_rdataset_init(&rdatasets[i]);
+ dns_rdatasetiter_current(rdsiter, &rdatasets[i]);
+ sorted[i] = &rdatasets[i];
+ }
+ n = i;
+ INSIST(n <= MAXSORT);
+
+ qsort(sorted, n, sizeof(sorted[0]), dump_order_compare);
+
+ for (i = 0; i < n; i++) {
+ dns_rdataset_t *rds = sorted[i];
+
+ if (ANCIENT(rds) &&
+ (ctx->style.flags & DNS_STYLEFLAG_EXPIRED) == 0)
+ {
+ /* Omit expired entries */
+ dns_rdataset_disassociate(rds);
+ continue;
+ }
+
+ if ((ctx->style.flags & DNS_STYLEFLAG_TRUST) != 0) {
+ if ((ctx->style.flags & DNS_STYLEFLAG_INDENT) != 0 ||
+ (ctx->style.flags & DNS_STYLEFLAG_YAML) != 0)
+ {
+ unsigned int j;
+ for (j = 0; j < ctx->indent.count; j++) {
+ fprintf(f, "%s", ctx->indent.string);
+ }
+ }
+ fprintf(f, "; %s\n", dns_trust_totext(rds->trust));
+ }
+ if (((rds->attributes & DNS_RDATASETATTR_NEGATIVE) != 0) &&
+ (ctx->style.flags & DNS_STYLEFLAG_NCACHE) == 0)
+ {
+ /* Omit negative cache entries */
+ } else {
+ isc_result_t result;
+ if (STALE(rds)) {
+ fprintf(f, "; stale\n");
+ } else if (ANCIENT(rds)) {
+ isc_buffer_t b;
+ char buf[sizeof("YYYYMMDDHHMMSS")];
+ memset(buf, 0, sizeof(buf));
+ isc_buffer_init(&b, buf, sizeof(buf) - 1);
+ dns_time64_totext((uint64_t)rds->ttl, &b);
+ fprintf(f,
+ "; expired since %s "
+ "(awaiting cleanup)\n",
+ buf);
+ }
+ result = dump_rdataset(mctx, name, rds, ctx, buffer, f);
+ if (result != ISC_R_SUCCESS) {
+ dumpresult = result;
+ }
+ if ((ctx->style.flags & DNS_STYLEFLAG_OMIT_OWNER) != 0)
+ {
+ name = NULL;
+ }
+ }
+ if (((ctx->style.flags & DNS_STYLEFLAG_RESIGN) != 0) &&
+ ((rds->attributes & DNS_RDATASETATTR_RESIGN) != 0))
+ {
+ isc_buffer_t b;
+ char buf[sizeof("YYYYMMDDHHMMSS")];
+ memset(buf, 0, sizeof(buf));
+ isc_buffer_init(&b, buf, sizeof(buf) - 1);
+ dns_time64_totext((uint64_t)rds->resign, &b);
+ if ((ctx->style.flags & DNS_STYLEFLAG_INDENT) != 0 ||
+ (ctx->style.flags & DNS_STYLEFLAG_YAML) != 0)
+ {
+ unsigned int j;
+ for (j = 0; j < ctx->indent.count; j++) {
+ fprintf(f, "%s", ctx->indent.string);
+ }
+ }
+ fprintf(f, "; resign=%s\n", buf);
+ }
+ dns_rdataset_disassociate(rds);
+ }
+
+ if (dumpresult != ISC_R_SUCCESS) {
+ return (dumpresult);
+ }
+
+ /*
+ * If we got more data than could be sorted at once,
+ * go handle the rest.
+ */
+ if (itresult == ISC_R_SUCCESS) {
+ goto again;
+ }
+
+ if (itresult == ISC_R_NOMORE) {
+ itresult = ISC_R_SUCCESS;
+ }
+
+ return (itresult);
+}
+
+/*
+ * Dump given RRsets in the "raw" format.
+ */
+static isc_result_t
+dump_rdataset_raw(isc_mem_t *mctx, const dns_name_t *name,
+ dns_rdataset_t *rdataset, isc_buffer_t *buffer, FILE *f) {
+ isc_result_t result;
+ uint32_t totallen;
+ uint16_t dlen;
+ isc_region_t r, r_hdr;
+
+ REQUIRE(buffer->length > 0);
+ REQUIRE(DNS_RDATASET_VALID(rdataset));
+
+ rdataset->attributes |= DNS_RDATASETATTR_LOADORDER;
+restart:
+ totallen = 0;
+ result = dns_rdataset_first(rdataset);
+ REQUIRE(result == ISC_R_SUCCESS);
+
+ isc_buffer_clear(buffer);
+
+ /*
+ * Common header and owner name (length followed by name)
+ * These fields should be in a moderate length, so we assume we
+ * can store all of them in the initial buffer.
+ */
+ isc_buffer_availableregion(buffer, &r_hdr);
+ INSIST(r_hdr.length >= sizeof(dns_masterrawrdataset_t));
+ isc_buffer_putuint32(buffer, totallen); /* XXX: leave space */
+ isc_buffer_putuint16(buffer, rdataset->rdclass); /* 16-bit class */
+ isc_buffer_putuint16(buffer, rdataset->type); /* 16-bit type */
+ isc_buffer_putuint16(buffer, rdataset->covers); /* same as type */
+ isc_buffer_putuint32(buffer, rdataset->ttl); /* 32-bit TTL */
+ isc_buffer_putuint32(buffer, dns_rdataset_count(rdataset));
+ totallen = isc_buffer_usedlength(buffer);
+ INSIST(totallen <= sizeof(dns_masterrawrdataset_t));
+
+ dns_name_toregion(name, &r);
+ INSIST(isc_buffer_availablelength(buffer) >= (sizeof(dlen) + r.length));
+ dlen = (uint16_t)r.length;
+ isc_buffer_putuint16(buffer, dlen);
+ isc_buffer_copyregion(buffer, &r);
+ totallen += sizeof(dlen) + r.length;
+
+ do {
+ dns_rdata_t rdata = DNS_RDATA_INIT;
+
+ dns_rdataset_current(rdataset, &rdata);
+ dns_rdata_toregion(&rdata, &r);
+ INSIST(r.length <= 0xffffU);
+ dlen = (uint16_t)r.length;
+
+ /*
+ * Copy the rdata into the buffer. If the buffer is too small,
+ * grow it. This should be rare, so we'll simply restart the
+ * entire procedure (or should we copy the old data and
+ * continue?).
+ */
+ if (isc_buffer_availablelength(buffer) <
+ sizeof(dlen) + r.length)
+ {
+ int newlength;
+ void *newmem;
+
+ newlength = buffer->length * 2;
+ newmem = isc_mem_get(mctx, newlength);
+ isc_mem_put(mctx, buffer->base, buffer->length);
+ isc_buffer_init(buffer, newmem, newlength);
+ goto restart;
+ }
+ isc_buffer_putuint16(buffer, dlen);
+ isc_buffer_copyregion(buffer, &r);
+ totallen += sizeof(dlen) + r.length;
+
+ result = dns_rdataset_next(rdataset);
+ } while (result == ISC_R_SUCCESS);
+
+ if (result != ISC_R_NOMORE) {
+ return (result);
+ }
+
+ /*
+ * Fill in the total length field.
+ * XXX: this is a bit tricky. Since we have already "used" the space
+ * for the total length in the buffer, we first remember the entire
+ * buffer length in the region, "rewind", and then write the value.
+ */
+ isc_buffer_usedregion(buffer, &r);
+ isc_buffer_clear(buffer);
+ isc_buffer_putuint32(buffer, totallen);
+ INSIST(isc_buffer_usedlength(buffer) < totallen);
+
+ /*
+ * Write the buffer contents to the raw master file.
+ */
+ result = isc_stdio_write(r.base, 1, (size_t)r.length, f, NULL);
+
+ if (result != ISC_R_SUCCESS) {
+ UNEXPECTED_ERROR("raw master file write failed: %s",
+ isc_result_totext(result));
+ return (result);
+ }
+
+ return (result);
+}
+
+static isc_result_t
+dump_rdatasets_raw(isc_mem_t *mctx, const dns_name_t *owner_name,
+ dns_rdatasetiter_t *rdsiter, dns_totext_ctx_t *ctx,
+ isc_buffer_t *buffer, FILE *f) {
+ isc_result_t result;
+ dns_rdataset_t rdataset;
+ dns_fixedname_t fixed;
+ dns_name_t *name;
+
+ name = dns_fixedname_initname(&fixed);
+ dns_name_copy(owner_name, name);
+ for (result = dns_rdatasetiter_first(rdsiter); result == ISC_R_SUCCESS;
+ result = dns_rdatasetiter_next(rdsiter))
+ {
+ dns_rdataset_init(&rdataset);
+ dns_rdatasetiter_current(rdsiter, &rdataset);
+
+ dns_rdataset_getownercase(&rdataset, name);
+
+ if (((rdataset.attributes & DNS_RDATASETATTR_NEGATIVE) != 0) &&
+ (ctx->style.flags & DNS_STYLEFLAG_NCACHE) == 0)
+ {
+ /* Omit negative cache entries */
+ } else {
+ result = dump_rdataset_raw(mctx, name, &rdataset,
+ buffer, f);
+ }
+ dns_rdataset_disassociate(&rdataset);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+ }
+
+ if (result == ISC_R_NOMORE) {
+ result = ISC_R_SUCCESS;
+ }
+
+ return (result);
+}
+
+/*
+ * Initial size of text conversion buffer. The buffer is used
+ * for several purposes: converting origin names, rdatasets,
+ * $DATE timestamps, and comment strings for $TTL directives.
+ *
+ * When converting rdatasets, it is dynamically resized, but
+ * when converting origins, timestamps, etc it is not. Therefore,
+ * the initial size must large enough to hold the longest possible
+ * text representation of any domain name (for $ORIGIN).
+ */
+static const int initial_buffer_length = 1200;
+
+static isc_result_t
+dumptostream(dns_dumpctx_t *dctx);
+
+static void
+dumpctx_destroy(dns_dumpctx_t *dctx) {
+ dctx->magic = 0;
+ isc_mutex_destroy(&dctx->lock);
+ dns_dbiterator_destroy(&dctx->dbiter);
+ if (dctx->version != NULL) {
+ dns_db_closeversion(dctx->db, &dctx->version, false);
+ }
+ dns_db_detach(&dctx->db);
+ if (dctx->task != NULL) {
+ isc_task_detach(&dctx->task);
+ }
+ if (dctx->file != NULL) {
+ isc_mem_free(dctx->mctx, dctx->file);
+ }
+ if (dctx->tmpfile != NULL) {
+ isc_mem_free(dctx->mctx, dctx->tmpfile);
+ }
+ isc_mem_putanddetach(&dctx->mctx, dctx, sizeof(*dctx));
+}
+
+void
+dns_dumpctx_attach(dns_dumpctx_t *source, dns_dumpctx_t **target) {
+ REQUIRE(DNS_DCTX_VALID(source));
+ REQUIRE(target != NULL && *target == NULL);
+
+ isc_refcount_increment(&source->references);
+
+ *target = source;
+}
+
+void
+dns_dumpctx_detach(dns_dumpctx_t **dctxp) {
+ dns_dumpctx_t *dctx;
+
+ REQUIRE(dctxp != NULL);
+ dctx = *dctxp;
+ *dctxp = NULL;
+ REQUIRE(DNS_DCTX_VALID(dctx));
+
+ if (isc_refcount_decrement(&dctx->references) == 1) {
+ dumpctx_destroy(dctx);
+ }
+}
+
+dns_dbversion_t *
+dns_dumpctx_version(dns_dumpctx_t *dctx) {
+ REQUIRE(DNS_DCTX_VALID(dctx));
+ return (dctx->version);
+}
+
+dns_db_t *
+dns_dumpctx_db(dns_dumpctx_t *dctx) {
+ REQUIRE(DNS_DCTX_VALID(dctx));
+ return (dctx->db);
+}
+
+void
+dns_dumpctx_cancel(dns_dumpctx_t *dctx) {
+ REQUIRE(DNS_DCTX_VALID(dctx));
+
+ atomic_store_release(&dctx->canceled, true);
+}
+
+static isc_result_t
+flushandsync(FILE *f, isc_result_t result, const char *temp) {
+ bool logit = (result == ISC_R_SUCCESS);
+
+ if (result == ISC_R_SUCCESS) {
+ result = isc_stdio_flush(f);
+ }
+ if (result != ISC_R_SUCCESS && logit) {
+ if (temp != NULL) {
+ isc_log_write(dns_lctx, ISC_LOGCATEGORY_GENERAL,
+ DNS_LOGMODULE_MASTERDUMP, ISC_LOG_ERROR,
+ "dumping to master file: %s: flush: %s",
+ temp, isc_result_totext(result));
+ } else {
+ isc_log_write(dns_lctx, ISC_LOGCATEGORY_GENERAL,
+ DNS_LOGMODULE_MASTERDUMP, ISC_LOG_ERROR,
+ "dumping to stream: flush: %s",
+ isc_result_totext(result));
+ }
+ logit = false;
+ }
+
+ if (result == ISC_R_SUCCESS) {
+ result = isc_stdio_sync(f);
+ }
+ if (result != ISC_R_SUCCESS && logit) {
+ if (temp != NULL) {
+ isc_log_write(dns_lctx, ISC_LOGCATEGORY_GENERAL,
+ DNS_LOGMODULE_MASTERDUMP, ISC_LOG_ERROR,
+ "dumping to master file: %s: fsync: %s",
+ temp, isc_result_totext(result));
+ } else {
+ isc_log_write(dns_lctx, ISC_LOGCATEGORY_GENERAL,
+ DNS_LOGMODULE_MASTERDUMP, ISC_LOG_ERROR,
+ "dumping to stream: fsync: %s",
+ isc_result_totext(result));
+ }
+ }
+ return (result);
+}
+
+static isc_result_t
+closeandrename(FILE *f, isc_result_t result, const char *temp,
+ const char *file) {
+ isc_result_t tresult;
+ bool logit = (result == ISC_R_SUCCESS);
+
+ result = flushandsync(f, result, temp);
+ if (result != ISC_R_SUCCESS) {
+ logit = false;
+ }
+
+ tresult = isc_stdio_close(f);
+ if (result == ISC_R_SUCCESS) {
+ result = tresult;
+ }
+ if (result != ISC_R_SUCCESS && logit) {
+ isc_log_write(dns_lctx, ISC_LOGCATEGORY_GENERAL,
+ DNS_LOGMODULE_MASTERDUMP, ISC_LOG_ERROR,
+ "dumping master file: %s: fclose: %s", temp,
+ isc_result_totext(result));
+ logit = false;
+ }
+ if (result == ISC_R_SUCCESS) {
+ result = isc_file_rename(temp, file);
+ } else {
+ (void)isc_file_remove(temp);
+ }
+ if (result != ISC_R_SUCCESS && logit) {
+ isc_log_write(dns_lctx, ISC_LOGCATEGORY_GENERAL,
+ DNS_LOGMODULE_MASTERDUMP, ISC_LOG_ERROR,
+ "dumping master file: rename: %s: %s", file,
+ isc_result_totext(result));
+ }
+ return (result);
+}
+
+/*
+ * This will run in a libuv threadpool thread.
+ */
+static void
+master_dump_cb(void *data) {
+ isc_result_t result = ISC_R_UNSET;
+ dns_dumpctx_t *dctx = data;
+ REQUIRE(DNS_DCTX_VALID(dctx));
+
+ if (atomic_load_acquire(&dctx->canceled)) {
+ result = ISC_R_CANCELED;
+ } else {
+ result = dumptostream(dctx);
+ }
+
+ if (dctx->file != NULL) {
+ isc_result_t tresult = ISC_R_UNSET;
+ tresult = closeandrename(dctx->f, result, dctx->tmpfile,
+ dctx->file);
+ if (tresult != ISC_R_SUCCESS && result == ISC_R_SUCCESS) {
+ result = tresult;
+ }
+ } else {
+ result = flushandsync(dctx->f, result, NULL);
+ }
+
+ dctx->result = result;
+}
+
+/*
+ * This will run in a network/task manager thread when the dump is complete.
+ */
+static void
+master_dump_done_cb(void *data, isc_result_t result) {
+ dns_dumpctx_t *dctx = data;
+
+ if (result == ISC_R_SUCCESS && dctx->result != ISC_R_SUCCESS) {
+ result = dctx->result;
+ }
+
+ (dctx->done)(dctx->done_arg, result);
+ dns_dumpctx_detach(&dctx);
+}
+
+/*
+ * This must be run from a network/task manager thread.
+ */
+static void
+setup_dump(isc_task_t *task, isc_event_t *event) {
+ dns_dumpctx_t *dctx = NULL;
+
+ REQUIRE(isc_nm_tid() >= 0);
+ REQUIRE(event != NULL);
+
+ dctx = event->ev_arg;
+
+ REQUIRE(DNS_DCTX_VALID(dctx));
+
+ isc_nm_work_offload(isc_task_getnetmgr(task), master_dump_cb,
+ master_dump_done_cb, dctx);
+
+ isc_event_free(&event);
+}
+
+static isc_result_t
+task_send(dns_dumpctx_t *dctx) {
+ isc_event_t *event;
+
+ event = isc_event_allocate(dctx->mctx, NULL, DNS_EVENT_DUMPQUANTUM,
+ setup_dump, dctx, sizeof(*event));
+ isc_task_send(dctx->task, &event);
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+dumpctx_create(isc_mem_t *mctx, dns_db_t *db, dns_dbversion_t *version,
+ const dns_master_style_t *style, FILE *f, dns_dumpctx_t **dctxp,
+ dns_masterformat_t format, dns_masterrawheader_t *header) {
+ dns_dumpctx_t *dctx;
+ isc_result_t result;
+ unsigned int options;
+
+ dctx = isc_mem_get(mctx, sizeof(*dctx));
+
+ dctx->mctx = NULL;
+ dctx->f = f;
+ dctx->dbiter = NULL;
+ dctx->db = NULL;
+ dctx->version = NULL;
+ dctx->done = NULL;
+ dctx->done_arg = NULL;
+ dctx->task = NULL;
+ atomic_init(&dctx->canceled, false);
+ dctx->file = NULL;
+ dctx->tmpfile = NULL;
+ dctx->format = format;
+ if (header == NULL) {
+ dns_master_initrawheader(&dctx->header);
+ } else {
+ dctx->header = *header;
+ }
+
+ switch (format) {
+ case dns_masterformat_text:
+ dctx->dumpsets = dump_rdatasets_text;
+ break;
+ case dns_masterformat_raw:
+ dctx->dumpsets = dump_rdatasets_raw;
+ break;
+ default:
+ UNREACHABLE();
+ }
+
+ result = totext_ctx_init(style, NULL, &dctx->tctx);
+ if (result != ISC_R_SUCCESS) {
+ UNEXPECTED_ERROR("could not set master file style");
+ goto cleanup;
+ }
+
+ isc_stdtime_get(&dctx->now);
+ dns_db_attach(db, &dctx->db);
+
+ dctx->do_date = dns_db_iscache(dctx->db);
+ if (dctx->do_date) {
+ (void)dns_db_getservestalettl(dctx->db,
+ &dctx->tctx.serve_stale_ttl);
+ }
+
+ if (dctx->format == dns_masterformat_text &&
+ (dctx->tctx.style.flags & DNS_STYLEFLAG_REL_OWNER) != 0)
+ {
+ options = DNS_DB_RELATIVENAMES;
+ } else {
+ options = 0;
+ }
+ result = dns_db_createiterator(dctx->db, options, &dctx->dbiter);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+
+ isc_mutex_init(&dctx->lock);
+
+ if (version != NULL) {
+ dns_db_attachversion(dctx->db, version, &dctx->version);
+ } else if (!dns_db_iscache(db)) {
+ dns_db_currentversion(dctx->db, &dctx->version);
+ }
+ isc_mem_attach(mctx, &dctx->mctx);
+
+ isc_refcount_init(&dctx->references, 1);
+ dctx->magic = DNS_DCTX_MAGIC;
+ *dctxp = dctx;
+ return (ISC_R_SUCCESS);
+
+cleanup:
+ if (dctx->dbiter != NULL) {
+ dns_dbiterator_destroy(&dctx->dbiter);
+ }
+ if (dctx->db != NULL) {
+ dns_db_detach(&dctx->db);
+ }
+ isc_mem_put(mctx, dctx, sizeof(*dctx));
+ return (result);
+}
+
+static isc_result_t
+writeheader(dns_dumpctx_t *dctx) {
+ isc_result_t result = ISC_R_SUCCESS;
+ isc_buffer_t buffer;
+ char *bufmem;
+ isc_region_t r;
+ dns_masterrawheader_t rawheader;
+ uint32_t rawversion, now32;
+
+ bufmem = isc_mem_get(dctx->mctx, initial_buffer_length);
+
+ isc_buffer_init(&buffer, bufmem, initial_buffer_length);
+
+ switch (dctx->format) {
+ case dns_masterformat_text:
+ /*
+ * If the database has cache semantics, output an
+ * RFC2540 $DATE directive so that the TTLs can be
+ * adjusted when it is reloaded. For zones it is not
+ * really needed, and it would make the file
+ * incompatible with pre-RFC2540 software, so we omit
+ * it in the zone case.
+ */
+ if (dctx->do_date) {
+ fprintf(dctx->f, "; using a %u second stale ttl\n",
+ dctx->tctx.serve_stale_ttl);
+ result = dns_time32_totext(dctx->now, &buffer);
+ RUNTIME_CHECK(result == ISC_R_SUCCESS);
+ isc_buffer_usedregion(&buffer, &r);
+ fprintf(dctx->f, "$DATE %.*s\n", (int)r.length,
+ (char *)r.base);
+ }
+ break;
+ case dns_masterformat_raw:
+ r.base = (unsigned char *)&rawheader;
+ r.length = sizeof(rawheader);
+ isc_buffer_region(&buffer, &r);
+ now32 = dctx->now;
+ rawversion = 1;
+ if ((dctx->header.flags & DNS_MASTERRAW_COMPAT) != 0) {
+ rawversion = 0;
+ }
+
+ isc_buffer_putuint32(&buffer, dctx->format);
+ isc_buffer_putuint32(&buffer, rawversion);
+ isc_buffer_putuint32(&buffer, now32);
+
+ if (rawversion == 1) {
+ isc_buffer_putuint32(&buffer, dctx->header.flags);
+ isc_buffer_putuint32(&buffer,
+ dctx->header.sourceserial);
+ isc_buffer_putuint32(&buffer, dctx->header.lastxfrin);
+ }
+
+ INSIST(isc_buffer_usedlength(&buffer) <= sizeof(rawheader));
+ result = isc_stdio_write(buffer.base, 1,
+ isc_buffer_usedlength(&buffer),
+ dctx->f, NULL);
+ if (result != ISC_R_SUCCESS) {
+ break;
+ }
+
+ break;
+ default:
+ UNREACHABLE();
+ }
+
+ isc_mem_put(dctx->mctx, buffer.base, buffer.length);
+ return (result);
+}
+
+static isc_result_t
+dumptostream(dns_dumpctx_t *dctx) {
+ isc_result_t result = ISC_R_SUCCESS;
+ isc_buffer_t buffer;
+ char *bufmem;
+ dns_name_t *name;
+ dns_fixedname_t fixname;
+ unsigned int options = DNS_DB_STALEOK;
+
+ if ((dctx->tctx.style.flags & DNS_STYLEFLAG_EXPIRED) != 0) {
+ options |= DNS_DB_EXPIREDOK;
+ }
+
+ bufmem = isc_mem_get(dctx->mctx, initial_buffer_length);
+
+ isc_buffer_init(&buffer, bufmem, initial_buffer_length);
+
+ name = dns_fixedname_initname(&fixname);
+
+ CHECK(writeheader(dctx));
+
+ result = dns_dbiterator_first(dctx->dbiter);
+ if (result != ISC_R_SUCCESS && result != ISC_R_NOMORE) {
+ goto cleanup;
+ }
+
+ while (result == ISC_R_SUCCESS) {
+ dns_rdatasetiter_t *rdsiter = NULL;
+ dns_dbnode_t *node = NULL;
+
+ result = dns_dbiterator_current(dctx->dbiter, &node, name);
+ if (result != ISC_R_SUCCESS && result != DNS_R_NEWORIGIN) {
+ break;
+ }
+ if (result == DNS_R_NEWORIGIN) {
+ dns_name_t *origin =
+ dns_fixedname_name(&dctx->tctx.origin_fixname);
+ result = dns_dbiterator_origin(dctx->dbiter, origin);
+ RUNTIME_CHECK(result == ISC_R_SUCCESS);
+ if ((dctx->tctx.style.flags & DNS_STYLEFLAG_REL_DATA) !=
+ 0)
+ {
+ dctx->tctx.origin = origin;
+ }
+ dctx->tctx.neworigin = origin;
+ }
+
+ result = dns_dbiterator_pause(dctx->dbiter);
+ RUNTIME_CHECK(result == ISC_R_SUCCESS);
+
+ result = dns_db_allrdatasets(dctx->db, node, dctx->version,
+ options, dctx->now, &rdsiter);
+ if (result != ISC_R_SUCCESS) {
+ dns_db_detachnode(dctx->db, &node);
+ goto cleanup;
+ }
+ result = (dctx->dumpsets)(dctx->mctx, name, rdsiter,
+ &dctx->tctx, &buffer, dctx->f);
+ dns_rdatasetiter_destroy(&rdsiter);
+ if (result != ISC_R_SUCCESS) {
+ dns_db_detachnode(dctx->db, &node);
+ goto cleanup;
+ }
+ dns_db_detachnode(dctx->db, &node);
+ result = dns_dbiterator_next(dctx->dbiter);
+ }
+
+ if (result == ISC_R_NOMORE) {
+ result = ISC_R_SUCCESS;
+ }
+cleanup:
+ RUNTIME_CHECK(dns_dbiterator_pause(dctx->dbiter) == ISC_R_SUCCESS);
+ isc_mem_put(dctx->mctx, buffer.base, buffer.length);
+ return (result);
+}
+
+isc_result_t
+dns_master_dumptostreamasync(isc_mem_t *mctx, dns_db_t *db,
+ dns_dbversion_t *version,
+ const dns_master_style_t *style, FILE *f,
+ isc_task_t *task, dns_dumpdonefunc_t done,
+ void *done_arg, dns_dumpctx_t **dctxp) {
+ dns_dumpctx_t *dctx = NULL;
+ isc_result_t result;
+
+ REQUIRE(task != NULL);
+ REQUIRE(f != NULL);
+ REQUIRE(done != NULL);
+
+ result = dumpctx_create(mctx, db, version, style, f, &dctx,
+ dns_masterformat_text, NULL);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+ isc_task_attach(task, &dctx->task);
+ dctx->done = done;
+ dctx->done_arg = done_arg;
+
+ result = task_send(dctx);
+ if (result == ISC_R_SUCCESS) {
+ dns_dumpctx_attach(dctx, dctxp);
+ return (DNS_R_CONTINUE);
+ }
+
+ dns_dumpctx_detach(&dctx);
+ return (result);
+}
+
+isc_result_t
+dns_master_dumptostream(isc_mem_t *mctx, dns_db_t *db, dns_dbversion_t *version,
+ const dns_master_style_t *style,
+ dns_masterformat_t format,
+ dns_masterrawheader_t *header, FILE *f) {
+ dns_dumpctx_t *dctx = NULL;
+ isc_result_t result;
+
+ result = dumpctx_create(mctx, db, version, style, f, &dctx, format,
+ header);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+
+ result = dumptostream(dctx);
+ INSIST(result != DNS_R_CONTINUE);
+ dns_dumpctx_detach(&dctx);
+
+ result = flushandsync(f, result, NULL);
+ return (result);
+}
+
+static isc_result_t
+opentmp(isc_mem_t *mctx, dns_masterformat_t format, const char *file,
+ char **tempp, FILE **fp) {
+ FILE *f = NULL;
+ isc_result_t result;
+ char *tempname = NULL;
+ int tempnamelen;
+
+ tempnamelen = strlen(file) + 20;
+ tempname = isc_mem_allocate(mctx, tempnamelen);
+
+ result = isc_file_mktemplate(file, tempname, tempnamelen);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+
+ if (format == dns_masterformat_text) {
+ result = isc_file_openunique(tempname, &f);
+ } else {
+ result = isc_file_bopenunique(tempname, &f);
+ }
+ if (result != ISC_R_SUCCESS) {
+ isc_log_write(dns_lctx, ISC_LOGCATEGORY_GENERAL,
+ DNS_LOGMODULE_MASTERDUMP, ISC_LOG_ERROR,
+ "dumping master file: %s: open: %s", tempname,
+ isc_result_totext(result));
+ goto cleanup;
+ }
+
+#if defined(POSIX_FADV_DONTNEED)
+ posix_fadvise(fileno(f), 0, 0, POSIX_FADV_DONTNEED);
+#endif
+
+ *tempp = tempname;
+ *fp = f;
+ return (ISC_R_SUCCESS);
+
+cleanup:
+ isc_mem_free(mctx, tempname);
+ return (result);
+}
+
+isc_result_t
+dns_master_dumpasync(isc_mem_t *mctx, dns_db_t *db, dns_dbversion_t *version,
+ const dns_master_style_t *style, const char *filename,
+ isc_task_t *task, dns_dumpdonefunc_t done, void *done_arg,
+ dns_dumpctx_t **dctxp, dns_masterformat_t format,
+ dns_masterrawheader_t *header) {
+ FILE *f = NULL;
+ isc_result_t result;
+ char *tempname = NULL;
+ char *file = NULL;
+ dns_dumpctx_t *dctx = NULL;
+
+ file = isc_mem_strdup(mctx, filename);
+
+ result = opentmp(mctx, format, filename, &tempname, &f);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+
+ result = dumpctx_create(mctx, db, version, style, f, &dctx, format,
+ header);
+ if (result != ISC_R_SUCCESS) {
+ (void)isc_stdio_close(f);
+ (void)isc_file_remove(tempname);
+ goto cleanup;
+ }
+
+ isc_task_attach(task, &dctx->task);
+ dctx->done = done;
+ dctx->done_arg = done_arg;
+ dctx->file = file;
+ file = NULL;
+ dctx->tmpfile = tempname;
+ tempname = NULL;
+
+ result = task_send(dctx);
+ if (result == ISC_R_SUCCESS) {
+ dns_dumpctx_attach(dctx, dctxp);
+ return (DNS_R_CONTINUE);
+ }
+
+cleanup:
+ if (dctx != NULL) {
+ dns_dumpctx_detach(&dctx);
+ }
+ if (file != NULL) {
+ isc_mem_free(mctx, file);
+ }
+ if (tempname != NULL) {
+ isc_mem_free(mctx, tempname);
+ }
+ return (result);
+}
+
+isc_result_t
+dns_master_dump(isc_mem_t *mctx, dns_db_t *db, dns_dbversion_t *version,
+ const dns_master_style_t *style, const char *filename,
+ dns_masterformat_t format, dns_masterrawheader_t *header) {
+ FILE *f = NULL;
+ isc_result_t result;
+ char *tempname;
+ dns_dumpctx_t *dctx = NULL;
+
+ result = opentmp(mctx, format, filename, &tempname, &f);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+
+ result = dumpctx_create(mctx, db, version, style, f, &dctx, format,
+ header);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+
+ result = dumptostream(dctx);
+ INSIST(result != DNS_R_CONTINUE);
+ dns_dumpctx_detach(&dctx);
+
+ result = closeandrename(f, result, tempname, filename);
+
+cleanup:
+ isc_mem_free(mctx, tempname);
+ return (result);
+}
+
+/*
+ * Dump a database node into a master file.
+ * XXX: this function assumes the text format.
+ */
+isc_result_t
+dns_master_dumpnodetostream(isc_mem_t *mctx, dns_db_t *db,
+ dns_dbversion_t *version, dns_dbnode_t *node,
+ const dns_name_t *name,
+ const dns_master_style_t *style, FILE *f) {
+ isc_result_t result;
+ isc_buffer_t buffer;
+ char *bufmem;
+ isc_stdtime_t now;
+ dns_totext_ctx_t ctx;
+ dns_rdatasetiter_t *rdsiter = NULL;
+ unsigned int options = DNS_DB_STALEOK;
+
+ if ((style->flags & DNS_STYLEFLAG_EXPIRED) != 0) {
+ options |= DNS_DB_EXPIREDOK;
+ }
+
+ result = totext_ctx_init(style, NULL, &ctx);
+ if (result != ISC_R_SUCCESS) {
+ UNEXPECTED_ERROR("could not set master file style");
+ return (ISC_R_UNEXPECTED);
+ }
+
+ isc_stdtime_get(&now);
+
+ bufmem = isc_mem_get(mctx, initial_buffer_length);
+
+ isc_buffer_init(&buffer, bufmem, initial_buffer_length);
+
+ result = dns_db_allrdatasets(db, node, version, options, now, &rdsiter);
+ if (result != ISC_R_SUCCESS) {
+ goto failure;
+ }
+ result = dump_rdatasets_text(mctx, name, rdsiter, &ctx, &buffer, f);
+ if (result != ISC_R_SUCCESS) {
+ goto failure;
+ }
+ dns_rdatasetiter_destroy(&rdsiter);
+
+ result = ISC_R_SUCCESS;
+
+failure:
+ isc_mem_put(mctx, buffer.base, buffer.length);
+ return (result);
+}
+
+isc_result_t
+dns_master_dumpnode(isc_mem_t *mctx, dns_db_t *db, dns_dbversion_t *version,
+ dns_dbnode_t *node, const dns_name_t *name,
+ const dns_master_style_t *style, const char *filename) {
+ FILE *f = NULL;
+ isc_result_t result;
+
+ result = isc_stdio_open(filename, "w", &f);
+ if (result != ISC_R_SUCCESS) {
+ isc_log_write(dns_lctx, ISC_LOGCATEGORY_GENERAL,
+ DNS_LOGMODULE_MASTERDUMP, ISC_LOG_ERROR,
+ "dumping node to file: %s: open: %s", filename,
+ isc_result_totext(result));
+ return (ISC_R_UNEXPECTED);
+ }
+
+ result = dns_master_dumpnodetostream(mctx, db, version, node, name,
+ style, f);
+ if (result != ISC_R_SUCCESS) {
+ isc_log_write(dns_lctx, ISC_LOGCATEGORY_GENERAL,
+ DNS_LOGMODULE_MASTERDUMP, ISC_LOG_ERROR,
+ "dumping master file: %s: dump: %s", filename,
+ isc_result_totext(result));
+ (void)isc_stdio_close(f);
+ return (ISC_R_UNEXPECTED);
+ }
+
+ result = isc_stdio_close(f);
+ if (result != ISC_R_SUCCESS) {
+ isc_log_write(dns_lctx, ISC_LOGCATEGORY_GENERAL,
+ DNS_LOGMODULE_MASTERDUMP, ISC_LOG_ERROR,
+ "dumping master file: %s: close: %s", filename,
+ isc_result_totext(result));
+ return (ISC_R_UNEXPECTED);
+ }
+
+ return (result);
+}
+
+dns_masterstyle_flags_t
+dns_master_styleflags(const dns_master_style_t *style) {
+ REQUIRE(style != NULL);
+ return (style->flags);
+}
+
+isc_result_t
+dns_master_stylecreate(dns_master_style_t **stylep,
+ dns_masterstyle_flags_t flags, unsigned int ttl_column,
+ unsigned int class_column, unsigned int type_column,
+ unsigned int rdata_column, unsigned int line_length,
+ unsigned int tab_width, unsigned int split_width,
+ isc_mem_t *mctx) {
+ dns_master_style_t *style;
+
+ REQUIRE(stylep != NULL && *stylep == NULL);
+ style = isc_mem_get(mctx, sizeof(*style));
+
+ style->flags = flags;
+ style->ttl_column = ttl_column;
+ style->class_column = class_column;
+ style->type_column = type_column;
+ style->rdata_column = rdata_column;
+ style->line_length = line_length;
+ style->tab_width = tab_width;
+ style->split_width = split_width;
+ *stylep = style;
+ return (ISC_R_SUCCESS);
+}
+
+void
+dns_master_styledestroy(dns_master_style_t **stylep, isc_mem_t *mctx) {
+ dns_master_style_t *style;
+
+ REQUIRE(stylep != NULL && *stylep != NULL);
+ style = *stylep;
+ *stylep = NULL;
+ isc_mem_put(mctx, style, sizeof(*style));
+}
diff --git a/lib/dns/message.c b/lib/dns/message.c
new file mode 100644
index 0000000..1b983d9
--- /dev/null
+++ b/lib/dns/message.c
@@ -0,0 +1,4849 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/*! \file */
+
+/***
+ *** Imports
+ ***/
+
+#include <ctype.h>
+#include <inttypes.h>
+#include <stdbool.h>
+
+#include <isc/buffer.h>
+#include <isc/mem.h>
+#include <isc/print.h>
+#include <isc/result.h>
+#include <isc/string.h> /* Required for HP/UX (and others?) */
+#include <isc/utf8.h>
+#include <isc/util.h>
+
+#include <dns/dnssec.h>
+#include <dns/keyvalues.h>
+#include <dns/log.h>
+#include <dns/masterdump.h>
+#include <dns/message.h>
+#include <dns/opcode.h>
+#include <dns/rcode.h>
+#include <dns/rdata.h>
+#include <dns/rdatalist.h>
+#include <dns/rdataset.h>
+#include <dns/rdatastruct.h>
+#include <dns/soa.h>
+#include <dns/tsig.h>
+#include <dns/ttl.h>
+#include <dns/view.h>
+
+#ifdef SKAN_MSG_DEBUG
+static void
+hexdump(const char *msg, const char *msg2, void *base, size_t len) {
+ unsigned char *p;
+ unsigned int cnt;
+
+ p = base;
+ cnt = 0;
+
+ printf("*** %s [%s] (%u bytes @ %p)\n", msg, msg2, (unsigned)len, base);
+
+ while (cnt < len) {
+ if (cnt % 16 == 0) {
+ printf("%p: ", p);
+ } else if (cnt % 8 == 0) {
+ printf(" |");
+ }
+ printf(" %02x %c", *p, (isprint(*p) ? *p : ' '));
+ p++;
+ cnt++;
+
+ if (cnt % 16 == 0) {
+ printf("\n");
+ }
+ }
+
+ if (cnt % 16 != 0) {
+ printf("\n");
+ }
+}
+#endif /* ifdef SKAN_MSG_DEBUG */
+
+#define DNS_MESSAGE_OPCODE_MASK 0x7800U
+#define DNS_MESSAGE_OPCODE_SHIFT 11
+#define DNS_MESSAGE_RCODE_MASK 0x000fU
+#define DNS_MESSAGE_FLAG_MASK 0x8ff0U
+#define DNS_MESSAGE_EDNSRCODE_MASK 0xff000000U
+#define DNS_MESSAGE_EDNSRCODE_SHIFT 24
+#define DNS_MESSAGE_EDNSVERSION_MASK 0x00ff0000U
+#define DNS_MESSAGE_EDNSVERSION_SHIFT 16
+
+#define VALID_NAMED_SECTION(s) \
+ (((s) > DNS_SECTION_ANY) && ((s) < DNS_SECTION_MAX))
+#define VALID_SECTION(s) (((s) >= DNS_SECTION_ANY) && ((s) < DNS_SECTION_MAX))
+#define ADD_STRING(b, s) \
+ { \
+ if (strlen(s) >= isc_buffer_availablelength(b)) { \
+ result = ISC_R_NOSPACE; \
+ goto cleanup; \
+ } else \
+ isc_buffer_putstr(b, s); \
+ }
+#define VALID_PSEUDOSECTION(s) \
+ (((s) >= DNS_PSEUDOSECTION_ANY) && ((s) < DNS_PSEUDOSECTION_MAX))
+
+#define OPTOUT(x) (((x)->attributes & DNS_RDATASETATTR_OPTOUT) != 0)
+
+/*%
+ * This is the size of each individual scratchpad buffer, and the numbers
+ * of various block allocations used within the server.
+ * XXXMLG These should come from a config setting.
+ */
+#define SCRATCHPAD_SIZE 1232
+#define NAME_FILLCOUNT 4
+#define NAME_FREEMAX 8 * NAME_FILLCOUNT
+#define OFFSET_COUNT 4
+#define RDATA_COUNT 8
+#define RDATALIST_COUNT 8
+#define RDATASET_FILLCOUNT 4
+#define RDATASET_FREEMAX 8 * RDATASET_FILLCOUNT
+
+/*%
+ * Text representation of the different items, for message_totext
+ * functions.
+ */
+static const char *sectiontext[] = { "QUESTION", "ANSWER", "AUTHORITY",
+ "ADDITIONAL" };
+
+static const char *updsectiontext[] = { "ZONE", "PREREQUISITE", "UPDATE",
+ "ADDITIONAL" };
+
+static const char *opcodetext[] = { "QUERY", "IQUERY", "STATUS",
+ "RESERVED3", "NOTIFY", "UPDATE",
+ "RESERVED6", "RESERVED7", "RESERVED8",
+ "RESERVED9", "RESERVED10", "RESERVED11",
+ "RESERVED12", "RESERVED13", "RESERVED14",
+ "RESERVED15" };
+
+static const char *edetext[] = { "Other",
+ "Unsupported DNSKEY Algorithm",
+ "Unsupported DS Digest Type",
+ "Stale Answer",
+ "Forged Answer",
+ "DNSSEC Indeterminate",
+ "DNSSEC Bogus",
+ "Signature Expired",
+ "Signature Not Yet Valid",
+ "DNSKEY Missing",
+ "RRSIGs Missing",
+ "No Zone Key Bit Set",
+ "NSEC Missing",
+ "Cached Error",
+ "Not Ready",
+ "Blocked",
+ "Censored",
+ "Filtered",
+ "Prohibited",
+ "Stale NXDOMAIN Answer",
+ "Not Authoritative",
+ "Not Supported",
+ "No Reachable Authority",
+ "Network Error",
+ "Invalid Data" };
+
+/*%
+ * "helper" type, which consists of a block of some type, and is linkable.
+ * For it to work, sizeof(dns_msgblock_t) must be a multiple of the pointer
+ * size, or the allocated elements will not be aligned correctly.
+ */
+struct dns_msgblock {
+ unsigned int count;
+ unsigned int remaining;
+ ISC_LINK(dns_msgblock_t) link;
+}; /* dynamically sized */
+
+static dns_msgblock_t *
+msgblock_allocate(isc_mem_t *, unsigned int, unsigned int);
+
+#define msgblock_get(block, type) \
+ ((type *)msgblock_internalget(block, sizeof(type)))
+
+static void *
+msgblock_internalget(dns_msgblock_t *, unsigned int);
+
+static void
+msgblock_reset(dns_msgblock_t *);
+
+static void
+msgblock_free(isc_mem_t *, dns_msgblock_t *, unsigned int);
+
+static void
+logfmtpacket(dns_message_t *message, const char *description,
+ const isc_sockaddr_t *address, isc_logcategory_t *category,
+ isc_logmodule_t *module, const dns_master_style_t *style,
+ int level, isc_mem_t *mctx);
+
+/*
+ * Allocate a new dns_msgblock_t, and return a pointer to it. If no memory
+ * is free, return NULL.
+ */
+static dns_msgblock_t *
+msgblock_allocate(isc_mem_t *mctx, unsigned int sizeof_type,
+ unsigned int count) {
+ dns_msgblock_t *block;
+ unsigned int length;
+
+ length = sizeof(dns_msgblock_t) + (sizeof_type * count);
+
+ block = isc_mem_get(mctx, length);
+
+ block->count = count;
+ block->remaining = count;
+
+ ISC_LINK_INIT(block, link);
+
+ return (block);
+}
+
+/*
+ * Return an element from the msgblock. If no more are available, return
+ * NULL.
+ */
+static void *
+msgblock_internalget(dns_msgblock_t *block, unsigned int sizeof_type) {
+ void *ptr;
+
+ if (block == NULL || block->remaining == 0) {
+ return (NULL);
+ }
+
+ block->remaining--;
+
+ ptr = (((unsigned char *)block) + sizeof(dns_msgblock_t) +
+ (sizeof_type * block->remaining));
+
+ return (ptr);
+}
+
+static void
+msgblock_reset(dns_msgblock_t *block) {
+ block->remaining = block->count;
+}
+
+/*
+ * Release memory associated with a message block.
+ */
+static void
+msgblock_free(isc_mem_t *mctx, dns_msgblock_t *block,
+ unsigned int sizeof_type) {
+ unsigned int length;
+
+ length = sizeof(dns_msgblock_t) + (sizeof_type * block->count);
+
+ isc_mem_put(mctx, block, length);
+}
+
+/*
+ * Allocate a new dynamic buffer, and attach it to this message as the
+ * "current" buffer. (which is always the last on the list, for our
+ * uses)
+ */
+static isc_result_t
+newbuffer(dns_message_t *msg, unsigned int size) {
+ isc_buffer_t *dynbuf;
+
+ dynbuf = NULL;
+ isc_buffer_allocate(msg->mctx, &dynbuf, size);
+
+ ISC_LIST_APPEND(msg->scratchpad, dynbuf, link);
+ return (ISC_R_SUCCESS);
+}
+
+static isc_buffer_t *
+currentbuffer(dns_message_t *msg) {
+ isc_buffer_t *dynbuf;
+
+ dynbuf = ISC_LIST_TAIL(msg->scratchpad);
+ INSIST(dynbuf != NULL);
+
+ return (dynbuf);
+}
+
+static void
+releaserdata(dns_message_t *msg, dns_rdata_t *rdata) {
+ ISC_LIST_PREPEND(msg->freerdata, rdata, link);
+}
+
+static dns_rdata_t *
+newrdata(dns_message_t *msg) {
+ dns_msgblock_t *msgblock;
+ dns_rdata_t *rdata;
+
+ rdata = ISC_LIST_HEAD(msg->freerdata);
+ if (rdata != NULL) {
+ ISC_LIST_UNLINK(msg->freerdata, rdata, link);
+ return (rdata);
+ }
+
+ msgblock = ISC_LIST_TAIL(msg->rdatas);
+ rdata = msgblock_get(msgblock, dns_rdata_t);
+ if (rdata == NULL) {
+ msgblock = msgblock_allocate(msg->mctx, sizeof(dns_rdata_t),
+ RDATA_COUNT);
+ ISC_LIST_APPEND(msg->rdatas, msgblock, link);
+
+ rdata = msgblock_get(msgblock, dns_rdata_t);
+ }
+
+ dns_rdata_init(rdata);
+ return (rdata);
+}
+
+static void
+releaserdatalist(dns_message_t *msg, dns_rdatalist_t *rdatalist) {
+ ISC_LIST_PREPEND(msg->freerdatalist, rdatalist, link);
+}
+
+static dns_rdatalist_t *
+newrdatalist(dns_message_t *msg) {
+ dns_msgblock_t *msgblock;
+ dns_rdatalist_t *rdatalist;
+
+ rdatalist = ISC_LIST_HEAD(msg->freerdatalist);
+ if (rdatalist != NULL) {
+ ISC_LIST_UNLINK(msg->freerdatalist, rdatalist, link);
+ goto out;
+ }
+
+ msgblock = ISC_LIST_TAIL(msg->rdatalists);
+ rdatalist = msgblock_get(msgblock, dns_rdatalist_t);
+ if (rdatalist == NULL) {
+ msgblock = msgblock_allocate(msg->mctx, sizeof(dns_rdatalist_t),
+ RDATALIST_COUNT);
+ ISC_LIST_APPEND(msg->rdatalists, msgblock, link);
+
+ rdatalist = msgblock_get(msgblock, dns_rdatalist_t);
+ }
+out:
+ dns_rdatalist_init(rdatalist);
+ return (rdatalist);
+}
+
+static dns_offsets_t *
+newoffsets(dns_message_t *msg) {
+ dns_msgblock_t *msgblock;
+ dns_offsets_t *offsets;
+
+ msgblock = ISC_LIST_TAIL(msg->offsets);
+ offsets = msgblock_get(msgblock, dns_offsets_t);
+ if (offsets == NULL) {
+ msgblock = msgblock_allocate(msg->mctx, sizeof(dns_offsets_t),
+ OFFSET_COUNT);
+ ISC_LIST_APPEND(msg->offsets, msgblock, link);
+
+ offsets = msgblock_get(msgblock, dns_offsets_t);
+ }
+
+ return (offsets);
+}
+
+static void
+msginitheader(dns_message_t *m) {
+ m->id = 0;
+ m->flags = 0;
+ m->rcode = 0;
+ m->opcode = 0;
+ m->rdclass = 0;
+}
+
+static void
+msginitprivate(dns_message_t *m) {
+ unsigned int i;
+
+ for (i = 0; i < DNS_SECTION_MAX; i++) {
+ m->cursors[i] = NULL;
+ m->counts[i] = 0;
+ }
+ m->opt = NULL;
+ m->sig0 = NULL;
+ m->sig0name = NULL;
+ m->tsig = NULL;
+ m->tsigname = NULL;
+ m->state = DNS_SECTION_ANY; /* indicate nothing parsed or rendered */
+ m->opt_reserved = 0;
+ m->sig_reserved = 0;
+ m->reserved = 0;
+ m->padding = 0;
+ m->padding_off = 0;
+ m->buffer = NULL;
+}
+
+static void
+msginittsig(dns_message_t *m) {
+ m->tsigstatus = dns_rcode_noerror;
+ m->querytsigstatus = dns_rcode_noerror;
+ m->tsigkey = NULL;
+ m->tsigctx = NULL;
+ m->sigstart = -1;
+ m->sig0key = NULL;
+ m->sig0status = dns_rcode_noerror;
+ m->timeadjust = 0;
+}
+
+/*
+ * Init elements to default state. Used both when allocating a new element
+ * and when resetting one.
+ */
+static void
+msginit(dns_message_t *m) {
+ msginitheader(m);
+ msginitprivate(m);
+ msginittsig(m);
+ m->header_ok = 0;
+ m->question_ok = 0;
+ m->tcp_continuation = 0;
+ m->verified_sig = 0;
+ m->verify_attempted = 0;
+ m->order = NULL;
+ m->order_arg.env = NULL;
+ m->order_arg.acl = NULL;
+ m->order_arg.element = NULL;
+ m->query.base = NULL;
+ m->query.length = 0;
+ m->free_query = 0;
+ m->saved.base = NULL;
+ m->saved.length = 0;
+ m->free_saved = 0;
+ m->cc_ok = 0;
+ m->cc_bad = 0;
+ m->tkey = 0;
+ m->rdclass_set = 0;
+ m->querytsig = NULL;
+ m->indent.string = "\t";
+ m->indent.count = 0;
+}
+
+static void
+msgresetnames(dns_message_t *msg, unsigned int first_section) {
+ unsigned int i;
+ dns_name_t *name, *next_name;
+ dns_rdataset_t *rds, *next_rds;
+
+ /*
+ * Clean up name lists by calling the rdataset disassociate function.
+ */
+ for (i = first_section; i < DNS_SECTION_MAX; i++) {
+ name = ISC_LIST_HEAD(msg->sections[i]);
+ while (name != NULL) {
+ next_name = ISC_LIST_NEXT(name, link);
+ ISC_LIST_UNLINK(msg->sections[i], name, link);
+
+ rds = ISC_LIST_HEAD(name->list);
+ while (rds != NULL) {
+ next_rds = ISC_LIST_NEXT(rds, link);
+ ISC_LIST_UNLINK(name->list, rds, link);
+
+ INSIST(dns_rdataset_isassociated(rds));
+ dns_rdataset_disassociate(rds);
+ isc_mempool_put(msg->rdspool, rds);
+ rds = next_rds;
+ }
+ dns_message_puttempname(msg, &name);
+ name = next_name;
+ }
+ }
+}
+
+static void
+msgresetopt(dns_message_t *msg) {
+ if (msg->opt != NULL) {
+ if (msg->opt_reserved > 0) {
+ dns_message_renderrelease(msg, msg->opt_reserved);
+ msg->opt_reserved = 0;
+ }
+ INSIST(dns_rdataset_isassociated(msg->opt));
+ dns_rdataset_disassociate(msg->opt);
+ isc_mempool_put(msg->rdspool, msg->opt);
+ msg->opt = NULL;
+ msg->cc_ok = 0;
+ msg->cc_bad = 0;
+ }
+}
+
+static void
+msgresetsigs(dns_message_t *msg, bool replying) {
+ if (msg->sig_reserved > 0) {
+ dns_message_renderrelease(msg, msg->sig_reserved);
+ msg->sig_reserved = 0;
+ }
+ if (msg->tsig != NULL) {
+ INSIST(dns_rdataset_isassociated(msg->tsig));
+ INSIST(msg->namepool != NULL);
+ if (replying) {
+ INSIST(msg->querytsig == NULL);
+ msg->querytsig = msg->tsig;
+ } else {
+ dns_rdataset_disassociate(msg->tsig);
+ isc_mempool_put(msg->rdspool, msg->tsig);
+ if (msg->querytsig != NULL) {
+ dns_rdataset_disassociate(msg->querytsig);
+ isc_mempool_put(msg->rdspool, msg->querytsig);
+ }
+ }
+ dns_message_puttempname(msg, &msg->tsigname);
+ msg->tsig = NULL;
+ } else if (msg->querytsig != NULL && !replying) {
+ dns_rdataset_disassociate(msg->querytsig);
+ isc_mempool_put(msg->rdspool, msg->querytsig);
+ msg->querytsig = NULL;
+ }
+ if (msg->sig0 != NULL) {
+ INSIST(dns_rdataset_isassociated(msg->sig0));
+ dns_rdataset_disassociate(msg->sig0);
+ isc_mempool_put(msg->rdspool, msg->sig0);
+ msg->sig0 = NULL;
+ }
+ if (msg->sig0name != NULL) {
+ dns_message_puttempname(msg, &msg->sig0name);
+ }
+}
+
+/*
+ * Free all but one (or everything) for this message. This is used by
+ * both dns_message_reset() and dns__message_destroy().
+ */
+static void
+msgreset(dns_message_t *msg, bool everything) {
+ dns_msgblock_t *msgblock, *next_msgblock;
+ isc_buffer_t *dynbuf, *next_dynbuf;
+ dns_rdata_t *rdata;
+ dns_rdatalist_t *rdatalist;
+
+ msgresetnames(msg, 0);
+ msgresetopt(msg);
+ msgresetsigs(msg, false);
+
+ /*
+ * Clean up linked lists.
+ */
+
+ /*
+ * Run through the free lists, and just unlink anything found there.
+ * The memory isn't lost since these are part of message blocks we
+ * have allocated.
+ */
+ rdata = ISC_LIST_HEAD(msg->freerdata);
+ while (rdata != NULL) {
+ ISC_LIST_UNLINK(msg->freerdata, rdata, link);
+ rdata = ISC_LIST_HEAD(msg->freerdata);
+ }
+ rdatalist = ISC_LIST_HEAD(msg->freerdatalist);
+ while (rdatalist != NULL) {
+ ISC_LIST_UNLINK(msg->freerdatalist, rdatalist, link);
+ rdatalist = ISC_LIST_HEAD(msg->freerdatalist);
+ }
+
+ dynbuf = ISC_LIST_HEAD(msg->scratchpad);
+ INSIST(dynbuf != NULL);
+ if (!everything) {
+ isc_buffer_clear(dynbuf);
+ dynbuf = ISC_LIST_NEXT(dynbuf, link);
+ }
+ while (dynbuf != NULL) {
+ next_dynbuf = ISC_LIST_NEXT(dynbuf, link);
+ ISC_LIST_UNLINK(msg->scratchpad, dynbuf, link);
+ isc_buffer_free(&dynbuf);
+ dynbuf = next_dynbuf;
+ }
+
+ msgblock = ISC_LIST_HEAD(msg->rdatas);
+ if (!everything && msgblock != NULL) {
+ msgblock_reset(msgblock);
+ msgblock = ISC_LIST_NEXT(msgblock, link);
+ }
+ while (msgblock != NULL) {
+ next_msgblock = ISC_LIST_NEXT(msgblock, link);
+ ISC_LIST_UNLINK(msg->rdatas, msgblock, link);
+ msgblock_free(msg->mctx, msgblock, sizeof(dns_rdata_t));
+ msgblock = next_msgblock;
+ }
+
+ /*
+ * rdatalists could be empty.
+ */
+
+ msgblock = ISC_LIST_HEAD(msg->rdatalists);
+ if (!everything && msgblock != NULL) {
+ msgblock_reset(msgblock);
+ msgblock = ISC_LIST_NEXT(msgblock, link);
+ }
+ while (msgblock != NULL) {
+ next_msgblock = ISC_LIST_NEXT(msgblock, link);
+ ISC_LIST_UNLINK(msg->rdatalists, msgblock, link);
+ msgblock_free(msg->mctx, msgblock, sizeof(dns_rdatalist_t));
+ msgblock = next_msgblock;
+ }
+
+ msgblock = ISC_LIST_HEAD(msg->offsets);
+ if (!everything && msgblock != NULL) {
+ msgblock_reset(msgblock);
+ msgblock = ISC_LIST_NEXT(msgblock, link);
+ }
+ while (msgblock != NULL) {
+ next_msgblock = ISC_LIST_NEXT(msgblock, link);
+ ISC_LIST_UNLINK(msg->offsets, msgblock, link);
+ msgblock_free(msg->mctx, msgblock, sizeof(dns_offsets_t));
+ msgblock = next_msgblock;
+ }
+
+ if (msg->tsigkey != NULL) {
+ dns_tsigkey_detach(&msg->tsigkey);
+ msg->tsigkey = NULL;
+ }
+
+ if (msg->tsigctx != NULL) {
+ dst_context_destroy(&msg->tsigctx);
+ }
+
+ if (msg->query.base != NULL) {
+ if (msg->free_query != 0) {
+ isc_mem_put(msg->mctx, msg->query.base,
+ msg->query.length);
+ }
+ msg->query.base = NULL;
+ msg->query.length = 0;
+ }
+
+ if (msg->saved.base != NULL) {
+ if (msg->free_saved != 0) {
+ isc_mem_put(msg->mctx, msg->saved.base,
+ msg->saved.length);
+ }
+ msg->saved.base = NULL;
+ msg->saved.length = 0;
+ }
+
+ /*
+ * cleanup the buffer cleanup list
+ */
+ dynbuf = ISC_LIST_HEAD(msg->cleanup);
+ while (dynbuf != NULL) {
+ next_dynbuf = ISC_LIST_NEXT(dynbuf, link);
+ ISC_LIST_UNLINK(msg->cleanup, dynbuf, link);
+ isc_buffer_free(&dynbuf);
+ dynbuf = next_dynbuf;
+ }
+
+ if (msg->order_arg.env != NULL) {
+ dns_aclenv_detach(&msg->order_arg.env);
+ }
+ if (msg->order_arg.acl != NULL) {
+ dns_acl_detach(&msg->order_arg.acl);
+ }
+
+ /*
+ * Set other bits to normal default values.
+ */
+ if (!everything) {
+ msginit(msg);
+ }
+
+ ENSURE(isc_mempool_getallocated(msg->namepool) == 0);
+ ENSURE(isc_mempool_getallocated(msg->rdspool) == 0);
+}
+
+static unsigned int
+spacefortsig(dns_tsigkey_t *key, int otherlen) {
+ isc_region_t r1, r2;
+ unsigned int x;
+ isc_result_t result;
+
+ /*
+ * The space required for an TSIG record is:
+ *
+ * n1 bytes for the name
+ * 2 bytes for the type
+ * 2 bytes for the class
+ * 4 bytes for the ttl
+ * 2 bytes for the rdlength
+ * n2 bytes for the algorithm name
+ * 6 bytes for the time signed
+ * 2 bytes for the fudge
+ * 2 bytes for the MAC size
+ * x bytes for the MAC
+ * 2 bytes for the original id
+ * 2 bytes for the error
+ * 2 bytes for the other data length
+ * y bytes for the other data (at most)
+ * ---------------------------------
+ * 26 + n1 + n2 + x + y bytes
+ */
+
+ dns_name_toregion(&key->name, &r1);
+ dns_name_toregion(key->algorithm, &r2);
+ if (key->key == NULL) {
+ x = 0;
+ } else {
+ result = dst_key_sigsize(key->key, &x);
+ if (result != ISC_R_SUCCESS) {
+ x = 0;
+ }
+ }
+ return (26 + r1.length + r2.length + x + otherlen);
+}
+
+void
+dns_message_create(isc_mem_t *mctx, unsigned int intent, dns_message_t **msgp) {
+ dns_message_t *m = NULL;
+ isc_buffer_t *dynbuf = NULL;
+ unsigned int i;
+
+ REQUIRE(mctx != NULL);
+ REQUIRE(msgp != NULL);
+ REQUIRE(*msgp == NULL);
+ REQUIRE(intent == DNS_MESSAGE_INTENTPARSE ||
+ intent == DNS_MESSAGE_INTENTRENDER);
+
+ m = isc_mem_get(mctx, sizeof(dns_message_t));
+ *m = (dns_message_t){ .from_to_wire = intent };
+ isc_mem_attach(mctx, &m->mctx);
+ msginit(m);
+
+ for (i = 0; i < DNS_SECTION_MAX; i++) {
+ ISC_LIST_INIT(m->sections[i]);
+ }
+
+ ISC_LIST_INIT(m->scratchpad);
+ ISC_LIST_INIT(m->cleanup);
+ ISC_LIST_INIT(m->rdatas);
+ ISC_LIST_INIT(m->rdatalists);
+ ISC_LIST_INIT(m->offsets);
+ ISC_LIST_INIT(m->freerdata);
+ ISC_LIST_INIT(m->freerdatalist);
+
+ isc_mempool_create(m->mctx, sizeof(dns_fixedname_t), &m->namepool);
+ isc_mempool_setfillcount(m->namepool, NAME_FILLCOUNT);
+ isc_mempool_setfreemax(m->namepool, NAME_FREEMAX);
+ isc_mempool_setname(m->namepool, "msg:names");
+
+ isc_mempool_create(m->mctx, sizeof(dns_rdataset_t), &m->rdspool);
+ isc_mempool_setfillcount(m->rdspool, RDATASET_FILLCOUNT);
+ isc_mempool_setfreemax(m->rdspool, RDATASET_FREEMAX);
+ isc_mempool_setname(m->rdspool, "msg:rdataset");
+
+ isc_buffer_allocate(mctx, &dynbuf, SCRATCHPAD_SIZE);
+ ISC_LIST_APPEND(m->scratchpad, dynbuf, link);
+
+ isc_refcount_init(&m->refcount, 1);
+ m->magic = DNS_MESSAGE_MAGIC;
+
+ *msgp = m;
+}
+
+void
+dns_message_reset(dns_message_t *msg, unsigned int intent) {
+ REQUIRE(DNS_MESSAGE_VALID(msg));
+ REQUIRE(intent == DNS_MESSAGE_INTENTPARSE ||
+ intent == DNS_MESSAGE_INTENTRENDER);
+
+ msgreset(msg, false);
+ msg->from_to_wire = intent;
+}
+
+static void
+dns__message_destroy(dns_message_t *msg) {
+ REQUIRE(msg != NULL);
+ REQUIRE(DNS_MESSAGE_VALID(msg));
+
+ msgreset(msg, true);
+ isc_mempool_destroy(&msg->namepool);
+ isc_mempool_destroy(&msg->rdspool);
+ isc_refcount_destroy(&msg->refcount);
+ msg->magic = 0;
+ isc_mem_putanddetach(&msg->mctx, msg, sizeof(dns_message_t));
+}
+
+void
+dns_message_attach(dns_message_t *source, dns_message_t **target) {
+ REQUIRE(DNS_MESSAGE_VALID(source));
+
+ isc_refcount_increment(&source->refcount);
+ *target = source;
+}
+
+void
+dns_message_detach(dns_message_t **messagep) {
+ REQUIRE(messagep != NULL && DNS_MESSAGE_VALID(*messagep));
+ dns_message_t *msg = *messagep;
+ *messagep = NULL;
+
+ if (isc_refcount_decrement(&msg->refcount) == 1) {
+ dns__message_destroy(msg);
+ }
+}
+
+static isc_result_t
+findname(dns_name_t **foundname, const dns_name_t *target,
+ dns_namelist_t *section) {
+ dns_name_t *curr;
+
+ for (curr = ISC_LIST_TAIL(*section); curr != NULL;
+ curr = ISC_LIST_PREV(curr, link))
+ {
+ if (dns_name_equal(curr, target)) {
+ if (foundname != NULL) {
+ *foundname = curr;
+ }
+ return (ISC_R_SUCCESS);
+ }
+ }
+
+ return (ISC_R_NOTFOUND);
+}
+
+isc_result_t
+dns_message_find(const dns_name_t *name, dns_rdataclass_t rdclass,
+ dns_rdatatype_t type, dns_rdatatype_t covers,
+ dns_rdataset_t **rdataset) {
+ dns_rdataset_t *curr;
+
+ REQUIRE(name != NULL);
+ REQUIRE(rdataset == NULL || *rdataset == NULL);
+
+ for (curr = ISC_LIST_TAIL(name->list); curr != NULL;
+ curr = ISC_LIST_PREV(curr, link))
+ {
+ if (curr->rdclass == rdclass && curr->type == type &&
+ curr->covers == covers)
+ {
+ if (rdataset != NULL) {
+ *rdataset = curr;
+ }
+ return (ISC_R_SUCCESS);
+ }
+ }
+
+ return (ISC_R_NOTFOUND);
+}
+
+isc_result_t
+dns_message_findtype(const dns_name_t *name, dns_rdatatype_t type,
+ dns_rdatatype_t covers, dns_rdataset_t **rdataset) {
+ dns_rdataset_t *curr;
+
+ REQUIRE(name != NULL);
+ REQUIRE(rdataset == NULL || *rdataset == NULL);
+
+ for (curr = ISC_LIST_TAIL(name->list); curr != NULL;
+ curr = ISC_LIST_PREV(curr, link))
+ {
+ if (curr->type == type && curr->covers == covers) {
+ if (rdataset != NULL) {
+ *rdataset = curr;
+ }
+ return (ISC_R_SUCCESS);
+ }
+ }
+
+ return (ISC_R_NOTFOUND);
+}
+
+/*
+ * Read a name from buffer "source".
+ */
+static isc_result_t
+getname(dns_name_t *name, isc_buffer_t *source, dns_message_t *msg,
+ dns_decompress_t *dctx) {
+ isc_buffer_t *scratch;
+ isc_result_t result;
+ unsigned int tries;
+
+ scratch = currentbuffer(msg);
+
+ /*
+ * First try: use current buffer.
+ * Second try: allocate a new buffer and use that.
+ */
+ tries = 0;
+ while (tries < 2) {
+ result = dns_name_fromwire(name, source, dctx, 0, scratch);
+
+ if (result == ISC_R_NOSPACE) {
+ tries++;
+
+ result = newbuffer(msg, SCRATCHPAD_SIZE);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+
+ scratch = currentbuffer(msg);
+ dns_name_reset(name);
+ } else {
+ return (result);
+ }
+ }
+
+ UNREACHABLE();
+}
+
+static isc_result_t
+getrdata(isc_buffer_t *source, dns_message_t *msg, dns_decompress_t *dctx,
+ dns_rdataclass_t rdclass, dns_rdatatype_t rdtype,
+ unsigned int rdatalen, dns_rdata_t *rdata) {
+ isc_buffer_t *scratch;
+ isc_result_t result;
+ unsigned int tries;
+ unsigned int trysize;
+
+ scratch = currentbuffer(msg);
+
+ isc_buffer_setactive(source, rdatalen);
+
+ /*
+ * First try: use current buffer.
+ * Second try: allocate a new buffer of size
+ * max(SCRATCHPAD_SIZE, 2 * compressed_rdatalen)
+ * (the data will fit if it was not more than 50% compressed)
+ * Subsequent tries: double buffer size on each try.
+ */
+ tries = 0;
+ trysize = 0;
+ /* XXX possibly change this to a while (tries < 2) loop */
+ for (;;) {
+ result = dns_rdata_fromwire(rdata, rdclass, rdtype, source,
+ dctx, 0, scratch);
+
+ if (result == ISC_R_NOSPACE) {
+ if (tries == 0) {
+ trysize = 2 * rdatalen;
+ if (trysize < SCRATCHPAD_SIZE) {
+ trysize = SCRATCHPAD_SIZE;
+ }
+ } else {
+ INSIST(trysize != 0);
+ if (trysize >= 65535) {
+ return (ISC_R_NOSPACE);
+ }
+ /* XXX DNS_R_RRTOOLONG? */
+ trysize *= 2;
+ }
+ tries++;
+ result = newbuffer(msg, trysize);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+
+ scratch = currentbuffer(msg);
+ } else {
+ return (result);
+ }
+ }
+}
+
+#define DO_ERROR(r) \
+ do { \
+ if (best_effort) { \
+ seen_problem = true; \
+ } else { \
+ result = r; \
+ goto cleanup; \
+ } \
+ } while (0)
+
+static isc_result_t
+getquestions(isc_buffer_t *source, dns_message_t *msg, dns_decompress_t *dctx,
+ unsigned int options) {
+ isc_region_t r;
+ unsigned int count;
+ dns_name_t *name = NULL;
+ dns_name_t *name2 = NULL;
+ dns_rdataset_t *rdataset = NULL;
+ dns_rdatalist_t *rdatalist = NULL;
+ isc_result_t result;
+ dns_rdatatype_t rdtype;
+ dns_rdataclass_t rdclass;
+ dns_namelist_t *section = &msg->sections[DNS_SECTION_QUESTION];
+ bool best_effort = ((options & DNS_MESSAGEPARSE_BESTEFFORT) != 0);
+ bool seen_problem = false;
+ bool free_name = false;
+
+ for (count = 0; count < msg->counts[DNS_SECTION_QUESTION]; count++) {
+ name = NULL;
+ result = dns_message_gettempname(msg, &name);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+ name->offsets = (unsigned char *)newoffsets(msg);
+ free_name = true;
+
+ /*
+ * Parse the name out of this packet.
+ */
+ isc_buffer_remainingregion(source, &r);
+ isc_buffer_setactive(source, r.length);
+ result = getname(name, source, msg, dctx);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+
+ /*
+ * Run through the section, looking to see if this name
+ * is already there. If it is found, put back the allocated
+ * name since we no longer need it, and set our name pointer
+ * to point to the name we found.
+ */
+ result = findname(&name2, name, section);
+
+ /*
+ * If it is the first name in the section, accept it.
+ *
+ * If it is not, but is not the same as the name already
+ * in the question section, append to the section. Note that
+ * here in the question section this is illegal, so return
+ * FORMERR. In the future, check the opcode to see if
+ * this should be legal or not. In either case we no longer
+ * need this name pointer.
+ */
+ if (result != ISC_R_SUCCESS) {
+ if (!ISC_LIST_EMPTY(*section)) {
+ DO_ERROR(DNS_R_FORMERR);
+ }
+ ISC_LIST_APPEND(*section, name, link);
+ free_name = false;
+ } else {
+ dns_message_puttempname(msg, &name);
+ name = name2;
+ name2 = NULL;
+ free_name = false;
+ }
+
+ /*
+ * Get type and class.
+ */
+ isc_buffer_remainingregion(source, &r);
+ if (r.length < 4) {
+ result = ISC_R_UNEXPECTEDEND;
+ goto cleanup;
+ }
+ rdtype = isc_buffer_getuint16(source);
+ rdclass = isc_buffer_getuint16(source);
+
+ /*
+ * If this class is different than the one we already read,
+ * this is an error.
+ */
+ if (msg->rdclass_set == 0) {
+ msg->rdclass = rdclass;
+ msg->rdclass_set = 1;
+ } else if (msg->rdclass != rdclass) {
+ DO_ERROR(DNS_R_FORMERR);
+ }
+
+ /*
+ * Is this a TKEY query?
+ */
+ if (rdtype == dns_rdatatype_tkey) {
+ msg->tkey = 1;
+ }
+
+ /*
+ * Can't ask the same question twice.
+ */
+ result = dns_message_find(name, rdclass, rdtype, 0, NULL);
+ if (result == ISC_R_SUCCESS) {
+ DO_ERROR(DNS_R_FORMERR);
+ }
+
+ /*
+ * Allocate a new rdatalist.
+ */
+ rdatalist = newrdatalist(msg);
+ if (rdatalist == NULL) {
+ result = ISC_R_NOMEMORY;
+ goto cleanup;
+ }
+ rdataset = isc_mempool_get(msg->rdspool);
+
+ /*
+ * Convert rdatalist to rdataset, and attach the latter to
+ * the name.
+ */
+ rdatalist->type = rdtype;
+ rdatalist->rdclass = rdclass;
+
+ dns_rdataset_init(rdataset);
+ result = dns_rdatalist_tordataset(rdatalist, rdataset);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+
+ rdataset->attributes |= DNS_RDATASETATTR_QUESTION;
+
+ ISC_LIST_APPEND(name->list, rdataset, link);
+ rdataset = NULL;
+ }
+
+ if (seen_problem) {
+ return (DNS_R_RECOVERABLE);
+ }
+ return (ISC_R_SUCCESS);
+
+cleanup:
+ if (rdataset != NULL) {
+ INSIST(!dns_rdataset_isassociated(rdataset));
+ isc_mempool_put(msg->rdspool, rdataset);
+ }
+ if (free_name) {
+ dns_message_puttempname(msg, &name);
+ }
+
+ return (result);
+}
+
+static bool
+update(dns_section_t section, dns_rdataclass_t rdclass) {
+ if (section == DNS_SECTION_PREREQUISITE) {
+ return (rdclass == dns_rdataclass_any ||
+ rdclass == dns_rdataclass_none);
+ }
+ if (section == DNS_SECTION_UPDATE) {
+ return (rdclass == dns_rdataclass_any);
+ }
+ return (false);
+}
+
+/*
+ * Check to confirm that all DNSSEC records (DS, NSEC, NSEC3) have
+ * covering RRSIGs.
+ */
+static bool
+auth_signed(dns_namelist_t *section) {
+ dns_name_t *name;
+
+ for (name = ISC_LIST_HEAD(*section); name != NULL;
+ name = ISC_LIST_NEXT(name, link))
+ {
+ int auth_dnssec = 0, auth_rrsig = 0;
+ dns_rdataset_t *rds;
+
+ for (rds = ISC_LIST_HEAD(name->list); rds != NULL;
+ rds = ISC_LIST_NEXT(rds, link))
+ {
+ switch (rds->type) {
+ case dns_rdatatype_ds:
+ auth_dnssec |= 0x1;
+ break;
+ case dns_rdatatype_nsec:
+ auth_dnssec |= 0x2;
+ break;
+ case dns_rdatatype_nsec3:
+ auth_dnssec |= 0x4;
+ break;
+ case dns_rdatatype_rrsig:
+ break;
+ default:
+ continue;
+ }
+
+ switch (rds->covers) {
+ case dns_rdatatype_ds:
+ auth_rrsig |= 0x1;
+ break;
+ case dns_rdatatype_nsec:
+ auth_rrsig |= 0x2;
+ break;
+ case dns_rdatatype_nsec3:
+ auth_rrsig |= 0x4;
+ break;
+ default:
+ break;
+ }
+ }
+
+ if (auth_dnssec != auth_rrsig) {
+ return (false);
+ }
+ }
+
+ return (true);
+}
+
+static isc_result_t
+getsection(isc_buffer_t *source, dns_message_t *msg, dns_decompress_t *dctx,
+ dns_section_t sectionid, unsigned int options) {
+ isc_region_t r;
+ unsigned int count, rdatalen;
+ dns_name_t *name = NULL;
+ dns_name_t *name2 = NULL;
+ dns_rdataset_t *rdataset = NULL;
+ dns_rdatalist_t *rdatalist = NULL;
+ isc_result_t result;
+ dns_rdatatype_t rdtype, covers;
+ dns_rdataclass_t rdclass;
+ dns_rdata_t *rdata = NULL;
+ dns_ttl_t ttl;
+ dns_namelist_t *section = &msg->sections[sectionid];
+ bool free_name = false, free_rdataset = false, seen_problem = false;
+ bool preserve_order = ((options & DNS_MESSAGEPARSE_PRESERVEORDER) != 0);
+ bool best_effort = ((options & DNS_MESSAGEPARSE_BESTEFFORT) != 0);
+ bool isedns, issigzero, istsig;
+
+ for (count = 0; count < msg->counts[sectionid]; count++) {
+ int recstart = source->current;
+ bool skip_name_search, skip_type_search;
+
+ skip_name_search = false;
+ skip_type_search = false;
+ free_rdataset = false;
+ isedns = false;
+ issigzero = false;
+ istsig = false;
+
+ name = NULL;
+ result = dns_message_gettempname(msg, &name);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+ name->offsets = (unsigned char *)newoffsets(msg);
+ free_name = true;
+
+ /*
+ * Parse the name out of this packet.
+ */
+ isc_buffer_remainingregion(source, &r);
+ isc_buffer_setactive(source, r.length);
+ result = getname(name, source, msg, dctx);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+
+ /*
+ * Get type, class, ttl, and rdatalen. Verify that at least
+ * rdatalen bytes remain. (Some of this is deferred to
+ * later.)
+ */
+ isc_buffer_remainingregion(source, &r);
+ if (r.length < 2 + 2 + 4 + 2) {
+ result = ISC_R_UNEXPECTEDEND;
+ goto cleanup;
+ }
+ rdtype = isc_buffer_getuint16(source);
+ rdclass = isc_buffer_getuint16(source);
+
+ /*
+ * If there was no question section, we may not yet have
+ * established a class. Do so now.
+ */
+ if (msg->rdclass_set == 0 &&
+ rdtype != dns_rdatatype_opt && /* class is UDP SIZE */
+ rdtype != dns_rdatatype_tsig && /* class is ANY */
+ rdtype != dns_rdatatype_tkey)
+ { /* class is undefined */
+ msg->rdclass = rdclass;
+ msg->rdclass_set = 1;
+ }
+
+ /*
+ * If this class is different than the one in the question
+ * section, bail.
+ */
+ if (msg->opcode != dns_opcode_update &&
+ rdtype != dns_rdatatype_tsig &&
+ rdtype != dns_rdatatype_opt &&
+ rdtype != dns_rdatatype_key && /* in a TKEY query */
+ rdtype != dns_rdatatype_sig && /* SIG(0) */
+ rdtype != dns_rdatatype_tkey && /* Win2000 TKEY */
+ msg->rdclass != dns_rdataclass_any &&
+ msg->rdclass != rdclass)
+ {
+ DO_ERROR(DNS_R_FORMERR);
+ }
+
+ /*
+ * If this is not a TKEY query/response then the KEY
+ * record's class needs to match.
+ */
+ if (msg->opcode != dns_opcode_update && !msg->tkey &&
+ rdtype == dns_rdatatype_key &&
+ msg->rdclass != dns_rdataclass_any &&
+ msg->rdclass != rdclass)
+ {
+ DO_ERROR(DNS_R_FORMERR);
+ }
+
+ /*
+ * Special type handling for TSIG, OPT, and TKEY.
+ */
+ if (rdtype == dns_rdatatype_tsig) {
+ /*
+ * If it is a tsig, verify that it is in the
+ * additional data section.
+ */
+ if (sectionid != DNS_SECTION_ADDITIONAL ||
+ rdclass != dns_rdataclass_any ||
+ count != msg->counts[sectionid] - 1)
+ {
+ DO_ERROR(DNS_R_BADTSIG);
+ } else {
+ skip_name_search = true;
+ skip_type_search = true;
+ istsig = true;
+ }
+ } else if (rdtype == dns_rdatatype_opt) {
+ /*
+ * The name of an OPT record must be ".", it
+ * must be in the additional data section, and
+ * it must be the first OPT we've seen.
+ */
+ if (!dns_name_equal(dns_rootname, name) ||
+ sectionid != DNS_SECTION_ADDITIONAL ||
+ msg->opt != NULL)
+ {
+ DO_ERROR(DNS_R_FORMERR);
+ } else {
+ skip_name_search = true;
+ skip_type_search = true;
+ isedns = true;
+ }
+ } else if (rdtype == dns_rdatatype_tkey) {
+ /*
+ * A TKEY must be in the additional section if this
+ * is a query, and the answer section if this is a
+ * response. Unless it's a Win2000 client.
+ *
+ * Its class is ignored.
+ */
+ dns_section_t tkeysection;
+
+ if ((msg->flags & DNS_MESSAGEFLAG_QR) == 0) {
+ tkeysection = DNS_SECTION_ADDITIONAL;
+ } else {
+ tkeysection = DNS_SECTION_ANSWER;
+ }
+ if (sectionid != tkeysection &&
+ sectionid != DNS_SECTION_ANSWER)
+ {
+ DO_ERROR(DNS_R_FORMERR);
+ }
+ }
+
+ /*
+ * ... now get ttl and rdatalen, and check buffer.
+ */
+ ttl = isc_buffer_getuint32(source);
+ rdatalen = isc_buffer_getuint16(source);
+ r.length -= (2 + 2 + 4 + 2);
+ if (r.length < rdatalen) {
+ result = ISC_R_UNEXPECTEDEND;
+ goto cleanup;
+ }
+
+ /*
+ * Read the rdata from the wire format. Interpret the
+ * rdata according to its actual class, even if it had a
+ * DynDNS meta-class in the packet (unless this is a TSIG).
+ * Then put the meta-class back into the finished rdata.
+ */
+ rdata = newrdata(msg);
+ if (rdata == NULL) {
+ result = ISC_R_NOMEMORY;
+ goto cleanup;
+ }
+ if (msg->opcode == dns_opcode_update &&
+ update(sectionid, rdclass))
+ {
+ if (rdatalen != 0) {
+ result = DNS_R_FORMERR;
+ goto cleanup;
+ }
+ /*
+ * When the rdata is empty, the data pointer is
+ * never dereferenced, but it must still be non-NULL.
+ * Casting 1 rather than "" avoids warnings about
+ * discarding the const attribute of a string,
+ * for compilers that would warn about such things.
+ */
+ rdata->data = (unsigned char *)1;
+ rdata->length = 0;
+ rdata->rdclass = rdclass;
+ rdata->type = rdtype;
+ rdata->flags = DNS_RDATA_UPDATE;
+ result = ISC_R_SUCCESS;
+ } else if (rdclass == dns_rdataclass_none &&
+ msg->opcode == dns_opcode_update &&
+ sectionid == DNS_SECTION_UPDATE)
+ {
+ result = getrdata(source, msg, dctx, msg->rdclass,
+ rdtype, rdatalen, rdata);
+ } else {
+ result = getrdata(source, msg, dctx, rdclass, rdtype,
+ rdatalen, rdata);
+ }
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+ rdata->rdclass = rdclass;
+ if (rdtype == dns_rdatatype_rrsig && rdata->flags == 0) {
+ covers = dns_rdata_covers(rdata);
+ if (covers == 0) {
+ DO_ERROR(DNS_R_FORMERR);
+ }
+ } else if (rdtype == dns_rdatatype_sig /* SIG(0) */ &&
+ rdata->flags == 0)
+ {
+ covers = dns_rdata_covers(rdata);
+ if (covers == 0) {
+ if (sectionid != DNS_SECTION_ADDITIONAL ||
+ count != msg->counts[sectionid] - 1 ||
+ !dns_name_equal(name, dns_rootname))
+ {
+ DO_ERROR(DNS_R_BADSIG0);
+ } else {
+ skip_name_search = true;
+ skip_type_search = true;
+ issigzero = true;
+ }
+ } else {
+ if (msg->rdclass != dns_rdataclass_any &&
+ msg->rdclass != rdclass)
+ {
+ DO_ERROR(DNS_R_FORMERR);
+ }
+ }
+ } else {
+ covers = 0;
+ }
+
+ /*
+ * Check the ownername of NSEC3 records
+ */
+ if (rdtype == dns_rdatatype_nsec3 &&
+ !dns_rdata_checkowner(name, msg->rdclass, rdtype, false))
+ {
+ result = DNS_R_BADOWNERNAME;
+ goto cleanup;
+ }
+
+ /*
+ * If we are doing a dynamic update or this is a meta-type,
+ * don't bother searching for a name, just append this one
+ * to the end of the message.
+ */
+ if (preserve_order || msg->opcode == dns_opcode_update ||
+ skip_name_search)
+ {
+ if (!isedns && !istsig && !issigzero) {
+ ISC_LIST_APPEND(*section, name, link);
+ free_name = false;
+ }
+ } else {
+ /*
+ * Run through the section, looking to see if this name
+ * is already there. If it is found, put back the
+ * allocated name since we no longer need it, and set
+ * our name pointer to point to the name we found.
+ */
+ result = findname(&name2, name, section);
+
+ /*
+ * If it is a new name, append to the section.
+ */
+ if (result == ISC_R_SUCCESS) {
+ dns_message_puttempname(msg, &name);
+ name = name2;
+ } else {
+ ISC_LIST_APPEND(*section, name, link);
+ }
+ free_name = false;
+ }
+
+ /*
+ * Search name for the particular type and class.
+ * Skip this stage if in update mode or this is a meta-type.
+ */
+ if (preserve_order || msg->opcode == dns_opcode_update ||
+ skip_type_search)
+ {
+ result = ISC_R_NOTFOUND;
+ } else {
+ /*
+ * If this is a type that can only occur in
+ * the question section, fail.
+ */
+ if (dns_rdatatype_questiononly(rdtype)) {
+ DO_ERROR(DNS_R_FORMERR);
+ }
+
+ rdataset = NULL;
+ result = dns_message_find(name, rdclass, rdtype, covers,
+ &rdataset);
+ }
+
+ /*
+ * If we found an rdataset that matches, we need to
+ * append this rdata to that set. If we did not, we need
+ * to create a new rdatalist, store the important bits there,
+ * convert it to an rdataset, and link the latter to the name.
+ * Yuck. When appending, make certain that the type isn't
+ * a singleton type, such as SOA or CNAME.
+ *
+ * Note that this check will be bypassed when preserving order,
+ * the opcode is an update, or the type search is skipped.
+ */
+ if (result == ISC_R_SUCCESS) {
+ if (dns_rdatatype_issingleton(rdtype)) {
+ dns_rdata_t *first;
+ dns_rdatalist_fromrdataset(rdataset,
+ &rdatalist);
+ first = ISC_LIST_HEAD(rdatalist->rdata);
+ INSIST(first != NULL);
+ if (dns_rdata_compare(rdata, first) != 0) {
+ DO_ERROR(DNS_R_FORMERR);
+ }
+ }
+ }
+
+ if (result == ISC_R_NOTFOUND) {
+ rdataset = isc_mempool_get(msg->rdspool);
+ free_rdataset = true;
+
+ rdatalist = newrdatalist(msg);
+ if (rdatalist == NULL) {
+ result = ISC_R_NOMEMORY;
+ goto cleanup;
+ }
+
+ rdatalist->type = rdtype;
+ rdatalist->covers = covers;
+ rdatalist->rdclass = rdclass;
+ rdatalist->ttl = ttl;
+
+ dns_rdataset_init(rdataset);
+ RUNTIME_CHECK(
+ dns_rdatalist_tordataset(rdatalist, rdataset) ==
+ ISC_R_SUCCESS);
+ dns_rdataset_setownercase(rdataset, name);
+
+ if (!isedns && !istsig && !issigzero) {
+ ISC_LIST_APPEND(name->list, rdataset, link);
+ free_rdataset = false;
+ }
+ }
+
+ /*
+ * Minimize TTLs.
+ *
+ * Section 5.2 of RFC2181 says we should drop
+ * nonauthoritative rrsets where the TTLs differ, but we
+ * currently treat them the as if they were authoritative and
+ * minimize them.
+ */
+ if (ttl != rdataset->ttl) {
+ rdataset->attributes |= DNS_RDATASETATTR_TTLADJUSTED;
+ if (ttl < rdataset->ttl) {
+ rdataset->ttl = ttl;
+ }
+ }
+
+ /* Append this rdata to the rdataset. */
+ dns_rdatalist_fromrdataset(rdataset, &rdatalist);
+ ISC_LIST_APPEND(rdatalist->rdata, rdata, link);
+
+ /*
+ * If this is an OPT, SIG(0) or TSIG record, remember it.
+ * Also, set the extended rcode for TSIG.
+ *
+ * Note msg->opt, msg->sig0 and msg->tsig will only be
+ * already set if best-effort parsing is enabled otherwise
+ * there will only be at most one of each.
+ */
+ if (isedns) {
+ dns_rcode_t ercode;
+
+ msg->opt = rdataset;
+ rdataset = NULL;
+ free_rdataset = false;
+ ercode = (dns_rcode_t)((msg->opt->ttl &
+ DNS_MESSAGE_EDNSRCODE_MASK) >>
+ 20);
+ msg->rcode |= ercode;
+ dns_message_puttempname(msg, &name);
+ free_name = false;
+ } else if (issigzero) {
+ msg->sig0 = rdataset;
+ msg->sig0name = name;
+ msg->sigstart = recstart;
+ rdataset = NULL;
+ free_rdataset = false;
+ free_name = false;
+ } else if (istsig) {
+ msg->tsig = rdataset;
+ msg->tsigname = name;
+ msg->sigstart = recstart;
+ /*
+ * Windows doesn't like TSIG names to be compressed.
+ */
+ msg->tsigname->attributes |= DNS_NAMEATTR_NOCOMPRESS;
+ rdataset = NULL;
+ free_rdataset = false;
+ free_name = false;
+ }
+
+ if (seen_problem) {
+ if (free_name) {
+ dns_message_puttempname(msg, &name);
+ }
+ if (free_rdataset) {
+ isc_mempool_put(msg->rdspool, rdataset);
+ }
+ free_name = free_rdataset = false;
+ }
+ INSIST(!free_name);
+ INSIST(!free_rdataset);
+ }
+
+ /*
+ * If any of DS, NSEC or NSEC3 appeared in the
+ * authority section of a query response without
+ * a covering RRSIG, FORMERR
+ */
+ if (sectionid == DNS_SECTION_AUTHORITY &&
+ msg->opcode == dns_opcode_query &&
+ ((msg->flags & DNS_MESSAGEFLAG_QR) != 0) &&
+ ((msg->flags & DNS_MESSAGEFLAG_TC) == 0) && !preserve_order &&
+ !auth_signed(section))
+ {
+ DO_ERROR(DNS_R_FORMERR);
+ }
+
+ if (seen_problem) {
+ return (DNS_R_RECOVERABLE);
+ }
+ return (ISC_R_SUCCESS);
+
+cleanup:
+ if (free_name) {
+ dns_message_puttempname(msg, &name);
+ }
+ if (free_rdataset) {
+ isc_mempool_put(msg->rdspool, rdataset);
+ }
+
+ return (result);
+}
+
+isc_result_t
+dns_message_parse(dns_message_t *msg, isc_buffer_t *source,
+ unsigned int options) {
+ isc_region_t r;
+ dns_decompress_t dctx;
+ isc_result_t ret;
+ uint16_t tmpflags;
+ isc_buffer_t origsource;
+ bool seen_problem;
+ bool ignore_tc;
+
+ REQUIRE(DNS_MESSAGE_VALID(msg));
+ REQUIRE(source != NULL);
+ REQUIRE(msg->from_to_wire == DNS_MESSAGE_INTENTPARSE);
+
+ seen_problem = false;
+ ignore_tc = ((options & DNS_MESSAGEPARSE_IGNORETRUNCATION) != 0);
+
+ origsource = *source;
+
+ msg->header_ok = 0;
+ msg->question_ok = 0;
+
+ if ((options & DNS_MESSAGEPARSE_CLONEBUFFER) == 0) {
+ isc_buffer_usedregion(&origsource, &msg->saved);
+ } else {
+ msg->saved.length = isc_buffer_usedlength(&origsource);
+ msg->saved.base = isc_mem_get(msg->mctx, msg->saved.length);
+ memmove(msg->saved.base, isc_buffer_base(&origsource),
+ msg->saved.length);
+ msg->free_saved = 1;
+ }
+
+ isc_buffer_remainingregion(source, &r);
+ if (r.length < DNS_MESSAGE_HEADERLEN) {
+ return (ISC_R_UNEXPECTEDEND);
+ }
+
+ msg->id = isc_buffer_getuint16(source);
+ tmpflags = isc_buffer_getuint16(source);
+ msg->opcode = ((tmpflags & DNS_MESSAGE_OPCODE_MASK) >>
+ DNS_MESSAGE_OPCODE_SHIFT);
+ msg->rcode = (dns_rcode_t)(tmpflags & DNS_MESSAGE_RCODE_MASK);
+ msg->flags = (tmpflags & DNS_MESSAGE_FLAG_MASK);
+ msg->counts[DNS_SECTION_QUESTION] = isc_buffer_getuint16(source);
+ msg->counts[DNS_SECTION_ANSWER] = isc_buffer_getuint16(source);
+ msg->counts[DNS_SECTION_AUTHORITY] = isc_buffer_getuint16(source);
+ msg->counts[DNS_SECTION_ADDITIONAL] = isc_buffer_getuint16(source);
+
+ msg->header_ok = 1;
+ msg->state = DNS_SECTION_QUESTION;
+
+ /*
+ * -1 means no EDNS.
+ */
+ dns_decompress_init(&dctx, -1, DNS_DECOMPRESS_ANY);
+
+ dns_decompress_setmethods(&dctx, DNS_COMPRESS_GLOBAL14);
+
+ ret = getquestions(source, msg, &dctx, options);
+ if (ret == ISC_R_UNEXPECTEDEND && ignore_tc) {
+ goto truncated;
+ }
+ if (ret == DNS_R_RECOVERABLE) {
+ seen_problem = true;
+ ret = ISC_R_SUCCESS;
+ }
+ if (ret != ISC_R_SUCCESS) {
+ return (ret);
+ }
+ msg->question_ok = 1;
+
+ ret = getsection(source, msg, &dctx, DNS_SECTION_ANSWER, options);
+ if (ret == ISC_R_UNEXPECTEDEND && ignore_tc) {
+ goto truncated;
+ }
+ if (ret == DNS_R_RECOVERABLE) {
+ seen_problem = true;
+ ret = ISC_R_SUCCESS;
+ }
+ if (ret != ISC_R_SUCCESS) {
+ return (ret);
+ }
+
+ ret = getsection(source, msg, &dctx, DNS_SECTION_AUTHORITY, options);
+ if (ret == ISC_R_UNEXPECTEDEND && ignore_tc) {
+ goto truncated;
+ }
+ if (ret == DNS_R_RECOVERABLE) {
+ seen_problem = true;
+ ret = ISC_R_SUCCESS;
+ }
+ if (ret != ISC_R_SUCCESS) {
+ return (ret);
+ }
+
+ ret = getsection(source, msg, &dctx, DNS_SECTION_ADDITIONAL, options);
+ if (ret == ISC_R_UNEXPECTEDEND && ignore_tc) {
+ goto truncated;
+ }
+ if (ret == DNS_R_RECOVERABLE) {
+ seen_problem = true;
+ ret = ISC_R_SUCCESS;
+ }
+ if (ret != ISC_R_SUCCESS) {
+ return (ret);
+ }
+
+ isc_buffer_remainingregion(source, &r);
+ if (r.length != 0) {
+ isc_log_write(dns_lctx, ISC_LOGCATEGORY_GENERAL,
+ DNS_LOGMODULE_MESSAGE, ISC_LOG_DEBUG(3),
+ "message has %u byte(s) of trailing garbage",
+ r.length);
+ }
+
+truncated:
+
+ if (ret == ISC_R_UNEXPECTEDEND && ignore_tc) {
+ return (DNS_R_RECOVERABLE);
+ }
+ if (seen_problem) {
+ return (DNS_R_RECOVERABLE);
+ }
+ return (ISC_R_SUCCESS);
+}
+
+isc_result_t
+dns_message_renderbegin(dns_message_t *msg, dns_compress_t *cctx,
+ isc_buffer_t *buffer) {
+ isc_region_t r;
+
+ REQUIRE(DNS_MESSAGE_VALID(msg));
+ REQUIRE(buffer != NULL);
+ REQUIRE(msg->buffer == NULL);
+ REQUIRE(msg->from_to_wire == DNS_MESSAGE_INTENTRENDER);
+
+ msg->cctx = cctx;
+
+ /*
+ * Erase the contents of this buffer.
+ */
+ isc_buffer_clear(buffer);
+
+ /*
+ * Make certain there is enough for at least the header in this
+ * buffer.
+ */
+ isc_buffer_availableregion(buffer, &r);
+ if (r.length < DNS_MESSAGE_HEADERLEN) {
+ return (ISC_R_NOSPACE);
+ }
+
+ if (r.length - DNS_MESSAGE_HEADERLEN < msg->reserved) {
+ return (ISC_R_NOSPACE);
+ }
+
+ /*
+ * Reserve enough space for the header in this buffer.
+ */
+ isc_buffer_add(buffer, DNS_MESSAGE_HEADERLEN);
+
+ msg->buffer = buffer;
+
+ return (ISC_R_SUCCESS);
+}
+
+isc_result_t
+dns_message_renderchangebuffer(dns_message_t *msg, isc_buffer_t *buffer) {
+ isc_region_t r, rn;
+
+ REQUIRE(DNS_MESSAGE_VALID(msg));
+ REQUIRE(buffer != NULL);
+ REQUIRE(msg->buffer != NULL);
+
+ /*
+ * Ensure that the new buffer is empty, and has enough space to
+ * hold the current contents.
+ */
+ isc_buffer_clear(buffer);
+
+ isc_buffer_availableregion(buffer, &rn);
+ isc_buffer_usedregion(msg->buffer, &r);
+ REQUIRE(rn.length > r.length);
+
+ /*
+ * Copy the contents from the old to the new buffer.
+ */
+ isc_buffer_add(buffer, r.length);
+ memmove(rn.base, r.base, r.length);
+
+ msg->buffer = buffer;
+
+ return (ISC_R_SUCCESS);
+}
+
+void
+dns_message_renderrelease(dns_message_t *msg, unsigned int space) {
+ REQUIRE(DNS_MESSAGE_VALID(msg));
+ REQUIRE(space <= msg->reserved);
+
+ msg->reserved -= space;
+}
+
+isc_result_t
+dns_message_renderreserve(dns_message_t *msg, unsigned int space) {
+ isc_region_t r;
+
+ REQUIRE(DNS_MESSAGE_VALID(msg));
+
+ if (msg->buffer != NULL) {
+ isc_buffer_availableregion(msg->buffer, &r);
+ if (r.length < (space + msg->reserved)) {
+ return (ISC_R_NOSPACE);
+ }
+ }
+
+ msg->reserved += space;
+
+ return (ISC_R_SUCCESS);
+}
+
+static bool
+wrong_priority(dns_rdataset_t *rds, int pass, dns_rdatatype_t preferred_glue) {
+ int pass_needed;
+
+ /*
+ * If we are not rendering class IN, this ordering is bogus.
+ */
+ if (rds->rdclass != dns_rdataclass_in) {
+ return (false);
+ }
+
+ switch (rds->type) {
+ case dns_rdatatype_a:
+ case dns_rdatatype_aaaa:
+ if (preferred_glue == rds->type) {
+ pass_needed = 4;
+ } else {
+ pass_needed = 3;
+ }
+ break;
+ case dns_rdatatype_rrsig:
+ case dns_rdatatype_dnskey:
+ pass_needed = 2;
+ break;
+ default:
+ pass_needed = 1;
+ }
+
+ if (pass_needed >= pass) {
+ return (false);
+ }
+
+ return (true);
+}
+
+static isc_result_t
+renderset(dns_rdataset_t *rdataset, const dns_name_t *owner_name,
+ dns_compress_t *cctx, isc_buffer_t *target, unsigned int reserved,
+ unsigned int options, unsigned int *countp) {
+ isc_result_t result;
+
+ /*
+ * Shrink the space in the buffer by the reserved amount.
+ */
+ if (target->length - target->used < reserved) {
+ return (ISC_R_NOSPACE);
+ }
+
+ target->length -= reserved;
+ result = dns_rdataset_towire(rdataset, owner_name, cctx, target,
+ options, countp);
+ target->length += reserved;
+
+ return (result);
+}
+
+static void
+maybe_clear_ad(dns_message_t *msg, dns_section_t sectionid) {
+ if (msg->counts[sectionid] == 0 &&
+ (sectionid == DNS_SECTION_ANSWER ||
+ (sectionid == DNS_SECTION_AUTHORITY &&
+ msg->counts[DNS_SECTION_ANSWER] == 0)))
+ {
+ msg->flags &= ~DNS_MESSAGEFLAG_AD;
+ }
+}
+
+static void
+update_min_section_ttl(dns_message_t *restrict msg,
+ const dns_section_t sectionid,
+ dns_rdataset_t *restrict rdataset) {
+ if (!msg->minttl[sectionid].is_set ||
+ rdataset->ttl < msg->minttl[sectionid].ttl)
+ {
+ msg->minttl[sectionid].is_set = true;
+ msg->minttl[sectionid].ttl = rdataset->ttl;
+ }
+}
+
+isc_result_t
+dns_message_rendersection(dns_message_t *msg, dns_section_t sectionid,
+ unsigned int options) {
+ dns_namelist_t *section;
+ dns_name_t *name, *next_name;
+ dns_rdataset_t *rdataset, *next_rdataset;
+ unsigned int count, total;
+ isc_result_t result;
+ isc_buffer_t st; /* for rollbacks */
+ int pass;
+ bool partial = false;
+ unsigned int rd_options;
+ dns_rdatatype_t preferred_glue = 0;
+
+ REQUIRE(DNS_MESSAGE_VALID(msg));
+ REQUIRE(msg->buffer != NULL);
+ REQUIRE(VALID_NAMED_SECTION(sectionid));
+
+ section = &msg->sections[sectionid];
+
+ if ((sectionid == DNS_SECTION_ADDITIONAL) &&
+ (options & DNS_MESSAGERENDER_ORDERED) == 0)
+ {
+ if ((options & DNS_MESSAGERENDER_PREFER_A) != 0) {
+ preferred_glue = dns_rdatatype_a;
+ pass = 4;
+ } else if ((options & DNS_MESSAGERENDER_PREFER_AAAA) != 0) {
+ preferred_glue = dns_rdatatype_aaaa;
+ pass = 4;
+ } else {
+ pass = 3;
+ }
+ } else {
+ pass = 1;
+ }
+
+ if ((options & DNS_MESSAGERENDER_OMITDNSSEC) == 0) {
+ rd_options = 0;
+ } else {
+ rd_options = DNS_RDATASETTOWIRE_OMITDNSSEC;
+ }
+
+ /*
+ * Shrink the space in the buffer by the reserved amount.
+ */
+ if (msg->buffer->length - msg->buffer->used < msg->reserved) {
+ return (ISC_R_NOSPACE);
+ }
+ msg->buffer->length -= msg->reserved;
+
+ total = 0;
+ if (msg->reserved == 0 && (options & DNS_MESSAGERENDER_PARTIAL) != 0) {
+ partial = true;
+ }
+
+ /*
+ * Render required glue first. Set TC if it won't fit.
+ */
+ name = ISC_LIST_HEAD(*section);
+ if (name != NULL) {
+ rdataset = ISC_LIST_HEAD(name->list);
+ if (rdataset != NULL &&
+ (rdataset->attributes & DNS_RDATASETATTR_REQUIREDGLUE) !=
+ 0 &&
+ (rdataset->attributes & DNS_RDATASETATTR_RENDERED) == 0)
+ {
+ const void *order_arg = &msg->order_arg;
+ st = *(msg->buffer);
+ count = 0;
+ if (partial) {
+ result = dns_rdataset_towirepartial(
+ rdataset, name, msg->cctx, msg->buffer,
+ msg->order, order_arg, rd_options,
+ &count, NULL);
+ } else {
+ result = dns_rdataset_towiresorted(
+ rdataset, name, msg->cctx, msg->buffer,
+ msg->order, order_arg, rd_options,
+ &count);
+ }
+ total += count;
+ if (partial && result == ISC_R_NOSPACE) {
+ msg->flags |= DNS_MESSAGEFLAG_TC;
+ msg->buffer->length += msg->reserved;
+ msg->counts[sectionid] += total;
+ return (result);
+ }
+ if (result == ISC_R_NOSPACE) {
+ msg->flags |= DNS_MESSAGEFLAG_TC;
+ }
+ if (result != ISC_R_SUCCESS) {
+ INSIST(st.used < 65536);
+ dns_compress_rollback(msg->cctx,
+ (uint16_t)st.used);
+ *(msg->buffer) = st; /* rollback */
+ msg->buffer->length += msg->reserved;
+ msg->counts[sectionid] += total;
+ return (result);
+ }
+
+ update_min_section_ttl(msg, sectionid, rdataset);
+
+ rdataset->attributes |= DNS_RDATASETATTR_RENDERED;
+ }
+ }
+
+ do {
+ name = ISC_LIST_HEAD(*section);
+ if (name == NULL) {
+ msg->buffer->length += msg->reserved;
+ msg->counts[sectionid] += total;
+ return (ISC_R_SUCCESS);
+ }
+
+ while (name != NULL) {
+ next_name = ISC_LIST_NEXT(name, link);
+
+ rdataset = ISC_LIST_HEAD(name->list);
+ while (rdataset != NULL) {
+ next_rdataset = ISC_LIST_NEXT(rdataset, link);
+
+ if ((rdataset->attributes &
+ DNS_RDATASETATTR_RENDERED) != 0)
+ {
+ goto next;
+ }
+
+ if (((options & DNS_MESSAGERENDER_ORDERED) ==
+ 0) &&
+ (sectionid == DNS_SECTION_ADDITIONAL) &&
+ wrong_priority(rdataset, pass,
+ preferred_glue))
+ {
+ goto next;
+ }
+
+ st = *(msg->buffer);
+
+ count = 0;
+ if (partial) {
+ result = dns_rdataset_towirepartial(
+ rdataset, name, msg->cctx,
+ msg->buffer, msg->order,
+ &msg->order_arg, rd_options,
+ &count, NULL);
+ } else {
+ result = dns_rdataset_towiresorted(
+ rdataset, name, msg->cctx,
+ msg->buffer, msg->order,
+ &msg->order_arg, rd_options,
+ &count);
+ }
+
+ total += count;
+
+ /*
+ * If out of space, record stats on what we
+ * rendered so far, and return that status.
+ *
+ * XXXMLG Need to change this when
+ * dns_rdataset_towire() can render partial
+ * sets starting at some arbitrary point in the
+ * set. This will include setting a bit in the
+ * rdataset to indicate that a partial
+ * rendering was done, and some state saved
+ * somewhere (probably in the message struct)
+ * to indicate where to continue from.
+ */
+ if (partial && result == ISC_R_NOSPACE) {
+ msg->buffer->length += msg->reserved;
+ msg->counts[sectionid] += total;
+ return (result);
+ }
+ if (result != ISC_R_SUCCESS) {
+ INSIST(st.used < 65536);
+ dns_compress_rollback(
+ msg->cctx, (uint16_t)st.used);
+ *(msg->buffer) = st; /* rollback */
+ msg->buffer->length += msg->reserved;
+ msg->counts[sectionid] += total;
+ maybe_clear_ad(msg, sectionid);
+ return (result);
+ }
+
+ /*
+ * If we have rendered non-validated data,
+ * ensure that the AD bit is not set.
+ */
+ if (rdataset->trust != dns_trust_secure &&
+ (sectionid == DNS_SECTION_ANSWER ||
+ sectionid == DNS_SECTION_AUTHORITY))
+ {
+ msg->flags &= ~DNS_MESSAGEFLAG_AD;
+ }
+ if (OPTOUT(rdataset)) {
+ msg->flags &= ~DNS_MESSAGEFLAG_AD;
+ }
+
+ update_min_section_ttl(msg, sectionid,
+ rdataset);
+
+ rdataset->attributes |=
+ DNS_RDATASETATTR_RENDERED;
+
+ next:
+ rdataset = next_rdataset;
+ }
+
+ name = next_name;
+ }
+ } while (--pass != 0);
+
+ msg->buffer->length += msg->reserved;
+ msg->counts[sectionid] += total;
+
+ return (ISC_R_SUCCESS);
+}
+
+void
+dns_message_renderheader(dns_message_t *msg, isc_buffer_t *target) {
+ uint16_t tmp;
+ isc_region_t r;
+
+ REQUIRE(DNS_MESSAGE_VALID(msg));
+ REQUIRE(target != NULL);
+
+ isc_buffer_availableregion(target, &r);
+ REQUIRE(r.length >= DNS_MESSAGE_HEADERLEN);
+
+ isc_buffer_putuint16(target, msg->id);
+
+ tmp = ((msg->opcode << DNS_MESSAGE_OPCODE_SHIFT) &
+ DNS_MESSAGE_OPCODE_MASK);
+ tmp |= (msg->rcode & DNS_MESSAGE_RCODE_MASK);
+ tmp |= (msg->flags & DNS_MESSAGE_FLAG_MASK);
+
+ INSIST(msg->counts[DNS_SECTION_QUESTION] < 65536 &&
+ msg->counts[DNS_SECTION_ANSWER] < 65536 &&
+ msg->counts[DNS_SECTION_AUTHORITY] < 65536 &&
+ msg->counts[DNS_SECTION_ADDITIONAL] < 65536);
+
+ isc_buffer_putuint16(target, tmp);
+ isc_buffer_putuint16(target,
+ (uint16_t)msg->counts[DNS_SECTION_QUESTION]);
+ isc_buffer_putuint16(target, (uint16_t)msg->counts[DNS_SECTION_ANSWER]);
+ isc_buffer_putuint16(target,
+ (uint16_t)msg->counts[DNS_SECTION_AUTHORITY]);
+ isc_buffer_putuint16(target,
+ (uint16_t)msg->counts[DNS_SECTION_ADDITIONAL]);
+}
+
+isc_result_t
+dns_message_renderend(dns_message_t *msg) {
+ isc_buffer_t tmpbuf;
+ isc_region_t r;
+ int result;
+ unsigned int count;
+
+ REQUIRE(DNS_MESSAGE_VALID(msg));
+ REQUIRE(msg->buffer != NULL);
+
+ if ((msg->rcode & ~DNS_MESSAGE_RCODE_MASK) != 0 && msg->opt == NULL) {
+ /*
+ * We have an extended rcode but are not using EDNS.
+ */
+ return (DNS_R_FORMERR);
+ }
+
+ /*
+ * If we're adding a OPT, TSIG or SIG(0) to a truncated message,
+ * clear all rdatasets from the message except for the question
+ * before adding the OPT, TSIG or SIG(0). If the question doesn't
+ * fit, don't include it.
+ */
+ if ((msg->tsigkey != NULL || msg->sig0key != NULL || msg->opt) &&
+ (msg->flags & DNS_MESSAGEFLAG_TC) != 0)
+ {
+ isc_buffer_t *buf;
+
+ msgresetnames(msg, DNS_SECTION_ANSWER);
+ buf = msg->buffer;
+ dns_message_renderreset(msg);
+ msg->buffer = buf;
+ isc_buffer_clear(msg->buffer);
+ isc_buffer_add(msg->buffer, DNS_MESSAGE_HEADERLEN);
+ dns_compress_rollback(msg->cctx, 0);
+ result = dns_message_rendersection(msg, DNS_SECTION_QUESTION,
+ 0);
+ if (result != ISC_R_SUCCESS && result != ISC_R_NOSPACE) {
+ return (result);
+ }
+ }
+
+ /*
+ * If we've got an OPT record, render it.
+ */
+ if (msg->opt != NULL) {
+ dns_message_renderrelease(msg, msg->opt_reserved);
+ msg->opt_reserved = 0;
+ /*
+ * Set the extended rcode. Cast msg->rcode to dns_ttl_t
+ * so that we do a unsigned shift.
+ */
+ msg->opt->ttl &= ~DNS_MESSAGE_EDNSRCODE_MASK;
+ msg->opt->ttl |= (((dns_ttl_t)(msg->rcode) << 20) &
+ DNS_MESSAGE_EDNSRCODE_MASK);
+ /*
+ * Render.
+ */
+ count = 0;
+ result = renderset(msg->opt, dns_rootname, msg->cctx,
+ msg->buffer, msg->reserved, 0, &count);
+ msg->counts[DNS_SECTION_ADDITIONAL] += count;
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+ }
+
+ /*
+ * Deal with EDNS padding.
+ *
+ * padding_off is the length of the OPT with the 0-length PAD
+ * at the end.
+ */
+ if (msg->padding_off > 0) {
+ unsigned char *cp = isc_buffer_used(msg->buffer);
+ unsigned int used, remaining;
+ uint16_t len, padsize = 0;
+
+ /* Check PAD */
+ if ((cp[-4] != 0) || (cp[-3] != DNS_OPT_PAD) || (cp[-2] != 0) ||
+ (cp[-1] != 0))
+ {
+ return (ISC_R_UNEXPECTED);
+ }
+
+ /*
+ * Zero-fill the PAD to the computed size;
+ * patch PAD length and OPT rdlength
+ */
+
+ /* Aligned used length + reserved to padding block */
+ used = isc_buffer_usedlength(msg->buffer);
+ if (msg->padding != 0) {
+ padsize = ((uint16_t)used + msg->reserved) %
+ msg->padding;
+ }
+ if (padsize != 0) {
+ padsize = msg->padding - padsize;
+ }
+ /* Stay below the available length */
+ remaining = isc_buffer_availablelength(msg->buffer);
+ if (padsize > remaining) {
+ padsize = remaining;
+ }
+
+ isc_buffer_add(msg->buffer, padsize);
+ memset(cp, 0, padsize);
+ cp[-2] = (unsigned char)((padsize & 0xff00U) >> 8);
+ cp[-1] = (unsigned char)(padsize & 0x00ffU);
+ cp -= msg->padding_off;
+ len = ((uint16_t)(cp[-2])) << 8;
+ len |= ((uint16_t)(cp[-1]));
+ len += padsize;
+ cp[-2] = (unsigned char)((len & 0xff00U) >> 8);
+ cp[-1] = (unsigned char)(len & 0x00ffU);
+ }
+
+ /*
+ * If we're adding a TSIG record, generate and render it.
+ */
+ if (msg->tsigkey != NULL) {
+ dns_message_renderrelease(msg, msg->sig_reserved);
+ msg->sig_reserved = 0;
+ result = dns_tsig_sign(msg);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+ count = 0;
+ result = renderset(msg->tsig, msg->tsigname, msg->cctx,
+ msg->buffer, msg->reserved, 0, &count);
+ msg->counts[DNS_SECTION_ADDITIONAL] += count;
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+ }
+
+ /*
+ * If we're adding a SIG(0) record, generate and render it.
+ */
+ if (msg->sig0key != NULL) {
+ dns_message_renderrelease(msg, msg->sig_reserved);
+ msg->sig_reserved = 0;
+ result = dns_dnssec_signmessage(msg, msg->sig0key);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+ count = 0;
+ /*
+ * Note: dns_rootname is used here, not msg->sig0name, since
+ * the owner name of a SIG(0) is irrelevant, and will not
+ * be set in a message being rendered.
+ */
+ result = renderset(msg->sig0, dns_rootname, msg->cctx,
+ msg->buffer, msg->reserved, 0, &count);
+ msg->counts[DNS_SECTION_ADDITIONAL] += count;
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+ }
+
+ isc_buffer_usedregion(msg->buffer, &r);
+ isc_buffer_init(&tmpbuf, r.base, r.length);
+
+ dns_message_renderheader(msg, &tmpbuf);
+
+ msg->buffer = NULL; /* forget about this buffer only on success XXX */
+
+ return (ISC_R_SUCCESS);
+}
+
+void
+dns_message_renderreset(dns_message_t *msg) {
+ unsigned int i;
+ dns_name_t *name;
+ dns_rdataset_t *rds;
+
+ /*
+ * Reset the message so that it may be rendered again.
+ */
+
+ REQUIRE(DNS_MESSAGE_VALID(msg));
+ REQUIRE(msg->from_to_wire == DNS_MESSAGE_INTENTRENDER);
+
+ msg->buffer = NULL;
+
+ for (i = 0; i < DNS_SECTION_MAX; i++) {
+ msg->cursors[i] = NULL;
+ msg->counts[i] = 0;
+ for (name = ISC_LIST_HEAD(msg->sections[i]); name != NULL;
+ name = ISC_LIST_NEXT(name, link))
+ {
+ for (rds = ISC_LIST_HEAD(name->list); rds != NULL;
+ rds = ISC_LIST_NEXT(rds, link))
+ {
+ rds->attributes &= ~DNS_RDATASETATTR_RENDERED;
+ }
+ }
+ }
+ if (msg->tsigname != NULL) {
+ dns_message_puttempname(msg, &msg->tsigname);
+ }
+ if (msg->tsig != NULL) {
+ dns_rdataset_disassociate(msg->tsig);
+ dns_message_puttemprdataset(msg, &msg->tsig);
+ }
+ if (msg->sig0name != NULL) {
+ dns_message_puttempname(msg, &msg->sig0name);
+ }
+ if (msg->sig0 != NULL) {
+ dns_rdataset_disassociate(msg->sig0);
+ dns_message_puttemprdataset(msg, &msg->sig0);
+ }
+}
+
+isc_result_t
+dns_message_firstname(dns_message_t *msg, dns_section_t section) {
+ REQUIRE(DNS_MESSAGE_VALID(msg));
+ REQUIRE(VALID_NAMED_SECTION(section));
+
+ msg->cursors[section] = ISC_LIST_HEAD(msg->sections[section]);
+
+ if (msg->cursors[section] == NULL) {
+ return (ISC_R_NOMORE);
+ }
+
+ return (ISC_R_SUCCESS);
+}
+
+isc_result_t
+dns_message_nextname(dns_message_t *msg, dns_section_t section) {
+ REQUIRE(DNS_MESSAGE_VALID(msg));
+ REQUIRE(VALID_NAMED_SECTION(section));
+ REQUIRE(msg->cursors[section] != NULL);
+
+ msg->cursors[section] = ISC_LIST_NEXT(msg->cursors[section], link);
+
+ if (msg->cursors[section] == NULL) {
+ return (ISC_R_NOMORE);
+ }
+
+ return (ISC_R_SUCCESS);
+}
+
+void
+dns_message_currentname(dns_message_t *msg, dns_section_t section,
+ dns_name_t **name) {
+ REQUIRE(DNS_MESSAGE_VALID(msg));
+ REQUIRE(VALID_NAMED_SECTION(section));
+ REQUIRE(name != NULL && *name == NULL);
+ REQUIRE(msg->cursors[section] != NULL);
+
+ *name = msg->cursors[section];
+}
+
+isc_result_t
+dns_message_findname(dns_message_t *msg, dns_section_t section,
+ const dns_name_t *target, dns_rdatatype_t type,
+ dns_rdatatype_t covers, dns_name_t **name,
+ dns_rdataset_t **rdataset) {
+ dns_name_t *foundname;
+ isc_result_t result;
+
+ /*
+ * XXX These requirements are probably too intensive, especially
+ * where things can be NULL, but as they are they ensure that if
+ * something is NON-NULL, indicating that the caller expects it
+ * to be filled in, that we can in fact fill it in.
+ */
+ REQUIRE(msg != NULL);
+ REQUIRE(VALID_SECTION(section));
+ REQUIRE(target != NULL);
+ REQUIRE(name == NULL || *name == NULL);
+
+ if (type == dns_rdatatype_any) {
+ REQUIRE(rdataset == NULL);
+ } else {
+ REQUIRE(rdataset == NULL || *rdataset == NULL);
+ }
+
+ result = findname(&foundname, target, &msg->sections[section]);
+
+ if (result == ISC_R_NOTFOUND) {
+ return (DNS_R_NXDOMAIN);
+ } else if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+
+ if (name != NULL) {
+ *name = foundname;
+ }
+
+ /*
+ * And now look for the type.
+ */
+ if (type == dns_rdatatype_any) {
+ return (ISC_R_SUCCESS);
+ }
+
+ result = dns_message_findtype(foundname, type, covers, rdataset);
+ if (result == ISC_R_NOTFOUND) {
+ return (DNS_R_NXRRSET);
+ }
+
+ return (result);
+}
+
+void
+dns_message_movename(dns_message_t *msg, dns_name_t *name,
+ dns_section_t fromsection, dns_section_t tosection) {
+ REQUIRE(msg != NULL);
+ REQUIRE(msg->from_to_wire == DNS_MESSAGE_INTENTRENDER);
+ REQUIRE(name != NULL);
+ REQUIRE(VALID_NAMED_SECTION(fromsection));
+ REQUIRE(VALID_NAMED_SECTION(tosection));
+
+ /*
+ * Unlink the name from the old section
+ */
+ ISC_LIST_UNLINK(msg->sections[fromsection], name, link);
+ ISC_LIST_APPEND(msg->sections[tosection], name, link);
+}
+
+void
+dns_message_addname(dns_message_t *msg, dns_name_t *name,
+ dns_section_t section) {
+ REQUIRE(msg != NULL);
+ REQUIRE(msg->from_to_wire == DNS_MESSAGE_INTENTRENDER);
+ REQUIRE(name != NULL);
+ REQUIRE(VALID_NAMED_SECTION(section));
+
+ ISC_LIST_APPEND(msg->sections[section], name, link);
+}
+
+void
+dns_message_removename(dns_message_t *msg, dns_name_t *name,
+ dns_section_t section) {
+ REQUIRE(msg != NULL);
+ REQUIRE(msg->from_to_wire == DNS_MESSAGE_INTENTRENDER);
+ REQUIRE(name != NULL);
+ REQUIRE(VALID_NAMED_SECTION(section));
+
+ ISC_LIST_UNLINK(msg->sections[section], name, link);
+}
+
+isc_result_t
+dns_message_gettempname(dns_message_t *msg, dns_name_t **item) {
+ dns_fixedname_t *fn = NULL;
+
+ REQUIRE(DNS_MESSAGE_VALID(msg));
+ REQUIRE(item != NULL && *item == NULL);
+
+ fn = isc_mempool_get(msg->namepool);
+ *item = dns_fixedname_initname(fn);
+
+ return (ISC_R_SUCCESS);
+}
+
+isc_result_t
+dns_message_gettemprdata(dns_message_t *msg, dns_rdata_t **item) {
+ REQUIRE(DNS_MESSAGE_VALID(msg));
+ REQUIRE(item != NULL && *item == NULL);
+
+ *item = newrdata(msg);
+ return (ISC_R_SUCCESS);
+}
+
+isc_result_t
+dns_message_gettemprdataset(dns_message_t *msg, dns_rdataset_t **item) {
+ REQUIRE(DNS_MESSAGE_VALID(msg));
+ REQUIRE(item != NULL && *item == NULL);
+
+ *item = isc_mempool_get(msg->rdspool);
+ dns_rdataset_init(*item);
+ return (ISC_R_SUCCESS);
+}
+
+isc_result_t
+dns_message_gettemprdatalist(dns_message_t *msg, dns_rdatalist_t **item) {
+ REQUIRE(DNS_MESSAGE_VALID(msg));
+ REQUIRE(item != NULL && *item == NULL);
+
+ *item = newrdatalist(msg);
+ return (ISC_R_SUCCESS);
+}
+
+void
+dns_message_puttempname(dns_message_t *msg, dns_name_t **itemp) {
+ dns_name_t *item = NULL;
+
+ REQUIRE(DNS_MESSAGE_VALID(msg));
+ REQUIRE(itemp != NULL && *itemp != NULL);
+
+ item = *itemp;
+ *itemp = NULL;
+
+ REQUIRE(!ISC_LINK_LINKED(item, link));
+ REQUIRE(ISC_LIST_HEAD(item->list) == NULL);
+
+ /*
+ * we need to check this in case dns_name_dup() was used.
+ */
+ if (dns_name_dynamic(item)) {
+ dns_name_free(item, msg->mctx);
+ }
+
+ /*
+ * 'name' is the first field in dns_fixedname_t, so putting
+ * back the address of name is the same as putting back
+ * the fixedname.
+ */
+ isc_mempool_put(msg->namepool, item);
+}
+
+void
+dns_message_puttemprdata(dns_message_t *msg, dns_rdata_t **item) {
+ REQUIRE(DNS_MESSAGE_VALID(msg));
+ REQUIRE(item != NULL && *item != NULL);
+
+ releaserdata(msg, *item);
+ *item = NULL;
+}
+
+void
+dns_message_puttemprdataset(dns_message_t *msg, dns_rdataset_t **item) {
+ REQUIRE(DNS_MESSAGE_VALID(msg));
+ REQUIRE(item != NULL && *item != NULL);
+
+ REQUIRE(!dns_rdataset_isassociated(*item));
+ isc_mempool_put(msg->rdspool, *item);
+ *item = NULL;
+}
+
+void
+dns_message_puttemprdatalist(dns_message_t *msg, dns_rdatalist_t **item) {
+ REQUIRE(DNS_MESSAGE_VALID(msg));
+ REQUIRE(item != NULL && *item != NULL);
+
+ releaserdatalist(msg, *item);
+ *item = NULL;
+}
+
+isc_result_t
+dns_message_peekheader(isc_buffer_t *source, dns_messageid_t *idp,
+ unsigned int *flagsp) {
+ isc_region_t r;
+ isc_buffer_t buffer;
+ dns_messageid_t id;
+ unsigned int flags;
+
+ REQUIRE(source != NULL);
+
+ buffer = *source;
+
+ isc_buffer_remainingregion(&buffer, &r);
+ if (r.length < DNS_MESSAGE_HEADERLEN) {
+ return (ISC_R_UNEXPECTEDEND);
+ }
+
+ id = isc_buffer_getuint16(&buffer);
+ flags = isc_buffer_getuint16(&buffer);
+ flags &= DNS_MESSAGE_FLAG_MASK;
+
+ if (flagsp != NULL) {
+ *flagsp = flags;
+ }
+ if (idp != NULL) {
+ *idp = id;
+ }
+
+ return (ISC_R_SUCCESS);
+}
+
+isc_result_t
+dns_message_reply(dns_message_t *msg, bool want_question_section) {
+ unsigned int clear_from;
+ isc_result_t result;
+
+ REQUIRE(DNS_MESSAGE_VALID(msg));
+ REQUIRE((msg->flags & DNS_MESSAGEFLAG_QR) == 0);
+
+ if (!msg->header_ok) {
+ return (DNS_R_FORMERR);
+ }
+ if (msg->opcode != dns_opcode_query && msg->opcode != dns_opcode_notify)
+ {
+ want_question_section = false;
+ }
+ if (msg->opcode == dns_opcode_update) {
+ clear_from = DNS_SECTION_PREREQUISITE;
+ } else if (want_question_section) {
+ if (!msg->question_ok) {
+ return (DNS_R_FORMERR);
+ }
+ clear_from = DNS_SECTION_ANSWER;
+ } else {
+ clear_from = DNS_SECTION_QUESTION;
+ }
+ msg->from_to_wire = DNS_MESSAGE_INTENTRENDER;
+ msgresetnames(msg, clear_from);
+ msgresetopt(msg);
+ msgresetsigs(msg, true);
+ msginitprivate(msg);
+ /*
+ * We now clear most flags and then set QR, ensuring that the
+ * reply's flags will be in a reasonable state.
+ */
+ if (msg->opcode == dns_opcode_query) {
+ msg->flags &= DNS_MESSAGE_REPLYPRESERVE;
+ } else {
+ msg->flags = 0;
+ }
+ msg->flags |= DNS_MESSAGEFLAG_QR;
+
+ /*
+ * This saves the query TSIG status, if the query was signed, and
+ * reserves space in the reply for the TSIG.
+ */
+ if (msg->tsigkey != NULL) {
+ unsigned int otherlen = 0;
+ msg->querytsigstatus = msg->tsigstatus;
+ msg->tsigstatus = dns_rcode_noerror;
+ if (msg->querytsigstatus == dns_tsigerror_badtime) {
+ otherlen = 6;
+ }
+ msg->sig_reserved = spacefortsig(msg->tsigkey, otherlen);
+ result = dns_message_renderreserve(msg, msg->sig_reserved);
+ if (result != ISC_R_SUCCESS) {
+ msg->sig_reserved = 0;
+ return (result);
+ }
+ }
+ if (msg->saved.base != NULL) {
+ msg->query.base = msg->saved.base;
+ msg->query.length = msg->saved.length;
+ msg->free_query = msg->free_saved;
+ msg->saved.base = NULL;
+ msg->saved.length = 0;
+ msg->free_saved = 0;
+ }
+
+ return (ISC_R_SUCCESS);
+}
+
+dns_rdataset_t *
+dns_message_getopt(dns_message_t *msg) {
+ /*
+ * Get the OPT record for 'msg'.
+ */
+
+ REQUIRE(DNS_MESSAGE_VALID(msg));
+
+ return (msg->opt);
+}
+
+isc_result_t
+dns_message_setopt(dns_message_t *msg, dns_rdataset_t *opt) {
+ isc_result_t result;
+ dns_rdata_t rdata = DNS_RDATA_INIT;
+
+ /*
+ * Set the OPT record for 'msg'.
+ */
+
+ /*
+ * The space required for an OPT record is:
+ *
+ * 1 byte for the name
+ * 2 bytes for the type
+ * 2 bytes for the class
+ * 4 bytes for the ttl
+ * 2 bytes for the rdata length
+ * ---------------------------------
+ * 11 bytes
+ *
+ * plus the length of the rdata.
+ */
+
+ REQUIRE(DNS_MESSAGE_VALID(msg));
+ REQUIRE(opt->type == dns_rdatatype_opt);
+ REQUIRE(msg->from_to_wire == DNS_MESSAGE_INTENTRENDER);
+ REQUIRE(msg->state == DNS_SECTION_ANY);
+
+ msgresetopt(msg);
+
+ result = dns_rdataset_first(opt);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+ dns_rdataset_current(opt, &rdata);
+ msg->opt_reserved = 11 + rdata.length;
+ result = dns_message_renderreserve(msg, msg->opt_reserved);
+ if (result != ISC_R_SUCCESS) {
+ msg->opt_reserved = 0;
+ goto cleanup;
+ }
+
+ msg->opt = opt;
+
+ return (ISC_R_SUCCESS);
+
+cleanup:
+ dns_rdataset_disassociate(opt);
+ dns_message_puttemprdataset(msg, &opt);
+ return (result);
+}
+
+dns_rdataset_t *
+dns_message_gettsig(dns_message_t *msg, const dns_name_t **owner) {
+ /*
+ * Get the TSIG record and owner for 'msg'.
+ */
+
+ REQUIRE(DNS_MESSAGE_VALID(msg));
+ REQUIRE(owner == NULL || *owner == NULL);
+
+ if (owner != NULL) {
+ *owner = msg->tsigname;
+ }
+ return (msg->tsig);
+}
+
+isc_result_t
+dns_message_settsigkey(dns_message_t *msg, dns_tsigkey_t *key) {
+ isc_result_t result;
+
+ /*
+ * Set the TSIG key for 'msg'
+ */
+
+ REQUIRE(DNS_MESSAGE_VALID(msg));
+
+ if (key == NULL && msg->tsigkey != NULL) {
+ if (msg->sig_reserved != 0) {
+ dns_message_renderrelease(msg, msg->sig_reserved);
+ msg->sig_reserved = 0;
+ }
+ dns_tsigkey_detach(&msg->tsigkey);
+ }
+ if (key != NULL) {
+ REQUIRE(msg->tsigkey == NULL && msg->sig0key == NULL);
+ dns_tsigkey_attach(key, &msg->tsigkey);
+ if (msg->from_to_wire == DNS_MESSAGE_INTENTRENDER) {
+ msg->sig_reserved = spacefortsig(msg->tsigkey, 0);
+ result = dns_message_renderreserve(msg,
+ msg->sig_reserved);
+ if (result != ISC_R_SUCCESS) {
+ dns_tsigkey_detach(&msg->tsigkey);
+ msg->sig_reserved = 0;
+ return (result);
+ }
+ }
+ }
+ return (ISC_R_SUCCESS);
+}
+
+dns_tsigkey_t *
+dns_message_gettsigkey(dns_message_t *msg) {
+ /*
+ * Get the TSIG key for 'msg'
+ */
+
+ REQUIRE(DNS_MESSAGE_VALID(msg));
+
+ return (msg->tsigkey);
+}
+
+isc_result_t
+dns_message_setquerytsig(dns_message_t *msg, isc_buffer_t *querytsig) {
+ dns_rdata_t *rdata = NULL;
+ dns_rdatalist_t *list = NULL;
+ dns_rdataset_t *set = NULL;
+ isc_buffer_t *buf = NULL;
+ isc_region_t r;
+ isc_result_t result;
+
+ REQUIRE(DNS_MESSAGE_VALID(msg));
+ REQUIRE(msg->querytsig == NULL);
+
+ if (querytsig == NULL) {
+ return (ISC_R_SUCCESS);
+ }
+
+ result = dns_message_gettemprdata(msg, &rdata);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+
+ result = dns_message_gettemprdatalist(msg, &list);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+ result = dns_message_gettemprdataset(msg, &set);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+
+ isc_buffer_usedregion(querytsig, &r);
+ isc_buffer_allocate(msg->mctx, &buf, r.length);
+ isc_buffer_putmem(buf, r.base, r.length);
+ isc_buffer_usedregion(buf, &r);
+ dns_rdata_init(rdata);
+ dns_rdata_fromregion(rdata, dns_rdataclass_any, dns_rdatatype_tsig, &r);
+ dns_message_takebuffer(msg, &buf);
+ ISC_LIST_APPEND(list->rdata, rdata, link);
+ result = dns_rdatalist_tordataset(list, set);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+
+ msg->querytsig = set;
+
+ return (result);
+
+cleanup:
+ if (rdata != NULL) {
+ dns_message_puttemprdata(msg, &rdata);
+ }
+ if (list != NULL) {
+ dns_message_puttemprdatalist(msg, &list);
+ }
+ if (set != NULL) {
+ dns_message_puttemprdataset(msg, &set);
+ }
+ return (ISC_R_NOMEMORY);
+}
+
+isc_result_t
+dns_message_getquerytsig(dns_message_t *msg, isc_mem_t *mctx,
+ isc_buffer_t **querytsig) {
+ isc_result_t result;
+ dns_rdata_t rdata = DNS_RDATA_INIT;
+ isc_region_t r;
+
+ REQUIRE(DNS_MESSAGE_VALID(msg));
+ REQUIRE(mctx != NULL);
+ REQUIRE(querytsig != NULL && *querytsig == NULL);
+
+ if (msg->tsig == NULL) {
+ return (ISC_R_SUCCESS);
+ }
+
+ result = dns_rdataset_first(msg->tsig);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+ dns_rdataset_current(msg->tsig, &rdata);
+ dns_rdata_toregion(&rdata, &r);
+
+ isc_buffer_allocate(mctx, querytsig, r.length);
+ isc_buffer_putmem(*querytsig, r.base, r.length);
+ return (ISC_R_SUCCESS);
+}
+
+dns_rdataset_t *
+dns_message_getsig0(dns_message_t *msg, const dns_name_t **owner) {
+ /*
+ * Get the SIG(0) record for 'msg'.
+ */
+
+ REQUIRE(DNS_MESSAGE_VALID(msg));
+ REQUIRE(owner == NULL || *owner == NULL);
+
+ if (msg->sig0 != NULL && owner != NULL) {
+ /* If dns_message_getsig0 is called on a rendered message
+ * after the SIG(0) has been applied, we need to return the
+ * root name, not NULL.
+ */
+ if (msg->sig0name == NULL) {
+ *owner = dns_rootname;
+ } else {
+ *owner = msg->sig0name;
+ }
+ }
+ return (msg->sig0);
+}
+
+isc_result_t
+dns_message_setsig0key(dns_message_t *msg, dst_key_t *key) {
+ isc_region_t r;
+ unsigned int x;
+ isc_result_t result;
+
+ /*
+ * Set the SIG(0) key for 'msg'
+ */
+
+ /*
+ * The space required for an SIG(0) record is:
+ *
+ * 1 byte for the name
+ * 2 bytes for the type
+ * 2 bytes for the class
+ * 4 bytes for the ttl
+ * 2 bytes for the type covered
+ * 1 byte for the algorithm
+ * 1 bytes for the labels
+ * 4 bytes for the original ttl
+ * 4 bytes for the signature expiration
+ * 4 bytes for the signature inception
+ * 2 bytes for the key tag
+ * n bytes for the signer's name
+ * x bytes for the signature
+ * ---------------------------------
+ * 27 + n + x bytes
+ */
+ REQUIRE(DNS_MESSAGE_VALID(msg));
+ REQUIRE(msg->from_to_wire == DNS_MESSAGE_INTENTRENDER);
+ REQUIRE(msg->state == DNS_SECTION_ANY);
+
+ if (key != NULL) {
+ REQUIRE(msg->sig0key == NULL && msg->tsigkey == NULL);
+ dns_name_toregion(dst_key_name(key), &r);
+ result = dst_key_sigsize(key, &x);
+ if (result != ISC_R_SUCCESS) {
+ msg->sig_reserved = 0;
+ return (result);
+ }
+ msg->sig_reserved = 27 + r.length + x;
+ result = dns_message_renderreserve(msg, msg->sig_reserved);
+ if (result != ISC_R_SUCCESS) {
+ msg->sig_reserved = 0;
+ return (result);
+ }
+ msg->sig0key = key;
+ }
+ return (ISC_R_SUCCESS);
+}
+
+dst_key_t *
+dns_message_getsig0key(dns_message_t *msg) {
+ /*
+ * Get the SIG(0) key for 'msg'
+ */
+
+ REQUIRE(DNS_MESSAGE_VALID(msg));
+
+ return (msg->sig0key);
+}
+
+void
+dns_message_takebuffer(dns_message_t *msg, isc_buffer_t **buffer) {
+ REQUIRE(DNS_MESSAGE_VALID(msg));
+ REQUIRE(buffer != NULL);
+ REQUIRE(ISC_BUFFER_VALID(*buffer));
+
+ ISC_LIST_APPEND(msg->cleanup, *buffer, link);
+ *buffer = NULL;
+}
+
+isc_result_t
+dns_message_signer(dns_message_t *msg, dns_name_t *signer) {
+ isc_result_t result = ISC_R_SUCCESS;
+ dns_rdata_t rdata = DNS_RDATA_INIT;
+
+ REQUIRE(DNS_MESSAGE_VALID(msg));
+ REQUIRE(signer != NULL);
+ REQUIRE(msg->from_to_wire == DNS_MESSAGE_INTENTPARSE);
+
+ if (msg->tsig == NULL && msg->sig0 == NULL) {
+ return (ISC_R_NOTFOUND);
+ }
+
+ if (msg->verify_attempted == 0) {
+ return (DNS_R_NOTVERIFIEDYET);
+ }
+
+ if (!dns_name_hasbuffer(signer)) {
+ isc_buffer_t *dynbuf = NULL;
+ isc_buffer_allocate(msg->mctx, &dynbuf, 512);
+ dns_name_setbuffer(signer, dynbuf);
+ dns_message_takebuffer(msg, &dynbuf);
+ }
+
+ if (msg->sig0 != NULL) {
+ dns_rdata_sig_t sig;
+
+ result = dns_rdataset_first(msg->sig0);
+ INSIST(result == ISC_R_SUCCESS);
+ dns_rdataset_current(msg->sig0, &rdata);
+
+ result = dns_rdata_tostruct(&rdata, &sig, NULL);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+
+ if (msg->verified_sig && msg->sig0status == dns_rcode_noerror) {
+ result = ISC_R_SUCCESS;
+ } else {
+ result = DNS_R_SIGINVALID;
+ }
+ dns_name_clone(&sig.signer, signer);
+ dns_rdata_freestruct(&sig);
+ } else {
+ const dns_name_t *identity;
+ dns_rdata_any_tsig_t tsig;
+
+ result = dns_rdataset_first(msg->tsig);
+ INSIST(result == ISC_R_SUCCESS);
+ dns_rdataset_current(msg->tsig, &rdata);
+
+ result = dns_rdata_tostruct(&rdata, &tsig, NULL);
+ INSIST(result == ISC_R_SUCCESS);
+ if (msg->verified_sig && msg->tsigstatus == dns_rcode_noerror &&
+ tsig.error == dns_rcode_noerror)
+ {
+ result = ISC_R_SUCCESS;
+ } else if ((!msg->verified_sig) ||
+ (msg->tsigstatus != dns_rcode_noerror))
+ {
+ result = DNS_R_TSIGVERIFYFAILURE;
+ } else {
+ INSIST(tsig.error != dns_rcode_noerror);
+ result = DNS_R_TSIGERRORSET;
+ }
+ dns_rdata_freestruct(&tsig);
+
+ if (msg->tsigkey == NULL) {
+ /*
+ * If msg->tsigstatus & tsig.error are both
+ * dns_rcode_noerror, the message must have been
+ * verified, which means msg->tsigkey will be
+ * non-NULL.
+ */
+ INSIST(result != ISC_R_SUCCESS);
+ } else {
+ identity = dns_tsigkey_identity(msg->tsigkey);
+ if (identity == NULL) {
+ if (result == ISC_R_SUCCESS) {
+ result = DNS_R_NOIDENTITY;
+ }
+ identity = &msg->tsigkey->name;
+ }
+ dns_name_clone(identity, signer);
+ }
+ }
+
+ return (result);
+}
+
+void
+dns_message_resetsig(dns_message_t *msg) {
+ REQUIRE(DNS_MESSAGE_VALID(msg));
+ msg->verified_sig = 0;
+ msg->verify_attempted = 0;
+ msg->tsigstatus = dns_rcode_noerror;
+ msg->sig0status = dns_rcode_noerror;
+ msg->timeadjust = 0;
+ if (msg->tsigkey != NULL) {
+ dns_tsigkey_detach(&msg->tsigkey);
+ msg->tsigkey = NULL;
+ }
+}
+
+isc_result_t
+dns_message_rechecksig(dns_message_t *msg, dns_view_t *view) {
+ dns_message_resetsig(msg);
+ return (dns_message_checksig(msg, view));
+}
+
+#ifdef SKAN_MSG_DEBUG
+void
+dns_message_dumpsig(dns_message_t *msg, char *txt1) {
+ dns_rdata_t querytsigrdata = DNS_RDATA_INIT;
+ dns_rdata_any_tsig_t querytsig;
+ isc_result_t result;
+
+ if (msg->tsig != NULL) {
+ result = dns_rdataset_first(msg->tsig);
+ RUNTIME_CHECK(result == ISC_R_SUCCESS);
+ dns_rdataset_current(msg->tsig, &querytsigrdata);
+ result = dns_rdata_tostruct(&querytsigrdata, &querytsig, NULL);
+ RUNTIME_CHECK(result == ISC_R_SUCCESS);
+ hexdump(txt1, "TSIG", querytsig.signature, querytsig.siglen);
+ }
+
+ if (msg->querytsig != NULL) {
+ result = dns_rdataset_first(msg->querytsig);
+ RUNTIME_CHECK(result == ISC_R_SUCCESS);
+ dns_rdataset_current(msg->querytsig, &querytsigrdata);
+ result = dns_rdata_tostruct(&querytsigrdata, &querytsig, NULL);
+ RUNTIME_CHECK(result == ISC_R_SUCCESS);
+ hexdump(txt1, "QUERYTSIG", querytsig.signature,
+ querytsig.siglen);
+ }
+}
+#endif /* ifdef SKAN_MSG_DEBUG */
+
+isc_result_t
+dns_message_checksig(dns_message_t *msg, dns_view_t *view) {
+ isc_buffer_t b, msgb;
+
+ REQUIRE(DNS_MESSAGE_VALID(msg));
+
+ if (msg->tsigkey == NULL && msg->tsig == NULL && msg->sig0 == NULL) {
+ return (ISC_R_SUCCESS);
+ }
+
+ INSIST(msg->saved.base != NULL);
+ isc_buffer_init(&msgb, msg->saved.base, msg->saved.length);
+ isc_buffer_add(&msgb, msg->saved.length);
+ if (msg->tsigkey != NULL || msg->tsig != NULL) {
+#ifdef SKAN_MSG_DEBUG
+ dns_message_dumpsig(msg, "dns_message_checksig#1");
+#endif /* ifdef SKAN_MSG_DEBUG */
+ if (view != NULL) {
+ return (dns_view_checksig(view, &msgb, msg));
+ } else {
+ return (dns_tsig_verify(&msgb, msg, NULL, NULL));
+ }
+ } else {
+ dns_rdata_t rdata = DNS_RDATA_INIT;
+ dns_rdata_sig_t sig;
+ dns_rdataset_t keyset;
+ isc_result_t result;
+
+ result = dns_rdataset_first(msg->sig0);
+ INSIST(result == ISC_R_SUCCESS);
+ dns_rdataset_current(msg->sig0, &rdata);
+
+ /*
+ * This can occur when the message is a dynamic update, since
+ * the rdata length checking is relaxed. This should not
+ * happen in a well-formed message, since the SIG(0) is only
+ * looked for in the additional section, and the dynamic update
+ * meta-records are in the prerequisite and update sections.
+ */
+ if (rdata.length == 0) {
+ return (ISC_R_UNEXPECTEDEND);
+ }
+
+ result = dns_rdata_tostruct(&rdata, &sig, NULL);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+
+ dns_rdataset_init(&keyset);
+ if (view == NULL) {
+ result = DNS_R_KEYUNAUTHORIZED;
+ goto freesig;
+ }
+ result = dns_view_simplefind(view, &sig.signer,
+ dns_rdatatype_key /* SIG(0) */, 0,
+ 0, false, &keyset, NULL);
+
+ if (result != ISC_R_SUCCESS) {
+ /* XXXBEW Should possibly create a fetch here */
+ result = DNS_R_KEYUNAUTHORIZED;
+ goto freesig;
+ } else if (keyset.trust < dns_trust_secure) {
+ /* XXXBEW Should call a validator here */
+ result = DNS_R_KEYUNAUTHORIZED;
+ goto freesig;
+ }
+ result = dns_rdataset_first(&keyset);
+ INSIST(result == ISC_R_SUCCESS);
+ for (; result == ISC_R_SUCCESS;
+ result = dns_rdataset_next(&keyset))
+ {
+ dst_key_t *key = NULL;
+
+ dns_rdata_reset(&rdata);
+ dns_rdataset_current(&keyset, &rdata);
+ isc_buffer_init(&b, rdata.data, rdata.length);
+ isc_buffer_add(&b, rdata.length);
+
+ result = dst_key_fromdns(&sig.signer, rdata.rdclass, &b,
+ view->mctx, &key);
+ if (result != ISC_R_SUCCESS) {
+ continue;
+ }
+ if (dst_key_alg(key) != sig.algorithm ||
+ dst_key_id(key) != sig.keyid ||
+ !(dst_key_proto(key) == DNS_KEYPROTO_DNSSEC ||
+ dst_key_proto(key) == DNS_KEYPROTO_ANY))
+ {
+ dst_key_free(&key);
+ continue;
+ }
+ result = dns_dnssec_verifymessage(&msgb, msg, key);
+ dst_key_free(&key);
+ if (result == ISC_R_SUCCESS) {
+ break;
+ }
+ }
+ if (result == ISC_R_NOMORE) {
+ result = DNS_R_KEYUNAUTHORIZED;
+ }
+
+ freesig:
+ if (dns_rdataset_isassociated(&keyset)) {
+ dns_rdataset_disassociate(&keyset);
+ }
+ dns_rdata_freestruct(&sig);
+ return (result);
+ }
+}
+
+#define INDENT(sp) \
+ do { \
+ unsigned int __i; \
+ dns_masterstyle_flags_t __flags = dns_master_styleflags(sp); \
+ if ((__flags & DNS_STYLEFLAG_INDENT) == 0ULL && \
+ (__flags & DNS_STYLEFLAG_YAML) == 0ULL) \
+ break; \
+ for (__i = 0; __i < msg->indent.count; __i++) { \
+ ADD_STRING(target, msg->indent.string); \
+ } \
+ } while (0)
+
+isc_result_t
+dns_message_sectiontotext(dns_message_t *msg, dns_section_t section,
+ const dns_master_style_t *style,
+ dns_messagetextflag_t flags, isc_buffer_t *target) {
+ dns_name_t *name, empty_name;
+ dns_rdataset_t *rdataset;
+ isc_result_t result = ISC_R_SUCCESS;
+ bool seensoa = false;
+ size_t saved_count;
+ dns_masterstyle_flags_t sflags;
+
+ REQUIRE(DNS_MESSAGE_VALID(msg));
+ REQUIRE(target != NULL);
+ REQUIRE(VALID_SECTION(section));
+
+ saved_count = msg->indent.count;
+
+ if (ISC_LIST_EMPTY(msg->sections[section])) {
+ goto cleanup;
+ }
+
+ sflags = dns_master_styleflags(style);
+
+ INDENT(style);
+ if ((sflags & DNS_STYLEFLAG_YAML) != 0) {
+ if (msg->opcode != dns_opcode_update) {
+ ADD_STRING(target, sectiontext[section]);
+ } else {
+ ADD_STRING(target, updsectiontext[section]);
+ }
+ ADD_STRING(target, "_SECTION:\n");
+ } else if ((flags & DNS_MESSAGETEXTFLAG_NOCOMMENTS) == 0) {
+ ADD_STRING(target, ";; ");
+ if (msg->opcode != dns_opcode_update) {
+ ADD_STRING(target, sectiontext[section]);
+ } else {
+ ADD_STRING(target, updsectiontext[section]);
+ }
+ ADD_STRING(target, " SECTION:\n");
+ }
+
+ dns_name_init(&empty_name, NULL);
+ result = dns_message_firstname(msg, section);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+ if ((sflags & DNS_STYLEFLAG_YAML) != 0) {
+ msg->indent.count++;
+ }
+ do {
+ name = NULL;
+ dns_message_currentname(msg, section, &name);
+ for (rdataset = ISC_LIST_HEAD(name->list); rdataset != NULL;
+ rdataset = ISC_LIST_NEXT(rdataset, link))
+ {
+ if (section == DNS_SECTION_ANSWER &&
+ rdataset->type == dns_rdatatype_soa)
+ {
+ if ((flags & DNS_MESSAGETEXTFLAG_OMITSOA) != 0)
+ {
+ continue;
+ }
+ if (seensoa &&
+ (flags & DNS_MESSAGETEXTFLAG_ONESOA) != 0)
+ {
+ continue;
+ }
+ seensoa = true;
+ }
+ if (section == DNS_SECTION_QUESTION) {
+ INDENT(style);
+ if ((sflags & DNS_STYLEFLAG_YAML) != 0) {
+ ADD_STRING(target, "- ");
+ } else {
+ ADD_STRING(target, ";");
+ }
+ result = dns_master_questiontotext(
+ name, rdataset, style, target);
+ } else {
+ result = dns_master_rdatasettotext(
+ name, rdataset, style, &msg->indent,
+ target);
+ }
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+ }
+ result = dns_message_nextname(msg, section);
+ } while (result == ISC_R_SUCCESS);
+ if ((sflags & DNS_STYLEFLAG_YAML) != 0) {
+ msg->indent.count--;
+ }
+ if ((flags & DNS_MESSAGETEXTFLAG_NOHEADERS) == 0 &&
+ (flags & DNS_MESSAGETEXTFLAG_NOCOMMENTS) == 0 &&
+ (sflags & DNS_STYLEFLAG_YAML) == 0)
+ {
+ INDENT(style);
+ ADD_STRING(target, "\n");
+ }
+ if (result == ISC_R_NOMORE) {
+ result = ISC_R_SUCCESS;
+ }
+
+cleanup:
+ msg->indent.count = saved_count;
+ return (result);
+}
+
+static isc_result_t
+render_ecs(isc_buffer_t *ecsbuf, isc_buffer_t *target) {
+ int i;
+ char addr[16], addr_text[64];
+ uint16_t family;
+ uint8_t addrlen, addrbytes, scopelen;
+ isc_result_t result;
+
+ /*
+ * Note: This routine needs to handle malformed ECS options.
+ */
+
+ if (isc_buffer_remaininglength(ecsbuf) < 4) {
+ return (DNS_R_OPTERR);
+ }
+ family = isc_buffer_getuint16(ecsbuf);
+ addrlen = isc_buffer_getuint8(ecsbuf);
+ scopelen = isc_buffer_getuint8(ecsbuf);
+
+ addrbytes = (addrlen + 7) / 8;
+ if (isc_buffer_remaininglength(ecsbuf) < addrbytes) {
+ return (DNS_R_OPTERR);
+ }
+
+ if (addrbytes > sizeof(addr)) {
+ return (DNS_R_OPTERR);
+ }
+
+ memset(addr, 0, sizeof(addr));
+ for (i = 0; i < addrbytes; i++) {
+ addr[i] = isc_buffer_getuint8(ecsbuf);
+ }
+
+ switch (family) {
+ case 0:
+ if (addrlen != 0U || scopelen != 0U) {
+ return (DNS_R_OPTERR);
+ }
+ strlcpy(addr_text, "0", sizeof(addr_text));
+ break;
+ case 1:
+ if (addrlen > 32 || scopelen > 32) {
+ return (DNS_R_OPTERR);
+ }
+ inet_ntop(AF_INET, addr, addr_text, sizeof(addr_text));
+ break;
+ case 2:
+ if (addrlen > 128 || scopelen > 128) {
+ return (DNS_R_OPTERR);
+ }
+ inet_ntop(AF_INET6, addr, addr_text, sizeof(addr_text));
+ break;
+ default:
+ return (DNS_R_OPTERR);
+ }
+
+ ADD_STRING(target, " ");
+ ADD_STRING(target, addr_text);
+ snprintf(addr_text, sizeof(addr_text), "/%d/%d", addrlen, scopelen);
+ ADD_STRING(target, addr_text);
+
+ result = ISC_R_SUCCESS;
+
+cleanup:
+ return (result);
+}
+
+static isc_result_t
+render_llq(isc_buffer_t *optbuf, isc_buffer_t *target) {
+ char buf[sizeof("18446744073709551615")]; /* 2^64-1 */
+ isc_result_t result = ISC_R_SUCCESS;
+ uint32_t u;
+ uint64_t q;
+
+ u = isc_buffer_getuint16(optbuf);
+ ADD_STRING(target, " Version: ");
+ snprintf(buf, sizeof(buf), "%u", u);
+ ADD_STRING(target, buf);
+
+ u = isc_buffer_getuint16(optbuf);
+ ADD_STRING(target, ", Opcode: ");
+ snprintf(buf, sizeof(buf), "%u", u);
+ ADD_STRING(target, buf);
+
+ u = isc_buffer_getuint16(optbuf);
+ ADD_STRING(target, ", Error: ");
+ snprintf(buf, sizeof(buf), "%u", u);
+ ADD_STRING(target, buf);
+
+ q = isc_buffer_getuint32(optbuf);
+ q <<= 32;
+ q |= isc_buffer_getuint32(optbuf);
+ ADD_STRING(target, ", Identifier: ");
+ snprintf(buf, sizeof(buf), "%" PRIu64, q);
+ ADD_STRING(target, buf);
+
+ u = isc_buffer_getuint32(optbuf);
+ ADD_STRING(target, ", Lifetime: ");
+ snprintf(buf, sizeof(buf), "%u", u);
+ ADD_STRING(target, buf);
+cleanup:
+ return (result);
+}
+
+static isc_result_t
+dns_message_pseudosectiontoyaml(dns_message_t *msg, dns_pseudosection_t section,
+ const dns_master_style_t *style,
+ dns_messagetextflag_t flags,
+ isc_buffer_t *target) {
+ dns_rdataset_t *ps = NULL;
+ const dns_name_t *name = NULL;
+ isc_result_t result = ISC_R_SUCCESS;
+ char buf[sizeof("1234567890")];
+ uint32_t mbz;
+ dns_rdata_t rdata;
+ isc_buffer_t optbuf;
+ uint16_t optcode, optlen;
+ size_t saved_count;
+ unsigned char *optdata;
+ unsigned int indent;
+
+ REQUIRE(DNS_MESSAGE_VALID(msg));
+ REQUIRE(target != NULL);
+ REQUIRE(VALID_PSEUDOSECTION(section));
+
+ saved_count = msg->indent.count;
+
+ switch (section) {
+ case DNS_PSEUDOSECTION_OPT:
+ ps = dns_message_getopt(msg);
+ if (ps == NULL) {
+ goto cleanup;
+ }
+
+ INDENT(style);
+ ADD_STRING(target, "OPT_PSEUDOSECTION:\n");
+ msg->indent.count++;
+
+ INDENT(style);
+ ADD_STRING(target, "EDNS:\n");
+ indent = ++msg->indent.count;
+
+ INDENT(style);
+ ADD_STRING(target, "version: ");
+ snprintf(buf, sizeof(buf), "%u",
+ (unsigned int)((ps->ttl & 0x00ff0000) >> 16));
+ ADD_STRING(target, buf);
+ ADD_STRING(target, "\n");
+ INDENT(style);
+ ADD_STRING(target, "flags:");
+ if ((ps->ttl & DNS_MESSAGEEXTFLAG_DO) != 0) {
+ ADD_STRING(target, " do");
+ }
+ ADD_STRING(target, "\n");
+ mbz = ps->ttl & 0xffff;
+ mbz &= ~DNS_MESSAGEEXTFLAG_DO; /* Known Flags. */
+ if (mbz != 0) {
+ INDENT(style);
+ ADD_STRING(target, "MBZ: ");
+ snprintf(buf, sizeof(buf), "0x%.4x", mbz);
+ ADD_STRING(target, buf);
+ ADD_STRING(target, "\n");
+ }
+ INDENT(style);
+ ADD_STRING(target, "udp: ");
+ snprintf(buf, sizeof(buf), "%u\n", (unsigned int)ps->rdclass);
+ ADD_STRING(target, buf);
+ result = dns_rdataset_first(ps);
+ if (result != ISC_R_SUCCESS) {
+ result = ISC_R_SUCCESS;
+ goto cleanup;
+ }
+
+ /*
+ * Print EDNS info, if any.
+ *
+ * WARNING: The option contents may be malformed as
+ * dig +ednsopt=value:<content> does not perform validity
+ * checking.
+ */
+ dns_rdata_init(&rdata);
+ dns_rdataset_current(ps, &rdata);
+
+ isc_buffer_init(&optbuf, rdata.data, rdata.length);
+ isc_buffer_add(&optbuf, rdata.length);
+ while (isc_buffer_remaininglength(&optbuf) != 0) {
+ bool extra_text = false;
+ msg->indent.count = indent;
+ INSIST(isc_buffer_remaininglength(&optbuf) >= 4U);
+ optcode = isc_buffer_getuint16(&optbuf);
+ optlen = isc_buffer_getuint16(&optbuf);
+ INSIST(isc_buffer_remaininglength(&optbuf) >= optlen);
+
+ if (optcode == DNS_OPT_LLQ) {
+ INDENT(style);
+ ADD_STRING(target, "LLQ:");
+ if (optlen == 18U) {
+ result = render_llq(&optbuf, target);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+ ADD_STRING(target, "\n");
+ continue;
+ }
+ } else if (optcode == DNS_OPT_NSID) {
+ INDENT(style);
+ ADD_STRING(target, "NSID:");
+ } else if (optcode == DNS_OPT_COOKIE) {
+ INDENT(style);
+ ADD_STRING(target, "COOKIE:");
+ } else if (optcode == DNS_OPT_CLIENT_SUBNET) {
+ isc_buffer_t ecsbuf;
+ INDENT(style);
+ ADD_STRING(target, "CLIENT-SUBNET:");
+ isc_buffer_init(&ecsbuf,
+ isc_buffer_current(&optbuf),
+ optlen);
+ isc_buffer_add(&ecsbuf, optlen);
+ result = render_ecs(&ecsbuf, target);
+ if (result == ISC_R_NOSPACE) {
+ goto cleanup;
+ }
+ if (result == ISC_R_SUCCESS) {
+ isc_buffer_forward(&optbuf, optlen);
+ ADD_STRING(target, "\n");
+ continue;
+ }
+ ADD_STRING(target, "\n");
+ } else if (optcode == DNS_OPT_EXPIRE) {
+ INDENT(style);
+ ADD_STRING(target, "EXPIRE:");
+ if (optlen == 4) {
+ uint32_t secs;
+ secs = isc_buffer_getuint32(&optbuf);
+ snprintf(buf, sizeof(buf), " %u", secs);
+ ADD_STRING(target, buf);
+ ADD_STRING(target, " (");
+ result = dns_ttl_totext(secs, true,
+ true, target);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+ ADD_STRING(target, ")\n");
+ continue;
+ }
+ } else if (optcode == DNS_OPT_TCP_KEEPALIVE) {
+ if (optlen == 2) {
+ unsigned int dsecs;
+ dsecs = isc_buffer_getuint16(&optbuf);
+ INDENT(style);
+ ADD_STRING(target, "TCP-KEEPALIVE: ");
+ snprintf(buf, sizeof(buf), "%u.%u",
+ dsecs / 10U, dsecs % 10U);
+ ADD_STRING(target, buf);
+ ADD_STRING(target, " secs\n");
+ continue;
+ }
+ INDENT(style);
+ ADD_STRING(target, "TCP-KEEPALIVE:");
+ } else if (optcode == DNS_OPT_PAD) {
+ INDENT(style);
+ ADD_STRING(target, "PAD:");
+ } else if (optcode == DNS_OPT_KEY_TAG) {
+ INDENT(style);
+ ADD_STRING(target, "KEY-TAG:");
+ if (optlen > 0U && (optlen % 2U) == 0U) {
+ const char *sep = "";
+ uint16_t id;
+ while (optlen > 0U) {
+ id = isc_buffer_getuint16(
+ &optbuf);
+ snprintf(buf, sizeof(buf),
+ "%s %u", sep, id);
+ ADD_STRING(target, buf);
+ sep = ",";
+ optlen -= 2;
+ }
+ ADD_STRING(target, "\n");
+ continue;
+ }
+ } else if (optcode == DNS_OPT_EDE) {
+ INDENT(style);
+ ADD_STRING(target, "EDE:");
+ if (optlen >= 2U) {
+ uint16_t ede;
+ ADD_STRING(target, "\n");
+ msg->indent.count++;
+ INDENT(style);
+ ADD_STRING(target, "INFO-CODE:");
+ ede = isc_buffer_getuint16(&optbuf);
+ snprintf(buf, sizeof(buf), " %u", ede);
+ ADD_STRING(target, buf);
+ if (ede < ARRAY_SIZE(edetext)) {
+ ADD_STRING(target, " (");
+ ADD_STRING(target,
+ edetext[ede]);
+ ADD_STRING(target, ")");
+ }
+ ADD_STRING(target, "\n");
+ optlen -= 2;
+ if (optlen != 0) {
+ INDENT(style);
+ ADD_STRING(target,
+ "EXTRA-TEXT:");
+ extra_text = true;
+ }
+ }
+ } else if (optcode == DNS_OPT_CLIENT_TAG) {
+ uint16_t id;
+ INDENT(style);
+ ADD_STRING(target, "CLIENT-TAG:");
+ if (optlen == 2U) {
+ id = isc_buffer_getuint16(&optbuf);
+ snprintf(buf, sizeof(buf), " %u\n", id);
+ ADD_STRING(target, buf);
+ optlen -= 2;
+ POST(optlen);
+ continue;
+ }
+ } else if (optcode == DNS_OPT_SERVER_TAG) {
+ uint16_t id;
+ INDENT(style);
+ ADD_STRING(target, "SERVER-TAG:");
+ if (optlen == 2U) {
+ id = isc_buffer_getuint16(&optbuf);
+ snprintf(buf, sizeof(buf), " %u\n", id);
+ ADD_STRING(target, buf);
+ optlen -= 2;
+ POST(optlen);
+ continue;
+ }
+ } else {
+ INDENT(style);
+ ADD_STRING(target, "OPT=");
+ snprintf(buf, sizeof(buf), "%u:", optcode);
+ ADD_STRING(target, buf);
+ }
+
+ if (optlen != 0) {
+ int i;
+ bool utf8ok = false;
+
+ ADD_STRING(target, " ");
+
+ optdata = isc_buffer_current(&optbuf);
+ if (extra_text) {
+ utf8ok = isc_utf8_valid(optdata,
+ optlen);
+ }
+ if (!utf8ok) {
+ for (i = 0; i < optlen; i++) {
+ const char *sep;
+ switch (optcode) {
+ case DNS_OPT_COOKIE:
+ sep = "";
+ break;
+ default:
+ sep = " ";
+ break;
+ }
+ snprintf(buf, sizeof(buf),
+ "%02x%s", optdata[i],
+ sep);
+ ADD_STRING(target, buf);
+ }
+ }
+
+ isc_buffer_forward(&optbuf, optlen);
+
+ if (optcode == DNS_OPT_COOKIE) {
+ /*
+ * Valid server cookie?
+ */
+ if (msg->cc_ok && optlen >= 16) {
+ ADD_STRING(target, " (good)");
+ }
+ /*
+ * Server cookie is not valid but
+ * we had our cookie echoed back.
+ */
+ if (msg->cc_ok && optlen < 16) {
+ ADD_STRING(target, " (echoed)");
+ }
+ /*
+ * We didn't get our cookie echoed
+ * back.
+ */
+ if (msg->cc_bad) {
+ ADD_STRING(target, " (bad)");
+ }
+ ADD_STRING(target, "\n");
+ continue;
+ }
+
+ if (optcode == DNS_OPT_CLIENT_SUBNET) {
+ ADD_STRING(target, "\n");
+ continue;
+ }
+
+ /*
+ * For non-COOKIE options, add a printable
+ * version
+ */
+ if (!extra_text) {
+ ADD_STRING(target, "(\"");
+ } else {
+ ADD_STRING(target, "\"");
+ }
+ if (isc_buffer_availablelength(target) < optlen)
+ {
+ result = ISC_R_NOSPACE;
+ goto cleanup;
+ }
+ for (i = 0; i < optlen; i++) {
+ if (isprint(optdata[i]) ||
+ (utf8ok && optdata[i] > 127))
+ {
+ isc_buffer_putmem(
+ target, &optdata[i], 1);
+ } else {
+ isc_buffer_putstr(target, ".");
+ }
+ }
+ if (!extra_text) {
+ ADD_STRING(target, "\")");
+ } else {
+ ADD_STRING(target, "\"");
+ }
+ }
+ ADD_STRING(target, "\n");
+ }
+ msg->indent.count = indent;
+ result = ISC_R_SUCCESS;
+ goto cleanup;
+ case DNS_PSEUDOSECTION_TSIG:
+ ps = dns_message_gettsig(msg, &name);
+ if (ps == NULL) {
+ result = ISC_R_SUCCESS;
+ goto cleanup;
+ }
+ INDENT(style);
+ ADD_STRING(target, "TSIG_PSEUDOSECTION:\n");
+ result = dns_master_rdatasettotext(name, ps, style,
+ &msg->indent, target);
+ ADD_STRING(target, "\n");
+ goto cleanup;
+ case DNS_PSEUDOSECTION_SIG0:
+ ps = dns_message_getsig0(msg, &name);
+ if (ps == NULL) {
+ result = ISC_R_SUCCESS;
+ goto cleanup;
+ }
+ INDENT(style);
+ ADD_STRING(target, "SIG0_PSEUDOSECTION:\n");
+ result = dns_master_rdatasettotext(name, ps, style,
+ &msg->indent, target);
+ if ((flags & DNS_MESSAGETEXTFLAG_NOHEADERS) == 0 &&
+ (flags & DNS_MESSAGETEXTFLAG_NOCOMMENTS) == 0)
+ {
+ ADD_STRING(target, "\n");
+ }
+ goto cleanup;
+ }
+
+ result = ISC_R_UNEXPECTED;
+
+cleanup:
+ msg->indent.count = saved_count;
+ return (result);
+}
+
+isc_result_t
+dns_message_pseudosectiontotext(dns_message_t *msg, dns_pseudosection_t section,
+ const dns_master_style_t *style,
+ dns_messagetextflag_t flags,
+ isc_buffer_t *target) {
+ dns_rdataset_t *ps = NULL;
+ const dns_name_t *name = NULL;
+ isc_result_t result;
+ char buf[sizeof(" (65000 bytes)")];
+ uint32_t mbz;
+ dns_rdata_t rdata;
+ isc_buffer_t optbuf;
+ uint16_t optcode, optlen;
+ unsigned char *optdata;
+
+ REQUIRE(DNS_MESSAGE_VALID(msg));
+ REQUIRE(target != NULL);
+ REQUIRE(VALID_PSEUDOSECTION(section));
+
+ if ((dns_master_styleflags(style) & DNS_STYLEFLAG_YAML) != 0) {
+ return (dns_message_pseudosectiontoyaml(msg, section, style,
+ flags, target));
+ }
+
+ switch (section) {
+ case DNS_PSEUDOSECTION_OPT:
+ ps = dns_message_getopt(msg);
+ if (ps == NULL) {
+ return (ISC_R_SUCCESS);
+ }
+ if ((flags & DNS_MESSAGETEXTFLAG_NOCOMMENTS) == 0) {
+ INDENT(style);
+ ADD_STRING(target, ";; OPT PSEUDOSECTION:\n");
+ }
+
+ INDENT(style);
+ ADD_STRING(target, "; EDNS: version: ");
+ snprintf(buf, sizeof(buf), "%u",
+ (unsigned int)((ps->ttl & 0x00ff0000) >> 16));
+ ADD_STRING(target, buf);
+ ADD_STRING(target, ", flags:");
+ if ((ps->ttl & DNS_MESSAGEEXTFLAG_DO) != 0) {
+ ADD_STRING(target, " do");
+ }
+ mbz = ps->ttl & 0xffff;
+ mbz &= ~DNS_MESSAGEEXTFLAG_DO; /* Known Flags. */
+ if (mbz != 0) {
+ ADD_STRING(target, "; MBZ: ");
+ snprintf(buf, sizeof(buf), "0x%.4x", mbz);
+ ADD_STRING(target, buf);
+ ADD_STRING(target, ", udp: ");
+ } else {
+ ADD_STRING(target, "; udp: ");
+ }
+ snprintf(buf, sizeof(buf), "%u\n", (unsigned int)ps->rdclass);
+ ADD_STRING(target, buf);
+
+ result = dns_rdataset_first(ps);
+ if (result != ISC_R_SUCCESS) {
+ return (ISC_R_SUCCESS);
+ }
+
+ /*
+ * Print EDNS info, if any.
+ *
+ * WARNING: The option contents may be malformed as
+ * dig +ednsopt=value:<content> does no validity
+ * checking.
+ */
+ dns_rdata_init(&rdata);
+ dns_rdataset_current(ps, &rdata);
+
+ isc_buffer_init(&optbuf, rdata.data, rdata.length);
+ isc_buffer_add(&optbuf, rdata.length);
+ while (isc_buffer_remaininglength(&optbuf) != 0) {
+ INSIST(isc_buffer_remaininglength(&optbuf) >= 4U);
+ optcode = isc_buffer_getuint16(&optbuf);
+ optlen = isc_buffer_getuint16(&optbuf);
+
+ INSIST(isc_buffer_remaininglength(&optbuf) >= optlen);
+
+ INDENT(style);
+
+ if (optcode == DNS_OPT_LLQ) {
+ ADD_STRING(target, "; LLQ:");
+ if (optlen == 18U) {
+ result = render_llq(&optbuf, target);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+ ADD_STRING(target, "\n");
+ continue;
+ }
+ } else if (optcode == DNS_OPT_NSID) {
+ ADD_STRING(target, "; NSID:");
+ } else if (optcode == DNS_OPT_COOKIE) {
+ ADD_STRING(target, "; COOKIE:");
+ } else if (optcode == DNS_OPT_CLIENT_SUBNET) {
+ isc_buffer_t ecsbuf;
+
+ ADD_STRING(target, "; CLIENT-SUBNET:");
+ isc_buffer_init(&ecsbuf,
+ isc_buffer_current(&optbuf),
+ optlen);
+ isc_buffer_add(&ecsbuf, optlen);
+ result = render_ecs(&ecsbuf, target);
+ if (result == ISC_R_NOSPACE) {
+ return (result);
+ }
+ if (result == ISC_R_SUCCESS) {
+ isc_buffer_forward(&optbuf, optlen);
+ ADD_STRING(target, "\n");
+ continue;
+ }
+ } else if (optcode == DNS_OPT_EXPIRE) {
+ ADD_STRING(target, "; EXPIRE:");
+ if (optlen == 4) {
+ uint32_t secs;
+ secs = isc_buffer_getuint32(&optbuf);
+ snprintf(buf, sizeof(buf), " %u", secs);
+ ADD_STRING(target, buf);
+ ADD_STRING(target, " (");
+ result = dns_ttl_totext(secs, true,
+ true, target);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+ ADD_STRING(target, ")\n");
+ continue;
+ }
+ } else if (optcode == DNS_OPT_TCP_KEEPALIVE) {
+ ADD_STRING(target, "; TCP KEEPALIVE:");
+ if (optlen == 2) {
+ unsigned int dsecs;
+ dsecs = isc_buffer_getuint16(&optbuf);
+ snprintf(buf, sizeof(buf), " %u.%u",
+ dsecs / 10U, dsecs % 10U);
+ ADD_STRING(target, buf);
+ ADD_STRING(target, " secs\n");
+ continue;
+ }
+ } else if (optcode == DNS_OPT_PAD) {
+ ADD_STRING(target, "; PAD:");
+ if (optlen > 0U) {
+ snprintf(buf, sizeof(buf),
+ " (%u bytes)", optlen);
+ ADD_STRING(target, buf);
+ isc_buffer_forward(&optbuf, optlen);
+ }
+ ADD_STRING(target, "\n");
+ continue;
+ } else if (optcode == DNS_OPT_KEY_TAG) {
+ ADD_STRING(target, "; KEY-TAG:");
+ if (optlen > 0U && (optlen % 2U) == 0U) {
+ const char *sep = "";
+ uint16_t id;
+ while (optlen > 0U) {
+ id = isc_buffer_getuint16(
+ &optbuf);
+ snprintf(buf, sizeof(buf),
+ "%s %u", sep, id);
+ ADD_STRING(target, buf);
+ sep = ",";
+ optlen -= 2;
+ }
+ ADD_STRING(target, "\n");
+ continue;
+ }
+ } else if (optcode == DNS_OPT_EDE) {
+ ADD_STRING(target, "; EDE:");
+ if (optlen >= 2U) {
+ uint16_t ede;
+ ede = isc_buffer_getuint16(&optbuf);
+ snprintf(buf, sizeof(buf), " %u", ede);
+ ADD_STRING(target, buf);
+ if (ede < ARRAY_SIZE(edetext)) {
+ ADD_STRING(target, " (");
+ ADD_STRING(target,
+ edetext[ede]);
+ ADD_STRING(target, ")");
+ }
+ optlen -= 2;
+ if (optlen != 0) {
+ ADD_STRING(target, ":");
+ }
+ } else if (optlen == 1U) {
+ /* Malformed */
+ optdata = isc_buffer_current(&optbuf);
+ snprintf(buf, sizeof(buf),
+ " %02x (\"%c\")\n", optdata[0],
+ isprint(optdata[0])
+ ? optdata[0]
+ : '.');
+ isc_buffer_forward(&optbuf, optlen);
+ ADD_STRING(target, buf);
+ continue;
+ }
+ } else if (optcode == DNS_OPT_CLIENT_TAG) {
+ uint16_t id;
+ ADD_STRING(target, "; CLIENT-TAG:");
+ if (optlen == 2U) {
+ id = isc_buffer_getuint16(&optbuf);
+ snprintf(buf, sizeof(buf), " %u\n", id);
+ ADD_STRING(target, buf);
+ optlen -= 2;
+ POST(optlen);
+ continue;
+ }
+ } else if (optcode == DNS_OPT_SERVER_TAG) {
+ uint16_t id;
+ ADD_STRING(target, "; SERVER-TAG:");
+ if (optlen == 2U) {
+ id = isc_buffer_getuint16(&optbuf);
+ snprintf(buf, sizeof(buf), " %u\n", id);
+ ADD_STRING(target, buf);
+ optlen -= 2;
+ POST(optlen);
+ continue;
+ }
+ } else {
+ ADD_STRING(target, "; OPT=");
+ snprintf(buf, sizeof(buf), "%u:", optcode);
+ ADD_STRING(target, buf);
+ }
+
+ if (optlen != 0) {
+ int i;
+ bool utf8ok = false;
+
+ ADD_STRING(target, " ");
+
+ optdata = isc_buffer_current(&optbuf);
+ if (optcode == DNS_OPT_EDE) {
+ utf8ok = isc_utf8_valid(optdata,
+ optlen);
+ }
+ if (!utf8ok) {
+ for (i = 0; i < optlen; i++) {
+ const char *sep;
+ switch (optcode) {
+ case DNS_OPT_COOKIE:
+ sep = "";
+ break;
+ default:
+ sep = " ";
+ break;
+ }
+ snprintf(buf, sizeof(buf),
+ "%02x%s", optdata[i],
+ sep);
+ ADD_STRING(target, buf);
+ }
+ }
+
+ isc_buffer_forward(&optbuf, optlen);
+
+ if (optcode == DNS_OPT_COOKIE) {
+ /*
+ * Valid server cookie?
+ */
+ if (msg->cc_ok && optlen >= 16) {
+ ADD_STRING(target, " (good)");
+ }
+ /*
+ * Server cookie is not valid but
+ * we had our cookie echoed back.
+ */
+ if (msg->cc_ok && optlen < 16) {
+ ADD_STRING(target, " (echoed)");
+ }
+ /*
+ * We didn't get our cookie echoed
+ * back.
+ */
+ if (msg->cc_bad) {
+ ADD_STRING(target, " (bad)");
+ }
+ ADD_STRING(target, "\n");
+ continue;
+ }
+
+ if (optcode == DNS_OPT_CLIENT_SUBNET) {
+ ADD_STRING(target, "\n");
+ continue;
+ }
+
+ /*
+ * For non-COOKIE options, add a printable
+ * version.
+ */
+ if (optcode != DNS_OPT_EDE) {
+ ADD_STRING(target, "(\"");
+ } else {
+ ADD_STRING(target, "(");
+ }
+ if (isc_buffer_availablelength(target) < optlen)
+ {
+ return (ISC_R_NOSPACE);
+ }
+ for (i = 0; i < optlen; i++) {
+ if (isprint(optdata[i]) ||
+ (utf8ok && optdata[i] > 127))
+ {
+ isc_buffer_putmem(
+ target, &optdata[i], 1);
+ } else {
+ isc_buffer_putstr(target, ".");
+ }
+ }
+ if (optcode != DNS_OPT_EDE) {
+ ADD_STRING(target, "\")");
+ } else {
+ ADD_STRING(target, ")");
+ }
+ }
+ ADD_STRING(target, "\n");
+ }
+ return (ISC_R_SUCCESS);
+ case DNS_PSEUDOSECTION_TSIG:
+ ps = dns_message_gettsig(msg, &name);
+ if (ps == NULL) {
+ return (ISC_R_SUCCESS);
+ }
+ INDENT(style);
+ if ((flags & DNS_MESSAGETEXTFLAG_NOCOMMENTS) == 0) {
+ ADD_STRING(target, ";; TSIG PSEUDOSECTION:\n");
+ }
+ result = dns_master_rdatasettotext(name, ps, style,
+ &msg->indent, target);
+ if ((flags & DNS_MESSAGETEXTFLAG_NOHEADERS) == 0 &&
+ (flags & DNS_MESSAGETEXTFLAG_NOCOMMENTS) == 0)
+ {
+ ADD_STRING(target, "\n");
+ }
+ return (result);
+ case DNS_PSEUDOSECTION_SIG0:
+ ps = dns_message_getsig0(msg, &name);
+ if (ps == NULL) {
+ return (ISC_R_SUCCESS);
+ }
+ INDENT(style);
+ if ((flags & DNS_MESSAGETEXTFLAG_NOCOMMENTS) == 0) {
+ ADD_STRING(target, ";; SIG0 PSEUDOSECTION:\n");
+ }
+ result = dns_master_rdatasettotext(name, ps, style,
+ &msg->indent, target);
+ if ((flags & DNS_MESSAGETEXTFLAG_NOHEADERS) == 0 &&
+ (flags & DNS_MESSAGETEXTFLAG_NOCOMMENTS) == 0)
+ {
+ ADD_STRING(target, "\n");
+ }
+ return (result);
+ }
+ result = ISC_R_UNEXPECTED;
+cleanup:
+ return (result);
+}
+
+isc_result_t
+dns_message_headertotext(dns_message_t *msg, const dns_master_style_t *style,
+ dns_messagetextflag_t flags, isc_buffer_t *target) {
+ char buf[sizeof("1234567890")];
+ isc_result_t result;
+
+ REQUIRE(DNS_MESSAGE_VALID(msg));
+ REQUIRE(target != NULL);
+
+ if ((flags & DNS_MESSAGETEXTFLAG_NOHEADERS) != 0) {
+ return (ISC_R_SUCCESS);
+ }
+
+ if (dns_master_styleflags(style) & DNS_STYLEFLAG_YAML) {
+ INDENT(style);
+ ADD_STRING(target, "opcode: ");
+ ADD_STRING(target, opcodetext[msg->opcode]);
+ ADD_STRING(target, "\n");
+ INDENT(style);
+ ADD_STRING(target, "status: ");
+ result = dns_rcode_totext(msg->rcode, target);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+ ADD_STRING(target, "\n");
+ INDENT(style);
+ ADD_STRING(target, "id: ");
+ snprintf(buf, sizeof(buf), "%u", msg->id);
+ ADD_STRING(target, buf);
+ ADD_STRING(target, "\n");
+ INDENT(style);
+ ADD_STRING(target, "flags:");
+ if ((msg->flags & DNS_MESSAGEFLAG_QR) != 0) {
+ ADD_STRING(target, " qr");
+ }
+ if ((msg->flags & DNS_MESSAGEFLAG_AA) != 0) {
+ ADD_STRING(target, " aa");
+ }
+ if ((msg->flags & DNS_MESSAGEFLAG_TC) != 0) {
+ ADD_STRING(target, " tc");
+ }
+ if ((msg->flags & DNS_MESSAGEFLAG_RD) != 0) {
+ ADD_STRING(target, " rd");
+ }
+ if ((msg->flags & DNS_MESSAGEFLAG_RA) != 0) {
+ ADD_STRING(target, " ra");
+ }
+ if ((msg->flags & DNS_MESSAGEFLAG_AD) != 0) {
+ ADD_STRING(target, " ad");
+ }
+ if ((msg->flags & DNS_MESSAGEFLAG_CD) != 0) {
+ ADD_STRING(target, " cd");
+ }
+ ADD_STRING(target, "\n");
+ /*
+ * The final unnamed flag must be zero.
+ */
+ if ((msg->flags & 0x0040U) != 0) {
+ INDENT(style);
+ ADD_STRING(target, "MBZ: 0x4");
+ ADD_STRING(target, "\n");
+ }
+ if (msg->opcode != dns_opcode_update) {
+ INDENT(style);
+ ADD_STRING(target, "QUESTION: ");
+ } else {
+ INDENT(style);
+ ADD_STRING(target, "ZONE: ");
+ }
+ snprintf(buf, sizeof(buf), "%1u",
+ msg->counts[DNS_SECTION_QUESTION]);
+ ADD_STRING(target, buf);
+ ADD_STRING(target, "\n");
+ if (msg->opcode != dns_opcode_update) {
+ INDENT(style);
+ ADD_STRING(target, "ANSWER: ");
+ } else {
+ INDENT(style);
+ ADD_STRING(target, "PREREQ: ");
+ }
+ snprintf(buf, sizeof(buf), "%1u",
+ msg->counts[DNS_SECTION_ANSWER]);
+ ADD_STRING(target, buf);
+ ADD_STRING(target, "\n");
+ if (msg->opcode != dns_opcode_update) {
+ INDENT(style);
+ ADD_STRING(target, "AUTHORITY: ");
+ } else {
+ INDENT(style);
+ ADD_STRING(target, "UPDATE: ");
+ }
+ snprintf(buf, sizeof(buf), "%1u",
+ msg->counts[DNS_SECTION_AUTHORITY]);
+ ADD_STRING(target, buf);
+ ADD_STRING(target, "\n");
+ INDENT(style);
+ ADD_STRING(target, "ADDITIONAL: ");
+ snprintf(buf, sizeof(buf), "%1u",
+ msg->counts[DNS_SECTION_ADDITIONAL]);
+ ADD_STRING(target, buf);
+ ADD_STRING(target, "\n");
+ } else {
+ INDENT(style);
+ ADD_STRING(target, ";; ->>HEADER<<- opcode: ");
+ ADD_STRING(target, opcodetext[msg->opcode]);
+ ADD_STRING(target, ", status: ");
+ result = dns_rcode_totext(msg->rcode, target);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+ ADD_STRING(target, ", id: ");
+ snprintf(buf, sizeof(buf), "%6u", msg->id);
+ ADD_STRING(target, buf);
+ ADD_STRING(target, "\n");
+ INDENT(style);
+ ADD_STRING(target, ";; flags:");
+ if ((msg->flags & DNS_MESSAGEFLAG_QR) != 0) {
+ ADD_STRING(target, " qr");
+ }
+ if ((msg->flags & DNS_MESSAGEFLAG_AA) != 0) {
+ ADD_STRING(target, " aa");
+ }
+ if ((msg->flags & DNS_MESSAGEFLAG_TC) != 0) {
+ ADD_STRING(target, " tc");
+ }
+ if ((msg->flags & DNS_MESSAGEFLAG_RD) != 0) {
+ ADD_STRING(target, " rd");
+ }
+ if ((msg->flags & DNS_MESSAGEFLAG_RA) != 0) {
+ ADD_STRING(target, " ra");
+ }
+ if ((msg->flags & DNS_MESSAGEFLAG_AD) != 0) {
+ ADD_STRING(target, " ad");
+ }
+ if ((msg->flags & DNS_MESSAGEFLAG_CD) != 0) {
+ ADD_STRING(target, " cd");
+ }
+ /*
+ * The final unnamed flag must be zero.
+ */
+ if ((msg->flags & 0x0040U) != 0) {
+ INDENT(style);
+ ADD_STRING(target, "; MBZ: 0x4");
+ }
+ if (msg->opcode != dns_opcode_update) {
+ INDENT(style);
+ ADD_STRING(target, "; QUESTION: ");
+ } else {
+ INDENT(style);
+ ADD_STRING(target, "; ZONE: ");
+ }
+ snprintf(buf, sizeof(buf), "%1u",
+ msg->counts[DNS_SECTION_QUESTION]);
+ ADD_STRING(target, buf);
+ if (msg->opcode != dns_opcode_update) {
+ ADD_STRING(target, ", ANSWER: ");
+ } else {
+ ADD_STRING(target, ", PREREQ: ");
+ }
+ snprintf(buf, sizeof(buf), "%1u",
+ msg->counts[DNS_SECTION_ANSWER]);
+ ADD_STRING(target, buf);
+ if (msg->opcode != dns_opcode_update) {
+ ADD_STRING(target, ", AUTHORITY: ");
+ } else {
+ ADD_STRING(target, ", UPDATE: ");
+ }
+ snprintf(buf, sizeof(buf), "%1u",
+ msg->counts[DNS_SECTION_AUTHORITY]);
+ ADD_STRING(target, buf);
+ ADD_STRING(target, ", ADDITIONAL: ");
+ snprintf(buf, sizeof(buf), "%1u",
+ msg->counts[DNS_SECTION_ADDITIONAL]);
+ ADD_STRING(target, buf);
+ ADD_STRING(target, "\n");
+ }
+
+cleanup:
+ return (result);
+}
+
+isc_result_t
+dns_message_totext(dns_message_t *msg, const dns_master_style_t *style,
+ dns_messagetextflag_t flags, isc_buffer_t *target) {
+ isc_result_t result;
+
+ REQUIRE(DNS_MESSAGE_VALID(msg));
+ REQUIRE(target != NULL);
+
+ result = dns_message_headertotext(msg, style, flags, target);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+
+ result = dns_message_pseudosectiontotext(msg, DNS_PSEUDOSECTION_OPT,
+ style, flags, target);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+
+ result = dns_message_sectiontotext(msg, DNS_SECTION_QUESTION, style,
+ flags, target);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+
+ result = dns_message_sectiontotext(msg, DNS_SECTION_ANSWER, style,
+ flags, target);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+
+ result = dns_message_sectiontotext(msg, DNS_SECTION_AUTHORITY, style,
+ flags, target);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+
+ result = dns_message_sectiontotext(msg, DNS_SECTION_ADDITIONAL, style,
+ flags, target);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+
+ result = dns_message_pseudosectiontotext(msg, DNS_PSEUDOSECTION_TSIG,
+ style, flags, target);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+
+ result = dns_message_pseudosectiontotext(msg, DNS_PSEUDOSECTION_SIG0,
+ style, flags, target);
+ return (result);
+}
+
+isc_region_t *
+dns_message_getrawmessage(dns_message_t *msg) {
+ REQUIRE(DNS_MESSAGE_VALID(msg));
+ return (&msg->saved);
+}
+
+void
+dns_message_setsortorder(dns_message_t *msg, dns_rdatasetorderfunc_t order,
+ dns_aclenv_t *env, dns_acl_t *acl,
+ const dns_aclelement_t *elem) {
+ REQUIRE(DNS_MESSAGE_VALID(msg));
+ REQUIRE((order == NULL) == (env == NULL));
+ REQUIRE(env == NULL || (acl != NULL || elem != NULL));
+
+ msg->order = order;
+ if (env != NULL) {
+ dns_aclenv_attach(env, &msg->order_arg.env);
+ }
+ if (acl != NULL) {
+ dns_acl_attach(acl, &msg->order_arg.acl);
+ }
+ msg->order_arg.element = elem;
+}
+
+void
+dns_message_settimeadjust(dns_message_t *msg, int timeadjust) {
+ REQUIRE(DNS_MESSAGE_VALID(msg));
+ msg->timeadjust = timeadjust;
+}
+
+int
+dns_message_gettimeadjust(dns_message_t *msg) {
+ REQUIRE(DNS_MESSAGE_VALID(msg));
+ return (msg->timeadjust);
+}
+
+isc_result_t
+dns_opcode_totext(dns_opcode_t opcode, isc_buffer_t *target) {
+ REQUIRE(opcode < 16);
+
+ if (isc_buffer_availablelength(target) < strlen(opcodetext[opcode])) {
+ return (ISC_R_NOSPACE);
+ }
+ isc_buffer_putstr(target, opcodetext[opcode]);
+ return (ISC_R_SUCCESS);
+}
+
+void
+dns_message_logpacket(dns_message_t *message, const char *description,
+ const isc_sockaddr_t *address,
+ isc_logcategory_t *category, isc_logmodule_t *module,
+ int level, isc_mem_t *mctx) {
+ REQUIRE(address != NULL);
+
+ logfmtpacket(message, description, address, category, module,
+ &dns_master_style_debug, level, mctx);
+}
+
+void
+dns_message_logfmtpacket(dns_message_t *message, const char *description,
+ const isc_sockaddr_t *address,
+ isc_logcategory_t *category, isc_logmodule_t *module,
+ const dns_master_style_t *style, int level,
+ isc_mem_t *mctx) {
+ REQUIRE(address != NULL);
+
+ logfmtpacket(message, description, address, category, module, style,
+ level, mctx);
+}
+
+static void
+logfmtpacket(dns_message_t *message, const char *description,
+ const isc_sockaddr_t *address, isc_logcategory_t *category,
+ isc_logmodule_t *module, const dns_master_style_t *style,
+ int level, isc_mem_t *mctx) {
+ char addrbuf[ISC_SOCKADDR_FORMATSIZE] = { 0 };
+ const char *newline = "\n";
+ const char *space = " ";
+ isc_buffer_t buffer;
+ char *buf = NULL;
+ int len = 1024;
+ isc_result_t result;
+
+ if (!isc_log_wouldlog(dns_lctx, level)) {
+ return;
+ }
+
+ /*
+ * Note that these are multiline debug messages. We want a newline
+ * to appear in the log after each message.
+ */
+
+ if (address != NULL) {
+ isc_sockaddr_format(address, addrbuf, sizeof(addrbuf));
+ } else {
+ newline = space = "";
+ }
+
+ do {
+ buf = isc_mem_get(mctx, len);
+ isc_buffer_init(&buffer, buf, len);
+ result = dns_message_totext(message, style, 0, &buffer);
+ if (result == ISC_R_NOSPACE) {
+ isc_mem_put(mctx, buf, len);
+ len += 1024;
+ } else if (result == ISC_R_SUCCESS) {
+ isc_log_write(dns_lctx, category, module, level,
+ "%s%s%s%s%.*s", description, space,
+ addrbuf, newline,
+ (int)isc_buffer_usedlength(&buffer), buf);
+ }
+ } while (result == ISC_R_NOSPACE);
+
+ if (buf != NULL) {
+ isc_mem_put(mctx, buf, len);
+ }
+}
+
+isc_result_t
+dns_message_buildopt(dns_message_t *message, dns_rdataset_t **rdatasetp,
+ unsigned int version, uint16_t udpsize, unsigned int flags,
+ dns_ednsopt_t *ednsopts, size_t count) {
+ dns_rdataset_t *rdataset = NULL;
+ dns_rdatalist_t *rdatalist = NULL;
+ dns_rdata_t *rdata = NULL;
+ isc_result_t result;
+ unsigned int len = 0, i;
+
+ REQUIRE(DNS_MESSAGE_VALID(message));
+ REQUIRE(rdatasetp != NULL && *rdatasetp == NULL);
+
+ result = dns_message_gettemprdatalist(message, &rdatalist);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+ result = dns_message_gettemprdata(message, &rdata);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+ result = dns_message_gettemprdataset(message, &rdataset);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+
+ rdatalist->type = dns_rdatatype_opt;
+
+ /*
+ * Set Maximum UDP buffer size.
+ */
+ rdatalist->rdclass = udpsize;
+
+ /*
+ * Set EXTENDED-RCODE and Z to 0.
+ */
+ rdatalist->ttl = (version << 16);
+ rdatalist->ttl |= (flags & 0xffff);
+
+ /*
+ * Set EDNS options if applicable
+ */
+ if (count != 0U) {
+ isc_buffer_t *buf = NULL;
+ bool seenpad = false;
+ for (i = 0; i < count; i++) {
+ len += ednsopts[i].length + 4;
+ }
+
+ if (len > 0xffffU) {
+ result = ISC_R_NOSPACE;
+ goto cleanup;
+ }
+
+ isc_buffer_allocate(message->mctx, &buf, len);
+
+ for (i = 0; i < count; i++) {
+ if (ednsopts[i].code == DNS_OPT_PAD &&
+ ednsopts[i].length == 0U && !seenpad)
+ {
+ seenpad = true;
+ continue;
+ }
+ isc_buffer_putuint16(buf, ednsopts[i].code);
+ isc_buffer_putuint16(buf, ednsopts[i].length);
+ if (ednsopts[i].length != 0) {
+ isc_buffer_putmem(buf, ednsopts[i].value,
+ ednsopts[i].length);
+ }
+ }
+
+ /* Padding must be the final option */
+ if (seenpad) {
+ isc_buffer_putuint16(buf, DNS_OPT_PAD);
+ isc_buffer_putuint16(buf, 0);
+ }
+ rdata->data = isc_buffer_base(buf);
+ rdata->length = len;
+ dns_message_takebuffer(message, &buf);
+ if (seenpad) {
+ message->padding_off = len;
+ }
+ } else {
+ rdata->data = NULL;
+ rdata->length = 0;
+ }
+
+ rdata->rdclass = rdatalist->rdclass;
+ rdata->type = rdatalist->type;
+ rdata->flags = 0;
+
+ ISC_LIST_APPEND(rdatalist->rdata, rdata, link);
+ result = dns_rdatalist_tordataset(rdatalist, rdataset);
+ RUNTIME_CHECK(result == ISC_R_SUCCESS);
+
+ *rdatasetp = rdataset;
+ return (ISC_R_SUCCESS);
+
+cleanup:
+ if (rdata != NULL) {
+ dns_message_puttemprdata(message, &rdata);
+ }
+ if (rdataset != NULL) {
+ dns_message_puttemprdataset(message, &rdataset);
+ }
+ if (rdatalist != NULL) {
+ dns_message_puttemprdatalist(message, &rdatalist);
+ }
+ return (result);
+}
+
+void
+dns_message_setclass(dns_message_t *msg, dns_rdataclass_t rdclass) {
+ REQUIRE(DNS_MESSAGE_VALID(msg));
+ REQUIRE(msg->from_to_wire == DNS_MESSAGE_INTENTPARSE);
+ REQUIRE(msg->state == DNS_SECTION_ANY);
+ REQUIRE(msg->rdclass_set == 0);
+
+ msg->rdclass = rdclass;
+ msg->rdclass_set = 1;
+}
+
+void
+dns_message_setpadding(dns_message_t *msg, uint16_t padding) {
+ REQUIRE(DNS_MESSAGE_VALID(msg));
+
+ /* Avoid silly large padding */
+ if (padding > 512) {
+ padding = 512;
+ }
+ msg->padding = padding;
+}
+
+void
+dns_message_clonebuffer(dns_message_t *msg) {
+ REQUIRE(DNS_MESSAGE_VALID(msg));
+
+ if (msg->free_saved == 0 && msg->saved.base != NULL) {
+ msg->saved.base =
+ memmove(isc_mem_get(msg->mctx, msg->saved.length),
+ msg->saved.base, msg->saved.length);
+ msg->free_saved = 1;
+ }
+ if (msg->free_query == 0 && msg->query.base != NULL) {
+ msg->query.base =
+ memmove(isc_mem_get(msg->mctx, msg->query.length),
+ msg->query.base, msg->query.length);
+ msg->free_query = 1;
+ }
+}
+
+static isc_result_t
+message_authority_soa_min(dns_message_t *msg, dns_ttl_t *pttl) {
+ isc_result_t result;
+ dns_rdataset_t *rdataset = NULL;
+ dns_name_t *name = NULL;
+
+ if (msg->counts[DNS_SECTION_AUTHORITY] == 0) {
+ return (ISC_R_NOTFOUND);
+ }
+
+ for (result = dns_message_firstname(msg, DNS_SECTION_AUTHORITY);
+ result == ISC_R_SUCCESS;
+ result = dns_message_nextname(msg, DNS_SECTION_AUTHORITY))
+ {
+ name = NULL;
+ dns_message_currentname(msg, DNS_SECTION_AUTHORITY, &name);
+ for (rdataset = ISC_LIST_HEAD(name->list); rdataset != NULL;
+ rdataset = ISC_LIST_NEXT(rdataset, link))
+ {
+ isc_result_t tresult;
+
+ if ((rdataset->attributes &
+ DNS_RDATASETATTR_RENDERED) == 0)
+ {
+ continue;
+ }
+
+ /* loop over the rdatas */
+ for (tresult = dns_rdataset_first(rdataset);
+ tresult == ISC_R_SUCCESS;
+ tresult = dns_rdataset_next(rdataset))
+ {
+ dns_name_t tmp;
+ isc_region_t r = { 0 };
+ dns_rdata_t rdata = DNS_RDATA_INIT;
+
+ dns_rdataset_current(rdataset, &rdata);
+
+ switch (rdata.type) {
+ case dns_rdatatype_soa:
+ /* SOA rdataset */
+ break;
+ case dns_rdatatype_none:
+ /*
+ * Negative cache rdataset: we need
+ * to inspect the rdata to determine
+ * whether it's an SOA.
+ */
+ dns_rdata_toregion(&rdata, &r);
+ dns_name_init(&tmp, NULL);
+ dns_name_fromregion(&tmp, &r);
+ isc_region_consume(&r, tmp.length);
+ if (r.length < 2) {
+ continue;
+ }
+ rdata.type = r.base[0] << 8 | r.base[1];
+ if (rdata.type != dns_rdatatype_soa) {
+ continue;
+ }
+ break;
+ default:
+ continue;
+ }
+
+ if (rdata.type == dns_rdatatype_soa) {
+ *pttl = ISC_MIN(
+ rdataset->ttl,
+ dns_soa_getminimum(&rdata));
+ return (ISC_R_SUCCESS);
+ }
+ }
+ }
+ }
+
+ return (ISC_R_NOTFOUND);
+}
+
+isc_result_t
+dns_message_minttl(dns_message_t *msg, const dns_section_t sectionid,
+ dns_ttl_t *pttl) {
+ REQUIRE(DNS_MESSAGE_VALID(msg));
+ REQUIRE(pttl != NULL);
+
+ if (!msg->minttl[sectionid].is_set) {
+ return (ISC_R_NOTFOUND);
+ }
+
+ *pttl = msg->minttl[sectionid].ttl;
+ return (ISC_R_SUCCESS);
+}
+
+isc_result_t
+dns_message_response_minttl(dns_message_t *msg, dns_ttl_t *pttl) {
+ isc_result_t result;
+
+ REQUIRE(DNS_MESSAGE_VALID(msg));
+ REQUIRE(pttl != NULL);
+
+ result = dns_message_minttl(msg, DNS_SECTION_ANSWER, pttl);
+ if (result != ISC_R_SUCCESS) {
+ return (message_authority_soa_min(msg, pttl));
+ }
+
+ return (ISC_R_SUCCESS);
+}
diff --git a/lib/dns/name.c b/lib/dns/name.c
new file mode 100644
index 0000000..8a258a2
--- /dev/null
+++ b/lib/dns/name.c
@@ -0,0 +1,2652 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/*! \file */
+
+#include <ctype.h>
+#include <inttypes.h>
+#include <stdbool.h>
+#include <stdlib.h>
+
+#include <isc/buffer.h>
+#include <isc/hash.h>
+#include <isc/mem.h>
+#include <isc/once.h>
+#include <isc/print.h>
+#include <isc/random.h>
+#include <isc/result.h>
+#include <isc/string.h>
+#include <isc/thread.h>
+#include <isc/util.h>
+
+#include <dns/compress.h>
+#include <dns/fixedname.h>
+#include <dns/name.h>
+
+#define VALID_NAME(n) ISC_MAGIC_VALID(n, DNS_NAME_MAGIC)
+
+typedef enum {
+ ft_init = 0,
+ ft_start,
+ ft_ordinary,
+ ft_initialescape,
+ ft_escape,
+ ft_escdecimal,
+ ft_at
+} ft_state;
+
+typedef enum { fw_start = 0, fw_ordinary, fw_newcurrent } fw_state;
+
+static char digitvalue[256] = {
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /*16*/
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /*32*/
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /*48*/
+ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, -1, -1, -1, -1, -1, -1, /*64*/
+ -1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1, /*80*/
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /*96*/
+ -1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1, /*112*/
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /*128*/
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /*256*/
+};
+
+static unsigned char maptolower[] = {
+ 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b,
+ 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17,
+ 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20, 0x21, 0x22, 0x23,
+ 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f,
+ 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b,
+ 0x3c, 0x3d, 0x3e, 0x3f, 0x40, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67,
+ 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, 0x70, 0x71, 0x72, 0x73,
+ 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f,
+ 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6a, 0x6b,
+ 0x6c, 0x6d, 0x6e, 0x6f, 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77,
+ 0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f, 0x80, 0x81, 0x82, 0x83,
+ 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a, 0x8b, 0x8c, 0x8d, 0x8e, 0x8f,
+ 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9a, 0x9b,
+ 0x9c, 0x9d, 0x9e, 0x9f, 0xa0, 0xa1, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7,
+ 0xa8, 0xa9, 0xaa, 0xab, 0xac, 0xad, 0xae, 0xaf, 0xb0, 0xb1, 0xb2, 0xb3,
+ 0xb4, 0xb5, 0xb6, 0xb7, 0xb8, 0xb9, 0xba, 0xbb, 0xbc, 0xbd, 0xbe, 0xbf,
+ 0xc0, 0xc1, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7, 0xc8, 0xc9, 0xca, 0xcb,
+ 0xcc, 0xcd, 0xce, 0xcf, 0xd0, 0xd1, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7,
+ 0xd8, 0xd9, 0xda, 0xdb, 0xdc, 0xdd, 0xde, 0xdf, 0xe0, 0xe1, 0xe2, 0xe3,
+ 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea, 0xeb, 0xec, 0xed, 0xee, 0xef,
+ 0xf0, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9, 0xfa, 0xfb,
+ 0xfc, 0xfd, 0xfe, 0xff
+};
+
+#define CONVERTTOASCII(c)
+#define CONVERTFROMASCII(c)
+
+#define INIT_OFFSETS(name, var, default_offsets) \
+ if ((name)->offsets != NULL) \
+ var = (name)->offsets; \
+ else \
+ var = (default_offsets);
+
+#define SETUP_OFFSETS(name, var, default_offsets) \
+ if ((name)->offsets != NULL) { \
+ var = (name)->offsets; \
+ } else { \
+ var = (default_offsets); \
+ set_offsets(name, var, NULL); \
+ }
+
+/*%
+ * Note: If additional attributes are added that should not be set for
+ * empty names, MAKE_EMPTY() must be changed so it clears them.
+ */
+#define MAKE_EMPTY(name) \
+ do { \
+ name->ndata = NULL; \
+ name->length = 0; \
+ name->labels = 0; \
+ name->attributes &= ~DNS_NAMEATTR_ABSOLUTE; \
+ } while (0);
+
+/*%
+ * A name is "bindable" if it can be set to point to a new value, i.e.
+ * name->ndata and name->length may be changed.
+ */
+#define BINDABLE(name) \
+ ((name->attributes & \
+ (DNS_NAMEATTR_READONLY | DNS_NAMEATTR_DYNAMIC)) == 0)
+
+/*%
+ * Note that the name data must be a char array, not a string
+ * literal, to avoid compiler warnings about discarding
+ * the const attribute of a string.
+ */
+static unsigned char root_ndata[] = { "" };
+static unsigned char root_offsets[] = { 0 };
+
+static dns_name_t root = DNS_NAME_INITABSOLUTE(root_ndata, root_offsets);
+const dns_name_t *dns_rootname = &root;
+
+static unsigned char wild_ndata[] = { "\001*" };
+static unsigned char wild_offsets[] = { 0 };
+
+static dns_name_t const wild = DNS_NAME_INITNONABSOLUTE(wild_ndata,
+ wild_offsets);
+
+const dns_name_t *dns_wildcardname = &wild;
+
+/*
+ * dns_name_t to text post-conversion procedure.
+ */
+static thread_local dns_name_totextfilter_t *totext_filter_proc = NULL;
+
+static void
+set_offsets(const dns_name_t *name, unsigned char *offsets,
+ dns_name_t *set_name);
+
+void
+dns_name_init(dns_name_t *name, unsigned char *offsets) {
+ /*
+ * Initialize 'name'.
+ */
+ DNS_NAME_INIT(name, offsets);
+}
+
+void
+dns_name_reset(dns_name_t *name) {
+ REQUIRE(VALID_NAME(name));
+ REQUIRE(BINDABLE(name));
+
+ DNS_NAME_RESET(name);
+}
+
+void
+dns_name_invalidate(dns_name_t *name) {
+ /*
+ * Make 'name' invalid.
+ */
+
+ REQUIRE(VALID_NAME(name));
+
+ name->magic = 0;
+ name->ndata = NULL;
+ name->length = 0;
+ name->labels = 0;
+ name->attributes = 0;
+ name->offsets = NULL;
+ name->buffer = NULL;
+ ISC_LINK_INIT(name, link);
+}
+
+bool
+dns_name_isvalid(const dns_name_t *name) {
+ unsigned char *ndata, *offsets;
+ unsigned int offset, count, length, nlabels;
+
+ if (!VALID_NAME(name)) {
+ return (false);
+ }
+
+ if (name->length > 255U || name->labels > 127U) {
+ return (false);
+ }
+
+ ndata = name->ndata;
+ length = name->length;
+ offsets = name->offsets;
+ offset = 0;
+ nlabels = 0;
+
+ while (offset != length) {
+ count = *ndata;
+ if (count > 63U) {
+ return (false);
+ }
+ if (offsets != NULL && offsets[nlabels] != offset) {
+ return (false);
+ }
+
+ nlabels++;
+ offset += count + 1;
+ ndata += count + 1;
+ if (offset > length) {
+ return (false);
+ }
+
+ if (count == 0) {
+ break;
+ }
+ }
+
+ if (nlabels != name->labels || offset != name->length) {
+ return (false);
+ }
+
+ return (true);
+}
+
+void
+dns_name_setbuffer(dns_name_t *name, isc_buffer_t *buffer) {
+ /*
+ * Dedicate a buffer for use with 'name'.
+ */
+
+ REQUIRE(VALID_NAME(name));
+ REQUIRE((buffer != NULL && name->buffer == NULL) || (buffer == NULL));
+
+ name->buffer = buffer;
+}
+
+bool
+dns_name_hasbuffer(const dns_name_t *name) {
+ /*
+ * Does 'name' have a dedicated buffer?
+ */
+
+ REQUIRE(VALID_NAME(name));
+
+ if (name->buffer != NULL) {
+ return (true);
+ }
+
+ return (false);
+}
+
+bool
+dns_name_isabsolute(const dns_name_t *name) {
+ /*
+ * Does 'name' end in the root label?
+ */
+
+ REQUIRE(VALID_NAME(name));
+
+ if ((name->attributes & DNS_NAMEATTR_ABSOLUTE) != 0) {
+ return (true);
+ }
+ return (false);
+}
+
+#define hyphenchar(c) ((c) == 0x2d)
+#define asterchar(c) ((c) == 0x2a)
+#define alphachar(c) \
+ (((c) >= 0x41 && (c) <= 0x5a) || ((c) >= 0x61 && (c) <= 0x7a))
+#define digitchar(c) ((c) >= 0x30 && (c) <= 0x39)
+#define borderchar(c) (alphachar(c) || digitchar(c))
+#define middlechar(c) (borderchar(c) || hyphenchar(c))
+#define domainchar(c) ((c) > 0x20 && (c) < 0x7f)
+
+bool
+dns_name_ismailbox(const dns_name_t *name) {
+ unsigned char *ndata, ch;
+ unsigned int n;
+ bool first;
+
+ REQUIRE(VALID_NAME(name));
+ REQUIRE(name->labels > 0);
+ REQUIRE(name->attributes & DNS_NAMEATTR_ABSOLUTE);
+
+ /*
+ * Root label.
+ */
+ if (name->length == 1) {
+ return (true);
+ }
+
+ ndata = name->ndata;
+ n = *ndata++;
+ INSIST(n <= 63);
+ while (n--) {
+ ch = *ndata++;
+ if (!domainchar(ch)) {
+ return (false);
+ }
+ }
+
+ if (ndata == name->ndata + name->length) {
+ return (false);
+ }
+
+ /*
+ * RFC952/RFC1123 hostname.
+ */
+ while (ndata < (name->ndata + name->length)) {
+ n = *ndata++;
+ INSIST(n <= 63);
+ first = true;
+ while (n--) {
+ ch = *ndata++;
+ if (first || n == 0) {
+ if (!borderchar(ch)) {
+ return (false);
+ }
+ } else {
+ if (!middlechar(ch)) {
+ return (false);
+ }
+ }
+ first = false;
+ }
+ }
+ return (true);
+}
+
+bool
+dns_name_ishostname(const dns_name_t *name, bool wildcard) {
+ unsigned char *ndata, ch;
+ unsigned int n;
+ bool first;
+
+ REQUIRE(VALID_NAME(name));
+ REQUIRE(name->labels > 0);
+ REQUIRE(name->attributes & DNS_NAMEATTR_ABSOLUTE);
+
+ /*
+ * Root label.
+ */
+ if (name->length == 1) {
+ return (true);
+ }
+
+ /*
+ * Skip wildcard if this is a ownername.
+ */
+ ndata = name->ndata;
+ if (wildcard && ndata[0] == 1 && ndata[1] == '*') {
+ ndata += 2;
+ }
+
+ /*
+ * RFC952/RFC1123 hostname.
+ */
+ while (ndata < (name->ndata + name->length)) {
+ n = *ndata++;
+ INSIST(n <= 63);
+ first = true;
+ while (n--) {
+ ch = *ndata++;
+ if (first || n == 0) {
+ if (!borderchar(ch)) {
+ return (false);
+ }
+ } else {
+ if (!middlechar(ch)) {
+ return (false);
+ }
+ }
+ first = false;
+ }
+ }
+ return (true);
+}
+
+bool
+dns_name_iswildcard(const dns_name_t *name) {
+ unsigned char *ndata;
+
+ /*
+ * Is 'name' a wildcard name?
+ */
+
+ REQUIRE(VALID_NAME(name));
+ REQUIRE(name->labels > 0);
+
+ if (name->length >= 2) {
+ ndata = name->ndata;
+ if (ndata[0] == 1 && ndata[1] == '*') {
+ return (true);
+ }
+ }
+
+ return (false);
+}
+
+bool
+dns_name_internalwildcard(const dns_name_t *name) {
+ unsigned char *ndata;
+ unsigned int count;
+ unsigned int label;
+
+ /*
+ * Does 'name' contain a internal wildcard?
+ */
+
+ REQUIRE(VALID_NAME(name));
+ REQUIRE(name->labels > 0);
+
+ /*
+ * Skip first label.
+ */
+ ndata = name->ndata;
+ count = *ndata++;
+ INSIST(count <= 63);
+ ndata += count;
+ label = 1;
+ /*
+ * Check all but the last of the remaining labels.
+ */
+ while (label + 1 < name->labels) {
+ count = *ndata++;
+ INSIST(count <= 63);
+ if (count == 1 && *ndata == '*') {
+ return (true);
+ }
+ ndata += count;
+ label++;
+ }
+ return (false);
+}
+
+unsigned int
+dns_name_hash(const dns_name_t *name, bool case_sensitive) {
+ unsigned int length;
+
+ /*
+ * Provide a hash value for 'name'.
+ */
+ REQUIRE(VALID_NAME(name));
+
+ if (name->labels == 0) {
+ return (0);
+ }
+
+ length = name->length;
+ if (length > 16) {
+ length = 16;
+ }
+
+ /* High bits are more random. */
+ return (isc_hash32(name->ndata, length, case_sensitive));
+}
+
+unsigned int
+dns_name_fullhash(const dns_name_t *name, bool case_sensitive) {
+ /*
+ * Provide a hash value for 'name'.
+ */
+ REQUIRE(VALID_NAME(name));
+
+ if (name->labels == 0) {
+ return (0);
+ }
+
+ /* High bits are more random. */
+ return (isc_hash32(name->ndata, name->length, case_sensitive));
+}
+
+dns_namereln_t
+dns_name_fullcompare(const dns_name_t *name1, const dns_name_t *name2,
+ int *orderp, unsigned int *nlabelsp) {
+ unsigned int l1, l2, l, count1, count2, count, nlabels;
+ int cdiff, ldiff, chdiff;
+ unsigned char *label1, *label2;
+ unsigned char *offsets1, *offsets2;
+ dns_offsets_t odata1, odata2;
+ dns_namereln_t namereln = dns_namereln_none;
+
+ /*
+ * Determine the relative ordering under the DNSSEC order relation of
+ * 'name1' and 'name2', and also determine the hierarchical
+ * relationship of the names.
+ *
+ * Note: It makes no sense for one of the names to be relative and the
+ * other absolute. If both names are relative, then to be meaningfully
+ * compared the caller must ensure that they are both relative to the
+ * same domain.
+ */
+
+ REQUIRE(VALID_NAME(name1));
+ REQUIRE(VALID_NAME(name2));
+ REQUIRE(orderp != NULL);
+ REQUIRE(nlabelsp != NULL);
+ /*
+ * Either name1 is absolute and name2 is absolute, or neither is.
+ */
+ REQUIRE((name1->attributes & DNS_NAMEATTR_ABSOLUTE) ==
+ (name2->attributes & DNS_NAMEATTR_ABSOLUTE));
+
+ if (name1 == name2) {
+ *orderp = 0;
+ *nlabelsp = name1->labels;
+ return (dns_namereln_equal);
+ }
+
+ SETUP_OFFSETS(name1, offsets1, odata1);
+ SETUP_OFFSETS(name2, offsets2, odata2);
+
+ nlabels = 0;
+ l1 = name1->labels;
+ l2 = name2->labels;
+ if (l2 > l1) {
+ l = l1;
+ ldiff = 0 - (l2 - l1);
+ } else {
+ l = l2;
+ ldiff = l1 - l2;
+ }
+
+ offsets1 += l1;
+ offsets2 += l2;
+
+ while (l > 0) {
+ l--;
+ offsets1--;
+ offsets2--;
+ label1 = &name1->ndata[*offsets1];
+ label2 = &name2->ndata[*offsets2];
+ count1 = *label1++;
+ count2 = *label2++;
+
+ /*
+ * We dropped bitstring labels, and we don't support any
+ * other extended label types.
+ */
+ INSIST(count1 <= 63 && count2 <= 63);
+
+ cdiff = (int)count1 - (int)count2;
+ if (cdiff < 0) {
+ count = count1;
+ } else {
+ count = count2;
+ }
+
+ /* Loop unrolled for performance */
+ while (count > 3) {
+ chdiff = (int)maptolower[label1[0]] -
+ (int)maptolower[label2[0]];
+ if (chdiff != 0) {
+ *orderp = chdiff;
+ goto done;
+ }
+ chdiff = (int)maptolower[label1[1]] -
+ (int)maptolower[label2[1]];
+ if (chdiff != 0) {
+ *orderp = chdiff;
+ goto done;
+ }
+ chdiff = (int)maptolower[label1[2]] -
+ (int)maptolower[label2[2]];
+ if (chdiff != 0) {
+ *orderp = chdiff;
+ goto done;
+ }
+ chdiff = (int)maptolower[label1[3]] -
+ (int)maptolower[label2[3]];
+ if (chdiff != 0) {
+ *orderp = chdiff;
+ goto done;
+ }
+ count -= 4;
+ label1 += 4;
+ label2 += 4;
+ }
+ while (count-- > 0) {
+ chdiff = (int)maptolower[*label1++] -
+ (int)maptolower[*label2++];
+ if (chdiff != 0) {
+ *orderp = chdiff;
+ goto done;
+ }
+ }
+ if (cdiff != 0) {
+ *orderp = cdiff;
+ goto done;
+ }
+ nlabels++;
+ }
+
+ *orderp = ldiff;
+ if (ldiff < 0) {
+ namereln = dns_namereln_contains;
+ } else if (ldiff > 0) {
+ namereln = dns_namereln_subdomain;
+ } else {
+ namereln = dns_namereln_equal;
+ }
+ *nlabelsp = nlabels;
+ return (namereln);
+
+done:
+ *nlabelsp = nlabels;
+ if (nlabels > 0) {
+ namereln = dns_namereln_commonancestor;
+ }
+
+ return (namereln);
+}
+
+int
+dns_name_compare(const dns_name_t *name1, const dns_name_t *name2) {
+ int order;
+ unsigned int nlabels;
+
+ /*
+ * Determine the relative ordering under the DNSSEC order relation of
+ * 'name1' and 'name2'.
+ *
+ * Note: It makes no sense for one of the names to be relative and the
+ * other absolute. If both names are relative, then to be meaningfully
+ * compared the caller must ensure that they are both relative to the
+ * same domain.
+ */
+
+ (void)dns_name_fullcompare(name1, name2, &order, &nlabels);
+
+ return (order);
+}
+
+bool
+dns_name_equal(const dns_name_t *name1, const dns_name_t *name2) {
+ unsigned int l, count;
+ unsigned char c;
+ unsigned char *label1, *label2;
+
+ /*
+ * Are 'name1' and 'name2' equal?
+ *
+ * Note: It makes no sense for one of the names to be relative and the
+ * other absolute. If both names are relative, then to be meaningfully
+ * compared the caller must ensure that they are both relative to the
+ * same domain.
+ */
+
+ REQUIRE(VALID_NAME(name1));
+ REQUIRE(VALID_NAME(name2));
+ /*
+ * Either name1 is absolute and name2 is absolute, or neither is.
+ */
+ REQUIRE((name1->attributes & DNS_NAMEATTR_ABSOLUTE) ==
+ (name2->attributes & DNS_NAMEATTR_ABSOLUTE));
+
+ if (name1 == name2) {
+ return (true);
+ }
+
+ if (name1->length != name2->length) {
+ return (false);
+ }
+
+ l = name1->labels;
+
+ if (l != name2->labels) {
+ return (false);
+ }
+
+ label1 = name1->ndata;
+ label2 = name2->ndata;
+ while (l-- > 0) {
+ count = *label1++;
+ if (count != *label2++) {
+ return (false);
+ }
+
+ INSIST(count <= 63); /* no bitstring support */
+
+ /* Loop unrolled for performance */
+ while (count > 3) {
+ c = maptolower[label1[0]];
+ if (c != maptolower[label2[0]]) {
+ return (false);
+ }
+ c = maptolower[label1[1]];
+ if (c != maptolower[label2[1]]) {
+ return (false);
+ }
+ c = maptolower[label1[2]];
+ if (c != maptolower[label2[2]]) {
+ return (false);
+ }
+ c = maptolower[label1[3]];
+ if (c != maptolower[label2[3]]) {
+ return (false);
+ }
+ count -= 4;
+ label1 += 4;
+ label2 += 4;
+ }
+ while (count-- > 0) {
+ c = maptolower[*label1++];
+ if (c != maptolower[*label2++]) {
+ return (false);
+ }
+ }
+ }
+
+ return (true);
+}
+
+bool
+dns_name_caseequal(const dns_name_t *name1, const dns_name_t *name2) {
+ /*
+ * Are 'name1' and 'name2' equal?
+ *
+ * Note: It makes no sense for one of the names to be relative and the
+ * other absolute. If both names are relative, then to be meaningfully
+ * compared the caller must ensure that they are both relative to the
+ * same domain.
+ */
+
+ REQUIRE(VALID_NAME(name1));
+ REQUIRE(VALID_NAME(name2));
+ /*
+ * Either name1 is absolute and name2 is absolute, or neither is.
+ */
+ REQUIRE((name1->attributes & DNS_NAMEATTR_ABSOLUTE) ==
+ (name2->attributes & DNS_NAMEATTR_ABSOLUTE));
+
+ if (name1->length != name2->length) {
+ return (false);
+ }
+
+ if (memcmp(name1->ndata, name2->ndata, name1->length) != 0) {
+ return (false);
+ }
+
+ return (true);
+}
+
+int
+dns_name_rdatacompare(const dns_name_t *name1, const dns_name_t *name2) {
+ unsigned int l1, l2, l, count1, count2, count;
+ unsigned char c1, c2;
+ unsigned char *label1, *label2;
+
+ /*
+ * Compare two absolute names as rdata.
+ */
+
+ REQUIRE(VALID_NAME(name1));
+ REQUIRE(name1->labels > 0);
+ REQUIRE((name1->attributes & DNS_NAMEATTR_ABSOLUTE) != 0);
+ REQUIRE(VALID_NAME(name2));
+ REQUIRE(name2->labels > 0);
+ REQUIRE((name2->attributes & DNS_NAMEATTR_ABSOLUTE) != 0);
+
+ l1 = name1->labels;
+ l2 = name2->labels;
+
+ l = (l1 < l2) ? l1 : l2;
+
+ label1 = name1->ndata;
+ label2 = name2->ndata;
+ while (l > 0) {
+ l--;
+ count1 = *label1++;
+ count2 = *label2++;
+
+ /* no bitstring support */
+ INSIST(count1 <= 63 && count2 <= 63);
+
+ if (count1 != count2) {
+ return ((count1 < count2) ? -1 : 1);
+ }
+ count = count1;
+ while (count > 0) {
+ count--;
+ c1 = maptolower[*label1++];
+ c2 = maptolower[*label2++];
+ if (c1 < c2) {
+ return (-1);
+ } else if (c1 > c2) {
+ return (1);
+ }
+ }
+ }
+
+ /*
+ * If one name had more labels than the other, their common
+ * prefix must have been different because the shorter name
+ * ended with the root label and the longer one can't have
+ * a root label in the middle of it. Therefore, if we get
+ * to this point, the lengths must be equal.
+ */
+ INSIST(l1 == l2);
+
+ return (0);
+}
+
+bool
+dns_name_issubdomain(const dns_name_t *name1, const dns_name_t *name2) {
+ int order;
+ unsigned int nlabels;
+ dns_namereln_t namereln;
+
+ /*
+ * Is 'name1' a subdomain of 'name2'?
+ *
+ * Note: It makes no sense for one of the names to be relative and the
+ * other absolute. If both names are relative, then to be meaningfully
+ * compared the caller must ensure that they are both relative to the
+ * same domain.
+ */
+
+ namereln = dns_name_fullcompare(name1, name2, &order, &nlabels);
+ if (namereln == dns_namereln_subdomain ||
+ namereln == dns_namereln_equal)
+ {
+ return (true);
+ }
+
+ return (false);
+}
+
+bool
+dns_name_matcheswildcard(const dns_name_t *name, const dns_name_t *wname) {
+ int order;
+ unsigned int nlabels, labels;
+ dns_name_t tname;
+
+ REQUIRE(VALID_NAME(name));
+ REQUIRE(name->labels > 0);
+ REQUIRE(VALID_NAME(wname));
+ labels = wname->labels;
+ REQUIRE(labels > 0);
+ REQUIRE(dns_name_iswildcard(wname));
+
+ DNS_NAME_INIT(&tname, NULL);
+ dns_name_getlabelsequence(wname, 1, labels - 1, &tname);
+ if (dns_name_fullcompare(name, &tname, &order, &nlabels) ==
+ dns_namereln_subdomain)
+ {
+ return (true);
+ }
+ return (false);
+}
+
+unsigned int
+dns_name_countlabels(const dns_name_t *name) {
+ /*
+ * How many labels does 'name' have?
+ */
+
+ REQUIRE(VALID_NAME(name));
+
+ ENSURE(name->labels <= 128);
+
+ return (name->labels);
+}
+
+void
+dns_name_getlabel(const dns_name_t *name, unsigned int n, dns_label_t *label) {
+ unsigned char *offsets;
+ dns_offsets_t odata;
+
+ /*
+ * Make 'label' refer to the 'n'th least significant label of 'name'.
+ */
+
+ REQUIRE(VALID_NAME(name));
+ REQUIRE(name->labels > 0);
+ REQUIRE(n < name->labels);
+ REQUIRE(label != NULL);
+
+ SETUP_OFFSETS(name, offsets, odata);
+
+ label->base = &name->ndata[offsets[n]];
+ if (n == name->labels - 1) {
+ label->length = name->length - offsets[n];
+ } else {
+ label->length = offsets[n + 1] - offsets[n];
+ }
+}
+
+void
+dns_name_getlabelsequence(const dns_name_t *source, unsigned int first,
+ unsigned int n, dns_name_t *target) {
+ unsigned char *p, l;
+ unsigned int firstoffset, endoffset;
+ unsigned int i;
+
+ /*
+ * Make 'target' refer to the 'n' labels including and following
+ * 'first' in 'source'.
+ */
+
+ REQUIRE(VALID_NAME(source));
+ REQUIRE(VALID_NAME(target));
+ REQUIRE(first <= source->labels);
+ REQUIRE(n <= source->labels - first); /* note first+n could overflow */
+ REQUIRE(BINDABLE(target));
+
+ p = source->ndata;
+ if (first == source->labels) {
+ firstoffset = source->length;
+ } else {
+ for (i = 0; i < first; i++) {
+ l = *p;
+ p += l + 1;
+ }
+ firstoffset = (unsigned int)(p - source->ndata);
+ }
+
+ if (first + n == source->labels) {
+ endoffset = source->length;
+ } else {
+ for (i = 0; i < n; i++) {
+ l = *p;
+ p += l + 1;
+ }
+ endoffset = (unsigned int)(p - source->ndata);
+ }
+
+ target->ndata = &source->ndata[firstoffset];
+ target->length = endoffset - firstoffset;
+
+ if (first + n == source->labels && n > 0 &&
+ (source->attributes & DNS_NAMEATTR_ABSOLUTE) != 0)
+ {
+ target->attributes |= DNS_NAMEATTR_ABSOLUTE;
+ } else {
+ target->attributes &= ~DNS_NAMEATTR_ABSOLUTE;
+ }
+
+ target->labels = n;
+
+ /*
+ * If source and target are the same, and we're making target
+ * a prefix of source, the offsets table is correct already
+ * so we don't need to call set_offsets().
+ */
+ if (target->offsets != NULL && (target != source || first != 0)) {
+ set_offsets(target, target->offsets, NULL);
+ }
+}
+
+void
+dns_name_clone(const dns_name_t *source, dns_name_t *target) {
+ /*
+ * Make 'target' refer to the same name as 'source'.
+ */
+
+ REQUIRE(VALID_NAME(source));
+ REQUIRE(VALID_NAME(target));
+ REQUIRE(BINDABLE(target));
+
+ target->ndata = source->ndata;
+ target->length = source->length;
+ target->labels = source->labels;
+ target->attributes = source->attributes &
+ (unsigned int)~(DNS_NAMEATTR_READONLY |
+ DNS_NAMEATTR_DYNAMIC |
+ DNS_NAMEATTR_DYNOFFSETS);
+ if (target->offsets != NULL && source->labels > 0) {
+ if (source->offsets != NULL) {
+ memmove(target->offsets, source->offsets,
+ source->labels);
+ } else {
+ set_offsets(target, target->offsets, NULL);
+ }
+ }
+}
+
+void
+dns_name_fromregion(dns_name_t *name, const isc_region_t *r) {
+ unsigned char *offsets;
+ dns_offsets_t odata;
+ unsigned int len;
+ isc_region_t r2;
+
+ /*
+ * Make 'name' refer to region 'r'.
+ */
+
+ REQUIRE(VALID_NAME(name));
+ REQUIRE(r != NULL);
+ REQUIRE(BINDABLE(name));
+
+ INIT_OFFSETS(name, offsets, odata);
+
+ if (name->buffer != NULL) {
+ isc_buffer_clear(name->buffer);
+ isc_buffer_availableregion(name->buffer, &r2);
+ len = (r->length < r2.length) ? r->length : r2.length;
+ if (len > DNS_NAME_MAXWIRE) {
+ len = DNS_NAME_MAXWIRE;
+ }
+ if (len != 0) {
+ memmove(r2.base, r->base, len);
+ }
+ name->ndata = r2.base;
+ name->length = len;
+ } else {
+ name->ndata = r->base;
+ name->length = (r->length <= DNS_NAME_MAXWIRE)
+ ? r->length
+ : DNS_NAME_MAXWIRE;
+ }
+
+ if (r->length > 0) {
+ set_offsets(name, offsets, name);
+ } else {
+ name->labels = 0;
+ name->attributes &= ~DNS_NAMEATTR_ABSOLUTE;
+ }
+
+ if (name->buffer != NULL) {
+ isc_buffer_add(name->buffer, name->length);
+ }
+}
+
+void
+dns_name_toregion(const dns_name_t *name, isc_region_t *r) {
+ /*
+ * Make 'r' refer to 'name'.
+ */
+
+ REQUIRE(VALID_NAME(name));
+ REQUIRE(r != NULL);
+
+ DNS_NAME_TOREGION(name, r);
+}
+
+isc_result_t
+dns_name_fromtext(dns_name_t *name, isc_buffer_t *source,
+ const dns_name_t *origin, unsigned int options,
+ isc_buffer_t *target) {
+ unsigned char *ndata, *label = NULL;
+ char *tdata;
+ char c;
+ ft_state state;
+ unsigned int value = 0, count = 0;
+ unsigned int n1 = 0, n2 = 0;
+ unsigned int tlen, nrem, nused, digits = 0, labels, tused;
+ bool done;
+ unsigned char *offsets;
+ dns_offsets_t odata;
+ bool downcase;
+
+ /*
+ * Convert the textual representation of a DNS name at source
+ * into uncompressed wire form stored in target.
+ *
+ * Notes:
+ * Relative domain names will have 'origin' appended to them
+ * unless 'origin' is NULL, in which case relative domain names
+ * will remain relative.
+ */
+
+ REQUIRE(VALID_NAME(name));
+ REQUIRE(ISC_BUFFER_VALID(source));
+ REQUIRE((target != NULL && ISC_BUFFER_VALID(target)) ||
+ (target == NULL && ISC_BUFFER_VALID(name->buffer)));
+
+ downcase = ((options & DNS_NAME_DOWNCASE) != 0);
+
+ if (target == NULL && name->buffer != NULL) {
+ target = name->buffer;
+ isc_buffer_clear(target);
+ }
+
+ REQUIRE(BINDABLE(name));
+
+ INIT_OFFSETS(name, offsets, odata);
+ offsets[0] = 0;
+
+ /*
+ * Make 'name' empty in case of failure.
+ */
+ MAKE_EMPTY(name);
+
+ /*
+ * Set up the state machine.
+ */
+ tdata = (char *)source->base + source->current;
+ tlen = isc_buffer_remaininglength(source);
+ tused = 0;
+ ndata = isc_buffer_used(target);
+ nrem = isc_buffer_availablelength(target);
+ if (nrem > 255) {
+ nrem = 255;
+ }
+ nused = 0;
+ labels = 0;
+ done = false;
+ state = ft_init;
+
+ while (nrem > 0 && tlen > 0 && !done) {
+ c = *tdata++;
+ tlen--;
+ tused++;
+
+ switch (state) {
+ case ft_init:
+ /*
+ * Is this the root name?
+ */
+ if (c == '.') {
+ if (tlen != 0) {
+ return (DNS_R_EMPTYLABEL);
+ }
+ labels++;
+ *ndata++ = 0;
+ nrem--;
+ nused++;
+ done = true;
+ break;
+ }
+ if (c == '@' && tlen == 0) {
+ state = ft_at;
+ break;
+ }
+
+ FALLTHROUGH;
+ case ft_start:
+ label = ndata;
+ ndata++;
+ nrem--;
+ nused++;
+ count = 0;
+ if (c == '\\') {
+ state = ft_initialescape;
+ break;
+ }
+ state = ft_ordinary;
+ if (nrem == 0) {
+ return (ISC_R_NOSPACE);
+ }
+ FALLTHROUGH;
+ case ft_ordinary:
+ if (c == '.') {
+ if (count == 0) {
+ return (DNS_R_EMPTYLABEL);
+ }
+ *label = count;
+ labels++;
+ INSIST(labels <= 127);
+ offsets[labels] = nused;
+ if (tlen == 0) {
+ labels++;
+ *ndata++ = 0;
+ nrem--;
+ nused++;
+ done = true;
+ }
+ state = ft_start;
+ } else if (c == '\\') {
+ state = ft_escape;
+ } else {
+ if (count >= 63) {
+ return (DNS_R_LABELTOOLONG);
+ }
+ count++;
+ CONVERTTOASCII(c);
+ if (downcase) {
+ c = maptolower[c & 0xff];
+ }
+ *ndata++ = c;
+ nrem--;
+ nused++;
+ }
+ break;
+ case ft_initialescape:
+ if (c == '[') {
+ /*
+ * This looks like a bitstring label, which
+ * was deprecated. Intentionally drop it.
+ */
+ return (DNS_R_BADLABELTYPE);
+ }
+ state = ft_escape;
+ POST(state);
+ FALLTHROUGH;
+ case ft_escape:
+ if (!isdigit((unsigned char)c)) {
+ if (count >= 63) {
+ return (DNS_R_LABELTOOLONG);
+ }
+ count++;
+ CONVERTTOASCII(c);
+ if (downcase) {
+ c = maptolower[c & 0xff];
+ }
+ *ndata++ = c;
+ nrem--;
+ nused++;
+ state = ft_ordinary;
+ break;
+ }
+ digits = 0;
+ value = 0;
+ state = ft_escdecimal;
+ FALLTHROUGH;
+ case ft_escdecimal:
+ if (!isdigit((unsigned char)c)) {
+ return (DNS_R_BADESCAPE);
+ }
+ value *= 10;
+ value += digitvalue[c & 0xff];
+ digits++;
+ if (digits == 3) {
+ if (value > 255) {
+ return (DNS_R_BADESCAPE);
+ }
+ if (count >= 63) {
+ return (DNS_R_LABELTOOLONG);
+ }
+ count++;
+ if (downcase) {
+ value = maptolower[value];
+ }
+ *ndata++ = value;
+ nrem--;
+ nused++;
+ state = ft_ordinary;
+ }
+ break;
+ default:
+ FATAL_ERROR("Unexpected state %d", state);
+ /* Does not return. */
+ }
+ }
+
+ if (!done) {
+ if (nrem == 0) {
+ return (ISC_R_NOSPACE);
+ }
+ INSIST(tlen == 0);
+ if (state != ft_ordinary && state != ft_at) {
+ return (ISC_R_UNEXPECTEDEND);
+ }
+ if (state == ft_ordinary) {
+ INSIST(count != 0);
+ INSIST(label != NULL);
+ *label = count;
+ labels++;
+ INSIST(labels <= 127);
+ offsets[labels] = nused;
+ }
+ if (origin != NULL) {
+ if (nrem < origin->length) {
+ return (ISC_R_NOSPACE);
+ }
+ label = origin->ndata;
+ n1 = origin->length;
+ nrem -= n1;
+ POST(nrem);
+ while (n1 > 0) {
+ n2 = *label++;
+ INSIST(n2 <= 63); /* no bitstring support */
+ *ndata++ = n2;
+ n1 -= n2 + 1;
+ nused += n2 + 1;
+ while (n2 > 0) {
+ c = *label++;
+ if (downcase) {
+ c = maptolower[c & 0xff];
+ }
+ *ndata++ = c;
+ n2--;
+ }
+ labels++;
+ if (n1 > 0) {
+ INSIST(labels <= 127);
+ offsets[labels] = nused;
+ }
+ }
+ if ((origin->attributes & DNS_NAMEATTR_ABSOLUTE) != 0) {
+ name->attributes |= DNS_NAMEATTR_ABSOLUTE;
+ }
+ }
+ } else {
+ name->attributes |= DNS_NAMEATTR_ABSOLUTE;
+ }
+
+ name->ndata = (unsigned char *)target->base + target->used;
+ name->labels = labels;
+ name->length = nused;
+
+ isc_buffer_forward(source, tused);
+ isc_buffer_add(target, name->length);
+
+ return (ISC_R_SUCCESS);
+}
+
+isc_result_t
+dns_name_totext(const dns_name_t *name, bool omit_final_dot,
+ isc_buffer_t *target) {
+ unsigned int options = DNS_NAME_MASTERFILE;
+
+ if (omit_final_dot) {
+ options |= DNS_NAME_OMITFINALDOT;
+ }
+ return (dns_name_totext2(name, options, target));
+}
+
+isc_result_t
+dns_name_toprincipal(const dns_name_t *name, isc_buffer_t *target) {
+ return (dns_name_totext2(name, DNS_NAME_OMITFINALDOT, target));
+}
+
+isc_result_t
+dns_name_totext2(const dns_name_t *name, unsigned int options,
+ isc_buffer_t *target) {
+ unsigned char *ndata;
+ char *tdata;
+ unsigned int nlen, tlen;
+ unsigned char c;
+ unsigned int trem, count;
+ unsigned int labels;
+ bool saw_root = false;
+ unsigned int oused;
+ bool omit_final_dot = ((options & DNS_NAME_OMITFINALDOT) != 0);
+
+ /*
+ * This function assumes the name is in proper uncompressed
+ * wire format.
+ */
+ REQUIRE(VALID_NAME(name));
+ REQUIRE(ISC_BUFFER_VALID(target));
+
+ oused = target->used;
+
+ ndata = name->ndata;
+ nlen = name->length;
+ labels = name->labels;
+ tdata = isc_buffer_used(target);
+ tlen = isc_buffer_availablelength(target);
+
+ trem = tlen;
+
+ if (labels == 0 && nlen == 0) {
+ /*
+ * Special handling for an empty name.
+ */
+ if (trem == 0) {
+ return (ISC_R_NOSPACE);
+ }
+
+ /*
+ * The names of these booleans are misleading in this case.
+ * This empty name is not necessarily from the root node of
+ * the DNS root zone, nor is a final dot going to be included.
+ * They need to be set this way, though, to keep the "@"
+ * from being trounced.
+ */
+ saw_root = true;
+ omit_final_dot = false;
+ *tdata++ = '@';
+ trem--;
+
+ /*
+ * Skip the while() loop.
+ */
+ nlen = 0;
+ } else if (nlen == 1 && labels == 1 && *ndata == '\0') {
+ /*
+ * Special handling for the root label.
+ */
+ if (trem == 0) {
+ return (ISC_R_NOSPACE);
+ }
+
+ saw_root = true;
+ omit_final_dot = false;
+ *tdata++ = '.';
+ trem--;
+
+ /*
+ * Skip the while() loop.
+ */
+ nlen = 0;
+ }
+
+ while (labels > 0 && nlen > 0 && trem > 0) {
+ labels--;
+ count = *ndata++;
+ nlen--;
+ if (count == 0) {
+ saw_root = true;
+ break;
+ }
+ if (count < 64) {
+ INSIST(nlen >= count);
+ while (count > 0) {
+ c = *ndata;
+ switch (c) {
+ /* Special modifiers in zone files. */
+ case 0x40: /* '@' */
+ case 0x24: /* '$' */
+ if ((options & DNS_NAME_MASTERFILE) ==
+ 0)
+ {
+ goto no_escape;
+ }
+ FALLTHROUGH;
+ case 0x22: /* '"' */
+ case 0x28: /* '(' */
+ case 0x29: /* ')' */
+ case 0x2E: /* '.' */
+ case 0x3B: /* ';' */
+ case 0x5C: /* '\\' */
+ if (trem < 2) {
+ return (ISC_R_NOSPACE);
+ }
+ *tdata++ = '\\';
+ CONVERTFROMASCII(c);
+ *tdata++ = c;
+ ndata++;
+ trem -= 2;
+ nlen--;
+ break;
+ no_escape:
+ default:
+ if (c > 0x20 && c < 0x7f) {
+ if (trem == 0) {
+ return (ISC_R_NOSPACE);
+ }
+ CONVERTFROMASCII(c);
+ *tdata++ = c;
+ ndata++;
+ trem--;
+ nlen--;
+ } else {
+ if (trem < 4) {
+ return (ISC_R_NOSPACE);
+ }
+ *tdata++ = 0x5c;
+ *tdata++ = 0x30 +
+ ((c / 100) % 10);
+ *tdata++ = 0x30 +
+ ((c / 10) % 10);
+ *tdata++ = 0x30 + (c % 10);
+ trem -= 4;
+ ndata++;
+ nlen--;
+ }
+ }
+ count--;
+ }
+ } else {
+ FATAL_ERROR("Unexpected label type %02x", count);
+ UNREACHABLE();
+ }
+
+ /*
+ * The following assumes names are absolute. If not, we
+ * fix things up later. Note that this means that in some
+ * cases one more byte of text buffer is required than is
+ * needed in the final output.
+ */
+ if (trem == 0) {
+ return (ISC_R_NOSPACE);
+ }
+ *tdata++ = '.';
+ trem--;
+ }
+
+ if (nlen != 0 && trem == 0) {
+ return (ISC_R_NOSPACE);
+ }
+
+ if (!saw_root || omit_final_dot) {
+ trem++;
+ tdata--;
+ }
+ if (trem > 0) {
+ *tdata = 0;
+ }
+ isc_buffer_add(target, tlen - trem);
+
+ if (totext_filter_proc != NULL) {
+ return ((totext_filter_proc)(target, oused));
+ }
+
+ return (ISC_R_SUCCESS);
+}
+
+isc_result_t
+dns_name_tofilenametext(const dns_name_t *name, bool omit_final_dot,
+ isc_buffer_t *target) {
+ unsigned char *ndata;
+ char *tdata;
+ unsigned int nlen, tlen;
+ unsigned char c;
+ unsigned int trem, count;
+ unsigned int labels;
+
+ /*
+ * This function assumes the name is in proper uncompressed
+ * wire format.
+ */
+ REQUIRE(VALID_NAME(name));
+ REQUIRE((name->attributes & DNS_NAMEATTR_ABSOLUTE) != 0);
+ REQUIRE(ISC_BUFFER_VALID(target));
+
+ ndata = name->ndata;
+ nlen = name->length;
+ labels = name->labels;
+ tdata = isc_buffer_used(target);
+ tlen = isc_buffer_availablelength(target);
+
+ trem = tlen;
+
+ if (nlen == 1 && labels == 1 && *ndata == '\0') {
+ /*
+ * Special handling for the root label.
+ */
+ if (trem == 0) {
+ return (ISC_R_NOSPACE);
+ }
+
+ omit_final_dot = false;
+ *tdata++ = '.';
+ trem--;
+
+ /*
+ * Skip the while() loop.
+ */
+ nlen = 0;
+ }
+
+ while (labels > 0 && nlen > 0 && trem > 0) {
+ labels--;
+ count = *ndata++;
+ nlen--;
+ if (count == 0) {
+ break;
+ }
+ if (count < 64) {
+ INSIST(nlen >= count);
+ while (count > 0) {
+ c = *ndata;
+ if ((c >= 0x30 && c <= 0x39) || /* digit */
+ (c >= 0x41 && c <= 0x5A) || /* uppercase */
+ (c >= 0x61 && c <= 0x7A) || /* lowercase */
+ c == 0x2D || /* hyphen */
+ c == 0x5F) /* underscore */
+ {
+ if (trem == 0) {
+ return (ISC_R_NOSPACE);
+ }
+ /* downcase */
+ if (c >= 0x41 && c <= 0x5A) {
+ c += 0x20;
+ }
+ CONVERTFROMASCII(c);
+ *tdata++ = c;
+ ndata++;
+ trem--;
+ nlen--;
+ } else {
+ if (trem < 4) {
+ return (ISC_R_NOSPACE);
+ }
+ snprintf(tdata, trem, "%%%02X", c);
+ tdata += 3;
+ trem -= 3;
+ ndata++;
+ nlen--;
+ }
+ count--;
+ }
+ } else {
+ FATAL_ERROR("Unexpected label type %02x", count);
+ UNREACHABLE();
+ }
+
+ /*
+ * The following assumes names are absolute. If not, we
+ * fix things up later. Note that this means that in some
+ * cases one more byte of text buffer is required than is
+ * needed in the final output.
+ */
+ if (trem == 0) {
+ return (ISC_R_NOSPACE);
+ }
+ *tdata++ = '.';
+ trem--;
+ }
+
+ if (nlen != 0 && trem == 0) {
+ return (ISC_R_NOSPACE);
+ }
+
+ if (omit_final_dot) {
+ trem++;
+ }
+
+ isc_buffer_add(target, tlen - trem);
+
+ return (ISC_R_SUCCESS);
+}
+
+isc_result_t
+dns_name_downcase(const dns_name_t *source, dns_name_t *name,
+ isc_buffer_t *target) {
+ unsigned char *sndata, *ndata;
+ unsigned int nlen, count, labels;
+ isc_buffer_t buffer;
+
+ /*
+ * Downcase 'source'.
+ */
+
+ REQUIRE(VALID_NAME(source));
+ REQUIRE(VALID_NAME(name));
+ if (source == name) {
+ REQUIRE((name->attributes & DNS_NAMEATTR_READONLY) == 0);
+ isc_buffer_init(&buffer, source->ndata, source->length);
+ target = &buffer;
+ ndata = source->ndata;
+ } else {
+ REQUIRE(BINDABLE(name));
+ REQUIRE((target != NULL && ISC_BUFFER_VALID(target)) ||
+ (target == NULL && ISC_BUFFER_VALID(name->buffer)));
+ if (target == NULL) {
+ target = name->buffer;
+ isc_buffer_clear(name->buffer);
+ }
+ ndata = (unsigned char *)target->base + target->used;
+ name->ndata = ndata;
+ }
+
+ sndata = source->ndata;
+ nlen = source->length;
+ labels = source->labels;
+
+ if (nlen > (target->length - target->used)) {
+ MAKE_EMPTY(name);
+ return (ISC_R_NOSPACE);
+ }
+
+ while (labels > 0 && nlen > 0) {
+ labels--;
+ count = *sndata++;
+ *ndata++ = count;
+ nlen--;
+ if (count < 64) {
+ INSIST(nlen >= count);
+ while (count > 0) {
+ *ndata++ = maptolower[(*sndata++)];
+ nlen--;
+ count--;
+ }
+ } else {
+ FATAL_ERROR("Unexpected label type %02x", count);
+ /* Does not return. */
+ }
+ }
+
+ if (source != name) {
+ name->labels = source->labels;
+ name->length = source->length;
+ if ((source->attributes & DNS_NAMEATTR_ABSOLUTE) != 0) {
+ name->attributes = DNS_NAMEATTR_ABSOLUTE;
+ } else {
+ name->attributes = 0;
+ }
+ if (name->labels > 0 && name->offsets != NULL) {
+ set_offsets(name, name->offsets, NULL);
+ }
+ }
+
+ isc_buffer_add(target, name->length);
+
+ return (ISC_R_SUCCESS);
+}
+
+static void
+set_offsets(const dns_name_t *name, unsigned char *offsets,
+ dns_name_t *set_name) {
+ unsigned int offset, count, length, nlabels;
+ unsigned char *ndata;
+ bool absolute;
+
+ ndata = name->ndata;
+ length = name->length;
+ offset = 0;
+ nlabels = 0;
+ absolute = false;
+ while (offset != length) {
+ INSIST(nlabels < 128);
+ offsets[nlabels++] = offset;
+ count = *ndata;
+ INSIST(count <= 63);
+ offset += count + 1;
+ ndata += count + 1;
+ INSIST(offset <= length);
+ if (count == 0) {
+ absolute = true;
+ break;
+ }
+ }
+ if (set_name != NULL) {
+ INSIST(set_name == name);
+
+ set_name->labels = nlabels;
+ set_name->length = offset;
+ if (absolute) {
+ set_name->attributes |= DNS_NAMEATTR_ABSOLUTE;
+ } else {
+ set_name->attributes &= ~DNS_NAMEATTR_ABSOLUTE;
+ }
+ }
+ INSIST(nlabels == name->labels);
+ INSIST(offset == name->length);
+}
+
+isc_result_t
+dns_name_fromwire(dns_name_t *const name, isc_buffer_t *const source,
+ dns_decompress_t *const dctx, unsigned int options,
+ isc_buffer_t *target) {
+ /*
+ * Copy the name at source into target, decompressing it.
+ *
+ * *** WARNING ***
+ *
+ * dns_name_fromwire() deals with raw network data. An error in this
+ * routine could result in the failure or hijacking of the server.
+ *
+ * The description of name compression in RFC 1035 section 4.1.4 is
+ * subtle wrt certain edge cases. The first important sentence is:
+ *
+ * > In this scheme, an entire domain name or a list of labels at the
+ * > end of a domain name is replaced with a pointer to a prior
+ * > occurance of the same name.
+ *
+ * The key word is "prior". This says that compression pointers must
+ * point strictly earlier in the message (before our "marker" variable),
+ * which is enough to prevent DoS attacks due to compression loops.
+ *
+ * The next important sentence is:
+ *
+ * > If a domain name is contained in a part of the message subject to a
+ * > length field (such as the RDATA section of an RR), and compression
+ * > is used, the length of the compressed name is used in the length
+ * > calculation, rather than the length of the expanded name.
+ *
+ * When decompressing, this means that the amount of the source buffer
+ * that we consumed (which is checked wrt the container's length field)
+ * is the length of the compressed name. A compressed name is defined as
+ * a sequence of labels ending with the root label or a compression
+ * pointer, that is, the segment of the name that dns_name_fromwire()
+ * examines first.
+ *
+ * This matters when handling names that play dirty tricks, like:
+ *
+ * +---+---+---+---+---+---+
+ * | 4 | 1 |'a'|192| 0 | 0 |
+ * +---+---+---+---+---+---+
+ *
+ * We start at octet 1. There is an ordinary single character label "a",
+ * followed by a compression pointer that refers back to octet zero.
+ * Here there is a label of length 4, which weirdly re-uses the octets
+ * we already examined as the data for the label. It is followed by the
+ * root label,
+ *
+ * The specification says that the compressed name ends after the first
+ * zero octet (after the compression pointer) not the second zero octet,
+ * even though the second octet is later in the message. This shows the
+ * correct way to set our "consumed" variable.
+ */
+
+ REQUIRE((options & DNS_NAME_DOWNCASE) == 0);
+ REQUIRE(VALID_NAME(name));
+ REQUIRE(BINDABLE(name));
+ REQUIRE(dctx != NULL);
+ REQUIRE((target != NULL && ISC_BUFFER_VALID(target)) ||
+ (target == NULL && ISC_BUFFER_VALID(name->buffer)));
+
+ if (target == NULL && name->buffer != NULL) {
+ target = name->buffer;
+ isc_buffer_clear(target);
+ }
+
+ uint8_t *const name_buf = isc_buffer_used(target);
+ const uint32_t name_max = ISC_MIN(DNS_NAME_MAXWIRE,
+ isc_buffer_availablelength(target));
+ uint32_t name_len = 0;
+ MAKE_EMPTY(name); /* in case of failure */
+
+ dns_offsets_t odata;
+ uint8_t *offsets = NULL;
+ uint32_t labels = 0;
+ INIT_OFFSETS(name, offsets, odata);
+
+ /*
+ * After chasing a compression pointer, these variables refer to the
+ * source buffer as follows:
+ *
+ * sb --- mr --- cr --- st --- cd --- sm
+ *
+ * sb = source_buf (const)
+ * mr = marker
+ * cr = cursor
+ * st = start (const)
+ * cd = consumed
+ * sm = source_max (const)
+ *
+ * The marker hops backwards for each pointer.
+ * The cursor steps forwards for each label.
+ * The amount of the source we consumed is set once.
+ */
+ const uint8_t *const source_buf = isc_buffer_base(source);
+ const uint8_t *const source_max = isc_buffer_used(source);
+ const uint8_t *const start = isc_buffer_current(source);
+ const uint8_t *marker = start;
+ const uint8_t *cursor = start;
+ const uint8_t *consumed = NULL;
+
+ /*
+ * One iteration per label.
+ */
+ while (cursor < source_max) {
+ const uint8_t label_len = *cursor++;
+ if (label_len < 64) {
+ /*
+ * Normal label: record its offset, and check bounds on
+ * the name length, which also ensures we don't overrun
+ * the offsets array. Don't touch any source bytes yet!
+ * The source bounds check will happen when we loop.
+ */
+ offsets[labels++] = name_len;
+ /* and then a step to the ri-i-i-i-i-ight */
+ cursor += label_len;
+ name_len += label_len + 1;
+ if (name_len > name_max) {
+ return (name_max == DNS_NAME_MAXWIRE
+ ? DNS_R_NAMETOOLONG
+ : ISC_R_NOSPACE);
+ } else if (label_len == 0) {
+ goto root_label;
+ }
+ } else if (label_len < 192) {
+ return (DNS_R_BADLABELTYPE);
+ } else if ((dctx->allowed & DNS_COMPRESS_GLOBAL14) == 0) {
+ return (DNS_R_DISALLOWED);
+ } else if (cursor < source_max) {
+ /*
+ * Compression pointer. Ensure it does not loop.
+ *
+ * Copy multiple labels in one go, to make the most of
+ * memmove() performance. Start at the marker and finish
+ * just before the pointer's hi+lo bytes, before the
+ * cursor. Bounds were already checked.
+ */
+ const uint32_t hi = label_len & 0x3F;
+ const uint32_t lo = *cursor++;
+ const uint8_t *pointer = source_buf + (256 * hi + lo);
+ if (pointer >= marker) {
+ return (DNS_R_BADPOINTER);
+ }
+ const uint32_t copy_len = (cursor - 2) - marker;
+ uint8_t *const dest = name_buf + name_len - copy_len;
+ memmove(dest, marker, copy_len);
+ consumed = consumed != NULL ? consumed : cursor;
+ /* it's just a jump to the left */
+ cursor = marker = pointer;
+ }
+ }
+ return (ISC_R_UNEXPECTEDEND);
+root_label:;
+ /*
+ * Copy labels almost like we do for compression pointers,
+ * from the marker up to and including the root label.
+ */
+ const uint32_t copy_len = cursor - marker;
+ memmove(name_buf + name_len - copy_len, marker, copy_len);
+ consumed = consumed != NULL ? consumed : cursor;
+ isc_buffer_forward(source, consumed - start);
+
+ name->attributes |= DNS_NAMEATTR_ABSOLUTE;
+ name->ndata = name_buf;
+ name->labels = labels;
+ name->length = name_len;
+ isc_buffer_add(target, name_len);
+
+ return (ISC_R_SUCCESS);
+}
+
+isc_result_t
+dns_name_towire(const dns_name_t *name, dns_compress_t *cctx,
+ isc_buffer_t *target) {
+ return (dns_name_towire2(name, cctx, target, NULL));
+}
+
+isc_result_t
+dns_name_towire2(const dns_name_t *name, dns_compress_t *cctx,
+ isc_buffer_t *target, uint16_t *comp_offsetp) {
+ unsigned int methods;
+ uint16_t offset;
+ dns_name_t gp; /* Global compression prefix */
+ bool gf; /* Global compression target found */
+ uint16_t go; /* Global compression offset */
+ dns_offsets_t clo;
+ dns_name_t clname;
+
+ /*
+ * Convert 'name' into wire format, compressing it as specified by the
+ * compression context 'cctx', and storing the result in 'target'.
+ */
+
+ REQUIRE(VALID_NAME(name));
+ REQUIRE(cctx != NULL);
+ REQUIRE(ISC_BUFFER_VALID(target));
+
+ /*
+ * If this exact name was already rendered before, and the
+ * offset of the previously rendered name is passed to us, write
+ * a compression pointer directly.
+ */
+ methods = dns_compress_getmethods(cctx);
+ if (comp_offsetp != NULL && *comp_offsetp < 0x4000 &&
+ (name->attributes & DNS_NAMEATTR_NOCOMPRESS) == 0 &&
+ (methods & DNS_COMPRESS_GLOBAL14) != 0)
+ {
+ if (target->length - target->used < 2) {
+ return (ISC_R_NOSPACE);
+ }
+ offset = *comp_offsetp;
+ offset |= 0xc000;
+ isc_buffer_putuint16(target, offset);
+ return (ISC_R_SUCCESS);
+ }
+
+ /*
+ * If 'name' doesn't have an offsets table, make a clone which
+ * has one.
+ */
+ if (name->offsets == NULL) {
+ DNS_NAME_INIT(&clname, clo);
+ dns_name_clone(name, &clname);
+ name = &clname;
+ }
+ DNS_NAME_INIT(&gp, NULL);
+
+ offset = target->used; /*XXX*/
+
+ if ((name->attributes & DNS_NAMEATTR_NOCOMPRESS) == 0 &&
+ (methods & DNS_COMPRESS_GLOBAL14) != 0)
+ {
+ gf = dns_compress_findglobal(cctx, name, &gp, &go);
+ } else {
+ gf = false;
+ }
+
+ /*
+ * If the offset is too high for 14 bit global compression, we're
+ * out of luck.
+ */
+ if (gf && go >= 0x4000) {
+ gf = false;
+ }
+
+ /*
+ * Will the compression pointer reduce the message size?
+ */
+ if (gf && (gp.length + 2) >= name->length) {
+ gf = false;
+ }
+
+ if (gf) {
+ if (target->length - target->used < gp.length) {
+ return (ISC_R_NOSPACE);
+ }
+ if (gp.length != 0) {
+ unsigned char *base = target->base;
+ (void)memmove(base + target->used, gp.ndata,
+ (size_t)gp.length);
+ }
+ isc_buffer_add(target, gp.length);
+ if (target->length - target->used < 2) {
+ return (ISC_R_NOSPACE);
+ }
+ isc_buffer_putuint16(target, go | 0xc000);
+ if (gp.length != 0) {
+ dns_compress_add(cctx, name, &gp, offset);
+ if (comp_offsetp != NULL) {
+ *comp_offsetp = offset;
+ }
+ } else if (comp_offsetp != NULL) {
+ *comp_offsetp = go;
+ }
+ } else {
+ if (target->length - target->used < name->length) {
+ return (ISC_R_NOSPACE);
+ }
+ if (name->length != 0) {
+ unsigned char *base = target->base;
+ (void)memmove(base + target->used, name->ndata,
+ (size_t)name->length);
+ }
+ isc_buffer_add(target, name->length);
+ dns_compress_add(cctx, name, name, offset);
+ if (comp_offsetp != NULL) {
+ *comp_offsetp = offset;
+ }
+ }
+
+ return (ISC_R_SUCCESS);
+}
+
+isc_result_t
+dns_name_concatenate(const dns_name_t *prefix, const dns_name_t *suffix,
+ dns_name_t *name, isc_buffer_t *target) {
+ unsigned char *ndata, *offsets;
+ unsigned int nrem, labels, prefix_length, length;
+ bool copy_prefix = true;
+ bool copy_suffix = true;
+ bool absolute = false;
+ dns_name_t tmp_name;
+ dns_offsets_t odata;
+
+ /*
+ * Concatenate 'prefix' and 'suffix'.
+ */
+
+ REQUIRE(prefix == NULL || VALID_NAME(prefix));
+ REQUIRE(suffix == NULL || VALID_NAME(suffix));
+ REQUIRE(name == NULL || VALID_NAME(name));
+ REQUIRE((target != NULL && ISC_BUFFER_VALID(target)) ||
+ (target == NULL && name != NULL &&
+ ISC_BUFFER_VALID(name->buffer)));
+ if (prefix == NULL || prefix->labels == 0) {
+ copy_prefix = false;
+ }
+ if (suffix == NULL || suffix->labels == 0) {
+ copy_suffix = false;
+ }
+ if (copy_prefix && (prefix->attributes & DNS_NAMEATTR_ABSOLUTE) != 0) {
+ absolute = true;
+ REQUIRE(!copy_suffix);
+ }
+ if (name == NULL) {
+ DNS_NAME_INIT(&tmp_name, odata);
+ name = &tmp_name;
+ }
+ if (target == NULL) {
+ INSIST(name->buffer != NULL);
+ target = name->buffer;
+ isc_buffer_clear(name->buffer);
+ }
+
+ REQUIRE(BINDABLE(name));
+
+ /*
+ * Set up.
+ */
+ nrem = target->length - target->used;
+ ndata = (unsigned char *)target->base + target->used;
+ if (nrem > DNS_NAME_MAXWIRE) {
+ nrem = DNS_NAME_MAXWIRE;
+ }
+ length = 0;
+ prefix_length = 0;
+ labels = 0;
+ if (copy_prefix) {
+ prefix_length = prefix->length;
+ length += prefix_length;
+ labels += prefix->labels;
+ }
+ if (copy_suffix) {
+ length += suffix->length;
+ labels += suffix->labels;
+ }
+ if (length > DNS_NAME_MAXWIRE) {
+ MAKE_EMPTY(name);
+ return (DNS_R_NAMETOOLONG);
+ }
+ if (length > nrem) {
+ MAKE_EMPTY(name);
+ return (ISC_R_NOSPACE);
+ }
+
+ if (copy_suffix) {
+ if ((suffix->attributes & DNS_NAMEATTR_ABSOLUTE) != 0) {
+ absolute = true;
+ }
+ memmove(ndata + prefix_length, suffix->ndata, suffix->length);
+ }
+
+ /*
+ * If 'prefix' and 'name' are the same object, and the object has
+ * a dedicated buffer, and we're using it, then we don't have to
+ * copy anything.
+ */
+ if (copy_prefix && (prefix != name || prefix->buffer != target)) {
+ memmove(ndata, prefix->ndata, prefix_length);
+ }
+
+ name->ndata = ndata;
+ name->labels = labels;
+ name->length = length;
+ if (absolute) {
+ name->attributes = DNS_NAMEATTR_ABSOLUTE;
+ } else {
+ name->attributes = 0;
+ }
+
+ if (name->labels > 0 && name->offsets != NULL) {
+ INIT_OFFSETS(name, offsets, odata);
+ set_offsets(name, offsets, NULL);
+ }
+
+ isc_buffer_add(target, name->length);
+
+ return (ISC_R_SUCCESS);
+}
+
+void
+dns_name_split(const dns_name_t *name, unsigned int suffixlabels,
+ dns_name_t *prefix, dns_name_t *suffix)
+
+{
+ unsigned int splitlabel;
+
+ REQUIRE(VALID_NAME(name));
+ REQUIRE(suffixlabels > 0);
+ REQUIRE(suffixlabels <= name->labels);
+ REQUIRE(prefix != NULL || suffix != NULL);
+ REQUIRE(prefix == NULL || (VALID_NAME(prefix) && BINDABLE(prefix)));
+ REQUIRE(suffix == NULL || (VALID_NAME(suffix) && BINDABLE(suffix)));
+
+ splitlabel = name->labels - suffixlabels;
+
+ if (prefix != NULL) {
+ dns_name_getlabelsequence(name, 0, splitlabel, prefix);
+ }
+
+ if (suffix != NULL) {
+ dns_name_getlabelsequence(name, splitlabel, suffixlabels,
+ suffix);
+ }
+
+ return;
+}
+
+void
+dns_name_dup(const dns_name_t *source, isc_mem_t *mctx, dns_name_t *target) {
+ /*
+ * Make 'target' a dynamically allocated copy of 'source'.
+ */
+
+ REQUIRE(VALID_NAME(source));
+ REQUIRE(source->length > 0);
+ REQUIRE(VALID_NAME(target));
+ REQUIRE(BINDABLE(target));
+
+ /*
+ * Make 'target' empty in case of failure.
+ */
+ MAKE_EMPTY(target);
+
+ target->ndata = isc_mem_get(mctx, source->length);
+
+ memmove(target->ndata, source->ndata, source->length);
+
+ target->length = source->length;
+ target->labels = source->labels;
+ target->attributes = DNS_NAMEATTR_DYNAMIC;
+ if ((source->attributes & DNS_NAMEATTR_ABSOLUTE) != 0) {
+ target->attributes |= DNS_NAMEATTR_ABSOLUTE;
+ }
+ if (target->offsets != NULL) {
+ if (source->offsets != NULL) {
+ memmove(target->offsets, source->offsets,
+ source->labels);
+ } else {
+ set_offsets(target, target->offsets, NULL);
+ }
+ }
+}
+
+isc_result_t
+dns_name_dupwithoffsets(const dns_name_t *source, isc_mem_t *mctx,
+ dns_name_t *target) {
+ /*
+ * Make 'target' a read-only dynamically allocated copy of 'source'.
+ * 'target' will also have a dynamically allocated offsets table.
+ */
+
+ REQUIRE(VALID_NAME(source));
+ REQUIRE(source->length > 0);
+ REQUIRE(VALID_NAME(target));
+ REQUIRE(BINDABLE(target));
+ REQUIRE(target->offsets == NULL);
+
+ /*
+ * Make 'target' empty in case of failure.
+ */
+ MAKE_EMPTY(target);
+
+ target->ndata = isc_mem_get(mctx, source->length + source->labels);
+
+ memmove(target->ndata, source->ndata, source->length);
+
+ target->length = source->length;
+ target->labels = source->labels;
+ target->attributes = DNS_NAMEATTR_DYNAMIC | DNS_NAMEATTR_DYNOFFSETS |
+ DNS_NAMEATTR_READONLY;
+ if ((source->attributes & DNS_NAMEATTR_ABSOLUTE) != 0) {
+ target->attributes |= DNS_NAMEATTR_ABSOLUTE;
+ }
+ target->offsets = target->ndata + source->length;
+ if (source->offsets != NULL) {
+ memmove(target->offsets, source->offsets, source->labels);
+ } else {
+ set_offsets(target, target->offsets, NULL);
+ }
+
+ return (ISC_R_SUCCESS);
+}
+
+void
+dns_name_free(dns_name_t *name, isc_mem_t *mctx) {
+ size_t size;
+
+ /*
+ * Free 'name'.
+ */
+
+ REQUIRE(VALID_NAME(name));
+ REQUIRE((name->attributes & DNS_NAMEATTR_DYNAMIC) != 0);
+
+ size = name->length;
+ if ((name->attributes & DNS_NAMEATTR_DYNOFFSETS) != 0) {
+ size += name->labels;
+ }
+ isc_mem_put(mctx, name->ndata, size);
+ dns_name_invalidate(name);
+}
+
+isc_result_t
+dns_name_digest(const dns_name_t *name, dns_digestfunc_t digest, void *arg) {
+ dns_name_t downname;
+ unsigned char data[256];
+ isc_buffer_t buffer;
+ isc_result_t result;
+ isc_region_t r;
+
+ /*
+ * Send 'name' in DNSSEC canonical form to 'digest'.
+ */
+
+ REQUIRE(VALID_NAME(name));
+ REQUIRE(digest != NULL);
+
+ DNS_NAME_INIT(&downname, NULL);
+
+ isc_buffer_init(&buffer, data, sizeof(data));
+
+ result = dns_name_downcase(name, &downname, &buffer);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+
+ isc_buffer_usedregion(&buffer, &r);
+
+ return ((digest)(arg, &r));
+}
+
+bool
+dns_name_dynamic(const dns_name_t *name) {
+ REQUIRE(VALID_NAME(name));
+
+ /*
+ * Returns whether there is dynamic memory associated with this name.
+ */
+
+ return ((name->attributes & DNS_NAMEATTR_DYNAMIC) != 0 ? true : false);
+}
+
+isc_result_t
+dns_name_print(const dns_name_t *name, FILE *stream) {
+ isc_result_t result;
+ isc_buffer_t b;
+ isc_region_t r;
+ char t[1024];
+
+ /*
+ * Print 'name' on 'stream'.
+ */
+
+ REQUIRE(VALID_NAME(name));
+
+ isc_buffer_init(&b, t, sizeof(t));
+ result = dns_name_totext(name, false, &b);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+ isc_buffer_usedregion(&b, &r);
+ fprintf(stream, "%.*s", (int)r.length, (char *)r.base);
+
+ return (ISC_R_SUCCESS);
+}
+
+isc_result_t
+dns_name_settotextfilter(dns_name_totextfilter_t *proc) {
+ /*
+ * If we already have been here set / clear as appropriate.
+ */
+ if (totext_filter_proc != NULL && proc != NULL) {
+ if (totext_filter_proc == proc) {
+ return (ISC_R_SUCCESS);
+ }
+ }
+ if (proc == NULL && totext_filter_proc != NULL) {
+ totext_filter_proc = NULL;
+ return (ISC_R_SUCCESS);
+ }
+
+ totext_filter_proc = proc;
+
+ return (ISC_R_SUCCESS);
+}
+
+void
+dns_name_format(const dns_name_t *name, char *cp, unsigned int size) {
+ isc_result_t result;
+ isc_buffer_t buf;
+
+ REQUIRE(size > 0);
+
+ /*
+ * Leave room for null termination after buffer.
+ */
+ isc_buffer_init(&buf, cp, size - 1);
+ result = dns_name_totext(name, true, &buf);
+ if (result == ISC_R_SUCCESS) {
+ isc_buffer_putuint8(&buf, (uint8_t)'\0');
+ } else {
+ snprintf(cp, size, "<unknown>");
+ }
+}
+
+/*
+ * dns_name_tostring() -- similar to dns_name_format() but allocates its own
+ * memory.
+ */
+isc_result_t
+dns_name_tostring(const dns_name_t *name, char **target, isc_mem_t *mctx) {
+ isc_result_t result;
+ isc_buffer_t buf;
+ isc_region_t reg;
+ char *p, txt[DNS_NAME_FORMATSIZE];
+
+ REQUIRE(VALID_NAME(name));
+ REQUIRE(target != NULL && *target == NULL);
+
+ isc_buffer_init(&buf, txt, sizeof(txt));
+ result = dns_name_totext(name, false, &buf);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+
+ isc_buffer_usedregion(&buf, &reg);
+ p = isc_mem_allocate(mctx, reg.length + 1);
+ memmove(p, (char *)reg.base, (int)reg.length);
+ p[reg.length] = '\0';
+
+ *target = p;
+ return (ISC_R_SUCCESS);
+}
+
+/*
+ * dns_name_fromstring() -- convert directly from a string to a name,
+ * allocating memory as needed
+ */
+isc_result_t
+dns_name_fromstring(dns_name_t *target, const char *src, unsigned int options,
+ isc_mem_t *mctx) {
+ return (dns_name_fromstring2(target, src, dns_rootname, options, mctx));
+}
+
+isc_result_t
+dns_name_fromstring2(dns_name_t *target, const char *src,
+ const dns_name_t *origin, unsigned int options,
+ isc_mem_t *mctx) {
+ isc_result_t result;
+ isc_buffer_t buf;
+ dns_fixedname_t fn;
+ dns_name_t *name;
+
+ REQUIRE(src != NULL);
+
+ isc_buffer_constinit(&buf, src, strlen(src));
+ isc_buffer_add(&buf, strlen(src));
+ if (BINDABLE(target) && target->buffer != NULL) {
+ name = target;
+ } else {
+ name = dns_fixedname_initname(&fn);
+ }
+
+ result = dns_name_fromtext(name, &buf, origin, options, NULL);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+
+ if (name != target) {
+ result = dns_name_dupwithoffsets(name, mctx, target);
+ }
+ return (result);
+}
+
+void
+dns_name_copy(const dns_name_t *source, dns_name_t *dest) {
+ isc_buffer_t *target = NULL;
+ unsigned char *ndata = NULL;
+
+ REQUIRE(VALID_NAME(source));
+ REQUIRE(VALID_NAME(dest));
+ REQUIRE(BINDABLE(dest));
+
+ target = dest->buffer;
+
+ REQUIRE(target != NULL);
+ REQUIRE(target->length >= source->length);
+
+ isc_buffer_clear(target);
+
+ ndata = (unsigned char *)target->base;
+ dest->ndata = target->base;
+
+ if (source->length != 0) {
+ memmove(ndata, source->ndata, source->length);
+ }
+
+ dest->ndata = ndata;
+ dest->labels = source->labels;
+ dest->length = source->length;
+ if ((source->attributes & DNS_NAMEATTR_ABSOLUTE) != 0) {
+ dest->attributes = DNS_NAMEATTR_ABSOLUTE;
+ } else {
+ dest->attributes = 0;
+ }
+
+ if (dest->labels > 0 && dest->offsets != NULL) {
+ if (source->offsets != NULL && source->labels != 0) {
+ memmove(dest->offsets, source->offsets, source->labels);
+ } else {
+ set_offsets(dest, dest->offsets, NULL);
+ }
+ }
+
+ isc_buffer_add(target, dest->length);
+}
+
+/*
+ * Service Discovery Prefixes RFC 6763.
+ */
+static unsigned char b_dns_sd_udp_data[] = "\001b\007_dns-sd\004_udp";
+static unsigned char b_dns_sd_udp_offsets[] = { 0, 2, 10 };
+static unsigned char db_dns_sd_udp_data[] = "\002db\007_dns-sd\004_udp";
+static unsigned char db_dns_sd_udp_offsets[] = { 0, 3, 11 };
+static unsigned char r_dns_sd_udp_data[] = "\001r\007_dns-sd\004_udp";
+static unsigned char r_dns_sd_udp_offsets[] = { 0, 2, 10 };
+static unsigned char dr_dns_sd_udp_data[] = "\002dr\007_dns-sd\004_udp";
+static unsigned char dr_dns_sd_udp_offsets[] = { 0, 3, 11 };
+static unsigned char lb_dns_sd_udp_data[] = "\002lb\007_dns-sd\004_udp";
+static unsigned char lb_dns_sd_udp_offsets[] = { 0, 3, 11 };
+
+static dns_name_t const dns_sd[] = {
+ DNS_NAME_INITNONABSOLUTE(b_dns_sd_udp_data, b_dns_sd_udp_offsets),
+ DNS_NAME_INITNONABSOLUTE(db_dns_sd_udp_data, db_dns_sd_udp_offsets),
+ DNS_NAME_INITNONABSOLUTE(r_dns_sd_udp_data, r_dns_sd_udp_offsets),
+ DNS_NAME_INITNONABSOLUTE(dr_dns_sd_udp_data, dr_dns_sd_udp_offsets),
+ DNS_NAME_INITNONABSOLUTE(lb_dns_sd_udp_data, lb_dns_sd_udp_offsets)
+};
+
+bool
+dns_name_isdnssd(const dns_name_t *name) {
+ size_t i;
+ dns_name_t prefix;
+
+ if (dns_name_countlabels(name) > 3U) {
+ dns_name_init(&prefix, NULL);
+ dns_name_getlabelsequence(name, 0, 3, &prefix);
+ for (i = 0; i < (sizeof(dns_sd) / sizeof(dns_sd[0])); i++) {
+ if (dns_name_equal(&prefix, &dns_sd[i])) {
+ return (true);
+ }
+ }
+ }
+
+ return (false);
+}
+
+static unsigned char inaddr10_offsets[] = { 0, 3, 11, 16 };
+static unsigned char inaddr172_offsets[] = { 0, 3, 7, 15, 20 };
+static unsigned char inaddr192_offsets[] = { 0, 4, 8, 16, 21 };
+
+static unsigned char inaddr10[] = "\00210\007IN-ADDR\004ARPA";
+
+static unsigned char inaddr16172[] = "\00216\003172\007IN-ADDR\004ARPA";
+static unsigned char inaddr17172[] = "\00217\003172\007IN-ADDR\004ARPA";
+static unsigned char inaddr18172[] = "\00218\003172\007IN-ADDR\004ARPA";
+static unsigned char inaddr19172[] = "\00219\003172\007IN-ADDR\004ARPA";
+static unsigned char inaddr20172[] = "\00220\003172\007IN-ADDR\004ARPA";
+static unsigned char inaddr21172[] = "\00221\003172\007IN-ADDR\004ARPA";
+static unsigned char inaddr22172[] = "\00222\003172\007IN-ADDR\004ARPA";
+static unsigned char inaddr23172[] = "\00223\003172\007IN-ADDR\004ARPA";
+static unsigned char inaddr24172[] = "\00224\003172\007IN-ADDR\004ARPA";
+static unsigned char inaddr25172[] = "\00225\003172\007IN-ADDR\004ARPA";
+static unsigned char inaddr26172[] = "\00226\003172\007IN-ADDR\004ARPA";
+static unsigned char inaddr27172[] = "\00227\003172\007IN-ADDR\004ARPA";
+static unsigned char inaddr28172[] = "\00228\003172\007IN-ADDR\004ARPA";
+static unsigned char inaddr29172[] = "\00229\003172\007IN-ADDR\004ARPA";
+static unsigned char inaddr30172[] = "\00230\003172\007IN-ADDR\004ARPA";
+static unsigned char inaddr31172[] = "\00231\003172\007IN-ADDR\004ARPA";
+
+static unsigned char inaddr168192[] = "\003168\003192\007IN-ADDR\004ARPA";
+
+static dns_name_t const rfc1918names[] = {
+ DNS_NAME_INITABSOLUTE(inaddr10, inaddr10_offsets),
+ DNS_NAME_INITABSOLUTE(inaddr16172, inaddr172_offsets),
+ DNS_NAME_INITABSOLUTE(inaddr17172, inaddr172_offsets),
+ DNS_NAME_INITABSOLUTE(inaddr18172, inaddr172_offsets),
+ DNS_NAME_INITABSOLUTE(inaddr19172, inaddr172_offsets),
+ DNS_NAME_INITABSOLUTE(inaddr20172, inaddr172_offsets),
+ DNS_NAME_INITABSOLUTE(inaddr21172, inaddr172_offsets),
+ DNS_NAME_INITABSOLUTE(inaddr22172, inaddr172_offsets),
+ DNS_NAME_INITABSOLUTE(inaddr23172, inaddr172_offsets),
+ DNS_NAME_INITABSOLUTE(inaddr24172, inaddr172_offsets),
+ DNS_NAME_INITABSOLUTE(inaddr25172, inaddr172_offsets),
+ DNS_NAME_INITABSOLUTE(inaddr26172, inaddr172_offsets),
+ DNS_NAME_INITABSOLUTE(inaddr27172, inaddr172_offsets),
+ DNS_NAME_INITABSOLUTE(inaddr28172, inaddr172_offsets),
+ DNS_NAME_INITABSOLUTE(inaddr29172, inaddr172_offsets),
+ DNS_NAME_INITABSOLUTE(inaddr30172, inaddr172_offsets),
+ DNS_NAME_INITABSOLUTE(inaddr31172, inaddr172_offsets),
+ DNS_NAME_INITABSOLUTE(inaddr168192, inaddr192_offsets)
+};
+
+bool
+dns_name_isrfc1918(const dns_name_t *name) {
+ size_t i;
+
+ for (i = 0; i < (sizeof(rfc1918names) / sizeof(*rfc1918names)); i++) {
+ if (dns_name_issubdomain(name, &rfc1918names[i])) {
+ return (true);
+ }
+ }
+ return (false);
+}
+
+static unsigned char ulaoffsets[] = { 0, 2, 4, 8, 13 };
+static unsigned char ip6fc[] = "\001c\001f\003ip6\004ARPA";
+static unsigned char ip6fd[] = "\001d\001f\003ip6\004ARPA";
+
+static dns_name_t const ulanames[] = { DNS_NAME_INITABSOLUTE(ip6fc, ulaoffsets),
+ DNS_NAME_INITABSOLUTE(ip6fd,
+ ulaoffsets) };
+
+bool
+dns_name_isula(const dns_name_t *name) {
+ size_t i;
+
+ for (i = 0; i < (sizeof(ulanames) / sizeof(*ulanames)); i++) {
+ if (dns_name_issubdomain(name, &ulanames[i])) {
+ return (true);
+ }
+ }
+ return (false);
+}
+
+/*
+ * Use a simple table as we don't want all the locale stuff
+ * associated with ishexdigit().
+ */
+const char ishex[256] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0,
+ 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
+
+bool
+dns_name_istat(const dns_name_t *name) {
+ unsigned char len;
+ const unsigned char *ndata;
+
+ REQUIRE(VALID_NAME(name));
+
+ if (name->labels < 1) {
+ return (false);
+ }
+
+ ndata = name->ndata;
+ len = ndata[0];
+ INSIST(len <= name->length);
+ ndata++;
+
+ /*
+ * Is there at least one trust anchor reported and is the
+ * label length consistent with a trust-anchor-telemetry label.
+ */
+ if ((len < 8) || (len - 3) % 5 != 0) {
+ return (false);
+ }
+
+ if (ndata[0] != '_' || maptolower[ndata[1]] != 't' ||
+ maptolower[ndata[2]] != 'a')
+ {
+ return (false);
+ }
+ ndata += 3;
+ len -= 3;
+
+ while (len > 0) {
+ INSIST(len >= 5);
+ if (ndata[0] != '-' || !ishex[ndata[1]] || !ishex[ndata[2]] ||
+ !ishex[ndata[3]] || !ishex[ndata[4]])
+ {
+ return (false);
+ }
+ ndata += 5;
+ len -= 5;
+ }
+ return (true);
+}
diff --git a/lib/dns/ncache.c b/lib/dns/ncache.c
new file mode 100644
index 0000000..9247ac1
--- /dev/null
+++ b/lib/dns/ncache.c
@@ -0,0 +1,777 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/*! \file */
+
+#include <inttypes.h>
+#include <stdbool.h>
+
+#include <isc/buffer.h>
+#include <isc/util.h>
+
+#include <dns/db.h>
+#include <dns/message.h>
+#include <dns/ncache.h>
+#include <dns/rdata.h>
+#include <dns/rdatalist.h>
+#include <dns/rdataset.h>
+#include <dns/rdatastruct.h>
+
+#define DNS_NCACHE_RDATA 100U
+
+/*
+ * The format of an ncache rdata is a sequence of zero or more records of
+ * the following format:
+ *
+ * owner name
+ * type
+ * trust
+ * rdata count
+ * rdata length These two occur 'rdata count'
+ * rdata times.
+ *
+ */
+
+static isc_result_t
+addoptout(dns_message_t *message, dns_db_t *cache, dns_dbnode_t *node,
+ dns_rdatatype_t covers, isc_stdtime_t now, dns_ttl_t minttl,
+ dns_ttl_t maxttl, bool optout, bool secure,
+ dns_rdataset_t *addedrdataset);
+
+static isc_result_t
+copy_rdataset(dns_rdataset_t *rdataset, isc_buffer_t *buffer) {
+ isc_result_t result;
+ unsigned int count;
+ isc_region_t ar, r;
+ dns_rdata_t rdata = DNS_RDATA_INIT;
+
+ /*
+ * Copy the rdataset count to the buffer.
+ */
+ isc_buffer_availableregion(buffer, &ar);
+ if (ar.length < 2) {
+ return (ISC_R_NOSPACE);
+ }
+ count = dns_rdataset_count(rdataset);
+ INSIST(count <= 65535);
+ isc_buffer_putuint16(buffer, (uint16_t)count);
+
+ result = dns_rdataset_first(rdataset);
+ while (result == ISC_R_SUCCESS) {
+ dns_rdataset_current(rdataset, &rdata);
+ dns_rdata_toregion(&rdata, &r);
+ INSIST(r.length <= 65535);
+ isc_buffer_availableregion(buffer, &ar);
+ if (ar.length < 2) {
+ return (ISC_R_NOSPACE);
+ }
+ /*
+ * Copy the rdata length to the buffer.
+ */
+ isc_buffer_putuint16(buffer, (uint16_t)r.length);
+ /*
+ * Copy the rdata to the buffer.
+ */
+ result = isc_buffer_copyregion(buffer, &r);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+ dns_rdata_reset(&rdata);
+ result = dns_rdataset_next(rdataset);
+ }
+ if (result != ISC_R_NOMORE) {
+ return (result);
+ }
+
+ return (ISC_R_SUCCESS);
+}
+
+isc_result_t
+dns_ncache_add(dns_message_t *message, dns_db_t *cache, dns_dbnode_t *node,
+ dns_rdatatype_t covers, isc_stdtime_t now, dns_ttl_t minttl,
+ dns_ttl_t maxttl, dns_rdataset_t *addedrdataset) {
+ return (addoptout(message, cache, node, covers, now, minttl, maxttl,
+ false, false, addedrdataset));
+}
+
+isc_result_t
+dns_ncache_addoptout(dns_message_t *message, dns_db_t *cache,
+ dns_dbnode_t *node, dns_rdatatype_t covers,
+ isc_stdtime_t now, dns_ttl_t minttl, dns_ttl_t maxttl,
+ bool optout, dns_rdataset_t *addedrdataset) {
+ return (addoptout(message, cache, node, covers, now, minttl, maxttl,
+ optout, true, addedrdataset));
+}
+
+static isc_result_t
+addoptout(dns_message_t *message, dns_db_t *cache, dns_dbnode_t *node,
+ dns_rdatatype_t covers, isc_stdtime_t now, dns_ttl_t minttl,
+ dns_ttl_t maxttl, bool optout, bool secure,
+ dns_rdataset_t *addedrdataset) {
+ isc_result_t result;
+ isc_buffer_t buffer;
+ isc_region_t r;
+ dns_rdataset_t *rdataset;
+ dns_rdatatype_t type;
+ dns_name_t *name;
+ dns_ttl_t ttl;
+ dns_trust_t trust;
+ dns_rdata_t rdata[DNS_NCACHE_RDATA];
+ dns_rdataset_t ncrdataset;
+ dns_rdatalist_t ncrdatalist;
+ unsigned char data[65536];
+ unsigned int next = 0;
+
+ /*
+ * Convert the authority data from 'message' into a negative cache
+ * rdataset, and store it in 'cache' at 'node'.
+ */
+
+ REQUIRE(message != NULL);
+
+ /*
+ * We assume that all data in the authority section has been
+ * validated by the caller.
+ */
+
+ /*
+ * Initialize the list.
+ */
+ dns_rdatalist_init(&ncrdatalist);
+ ncrdatalist.rdclass = dns_db_class(cache);
+ ncrdatalist.covers = covers;
+ ncrdatalist.ttl = maxttl;
+
+ /*
+ * Build an ncache rdatas into buffer.
+ */
+ ttl = maxttl;
+ trust = 0xffff;
+ isc_buffer_init(&buffer, data, sizeof(data));
+ if (message->counts[DNS_SECTION_AUTHORITY]) {
+ result = dns_message_firstname(message, DNS_SECTION_AUTHORITY);
+ } else {
+ result = ISC_R_NOMORE;
+ }
+ while (result == ISC_R_SUCCESS) {
+ name = NULL;
+ dns_message_currentname(message, DNS_SECTION_AUTHORITY, &name);
+ if ((name->attributes & DNS_NAMEATTR_NCACHE) != 0) {
+ for (rdataset = ISC_LIST_HEAD(name->list);
+ rdataset != NULL;
+ rdataset = ISC_LIST_NEXT(rdataset, link))
+ {
+ if ((rdataset->attributes &
+ DNS_RDATASETATTR_NCACHE) == 0)
+ {
+ continue;
+ }
+ type = rdataset->type;
+ if (type == dns_rdatatype_rrsig) {
+ type = rdataset->covers;
+ }
+ if (type == dns_rdatatype_soa ||
+ type == dns_rdatatype_nsec ||
+ type == dns_rdatatype_nsec3)
+ {
+ if (ttl > rdataset->ttl) {
+ ttl = rdataset->ttl;
+ }
+ if (ttl < minttl) {
+ ttl = minttl;
+ }
+ if (trust > rdataset->trust) {
+ trust = rdataset->trust;
+ }
+ /*
+ * Copy the owner name to the buffer.
+ */
+ dns_name_toregion(name, &r);
+ result = isc_buffer_copyregion(&buffer,
+ &r);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+ /*
+ * Copy the type to the buffer.
+ */
+ isc_buffer_availableregion(&buffer, &r);
+ if (r.length < 3) {
+ return (ISC_R_NOSPACE);
+ }
+ isc_buffer_putuint16(&buffer,
+ rdataset->type);
+ isc_buffer_putuint8(
+ &buffer,
+ (unsigned char)rdataset->trust);
+ /*
+ * Copy the rdataset into the buffer.
+ */
+ result = copy_rdataset(rdataset,
+ &buffer);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+
+ if (next >= DNS_NCACHE_RDATA) {
+ return (ISC_R_NOSPACE);
+ }
+ dns_rdata_init(&rdata[next]);
+ isc_buffer_remainingregion(&buffer, &r);
+ rdata[next].data = r.base;
+ rdata[next].length = r.length;
+ rdata[next].rdclass =
+ ncrdatalist.rdclass;
+ rdata[next].type = 0;
+ rdata[next].flags = 0;
+ ISC_LIST_APPEND(ncrdatalist.rdata,
+ &rdata[next], link);
+ isc_buffer_forward(&buffer, r.length);
+ next++;
+ }
+ }
+ }
+ result = dns_message_nextname(message, DNS_SECTION_AUTHORITY);
+ }
+ if (result != ISC_R_NOMORE) {
+ return (result);
+ }
+
+ if (trust == 0xffff) {
+ if ((message->flags & DNS_MESSAGEFLAG_AA) != 0 &&
+ message->counts[DNS_SECTION_ANSWER] == 0)
+ {
+ /*
+ * The response has aa set and we haven't followed
+ * any CNAME or DNAME chains.
+ */
+ trust = dns_trust_authauthority;
+ } else {
+ trust = dns_trust_additional;
+ }
+ ttl = 0;
+ }
+
+ INSIST(trust != 0xffff);
+
+ ncrdatalist.ttl = ttl;
+
+ dns_rdataset_init(&ncrdataset);
+ RUNTIME_CHECK(dns_rdatalist_tordataset(&ncrdatalist, &ncrdataset) ==
+ ISC_R_SUCCESS);
+ if (!secure && trust > dns_trust_answer) {
+ trust = dns_trust_answer;
+ }
+ ncrdataset.trust = trust;
+ ncrdataset.attributes |= DNS_RDATASETATTR_NEGATIVE;
+ if (message->rcode == dns_rcode_nxdomain) {
+ ncrdataset.attributes |= DNS_RDATASETATTR_NXDOMAIN;
+ }
+ if (optout) {
+ ncrdataset.attributes |= DNS_RDATASETATTR_OPTOUT;
+ }
+
+ return (dns_db_addrdataset(cache, node, NULL, now, &ncrdataset, 0,
+ addedrdataset));
+}
+
+isc_result_t
+dns_ncache_towire(dns_rdataset_t *rdataset, dns_compress_t *cctx,
+ isc_buffer_t *target, unsigned int options,
+ unsigned int *countp) {
+ dns_rdata_t rdata = DNS_RDATA_INIT;
+ isc_result_t result;
+ isc_region_t remaining, tavailable;
+ isc_buffer_t source, savedbuffer, rdlen;
+ dns_name_t name;
+ dns_rdatatype_t type;
+ unsigned int i, rcount, count;
+
+ /*
+ * Convert the negative caching rdataset 'rdataset' to wire format,
+ * compressing names as specified in 'cctx', and storing the result in
+ * 'target'.
+ */
+
+ REQUIRE(rdataset != NULL);
+ REQUIRE(rdataset->type == 0);
+ REQUIRE((rdataset->attributes & DNS_RDATASETATTR_NEGATIVE) != 0);
+
+ savedbuffer = *target;
+ count = 0;
+
+ result = dns_rdataset_first(rdataset);
+ while (result == ISC_R_SUCCESS) {
+ dns_rdataset_current(rdataset, &rdata);
+ isc_buffer_init(&source, rdata.data, rdata.length);
+ isc_buffer_add(&source, rdata.length);
+ dns_name_init(&name, NULL);
+ isc_buffer_remainingregion(&source, &remaining);
+ dns_name_fromregion(&name, &remaining);
+ INSIST(remaining.length >= name.length);
+ isc_buffer_forward(&source, name.length);
+ remaining.length -= name.length;
+
+ INSIST(remaining.length >= 5);
+ type = isc_buffer_getuint16(&source);
+ isc_buffer_forward(&source, 1);
+ rcount = isc_buffer_getuint16(&source);
+
+ for (i = 0; i < rcount; i++) {
+ /*
+ * Get the length of this rdata and set up an
+ * rdata structure for it.
+ */
+ isc_buffer_remainingregion(&source, &remaining);
+ INSIST(remaining.length >= 2);
+ dns_rdata_reset(&rdata);
+ rdata.length = isc_buffer_getuint16(&source);
+ isc_buffer_remainingregion(&source, &remaining);
+ rdata.data = remaining.base;
+ rdata.type = type;
+ rdata.rdclass = rdataset->rdclass;
+ INSIST(remaining.length >= rdata.length);
+ isc_buffer_forward(&source, rdata.length);
+
+ if ((options & DNS_NCACHETOWIRE_OMITDNSSEC) != 0 &&
+ dns_rdatatype_isdnssec(type))
+ {
+ continue;
+ }
+
+ /*
+ * Write the name.
+ */
+ dns_compress_setmethods(cctx, DNS_COMPRESS_GLOBAL14);
+ result = dns_name_towire(&name, cctx, target);
+ if (result != ISC_R_SUCCESS) {
+ goto rollback;
+ }
+
+ /*
+ * See if we have space for type, class, ttl, and
+ * rdata length. Write the type, class, and ttl.
+ */
+ isc_buffer_availableregion(target, &tavailable);
+ if (tavailable.length < 10) {
+ result = ISC_R_NOSPACE;
+ goto rollback;
+ }
+ isc_buffer_putuint16(target, type);
+ isc_buffer_putuint16(target, rdataset->rdclass);
+ isc_buffer_putuint32(target, rdataset->ttl);
+
+ /*
+ * Save space for rdata length.
+ */
+ rdlen = *target;
+ isc_buffer_add(target, 2);
+
+ /*
+ * Write the rdata.
+ */
+ result = dns_rdata_towire(&rdata, cctx, target);
+ if (result != ISC_R_SUCCESS) {
+ goto rollback;
+ }
+
+ /*
+ * Set the rdata length field to the compressed
+ * length.
+ */
+ INSIST((target->used >= rdlen.used + 2) &&
+ (target->used - rdlen.used - 2 < 65536));
+ isc_buffer_putuint16(
+ &rdlen,
+ (uint16_t)(target->used - rdlen.used - 2));
+
+ count++;
+ }
+ INSIST(isc_buffer_remaininglength(&source) == 0);
+ result = dns_rdataset_next(rdataset);
+ dns_rdata_reset(&rdata);
+ }
+ if (result != ISC_R_NOMORE) {
+ goto rollback;
+ }
+
+ *countp = count;
+
+ return (ISC_R_SUCCESS);
+
+rollback:
+ INSIST(savedbuffer.used < 65536);
+ dns_compress_rollback(cctx, (uint16_t)savedbuffer.used);
+ *countp = 0;
+ *target = savedbuffer;
+
+ return (result);
+}
+
+static void
+rdataset_disassociate(dns_rdataset_t *rdataset) {
+ UNUSED(rdataset);
+}
+
+static isc_result_t
+rdataset_first(dns_rdataset_t *rdataset) {
+ unsigned char *raw = rdataset->private3;
+ unsigned int count;
+
+ count = raw[0] * 256 + raw[1];
+ if (count == 0) {
+ rdataset->private5 = NULL;
+ return (ISC_R_NOMORE);
+ }
+ raw += 2;
+ /*
+ * The privateuint4 field is the number of rdata beyond the cursor
+ * position, so we decrement the total count by one before storing
+ * it.
+ */
+ count--;
+ rdataset->privateuint4 = count;
+ rdataset->private5 = raw;
+
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+rdataset_next(dns_rdataset_t *rdataset) {
+ unsigned int count;
+ unsigned int length;
+ unsigned char *raw;
+
+ count = rdataset->privateuint4;
+ if (count == 0) {
+ return (ISC_R_NOMORE);
+ }
+ count--;
+ rdataset->privateuint4 = count;
+ raw = rdataset->private5;
+ length = raw[0] * 256 + raw[1];
+ raw += length + 2;
+ rdataset->private5 = raw;
+
+ return (ISC_R_SUCCESS);
+}
+
+static void
+rdataset_current(dns_rdataset_t *rdataset, dns_rdata_t *rdata) {
+ unsigned char *raw = rdataset->private5;
+ isc_region_t r;
+
+ REQUIRE(raw != NULL);
+
+ r.length = raw[0] * 256 + raw[1];
+ raw += 2;
+ r.base = raw;
+ dns_rdata_fromregion(rdata, rdataset->rdclass, rdataset->type, &r);
+}
+
+static void
+rdataset_clone(dns_rdataset_t *source, dns_rdataset_t *target) {
+ *target = *source;
+
+ /*
+ * Reset iterator state.
+ */
+ target->privateuint4 = 0;
+ target->private5 = NULL;
+}
+
+static unsigned int
+rdataset_count(dns_rdataset_t *rdataset) {
+ unsigned char *raw = rdataset->private3;
+ unsigned int count;
+
+ count = raw[0] * 256 + raw[1];
+
+ return (count);
+}
+
+static void
+rdataset_settrust(dns_rdataset_t *rdataset, dns_trust_t trust) {
+ unsigned char *raw = rdataset->private3;
+
+ raw[-1] = (unsigned char)trust;
+ rdataset->trust = trust;
+}
+
+static dns_rdatasetmethods_t rdataset_methods = {
+ rdataset_disassociate,
+ rdataset_first,
+ rdataset_next,
+ rdataset_current,
+ rdataset_clone,
+ rdataset_count,
+ NULL, /* addnoqname */
+ NULL, /* getnoqname */
+ NULL, /* addclosest */
+ NULL, /* getclosest */
+ rdataset_settrust, /* settrust */
+ NULL, /* expire */
+ NULL, /* clearprefetch */
+ NULL, /* setownercase */
+ NULL, /* getownercase */
+ NULL /* addglue */
+};
+
+isc_result_t
+dns_ncache_getrdataset(dns_rdataset_t *ncacherdataset, dns_name_t *name,
+ dns_rdatatype_t type, dns_rdataset_t *rdataset) {
+ isc_result_t result;
+ dns_rdata_t rdata = DNS_RDATA_INIT;
+ isc_region_t remaining;
+ isc_buffer_t source;
+ dns_name_t tname;
+ dns_rdatatype_t ttype;
+ dns_trust_t trust = dns_trust_none;
+ dns_rdataset_t rclone;
+
+ REQUIRE(ncacherdataset != NULL);
+ REQUIRE(ncacherdataset->type == 0);
+ REQUIRE((ncacherdataset->attributes & DNS_RDATASETATTR_NEGATIVE) != 0);
+ REQUIRE(name != NULL);
+ REQUIRE(!dns_rdataset_isassociated(rdataset));
+ REQUIRE(type != dns_rdatatype_rrsig);
+
+ dns_rdataset_init(&rclone);
+ dns_rdataset_clone(ncacherdataset, &rclone);
+ result = dns_rdataset_first(&rclone);
+ while (result == ISC_R_SUCCESS) {
+ dns_rdataset_current(&rclone, &rdata);
+ isc_buffer_init(&source, rdata.data, rdata.length);
+ isc_buffer_add(&source, rdata.length);
+ dns_name_init(&tname, NULL);
+ isc_buffer_remainingregion(&source, &remaining);
+ dns_name_fromregion(&tname, &remaining);
+ INSIST(remaining.length >= tname.length);
+ isc_buffer_forward(&source, tname.length);
+ remaining.length -= tname.length;
+
+ INSIST(remaining.length >= 3);
+ ttype = isc_buffer_getuint16(&source);
+
+ if (ttype == type && dns_name_equal(&tname, name)) {
+ trust = isc_buffer_getuint8(&source);
+ INSIST(trust <= dns_trust_ultimate);
+ isc_buffer_remainingregion(&source, &remaining);
+ break;
+ }
+ result = dns_rdataset_next(&rclone);
+ dns_rdata_reset(&rdata);
+ }
+ dns_rdataset_disassociate(&rclone);
+ if (result == ISC_R_NOMORE) {
+ return (ISC_R_NOTFOUND);
+ }
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+
+ INSIST(remaining.length != 0);
+
+ rdataset->methods = &rdataset_methods;
+ rdataset->rdclass = ncacherdataset->rdclass;
+ rdataset->type = type;
+ rdataset->covers = 0;
+ rdataset->ttl = ncacherdataset->ttl;
+ rdataset->trust = trust;
+ rdataset->private1 = NULL;
+ rdataset->private2 = NULL;
+
+ rdataset->private3 = remaining.base;
+
+ /*
+ * Reset iterator state.
+ */
+ rdataset->privateuint4 = 0;
+ rdataset->private5 = NULL;
+ rdataset->private6 = NULL;
+ return (ISC_R_SUCCESS);
+}
+
+isc_result_t
+dns_ncache_getsigrdataset(dns_rdataset_t *ncacherdataset, dns_name_t *name,
+ dns_rdatatype_t covers, dns_rdataset_t *rdataset) {
+ dns_name_t tname;
+ dns_rdata_rrsig_t rrsig;
+ dns_rdata_t rdata = DNS_RDATA_INIT;
+ dns_rdataset_t rclone;
+ dns_rdatatype_t type;
+ dns_trust_t trust = dns_trust_none;
+ isc_buffer_t source;
+ isc_region_t remaining, sigregion;
+ isc_result_t result;
+ unsigned char *raw;
+ unsigned int count;
+
+ REQUIRE(ncacherdataset != NULL);
+ REQUIRE(ncacherdataset->type == 0);
+ REQUIRE((ncacherdataset->attributes & DNS_RDATASETATTR_NEGATIVE) != 0);
+ REQUIRE(name != NULL);
+ REQUIRE(!dns_rdataset_isassociated(rdataset));
+
+ dns_rdataset_init(&rclone);
+ dns_rdataset_clone(ncacherdataset, &rclone);
+ result = dns_rdataset_first(&rclone);
+ while (result == ISC_R_SUCCESS) {
+ dns_rdataset_current(&rclone, &rdata);
+ isc_buffer_init(&source, rdata.data, rdata.length);
+ isc_buffer_add(&source, rdata.length);
+ dns_name_init(&tname, NULL);
+ isc_buffer_remainingregion(&source, &remaining);
+ dns_name_fromregion(&tname, &remaining);
+ INSIST(remaining.length >= tname.length);
+ isc_buffer_forward(&source, tname.length);
+ isc_region_consume(&remaining, tname.length);
+
+ INSIST(remaining.length >= 2);
+ type = isc_buffer_getuint16(&source);
+ isc_region_consume(&remaining, 2);
+
+ if (type != dns_rdatatype_rrsig ||
+ !dns_name_equal(&tname, name))
+ {
+ result = dns_rdataset_next(&rclone);
+ dns_rdata_reset(&rdata);
+ continue;
+ }
+
+ INSIST(remaining.length >= 1);
+ trust = isc_buffer_getuint8(&source);
+ INSIST(trust <= dns_trust_ultimate);
+ isc_region_consume(&remaining, 1);
+
+ raw = remaining.base;
+ count = raw[0] * 256 + raw[1];
+ INSIST(count > 0);
+ raw += 2;
+ sigregion.length = raw[0] * 256 + raw[1];
+ raw += 2;
+ sigregion.base = raw;
+ dns_rdata_reset(&rdata);
+ dns_rdata_fromregion(&rdata, rdataset->rdclass,
+ dns_rdatatype_rrsig, &sigregion);
+ (void)dns_rdata_tostruct(&rdata, &rrsig, NULL);
+ if (rrsig.covered == covers) {
+ isc_buffer_remainingregion(&source, &remaining);
+ break;
+ }
+
+ result = dns_rdataset_next(&rclone);
+ dns_rdata_reset(&rdata);
+ }
+ dns_rdataset_disassociate(&rclone);
+ if (result == ISC_R_NOMORE) {
+ return (ISC_R_NOTFOUND);
+ }
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+
+ INSIST(remaining.length != 0);
+
+ rdataset->methods = &rdataset_methods;
+ rdataset->rdclass = ncacherdataset->rdclass;
+ rdataset->type = dns_rdatatype_rrsig;
+ rdataset->covers = covers;
+ rdataset->ttl = ncacherdataset->ttl;
+ rdataset->trust = trust;
+ rdataset->private1 = NULL;
+ rdataset->private2 = NULL;
+
+ rdataset->private3 = remaining.base;
+
+ /*
+ * Reset iterator state.
+ */
+ rdataset->privateuint4 = 0;
+ rdataset->private5 = NULL;
+ rdataset->private6 = NULL;
+ return (ISC_R_SUCCESS);
+}
+
+void
+dns_ncache_current(dns_rdataset_t *ncacherdataset, dns_name_t *found,
+ dns_rdataset_t *rdataset) {
+ dns_rdata_t rdata = DNS_RDATA_INIT;
+ dns_trust_t trust;
+ isc_region_t remaining, sigregion;
+ isc_buffer_t source;
+ dns_name_t tname;
+ dns_rdatatype_t type;
+ unsigned int count;
+ dns_rdata_rrsig_t rrsig;
+ unsigned char *raw;
+
+ REQUIRE(ncacherdataset != NULL);
+ REQUIRE(ncacherdataset->type == 0);
+ REQUIRE((ncacherdataset->attributes & DNS_RDATASETATTR_NEGATIVE) != 0);
+ REQUIRE(found != NULL);
+ REQUIRE(!dns_rdataset_isassociated(rdataset));
+
+ dns_rdataset_current(ncacherdataset, &rdata);
+ isc_buffer_init(&source, rdata.data, rdata.length);
+ isc_buffer_add(&source, rdata.length);
+
+ dns_name_init(&tname, NULL);
+ isc_buffer_remainingregion(&source, &remaining);
+ dns_name_fromregion(found, &remaining);
+ INSIST(remaining.length >= found->length);
+ isc_buffer_forward(&source, found->length);
+ remaining.length -= found->length;
+
+ INSIST(remaining.length >= 5);
+ type = isc_buffer_getuint16(&source);
+ trust = isc_buffer_getuint8(&source);
+ INSIST(trust <= dns_trust_ultimate);
+ isc_buffer_remainingregion(&source, &remaining);
+
+ rdataset->methods = &rdataset_methods;
+ rdataset->rdclass = ncacherdataset->rdclass;
+ rdataset->type = type;
+ if (type == dns_rdatatype_rrsig) {
+ /*
+ * Extract covers from RRSIG.
+ */
+ raw = remaining.base;
+ count = raw[0] * 256 + raw[1];
+ INSIST(count > 0);
+ raw += 2;
+ sigregion.length = raw[0] * 256 + raw[1];
+ raw += 2;
+ sigregion.base = raw;
+ dns_rdata_reset(&rdata);
+ dns_rdata_fromregion(&rdata, rdataset->rdclass, rdataset->type,
+ &sigregion);
+ (void)dns_rdata_tostruct(&rdata, &rrsig, NULL);
+ rdataset->covers = rrsig.covered;
+ } else {
+ rdataset->covers = 0;
+ }
+ rdataset->ttl = ncacherdataset->ttl;
+ rdataset->trust = trust;
+ rdataset->private1 = NULL;
+ rdataset->private2 = NULL;
+
+ rdataset->private3 = remaining.base;
+
+ /*
+ * Reset iterator state.
+ */
+ rdataset->privateuint4 = 0;
+ rdataset->private5 = NULL;
+ rdataset->private6 = NULL;
+}
diff --git a/lib/dns/nsec.c b/lib/dns/nsec.c
new file mode 100644
index 0000000..7cbf636
--- /dev/null
+++ b/lib/dns/nsec.c
@@ -0,0 +1,533 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/*! \file */
+
+#include <stdbool.h>
+
+#include <isc/log.h>
+#include <isc/result.h>
+#include <isc/string.h>
+#include <isc/util.h>
+
+#include <dns/db.h>
+#include <dns/nsec.h>
+#include <dns/rdata.h>
+#include <dns/rdatalist.h>
+#include <dns/rdataset.h>
+#include <dns/rdatasetiter.h>
+#include <dns/rdatastruct.h>
+
+#include <dst/dst.h>
+
+#define RETERR(x) \
+ do { \
+ result = (x); \
+ if (result != ISC_R_SUCCESS) \
+ goto failure; \
+ } while (0)
+
+void
+dns_nsec_setbit(unsigned char *array, unsigned int type, unsigned int bit) {
+ unsigned int shift, mask;
+
+ shift = 7 - (type % 8);
+ mask = 1 << shift;
+
+ if (bit != 0) {
+ array[type / 8] |= mask;
+ } else {
+ array[type / 8] &= (~mask & 0xFF);
+ }
+}
+
+bool
+dns_nsec_isset(const unsigned char *array, unsigned int type) {
+ unsigned int byte, shift, mask;
+
+ byte = array[type / 8];
+ shift = 7 - (type % 8);
+ mask = 1 << shift;
+
+ return ((byte & mask) != 0);
+}
+
+unsigned int
+dns_nsec_compressbitmap(unsigned char *map, const unsigned char *raw,
+ unsigned int max_type) {
+ unsigned char *start = map;
+ unsigned int window;
+ int octet;
+
+ if (raw == NULL) {
+ return (0);
+ }
+
+ for (window = 0; window < 256; window++) {
+ if (window * 256 > max_type) {
+ break;
+ }
+ for (octet = 31; octet >= 0; octet--) {
+ if (*(raw + octet) != 0) {
+ break;
+ }
+ }
+ if (octet < 0) {
+ raw += 32;
+ continue;
+ }
+ *map++ = window;
+ *map++ = octet + 1;
+ /*
+ * Note: potential overlapping move.
+ */
+ memmove(map, raw, octet + 1);
+ map += octet + 1;
+ raw += 32;
+ }
+ return ((unsigned int)(map - start));
+}
+
+isc_result_t
+dns_nsec_buildrdata(dns_db_t *db, dns_dbversion_t *version, dns_dbnode_t *node,
+ const dns_name_t *target, unsigned char *buffer,
+ dns_rdata_t *rdata) {
+ isc_result_t result;
+ dns_rdataset_t rdataset;
+ isc_region_t r;
+ unsigned int i;
+
+ unsigned char *nsec_bits, *bm;
+ unsigned int max_type;
+ dns_rdatasetiter_t *rdsiter;
+
+ REQUIRE(target != NULL);
+
+ memset(buffer, 0, DNS_NSEC_BUFFERSIZE);
+ dns_name_toregion(target, &r);
+ memmove(buffer, r.base, r.length);
+ r.base = buffer;
+ /*
+ * Use the end of the space for a raw bitmap leaving enough
+ * space for the window identifiers and length octets.
+ */
+ bm = r.base + r.length + 512;
+ nsec_bits = r.base + r.length;
+ dns_nsec_setbit(bm, dns_rdatatype_rrsig, 1);
+ dns_nsec_setbit(bm, dns_rdatatype_nsec, 1);
+ max_type = dns_rdatatype_nsec;
+ dns_rdataset_init(&rdataset);
+ rdsiter = NULL;
+ result = dns_db_allrdatasets(db, node, version, 0, 0, &rdsiter);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+ for (result = dns_rdatasetiter_first(rdsiter); result == ISC_R_SUCCESS;
+ result = dns_rdatasetiter_next(rdsiter))
+ {
+ dns_rdatasetiter_current(rdsiter, &rdataset);
+ if (rdataset.type != dns_rdatatype_nsec &&
+ rdataset.type != dns_rdatatype_nsec3 &&
+ rdataset.type != dns_rdatatype_rrsig)
+ {
+ if (rdataset.type > max_type) {
+ max_type = rdataset.type;
+ }
+ dns_nsec_setbit(bm, rdataset.type, 1);
+ }
+ dns_rdataset_disassociate(&rdataset);
+ }
+
+ /*
+ * At zone cuts, deny the existence of glue in the parent zone.
+ */
+ if (dns_nsec_isset(bm, dns_rdatatype_ns) &&
+ !dns_nsec_isset(bm, dns_rdatatype_soa))
+ {
+ for (i = 0; i <= max_type; i++) {
+ if (dns_nsec_isset(bm, i) &&
+ !dns_rdatatype_iszonecutauth((dns_rdatatype_t)i))
+ {
+ dns_nsec_setbit(bm, i, 0);
+ }
+ }
+ }
+
+ dns_rdatasetiter_destroy(&rdsiter);
+ if (result != ISC_R_NOMORE) {
+ return (result);
+ }
+
+ nsec_bits += dns_nsec_compressbitmap(nsec_bits, bm, max_type);
+
+ r.length = (unsigned int)(nsec_bits - r.base);
+ INSIST(r.length <= DNS_NSEC_BUFFERSIZE);
+ dns_rdata_fromregion(rdata, dns_db_class(db), dns_rdatatype_nsec, &r);
+
+ return (ISC_R_SUCCESS);
+}
+
+isc_result_t
+dns_nsec_build(dns_db_t *db, dns_dbversion_t *version, dns_dbnode_t *node,
+ const dns_name_t *target, dns_ttl_t ttl) {
+ isc_result_t result;
+ dns_rdata_t rdata = DNS_RDATA_INIT;
+ unsigned char data[DNS_NSEC_BUFFERSIZE];
+ dns_rdatalist_t rdatalist;
+ dns_rdataset_t rdataset;
+
+ dns_rdataset_init(&rdataset);
+ dns_rdata_init(&rdata);
+
+ RETERR(dns_nsec_buildrdata(db, version, node, target, data, &rdata));
+
+ dns_rdatalist_init(&rdatalist);
+ rdatalist.rdclass = dns_db_class(db);
+ rdatalist.type = dns_rdatatype_nsec;
+ rdatalist.ttl = ttl;
+ ISC_LIST_APPEND(rdatalist.rdata, &rdata, link);
+ RETERR(dns_rdatalist_tordataset(&rdatalist, &rdataset));
+ result = dns_db_addrdataset(db, node, version, 0, &rdataset, 0, NULL);
+ if (result == DNS_R_UNCHANGED) {
+ result = ISC_R_SUCCESS;
+ }
+
+failure:
+ if (dns_rdataset_isassociated(&rdataset)) {
+ dns_rdataset_disassociate(&rdataset);
+ }
+ return (result);
+}
+
+bool
+dns_nsec_typepresent(dns_rdata_t *nsec, dns_rdatatype_t type) {
+ dns_rdata_nsec_t nsecstruct;
+ isc_result_t result;
+ bool present;
+ unsigned int i, len, window;
+
+ REQUIRE(nsec != NULL);
+ REQUIRE(nsec->type == dns_rdatatype_nsec);
+
+ /* This should never fail */
+ result = dns_rdata_tostruct(nsec, &nsecstruct, NULL);
+ INSIST(result == ISC_R_SUCCESS);
+
+ present = false;
+ for (i = 0; i < nsecstruct.len; i += len) {
+ INSIST(i + 2 <= nsecstruct.len);
+ window = nsecstruct.typebits[i];
+ len = nsecstruct.typebits[i + 1];
+ INSIST(len > 0 && len <= 32);
+ i += 2;
+ INSIST(i + len <= nsecstruct.len);
+ if (window * 256 > type) {
+ break;
+ }
+ if ((window + 1) * 256 <= type) {
+ continue;
+ }
+ if (type < (window * 256) + len * 8) {
+ present = dns_nsec_isset(&nsecstruct.typebits[i],
+ type % 256);
+ }
+ break;
+ }
+ dns_rdata_freestruct(&nsecstruct);
+ return (present);
+}
+
+isc_result_t
+dns_nsec_nseconly(dns_db_t *db, dns_dbversion_t *version, dns_diff_t *diff,
+ bool *answer) {
+ dns_dbnode_t *node = NULL;
+ dns_rdataset_t rdataset;
+ dns_rdata_dnskey_t dnskey;
+ isc_result_t result;
+
+ REQUIRE(answer != NULL);
+
+ dns_rdataset_init(&rdataset);
+
+ result = dns_db_getoriginnode(db, &node);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+
+ result = dns_db_findrdataset(db, node, version, dns_rdatatype_dnskey, 0,
+ 0, &rdataset, NULL);
+ dns_db_detachnode(db, &node);
+
+ if (result == ISC_R_NOTFOUND) {
+ *answer = false;
+ }
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+ for (result = dns_rdataset_first(&rdataset); result == ISC_R_SUCCESS;
+ result = dns_rdataset_next(&rdataset))
+ {
+ dns_rdata_t rdata = DNS_RDATA_INIT;
+
+ dns_rdataset_current(&rdataset, &rdata);
+ result = dns_rdata_tostruct(&rdata, &dnskey, NULL);
+ RUNTIME_CHECK(result == ISC_R_SUCCESS);
+
+ if (dnskey.algorithm == DST_ALG_RSAMD5 ||
+ dnskey.algorithm == DST_ALG_DH ||
+ dnskey.algorithm == DST_ALG_DSA ||
+ dnskey.algorithm == DST_ALG_RSASHA1)
+ {
+ bool deleted = false;
+ if (diff != NULL) {
+ for (dns_difftuple_t *tuple =
+ ISC_LIST_HEAD(diff->tuples);
+ tuple != NULL;
+ tuple = ISC_LIST_NEXT(tuple, link))
+ {
+ if (tuple->rdata.type !=
+ dns_rdatatype_dnskey ||
+ tuple->op != DNS_DIFFOP_DEL)
+ {
+ continue;
+ }
+
+ if (dns_rdata_compare(
+ &rdata, &tuple->rdata) == 0)
+ {
+ deleted = true;
+ break;
+ }
+ }
+ }
+
+ if (!deleted) {
+ break;
+ }
+ }
+ }
+ dns_rdataset_disassociate(&rdataset);
+ if (result == ISC_R_SUCCESS) {
+ *answer = true;
+ }
+ if (result == ISC_R_NOMORE) {
+ *answer = false;
+ result = ISC_R_SUCCESS;
+ }
+ return (result);
+}
+
+/*%
+ * Return ISC_R_SUCCESS if we can determine that the name doesn't exist
+ * or we can determine whether there is data or not at the name.
+ * If the name does not exist return the wildcard name.
+ *
+ * Return ISC_R_IGNORE when the NSEC is not the appropriate one.
+ */
+isc_result_t
+dns_nsec_noexistnodata(dns_rdatatype_t type, const dns_name_t *name,
+ const dns_name_t *nsecname, dns_rdataset_t *nsecset,
+ bool *exists, bool *data, dns_name_t *wild,
+ dns_nseclog_t logit, void *arg) {
+ int order;
+ dns_rdata_t rdata = DNS_RDATA_INIT;
+ isc_result_t result;
+ dns_namereln_t relation;
+ unsigned int olabels, nlabels, labels;
+ dns_rdata_nsec_t nsec;
+ bool atparent;
+ bool ns;
+ bool soa;
+
+ REQUIRE(exists != NULL);
+ REQUIRE(data != NULL);
+ REQUIRE(nsecset != NULL && nsecset->type == dns_rdatatype_nsec);
+
+ result = dns_rdataset_first(nsecset);
+ if (result != ISC_R_SUCCESS) {
+ (*logit)(arg, ISC_LOG_DEBUG(3), "failure processing NSEC set");
+ return (result);
+ }
+ dns_rdataset_current(nsecset, &rdata);
+
+#ifdef notyet
+ if (!dns_nsec_typepresent(&rdata, dns_rdatatype_rrsig) ||
+ !dns_nsec_typepresent(&rdata, dns_rdatatype_nsec))
+ {
+ (*logit)(arg, ISC_LOG_DEBUG(3),
+ "NSEC missing RRSIG and/or NSEC from type map");
+ return (ISC_R_IGNORE);
+ }
+#endif
+
+ (*logit)(arg, ISC_LOG_DEBUG(3), "looking for relevant NSEC");
+ relation = dns_name_fullcompare(name, nsecname, &order, &olabels);
+
+ if (order < 0) {
+ /*
+ * The name is not within the NSEC range.
+ */
+ (*logit)(arg, ISC_LOG_DEBUG(3),
+ "NSEC does not cover name, before NSEC");
+ return (ISC_R_IGNORE);
+ }
+
+ if (order == 0) {
+ /*
+ * The names are the same. If we are validating "."
+ * then atparent should not be set as there is no parent.
+ */
+ atparent = (olabels != 1) && dns_rdatatype_atparent(type);
+ ns = dns_nsec_typepresent(&rdata, dns_rdatatype_ns);
+ soa = dns_nsec_typepresent(&rdata, dns_rdatatype_soa);
+ if (ns && !soa) {
+ if (!atparent) {
+ /*
+ * This NSEC record is from somewhere higher in
+ * the DNS, and at the parent of a delegation.
+ * It can not be legitimately used here.
+ */
+ (*logit)(arg, ISC_LOG_DEBUG(3),
+ "ignoring parent nsec");
+ return (ISC_R_IGNORE);
+ }
+ } else if (atparent && ns && soa) {
+ /*
+ * This NSEC record is from the child.
+ * It can not be legitimately used here.
+ */
+ (*logit)(arg, ISC_LOG_DEBUG(3), "ignoring child nsec");
+ return (ISC_R_IGNORE);
+ }
+ if (type == dns_rdatatype_cname || type == dns_rdatatype_nxt ||
+ type == dns_rdatatype_nsec || type == dns_rdatatype_key ||
+ !dns_nsec_typepresent(&rdata, dns_rdatatype_cname))
+ {
+ *exists = true;
+ *data = dns_nsec_typepresent(&rdata, type);
+ (*logit)(arg, ISC_LOG_DEBUG(3),
+ "nsec proves name exists (owner) data=%d",
+ *data);
+ return (ISC_R_SUCCESS);
+ }
+ (*logit)(arg, ISC_LOG_DEBUG(3), "NSEC proves CNAME exists");
+ return (ISC_R_IGNORE);
+ }
+
+ if (relation == dns_namereln_subdomain &&
+ dns_nsec_typepresent(&rdata, dns_rdatatype_ns) &&
+ !dns_nsec_typepresent(&rdata, dns_rdatatype_soa))
+ {
+ /*
+ * This NSEC record is from somewhere higher in
+ * the DNS, and at the parent of a delegation or
+ * at a DNAME.
+ * It can not be legitimately used here.
+ */
+ (*logit)(arg, ISC_LOG_DEBUG(3), "ignoring parent nsec");
+ return (ISC_R_IGNORE);
+ }
+
+ if (relation == dns_namereln_subdomain &&
+ dns_nsec_typepresent(&rdata, dns_rdatatype_dname))
+ {
+ (*logit)(arg, ISC_LOG_DEBUG(3), "nsec proves covered by dname");
+ *exists = false;
+ return (DNS_R_DNAME);
+ }
+
+ result = dns_rdata_tostruct(&rdata, &nsec, NULL);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+ relation = dns_name_fullcompare(&nsec.next, name, &order, &nlabels);
+ if (order == 0) {
+ dns_rdata_freestruct(&nsec);
+ (*logit)(arg, ISC_LOG_DEBUG(3),
+ "ignoring nsec matches next name");
+ return (ISC_R_IGNORE);
+ }
+
+ if (order < 0 && !dns_name_issubdomain(nsecname, &nsec.next)) {
+ /*
+ * The name is not within the NSEC range.
+ */
+ dns_rdata_freestruct(&nsec);
+ (*logit)(arg, ISC_LOG_DEBUG(3),
+ "ignoring nsec because name is past end of range");
+ return (ISC_R_IGNORE);
+ }
+
+ if (order > 0 && relation == dns_namereln_subdomain) {
+ (*logit)(arg, ISC_LOG_DEBUG(3),
+ "nsec proves name exist (empty)");
+ dns_rdata_freestruct(&nsec);
+ *exists = true;
+ *data = false;
+ return (ISC_R_SUCCESS);
+ }
+ if (wild != NULL) {
+ dns_name_t common;
+ dns_name_init(&common, NULL);
+ if (olabels > nlabels) {
+ labels = dns_name_countlabels(nsecname);
+ dns_name_getlabelsequence(nsecname, labels - olabels,
+ olabels, &common);
+ } else {
+ labels = dns_name_countlabels(&nsec.next);
+ dns_name_getlabelsequence(&nsec.next, labels - nlabels,
+ nlabels, &common);
+ }
+ result = dns_name_concatenate(dns_wildcardname, &common, wild,
+ NULL);
+ if (result != ISC_R_SUCCESS) {
+ dns_rdata_freestruct(&nsec);
+ (*logit)(arg, ISC_LOG_DEBUG(3),
+ "failure generating wildcard name");
+ return (result);
+ }
+ }
+ dns_rdata_freestruct(&nsec);
+ (*logit)(arg, ISC_LOG_DEBUG(3), "nsec range ok");
+ *exists = false;
+ return (ISC_R_SUCCESS);
+}
+
+bool
+dns_nsec_requiredtypespresent(dns_rdataset_t *nsecset) {
+ dns_rdataset_t rdataset;
+ isc_result_t result;
+ bool found = false;
+
+ REQUIRE(DNS_RDATASET_VALID(nsecset));
+ REQUIRE(nsecset->type == dns_rdatatype_nsec);
+
+ dns_rdataset_init(&rdataset);
+ dns_rdataset_clone(nsecset, &rdataset);
+
+ for (result = dns_rdataset_first(&rdataset); result == ISC_R_SUCCESS;
+ result = dns_rdataset_next(&rdataset))
+ {
+ dns_rdata_t rdata = DNS_RDATA_INIT;
+ dns_rdataset_current(&rdataset, &rdata);
+ if (!dns_nsec_typepresent(&rdata, dns_rdatatype_nsec) ||
+ !dns_nsec_typepresent(&rdata, dns_rdatatype_rrsig))
+ {
+ dns_rdataset_disassociate(&rdataset);
+ return (false);
+ }
+ found = true;
+ }
+ dns_rdataset_disassociate(&rdataset);
+ return (found);
+}
diff --git a/lib/dns/nsec3.c b/lib/dns/nsec3.c
new file mode 100644
index 0000000..b9fc699
--- /dev/null
+++ b/lib/dns/nsec3.c
@@ -0,0 +1,2195 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#include <inttypes.h>
+#include <stdbool.h>
+
+#include <isc/base32.h>
+#include <isc/buffer.h>
+#include <isc/hex.h>
+#include <isc/iterated_hash.h>
+#include <isc/md.h>
+#include <isc/nonce.h>
+#include <isc/result.h>
+#include <isc/safe.h>
+#include <isc/string.h>
+#include <isc/util.h>
+
+#include <dns/compress.h>
+#include <dns/db.h>
+#include <dns/dbiterator.h>
+#include <dns/diff.h>
+#include <dns/fixedname.h>
+#include <dns/nsec.h>
+#include <dns/nsec3.h>
+#include <dns/rdata.h>
+#include <dns/rdatalist.h>
+#include <dns/rdataset.h>
+#include <dns/rdatasetiter.h>
+#include <dns/rdatastruct.h>
+#include <dns/zone.h>
+
+#include <dst/dst.h>
+
+#define CHECK(x) \
+ do { \
+ result = (x); \
+ if (result != ISC_R_SUCCESS) \
+ goto failure; \
+ } while (0)
+
+#define OPTOUT(x) (((x)&DNS_NSEC3FLAG_OPTOUT) != 0)
+#define CREATE(x) (((x)&DNS_NSEC3FLAG_CREATE) != 0)
+#define INITIAL(x) (((x)&DNS_NSEC3FLAG_INITIAL) != 0)
+#define REMOVE(x) (((x)&DNS_NSEC3FLAG_REMOVE) != 0)
+
+isc_result_t
+dns_nsec3_buildrdata(dns_db_t *db, dns_dbversion_t *version, dns_dbnode_t *node,
+ unsigned int hashalg, unsigned int flags,
+ unsigned int iterations, const unsigned char *salt,
+ size_t salt_length, const unsigned char *nexthash,
+ size_t hash_length, unsigned char *buffer,
+ dns_rdata_t *rdata) {
+ isc_result_t result;
+ dns_rdataset_t rdataset;
+ isc_region_t r;
+ unsigned int i;
+ bool found;
+ bool found_ns;
+ bool need_rrsig;
+
+ unsigned char *nsec_bits, *bm;
+ unsigned int max_type;
+ dns_rdatasetiter_t *rdsiter;
+ unsigned char *p;
+
+ REQUIRE(salt_length < 256U);
+ REQUIRE(hash_length < 256U);
+ REQUIRE(flags <= 0xffU);
+ REQUIRE(hashalg <= 0xffU);
+ REQUIRE(iterations <= 0xffffU);
+
+ switch (hashalg) {
+ case dns_hash_sha1:
+ REQUIRE(hash_length == ISC_SHA1_DIGESTLENGTH);
+ break;
+ }
+
+ memset(buffer, 0, DNS_NSEC3_BUFFERSIZE);
+
+ p = buffer;
+
+ *p++ = hashalg;
+ *p++ = flags;
+
+ *p++ = iterations >> 8;
+ *p++ = iterations;
+
+ *p++ = (unsigned char)salt_length;
+ memmove(p, salt, salt_length);
+ p += salt_length;
+
+ *p++ = (unsigned char)hash_length;
+ memmove(p, nexthash, hash_length);
+ p += hash_length;
+
+ r.length = (unsigned int)(p - buffer);
+ r.base = buffer;
+
+ /*
+ * Use the end of the space for a raw bitmap leaving enough
+ * space for the window identifiers and length octets.
+ */
+ bm = r.base + r.length + 512;
+ nsec_bits = r.base + r.length;
+ max_type = 0;
+ if (node == NULL) {
+ goto collapse_bitmap;
+ }
+ dns_rdataset_init(&rdataset);
+ rdsiter = NULL;
+ result = dns_db_allrdatasets(db, node, version, 0, 0, &rdsiter);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+ found = found_ns = need_rrsig = false;
+ for (result = dns_rdatasetiter_first(rdsiter); result == ISC_R_SUCCESS;
+ result = dns_rdatasetiter_next(rdsiter))
+ {
+ dns_rdatasetiter_current(rdsiter, &rdataset);
+ if (rdataset.type != dns_rdatatype_nsec &&
+ rdataset.type != dns_rdatatype_nsec3 &&
+ rdataset.type != dns_rdatatype_rrsig)
+ {
+ if (rdataset.type > max_type) {
+ max_type = rdataset.type;
+ }
+ dns_nsec_setbit(bm, rdataset.type, 1);
+ /*
+ * Work out if we need to set the RRSIG bit for
+ * this node. We set the RRSIG bit if either of
+ * the following conditions are met:
+ * 1) We have a SOA or DS then we need to set
+ * the RRSIG bit as both always will be signed.
+ * 2) We set the RRSIG bit if we don't have
+ * a NS record but do have other data.
+ */
+ if (rdataset.type == dns_rdatatype_soa ||
+ rdataset.type == dns_rdatatype_ds)
+ {
+ need_rrsig = true;
+ } else if (rdataset.type == dns_rdatatype_ns) {
+ found_ns = true;
+ } else {
+ found = true;
+ }
+ }
+ dns_rdataset_disassociate(&rdataset);
+ }
+ if ((found && !found_ns) || need_rrsig) {
+ if (dns_rdatatype_rrsig > max_type) {
+ max_type = dns_rdatatype_rrsig;
+ }
+ dns_nsec_setbit(bm, dns_rdatatype_rrsig, 1);
+ }
+
+ /*
+ * At zone cuts, deny the existence of glue in the parent zone.
+ */
+ if (dns_nsec_isset(bm, dns_rdatatype_ns) &&
+ !dns_nsec_isset(bm, dns_rdatatype_soa))
+ {
+ for (i = 0; i <= max_type; i++) {
+ if (dns_nsec_isset(bm, i) &&
+ !dns_rdatatype_iszonecutauth((dns_rdatatype_t)i))
+ {
+ dns_nsec_setbit(bm, i, 0);
+ }
+ }
+ }
+
+ dns_rdatasetiter_destroy(&rdsiter);
+ if (result != ISC_R_NOMORE) {
+ return (result);
+ }
+
+collapse_bitmap:
+ nsec_bits += dns_nsec_compressbitmap(nsec_bits, bm, max_type);
+ r.length = (unsigned int)(nsec_bits - r.base);
+ INSIST(r.length <= DNS_NSEC3_BUFFERSIZE);
+ dns_rdata_fromregion(rdata, dns_db_class(db), dns_rdatatype_nsec3, &r);
+
+ return (ISC_R_SUCCESS);
+}
+
+bool
+dns_nsec3_typepresent(dns_rdata_t *rdata, dns_rdatatype_t type) {
+ dns_rdata_nsec3_t nsec3;
+ isc_result_t result;
+ bool present;
+ unsigned int i, len, window;
+
+ REQUIRE(rdata != NULL);
+ REQUIRE(rdata->type == dns_rdatatype_nsec3);
+
+ /* This should never fail */
+ result = dns_rdata_tostruct(rdata, &nsec3, NULL);
+ INSIST(result == ISC_R_SUCCESS);
+
+ present = false;
+ for (i = 0; i < nsec3.len; i += len) {
+ INSIST(i + 2 <= nsec3.len);
+ window = nsec3.typebits[i];
+ len = nsec3.typebits[i + 1];
+ INSIST(len > 0 && len <= 32);
+ i += 2;
+ INSIST(i + len <= nsec3.len);
+ if (window * 256 > type) {
+ break;
+ }
+ if ((window + 1) * 256 <= type) {
+ continue;
+ }
+ if (type < (window * 256) + len * 8) {
+ present = dns_nsec_isset(&nsec3.typebits[i],
+ type % 256);
+ }
+ break;
+ }
+ dns_rdata_freestruct(&nsec3);
+ return (present);
+}
+
+isc_result_t
+dns_nsec3_generate_salt(unsigned char *salt, size_t saltlen) {
+ if (saltlen > 255U) {
+ return (ISC_R_RANGE);
+ }
+ isc_nonce_buf(salt, saltlen);
+ return (ISC_R_SUCCESS);
+}
+
+isc_result_t
+dns_nsec3_hashname(dns_fixedname_t *result,
+ unsigned char rethash[NSEC3_MAX_HASH_LENGTH],
+ size_t *hash_length, const dns_name_t *name,
+ const dns_name_t *origin, dns_hash_t hashalg,
+ unsigned int iterations, const unsigned char *salt,
+ size_t saltlength) {
+ unsigned char hash[NSEC3_MAX_HASH_LENGTH];
+ unsigned char nametext[DNS_NAME_FORMATSIZE];
+ dns_fixedname_t fixed;
+ dns_name_t *downcased;
+ isc_buffer_t namebuffer;
+ isc_region_t region;
+ size_t len;
+
+ if (rethash == NULL) {
+ rethash = hash;
+ }
+
+ memset(rethash, 0, NSEC3_MAX_HASH_LENGTH);
+
+ downcased = dns_fixedname_initname(&fixed);
+ dns_name_downcase(name, downcased, NULL);
+
+ /* hash the node name */
+ len = isc_iterated_hash(rethash, hashalg, iterations, salt,
+ (int)saltlength, downcased->ndata,
+ downcased->length);
+ if (len == 0U) {
+ return (DNS_R_BADALG);
+ }
+
+ if (hash_length != NULL) {
+ *hash_length = len;
+ }
+
+ /* convert the hash to base32hex non-padded */
+ region.base = rethash;
+ region.length = (unsigned int)len;
+ isc_buffer_init(&namebuffer, nametext, sizeof nametext);
+ isc_base32hexnp_totext(&region, 1, "", &namebuffer);
+
+ /* convert the hex to a domain name */
+ dns_fixedname_init(result);
+ return (dns_name_fromtext(dns_fixedname_name(result), &namebuffer,
+ origin, 0, NULL));
+}
+
+unsigned int
+dns_nsec3_hashlength(dns_hash_t hash) {
+ switch (hash) {
+ case dns_hash_sha1:
+ return (ISC_SHA1_DIGESTLENGTH);
+ }
+ return (0);
+}
+
+bool
+dns_nsec3_supportedhash(dns_hash_t hash) {
+ switch (hash) {
+ case dns_hash_sha1:
+ return (true);
+ }
+ return (false);
+}
+
+/*%
+ * Update a single RR in version 'ver' of 'db' and log the
+ * update in 'diff'.
+ *
+ * Ensures:
+ * \li '*tuple' == NULL. Either the tuple is freed, or its
+ * ownership has been transferred to the diff.
+ */
+static isc_result_t
+do_one_tuple(dns_difftuple_t **tuple, dns_db_t *db, dns_dbversion_t *ver,
+ dns_diff_t *diff) {
+ dns_diff_t temp_diff;
+ isc_result_t result;
+
+ /*
+ * Create a singleton diff.
+ */
+ dns_diff_init(diff->mctx, &temp_diff);
+ ISC_LIST_APPEND(temp_diff.tuples, *tuple, link);
+
+ /*
+ * Apply it to the database.
+ */
+ result = dns_diff_apply(&temp_diff, db, ver);
+ ISC_LIST_UNLINK(temp_diff.tuples, *tuple, link);
+ if (result != ISC_R_SUCCESS) {
+ dns_difftuple_free(tuple);
+ return (result);
+ }
+
+ /*
+ * Merge it into the current pending journal entry.
+ */
+ dns_diff_appendminimal(diff, tuple);
+
+ /*
+ * Do not clear temp_diff.
+ */
+ return (ISC_R_SUCCESS);
+}
+
+/*%
+ * Set '*exists' to true iff the given name exists, to false otherwise.
+ */
+static isc_result_t
+name_exists(dns_db_t *db, dns_dbversion_t *version, const dns_name_t *name,
+ bool *exists) {
+ isc_result_t result;
+ dns_dbnode_t *node = NULL;
+ dns_rdatasetiter_t *iter = NULL;
+
+ result = dns_db_findnode(db, name, false, &node);
+ if (result == ISC_R_NOTFOUND) {
+ *exists = false;
+ return (ISC_R_SUCCESS);
+ }
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+
+ result = dns_db_allrdatasets(db, node, version, 0, (isc_stdtime_t)0,
+ &iter);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup_node;
+ }
+
+ result = dns_rdatasetiter_first(iter);
+ if (result == ISC_R_SUCCESS) {
+ *exists = true;
+ } else if (result == ISC_R_NOMORE) {
+ *exists = false;
+ result = ISC_R_SUCCESS;
+ } else {
+ *exists = false;
+ }
+ dns_rdatasetiter_destroy(&iter);
+
+cleanup_node:
+ dns_db_detachnode(db, &node);
+ return (result);
+}
+
+static bool
+match_nsec3param(const dns_rdata_nsec3_t *nsec3,
+ const dns_rdata_nsec3param_t *nsec3param) {
+ if (nsec3->hash == nsec3param->hash &&
+ nsec3->iterations == nsec3param->iterations &&
+ nsec3->salt_length == nsec3param->salt_length &&
+ !memcmp(nsec3->salt, nsec3param->salt, nsec3->salt_length))
+ {
+ return (true);
+ }
+ return (false);
+}
+
+/*%
+ * Delete NSEC3 records at "name" which match "param", recording the
+ * change in "diff".
+ */
+static isc_result_t
+delnsec3(dns_db_t *db, dns_dbversion_t *version, const dns_name_t *name,
+ const dns_rdata_nsec3param_t *nsec3param, dns_diff_t *diff) {
+ dns_dbnode_t *node = NULL;
+ dns_difftuple_t *tuple = NULL;
+ dns_rdata_nsec3_t nsec3;
+ dns_rdataset_t rdataset;
+ isc_result_t result;
+
+ result = dns_db_findnsec3node(db, name, false, &node);
+ if (result == ISC_R_NOTFOUND) {
+ return (ISC_R_SUCCESS);
+ }
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+
+ dns_rdataset_init(&rdataset);
+ result = dns_db_findrdataset(db, node, version, dns_rdatatype_nsec3, 0,
+ (isc_stdtime_t)0, &rdataset, NULL);
+
+ if (result == ISC_R_NOTFOUND) {
+ result = ISC_R_SUCCESS;
+ goto cleanup_node;
+ }
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup_node;
+ }
+
+ for (result = dns_rdataset_first(&rdataset); result == ISC_R_SUCCESS;
+ result = dns_rdataset_next(&rdataset))
+ {
+ dns_rdata_t rdata = DNS_RDATA_INIT;
+ dns_rdataset_current(&rdataset, &rdata);
+ CHECK(dns_rdata_tostruct(&rdata, &nsec3, NULL));
+
+ if (!match_nsec3param(&nsec3, nsec3param)) {
+ continue;
+ }
+
+ result = dns_difftuple_create(diff->mctx, DNS_DIFFOP_DEL, name,
+ rdataset.ttl, &rdata, &tuple);
+ if (result != ISC_R_SUCCESS) {
+ goto failure;
+ }
+ result = do_one_tuple(&tuple, db, version, diff);
+ if (result != ISC_R_SUCCESS) {
+ goto failure;
+ }
+ }
+ if (result != ISC_R_NOMORE) {
+ goto failure;
+ }
+ result = ISC_R_SUCCESS;
+
+failure:
+ dns_rdataset_disassociate(&rdataset);
+cleanup_node:
+ dns_db_detachnode(db, &node);
+
+ return (result);
+}
+
+static bool
+better_param(dns_rdataset_t *nsec3paramset, dns_rdata_t *param) {
+ dns_rdataset_t rdataset;
+ isc_result_t result;
+
+ if (REMOVE(param->data[1])) {
+ return (true);
+ }
+
+ dns_rdataset_init(&rdataset);
+ dns_rdataset_clone(nsec3paramset, &rdataset);
+ for (result = dns_rdataset_first(&rdataset); result == ISC_R_SUCCESS;
+ result = dns_rdataset_next(&rdataset))
+ {
+ dns_rdata_t rdata = DNS_RDATA_INIT;
+ unsigned char buf[DNS_NSEC3PARAM_BUFFERSIZE];
+
+ if (rdataset.type != dns_rdatatype_nsec3param) {
+ dns_rdata_t tmprdata = DNS_RDATA_INIT;
+ dns_rdataset_current(&rdataset, &tmprdata);
+ if (!dns_nsec3param_fromprivate(&tmprdata, &rdata, buf,
+ sizeof(buf)))
+ {
+ continue;
+ }
+ } else {
+ dns_rdataset_current(&rdataset, &rdata);
+ }
+
+ if (rdata.length != param->length) {
+ continue;
+ }
+ if (rdata.data[0] != param->data[0] || REMOVE(rdata.data[1]) ||
+ rdata.data[2] != param->data[2] ||
+ rdata.data[3] != param->data[3] ||
+ rdata.data[4] != param->data[4] ||
+ memcmp(&rdata.data[5], &param->data[5], param->data[4]))
+ {
+ continue;
+ }
+ if (CREATE(rdata.data[1]) && !CREATE(param->data[1])) {
+ dns_rdataset_disassociate(&rdataset);
+ return (true);
+ }
+ }
+ dns_rdataset_disassociate(&rdataset);
+ return (false);
+}
+
+static isc_result_t
+find_nsec3(dns_rdata_nsec3_t *nsec3, dns_rdataset_t *rdataset,
+ const dns_rdata_nsec3param_t *nsec3param) {
+ isc_result_t result;
+ for (result = dns_rdataset_first(rdataset); result == ISC_R_SUCCESS;
+ result = dns_rdataset_next(rdataset))
+ {
+ dns_rdata_t rdata = DNS_RDATA_INIT;
+
+ dns_rdataset_current(rdataset, &rdata);
+ CHECK(dns_rdata_tostruct(&rdata, nsec3, NULL));
+ dns_rdata_reset(&rdata);
+ if (match_nsec3param(nsec3, nsec3param)) {
+ break;
+ }
+ }
+failure:
+ return (result);
+}
+
+isc_result_t
+dns_nsec3_addnsec3(dns_db_t *db, dns_dbversion_t *version,
+ const dns_name_t *name,
+ const dns_rdata_nsec3param_t *nsec3param, dns_ttl_t nsecttl,
+ bool unsecure, dns_diff_t *diff) {
+ dns_dbiterator_t *dbit = NULL;
+ dns_dbnode_t *node = NULL;
+ dns_dbnode_t *newnode = NULL;
+ dns_difftuple_t *tuple = NULL;
+ dns_fixedname_t fixed;
+ dns_fixedname_t fprev;
+ dns_hash_t hash;
+ dns_name_t *hashname;
+ dns_name_t *origin;
+ dns_name_t *prev;
+ dns_name_t empty;
+ dns_rdata_nsec3_t nsec3;
+ dns_rdata_t rdata = DNS_RDATA_INIT;
+ dns_rdataset_t rdataset;
+ int pass;
+ bool exists = false;
+ bool maybe_remove_unsecure = false;
+ uint8_t flags;
+ isc_buffer_t buffer;
+ isc_result_t result;
+ unsigned char *old_next;
+ unsigned char *salt;
+ unsigned char nexthash[NSEC3_MAX_HASH_LENGTH];
+ unsigned char nsec3buf[DNS_NSEC3_BUFFERSIZE];
+ unsigned int iterations;
+ unsigned int labels;
+ size_t next_length;
+ unsigned int old_length;
+ unsigned int salt_length;
+
+ hashname = dns_fixedname_initname(&fixed);
+ prev = dns_fixedname_initname(&fprev);
+
+ dns_rdataset_init(&rdataset);
+
+ origin = dns_db_origin(db);
+
+ /*
+ * Chain parameters.
+ */
+ hash = nsec3param->hash;
+ iterations = nsec3param->iterations;
+ salt_length = nsec3param->salt_length;
+ salt = nsec3param->salt;
+
+ /*
+ * Default flags for a new chain.
+ */
+ flags = nsec3param->flags & DNS_NSEC3FLAG_OPTOUT;
+
+ /*
+ * If this is the first NSEC3 in the chain nexthash will
+ * remain pointing to itself.
+ */
+ next_length = sizeof(nexthash);
+ CHECK(dns_nsec3_hashname(&fixed, nexthash, &next_length, name, origin,
+ hash, iterations, salt, salt_length));
+ INSIST(next_length <= sizeof(nexthash));
+
+ /*
+ * Create the node if it doesn't exist and hold
+ * a reference to it until we have added the NSEC3.
+ */
+ CHECK(dns_db_findnsec3node(db, hashname, true, &newnode));
+
+ /*
+ * Seek the iterator to the 'newnode'.
+ */
+ CHECK(dns_db_createiterator(db, DNS_DB_NSEC3ONLY, &dbit));
+ CHECK(dns_dbiterator_seek(dbit, hashname));
+ CHECK(dns_dbiterator_pause(dbit));
+ result = dns_db_findrdataset(db, newnode, version, dns_rdatatype_nsec3,
+ 0, (isc_stdtime_t)0, &rdataset, NULL);
+ /*
+ * If we updating a existing NSEC3 then find its
+ * next field.
+ */
+ if (result == ISC_R_SUCCESS) {
+ result = find_nsec3(&nsec3, &rdataset, nsec3param);
+ if (result == ISC_R_SUCCESS) {
+ if (!CREATE(nsec3param->flags)) {
+ flags = nsec3.flags;
+ }
+ next_length = nsec3.next_length;
+ INSIST(next_length <= sizeof(nexthash));
+ memmove(nexthash, nsec3.next, next_length);
+ dns_rdataset_disassociate(&rdataset);
+ /*
+ * If the NSEC3 is not for a unsecure delegation then
+ * we are just updating it. If it is for a unsecure
+ * delegation then we need find out if we need to
+ * remove the NSEC3 record or not by examining the
+ * previous NSEC3 record.
+ */
+ if (!unsecure) {
+ goto addnsec3;
+ } else if (CREATE(nsec3param->flags) && OPTOUT(flags)) {
+ result = dns_nsec3_delnsec3(db, version, name,
+ nsec3param, diff);
+ goto failure;
+ } else {
+ maybe_remove_unsecure = true;
+ }
+ } else {
+ dns_rdataset_disassociate(&rdataset);
+ if (result != ISC_R_NOMORE) {
+ goto failure;
+ }
+ }
+ }
+
+ /*
+ * Find the previous NSEC3 (if any) and update it if required.
+ */
+ pass = 0;
+ do {
+ result = dns_dbiterator_prev(dbit);
+ if (result == ISC_R_NOMORE) {
+ pass++;
+ CHECK(dns_dbiterator_last(dbit));
+ }
+ CHECK(dns_dbiterator_current(dbit, &node, prev));
+ CHECK(dns_dbiterator_pause(dbit));
+ result = dns_db_findrdataset(db, node, version,
+ dns_rdatatype_nsec3, 0,
+ (isc_stdtime_t)0, &rdataset, NULL);
+ dns_db_detachnode(db, &node);
+ if (result != ISC_R_SUCCESS) {
+ continue;
+ }
+
+ result = find_nsec3(&nsec3, &rdataset, nsec3param);
+ if (result == ISC_R_NOMORE) {
+ dns_rdataset_disassociate(&rdataset);
+ continue;
+ }
+ if (result != ISC_R_SUCCESS) {
+ goto failure;
+ }
+
+ if (maybe_remove_unsecure) {
+ dns_rdataset_disassociate(&rdataset);
+ /*
+ * If we have OPTOUT set in the previous NSEC3 record
+ * we actually need to delete the NSEC3 record.
+ * Otherwise we just need to replace the NSEC3 record.
+ */
+ if (OPTOUT(nsec3.flags)) {
+ result = dns_nsec3_delnsec3(db, version, name,
+ nsec3param, diff);
+ goto failure;
+ }
+ goto addnsec3;
+ } else {
+ /*
+ * Is this is a unsecure delegation we are adding?
+ * If so no change is required.
+ */
+ if (OPTOUT(nsec3.flags) && unsecure) {
+ dns_rdataset_disassociate(&rdataset);
+ goto failure;
+ }
+ }
+
+ old_next = nsec3.next;
+ old_length = nsec3.next_length;
+
+ /*
+ * Delete the old previous NSEC3.
+ */
+ CHECK(delnsec3(db, version, prev, nsec3param, diff));
+
+ /*
+ * Fixup the previous NSEC3.
+ */
+ nsec3.next = nexthash;
+ nsec3.next_length = (unsigned char)next_length;
+ isc_buffer_init(&buffer, nsec3buf, sizeof(nsec3buf));
+ CHECK(dns_rdata_fromstruct(&rdata, rdataset.rdclass,
+ dns_rdatatype_nsec3, &nsec3,
+ &buffer));
+ CHECK(dns_difftuple_create(diff->mctx, DNS_DIFFOP_ADD, prev,
+ rdataset.ttl, &rdata, &tuple));
+ CHECK(do_one_tuple(&tuple, db, version, diff));
+ INSIST(old_length <= sizeof(nexthash));
+ memmove(nexthash, old_next, old_length);
+ if (!CREATE(nsec3param->flags)) {
+ flags = nsec3.flags;
+ }
+ dns_rdata_reset(&rdata);
+ dns_rdataset_disassociate(&rdataset);
+ break;
+ } while (pass < 2);
+
+addnsec3:
+ /*
+ * Create the NSEC3 RDATA.
+ */
+ CHECK(dns_db_findnode(db, name, false, &node));
+ CHECK(dns_nsec3_buildrdata(db, version, node, hash, flags, iterations,
+ salt, salt_length, nexthash, next_length,
+ nsec3buf, &rdata));
+ dns_db_detachnode(db, &node);
+
+ /*
+ * Delete the old NSEC3 and record the change.
+ */
+ CHECK(delnsec3(db, version, hashname, nsec3param, diff));
+ /*
+ * Add the new NSEC3 and record the change.
+ */
+ CHECK(dns_difftuple_create(diff->mctx, DNS_DIFFOP_ADD, hashname,
+ nsecttl, &rdata, &tuple));
+ CHECK(do_one_tuple(&tuple, db, version, diff));
+ INSIST(tuple == NULL);
+ dns_rdata_reset(&rdata);
+ dns_db_detachnode(db, &newnode);
+
+ /*
+ * Add missing NSEC3 records for empty nodes
+ */
+ dns_name_init(&empty, NULL);
+ dns_name_clone(name, &empty);
+ do {
+ labels = dns_name_countlabels(&empty) - 1;
+ if (labels <= dns_name_countlabels(origin)) {
+ break;
+ }
+ dns_name_getlabelsequence(&empty, 1, labels, &empty);
+ CHECK(name_exists(db, version, &empty, &exists));
+ if (exists) {
+ break;
+ }
+ CHECK(dns_nsec3_hashname(&fixed, nexthash, &next_length, &empty,
+ origin, hash, iterations, salt,
+ salt_length));
+
+ /*
+ * Create the node if it doesn't exist and hold
+ * a reference to it until we have added the NSEC3
+ * or we discover we don't need to add make a change.
+ */
+ CHECK(dns_db_findnsec3node(db, hashname, true, &newnode));
+ result = dns_db_findrdataset(db, newnode, version,
+ dns_rdatatype_nsec3, 0,
+ (isc_stdtime_t)0, &rdataset, NULL);
+ if (result == ISC_R_SUCCESS) {
+ result = find_nsec3(&nsec3, &rdataset, nsec3param);
+ dns_rdataset_disassociate(&rdataset);
+ if (result == ISC_R_SUCCESS) {
+ dns_db_detachnode(db, &newnode);
+ break;
+ }
+ if (result != ISC_R_NOMORE) {
+ goto failure;
+ }
+ }
+
+ /*
+ * Find the previous NSEC3 and update it.
+ */
+ CHECK(dns_dbiterator_seek(dbit, hashname));
+ pass = 0;
+ do {
+ result = dns_dbiterator_prev(dbit);
+ if (result == ISC_R_NOMORE) {
+ pass++;
+ CHECK(dns_dbiterator_last(dbit));
+ }
+ CHECK(dns_dbiterator_current(dbit, &node, prev));
+ CHECK(dns_dbiterator_pause(dbit));
+ result = dns_db_findrdataset(
+ db, node, version, dns_rdatatype_nsec3, 0,
+ (isc_stdtime_t)0, &rdataset, NULL);
+ dns_db_detachnode(db, &node);
+ if (result != ISC_R_SUCCESS) {
+ continue;
+ }
+ result = find_nsec3(&nsec3, &rdataset, nsec3param);
+ if (result == ISC_R_NOMORE) {
+ dns_rdataset_disassociate(&rdataset);
+ continue;
+ }
+ if (result != ISC_R_SUCCESS) {
+ goto failure;
+ }
+
+ old_next = nsec3.next;
+ old_length = nsec3.next_length;
+
+ /*
+ * Delete the old previous NSEC3.
+ */
+ CHECK(delnsec3(db, version, prev, nsec3param, diff));
+
+ /*
+ * Fixup the previous NSEC3.
+ */
+ nsec3.next = nexthash;
+ nsec3.next_length = (unsigned char)next_length;
+ isc_buffer_init(&buffer, nsec3buf, sizeof(nsec3buf));
+ CHECK(dns_rdata_fromstruct(&rdata, rdataset.rdclass,
+ dns_rdatatype_nsec3, &nsec3,
+ &buffer));
+ CHECK(dns_difftuple_create(diff->mctx, DNS_DIFFOP_ADD,
+ prev, rdataset.ttl, &rdata,
+ &tuple));
+ CHECK(do_one_tuple(&tuple, db, version, diff));
+ INSIST(old_length <= sizeof(nexthash));
+ memmove(nexthash, old_next, old_length);
+ if (!CREATE(nsec3param->flags)) {
+ flags = nsec3.flags;
+ }
+ dns_rdata_reset(&rdata);
+ dns_rdataset_disassociate(&rdataset);
+ break;
+ } while (pass < 2);
+
+ INSIST(pass < 2);
+
+ /*
+ * Create the NSEC3 RDATA for the empty node.
+ */
+ CHECK(dns_nsec3_buildrdata(
+ db, version, NULL, hash, flags, iterations, salt,
+ salt_length, nexthash, next_length, nsec3buf, &rdata));
+ /*
+ * Delete the old NSEC3 and record the change.
+ */
+ CHECK(delnsec3(db, version, hashname, nsec3param, diff));
+
+ /*
+ * Add the new NSEC3 and record the change.
+ */
+ CHECK(dns_difftuple_create(diff->mctx, DNS_DIFFOP_ADD, hashname,
+ nsecttl, &rdata, &tuple));
+ CHECK(do_one_tuple(&tuple, db, version, diff));
+ INSIST(tuple == NULL);
+ dns_rdata_reset(&rdata);
+ dns_db_detachnode(db, &newnode);
+ } while (1);
+
+ /* result cannot be ISC_R_NOMORE here */
+ INSIST(result != ISC_R_NOMORE);
+
+failure:
+ if (dbit != NULL) {
+ dns_dbiterator_destroy(&dbit);
+ }
+ if (dns_rdataset_isassociated(&rdataset)) {
+ dns_rdataset_disassociate(&rdataset);
+ }
+ if (node != NULL) {
+ dns_db_detachnode(db, &node);
+ }
+ if (newnode != NULL) {
+ dns_db_detachnode(db, &newnode);
+ }
+ return (result);
+}
+
+/*%
+ * Add NSEC3 records for "name", recording the change in "diff".
+ * The existing NSEC3 records are removed.
+ */
+isc_result_t
+dns_nsec3_addnsec3s(dns_db_t *db, dns_dbversion_t *version,
+ const dns_name_t *name, dns_ttl_t nsecttl, bool unsecure,
+ dns_diff_t *diff) {
+ dns_dbnode_t *node = NULL;
+ dns_rdata_nsec3param_t nsec3param;
+ dns_rdataset_t rdataset;
+ isc_result_t result;
+
+ dns_rdataset_init(&rdataset);
+
+ /*
+ * Find the NSEC3 parameters for this zone.
+ */
+ result = dns_db_getoriginnode(db, &node);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+
+ result = dns_db_findrdataset(db, node, version,
+ dns_rdatatype_nsec3param, 0, 0, &rdataset,
+ NULL);
+ dns_db_detachnode(db, &node);
+ if (result == ISC_R_NOTFOUND) {
+ return (ISC_R_SUCCESS);
+ }
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+
+ /*
+ * Update each active NSEC3 chain.
+ */
+ for (result = dns_rdataset_first(&rdataset); result == ISC_R_SUCCESS;
+ result = dns_rdataset_next(&rdataset))
+ {
+ dns_rdata_t rdata = DNS_RDATA_INIT;
+
+ dns_rdataset_current(&rdataset, &rdata);
+ CHECK(dns_rdata_tostruct(&rdata, &nsec3param, NULL));
+
+ if (nsec3param.flags != 0) {
+ continue;
+ }
+ /*
+ * We have a active chain. Update it.
+ */
+ CHECK(dns_nsec3_addnsec3(db, version, name, &nsec3param,
+ nsecttl, unsecure, diff));
+ }
+ if (result == ISC_R_NOMORE) {
+ result = ISC_R_SUCCESS;
+ }
+
+failure:
+ if (dns_rdataset_isassociated(&rdataset)) {
+ dns_rdataset_disassociate(&rdataset);
+ }
+ if (node != NULL) {
+ dns_db_detachnode(db, &node);
+ }
+
+ return (result);
+}
+
+bool
+dns_nsec3param_fromprivate(dns_rdata_t *src, dns_rdata_t *target,
+ unsigned char *buf, size_t buflen) {
+ dns_decompress_t dctx;
+ isc_result_t result;
+ isc_buffer_t buf1;
+ isc_buffer_t buf2;
+
+ /*
+ * Algorithm 0 (reserved by RFC 4034) is used to identify
+ * NSEC3PARAM records from DNSKEY pointers.
+ */
+ if (src->length < 1 || src->data[0] != 0) {
+ return (false);
+ }
+
+ isc_buffer_init(&buf1, src->data + 1, src->length - 1);
+ isc_buffer_add(&buf1, src->length - 1);
+ isc_buffer_setactive(&buf1, src->length - 1);
+ isc_buffer_init(&buf2, buf, (unsigned int)buflen);
+ dns_decompress_init(&dctx, -1, DNS_DECOMPRESS_NONE);
+ result = dns_rdata_fromwire(target, src->rdclass,
+ dns_rdatatype_nsec3param, &buf1, &dctx, 0,
+ &buf2);
+ dns_decompress_invalidate(&dctx);
+
+ return (result == ISC_R_SUCCESS);
+}
+
+void
+dns_nsec3param_toprivate(dns_rdata_t *src, dns_rdata_t *target,
+ dns_rdatatype_t privatetype, unsigned char *buf,
+ size_t buflen) {
+ REQUIRE(buflen >= src->length + 1);
+
+ REQUIRE(DNS_RDATA_INITIALIZED(target));
+
+ memmove(buf + 1, src->data, src->length);
+ buf[0] = 0;
+ target->data = buf;
+ target->length = src->length + 1;
+ target->type = privatetype;
+ target->rdclass = src->rdclass;
+ target->flags = 0;
+ ISC_LINK_INIT(target, link);
+}
+
+static isc_result_t
+rr_exists(dns_db_t *db, dns_dbversion_t *ver, const dns_name_t *name,
+ const dns_rdata_t *rdata, bool *flag) {
+ dns_rdataset_t rdataset;
+ dns_dbnode_t *node = NULL;
+ isc_result_t result;
+
+ dns_rdataset_init(&rdataset);
+ if (rdata->type == dns_rdatatype_nsec3) {
+ CHECK(dns_db_findnsec3node(db, name, false, &node));
+ } else {
+ CHECK(dns_db_findnode(db, name, false, &node));
+ }
+ result = dns_db_findrdataset(db, node, ver, rdata->type, 0,
+ (isc_stdtime_t)0, &rdataset, NULL);
+ if (result == ISC_R_NOTFOUND) {
+ *flag = false;
+ result = ISC_R_SUCCESS;
+ goto failure;
+ }
+
+ for (result = dns_rdataset_first(&rdataset); result == ISC_R_SUCCESS;
+ result = dns_rdataset_next(&rdataset))
+ {
+ dns_rdata_t myrdata = DNS_RDATA_INIT;
+ dns_rdataset_current(&rdataset, &myrdata);
+ if (!dns_rdata_casecompare(&myrdata, rdata)) {
+ break;
+ }
+ }
+ dns_rdataset_disassociate(&rdataset);
+ if (result == ISC_R_SUCCESS) {
+ *flag = true;
+ } else if (result == ISC_R_NOMORE) {
+ *flag = false;
+ result = ISC_R_SUCCESS;
+ }
+
+failure:
+ if (node != NULL) {
+ dns_db_detachnode(db, &node);
+ }
+ return (result);
+}
+
+isc_result_t
+dns_nsec3param_salttotext(dns_rdata_nsec3param_t *nsec3param, char *dst,
+ size_t dstlen) {
+ isc_result_t result;
+ isc_region_t r;
+ isc_buffer_t b;
+
+ REQUIRE(nsec3param != NULL);
+ REQUIRE(dst != NULL);
+
+ if (nsec3param->salt_length == 0) {
+ if (dstlen < 2U) {
+ return (ISC_R_NOSPACE);
+ }
+ strlcpy(dst, "-", dstlen);
+ return (ISC_R_SUCCESS);
+ }
+
+ r.base = nsec3param->salt;
+ r.length = nsec3param->salt_length;
+ isc_buffer_init(&b, dst, (unsigned int)dstlen);
+
+ result = isc_hex_totext(&r, 2, "", &b);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+
+ if (isc_buffer_availablelength(&b) < 1) {
+ return (ISC_R_NOSPACE);
+ }
+ isc_buffer_putuint8(&b, 0);
+
+ return (ISC_R_SUCCESS);
+}
+
+isc_result_t
+dns_nsec3param_deletechains(dns_db_t *db, dns_dbversion_t *ver,
+ dns_zone_t *zone, bool nonsec, dns_diff_t *diff) {
+ dns_dbnode_t *node = NULL;
+ dns_difftuple_t *tuple = NULL;
+ dns_name_t next;
+ dns_rdata_t rdata = DNS_RDATA_INIT;
+ dns_rdataset_t rdataset;
+ bool flag;
+ isc_result_t result = ISC_R_SUCCESS;
+ unsigned char buf[DNS_NSEC3PARAM_BUFFERSIZE + 1];
+ dns_name_t *origin = dns_zone_getorigin(zone);
+ dns_rdatatype_t privatetype = dns_zone_getprivatetype(zone);
+
+ dns_name_init(&next, NULL);
+ dns_rdataset_init(&rdataset);
+
+ result = dns_db_getoriginnode(db, &node);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+
+ /*
+ * Cause all NSEC3 chains to be deleted.
+ */
+ result = dns_db_findrdataset(db, node, ver, dns_rdatatype_nsec3param, 0,
+ (isc_stdtime_t)0, &rdataset, NULL);
+ if (result == ISC_R_NOTFOUND) {
+ goto try_private;
+ }
+ if (result != ISC_R_SUCCESS) {
+ goto failure;
+ }
+
+ for (result = dns_rdataset_first(&rdataset); result == ISC_R_SUCCESS;
+ result = dns_rdataset_next(&rdataset))
+ {
+ dns_rdata_t private = DNS_RDATA_INIT;
+
+ dns_rdataset_current(&rdataset, &rdata);
+
+ CHECK(dns_difftuple_create(diff->mctx, DNS_DIFFOP_DEL, origin,
+ rdataset.ttl, &rdata, &tuple));
+ CHECK(do_one_tuple(&tuple, db, ver, diff));
+ INSIST(tuple == NULL);
+
+ dns_nsec3param_toprivate(&rdata, &private, privatetype, buf,
+ sizeof(buf));
+ buf[2] = DNS_NSEC3FLAG_REMOVE;
+ if (nonsec) {
+ buf[2] |= DNS_NSEC3FLAG_NONSEC;
+ }
+
+ CHECK(rr_exists(db, ver, origin, &private, &flag));
+
+ if (!flag) {
+ CHECK(dns_difftuple_create(diff->mctx, DNS_DIFFOP_ADD,
+ origin, 0, &private,
+ &tuple));
+ CHECK(do_one_tuple(&tuple, db, ver, diff));
+ INSIST(tuple == NULL);
+ }
+ dns_rdata_reset(&rdata);
+ }
+ if (result != ISC_R_NOMORE) {
+ goto failure;
+ }
+
+ dns_rdataset_disassociate(&rdataset);
+
+try_private:
+ if (privatetype == 0) {
+ goto success;
+ }
+ result = dns_db_findrdataset(db, node, ver, privatetype, 0,
+ (isc_stdtime_t)0, &rdataset, NULL);
+ if (result == ISC_R_NOTFOUND) {
+ goto success;
+ }
+ if (result != ISC_R_SUCCESS) {
+ goto failure;
+ }
+
+ for (result = dns_rdataset_first(&rdataset); result == ISC_R_SUCCESS;
+ result = dns_rdataset_next(&rdataset))
+ {
+ dns_rdata_reset(&rdata);
+ dns_rdataset_current(&rdataset, &rdata);
+ INSIST(rdata.length <= sizeof(buf));
+ memmove(buf, rdata.data, rdata.length);
+
+ /*
+ * Private NSEC3 record length >= 6.
+ * <0(1), hash(1), flags(1), iterations(2), saltlen(1)>
+ */
+ if (rdata.length < 6 || buf[0] != 0 ||
+ (buf[2] & DNS_NSEC3FLAG_REMOVE) != 0 ||
+ (nonsec && (buf[2] & DNS_NSEC3FLAG_NONSEC) != 0))
+ {
+ continue;
+ }
+
+ CHECK(dns_difftuple_create(diff->mctx, DNS_DIFFOP_DEL, origin,
+ 0, &rdata, &tuple));
+ CHECK(do_one_tuple(&tuple, db, ver, diff));
+ INSIST(tuple == NULL);
+
+ rdata.data = buf;
+ buf[2] = DNS_NSEC3FLAG_REMOVE;
+ if (nonsec) {
+ buf[2] |= DNS_NSEC3FLAG_NONSEC;
+ }
+
+ CHECK(rr_exists(db, ver, origin, &rdata, &flag));
+
+ if (!flag) {
+ CHECK(dns_difftuple_create(diff->mctx, DNS_DIFFOP_ADD,
+ origin, 0, &rdata, &tuple));
+ CHECK(do_one_tuple(&tuple, db, ver, diff));
+ INSIST(tuple == NULL);
+ }
+ }
+ if (result != ISC_R_NOMORE) {
+ goto failure;
+ }
+success:
+ result = ISC_R_SUCCESS;
+
+failure:
+ if (dns_rdataset_isassociated(&rdataset)) {
+ dns_rdataset_disassociate(&rdataset);
+ }
+ dns_db_detachnode(db, &node);
+ return (result);
+}
+
+isc_result_t
+dns_nsec3_addnsec3sx(dns_db_t *db, dns_dbversion_t *version,
+ const dns_name_t *name, dns_ttl_t nsecttl, bool unsecure,
+ dns_rdatatype_t type, dns_diff_t *diff) {
+ dns_dbnode_t *node = NULL;
+ dns_rdata_nsec3param_t nsec3param;
+ dns_rdataset_t rdataset;
+ dns_rdataset_t prdataset;
+ isc_result_t result;
+
+ dns_rdataset_init(&rdataset);
+ dns_rdataset_init(&prdataset);
+
+ /*
+ * Find the NSEC3 parameters for this zone.
+ */
+ result = dns_db_getoriginnode(db, &node);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+
+ result = dns_db_findrdataset(db, node, version, type, 0, 0, &prdataset,
+ NULL);
+ if (result != ISC_R_SUCCESS && result != ISC_R_NOTFOUND) {
+ goto failure;
+ }
+
+ result = dns_db_findrdataset(db, node, version,
+ dns_rdatatype_nsec3param, 0, 0, &rdataset,
+ NULL);
+ if (result == ISC_R_NOTFOUND) {
+ goto try_private;
+ }
+ if (result != ISC_R_SUCCESS) {
+ goto failure;
+ }
+
+ /*
+ * Update each active NSEC3 chain.
+ */
+ for (result = dns_rdataset_first(&rdataset); result == ISC_R_SUCCESS;
+ result = dns_rdataset_next(&rdataset))
+ {
+ dns_rdata_t rdata = DNS_RDATA_INIT;
+
+ dns_rdataset_current(&rdataset, &rdata);
+ CHECK(dns_rdata_tostruct(&rdata, &nsec3param, NULL));
+
+ if (nsec3param.flags != 0) {
+ continue;
+ }
+
+ /*
+ * We have a active chain. Update it.
+ */
+ CHECK(dns_nsec3_addnsec3(db, version, name, &nsec3param,
+ nsecttl, unsecure, diff));
+ }
+ if (result != ISC_R_NOMORE) {
+ goto failure;
+ }
+
+ dns_rdataset_disassociate(&rdataset);
+
+try_private:
+ if (!dns_rdataset_isassociated(&prdataset)) {
+ goto success;
+ }
+ /*
+ * Update each active NSEC3 chain.
+ */
+ for (result = dns_rdataset_first(&prdataset); result == ISC_R_SUCCESS;
+ result = dns_rdataset_next(&prdataset))
+ {
+ dns_rdata_t rdata1 = DNS_RDATA_INIT;
+ dns_rdata_t rdata2 = DNS_RDATA_INIT;
+ unsigned char buf[DNS_NSEC3PARAM_BUFFERSIZE];
+
+ dns_rdataset_current(&prdataset, &rdata1);
+ if (!dns_nsec3param_fromprivate(&rdata1, &rdata2, buf,
+ sizeof(buf)))
+ {
+ continue;
+ }
+ CHECK(dns_rdata_tostruct(&rdata2, &nsec3param, NULL));
+
+ if ((nsec3param.flags & DNS_NSEC3FLAG_REMOVE) != 0) {
+ continue;
+ }
+ if (better_param(&prdataset, &rdata2)) {
+ continue;
+ }
+
+ /*
+ * We have a active chain. Update it.
+ */
+ CHECK(dns_nsec3_addnsec3(db, version, name, &nsec3param,
+ nsecttl, unsecure, diff));
+ }
+ if (result == ISC_R_NOMORE) {
+ success:
+ result = ISC_R_SUCCESS;
+ }
+failure:
+ if (dns_rdataset_isassociated(&rdataset)) {
+ dns_rdataset_disassociate(&rdataset);
+ }
+ if (dns_rdataset_isassociated(&prdataset)) {
+ dns_rdataset_disassociate(&prdataset);
+ }
+ if (node != NULL) {
+ dns_db_detachnode(db, &node);
+ }
+
+ return (result);
+}
+
+/*%
+ * Determine whether any NSEC3 records that were associated with
+ * 'name' should be deleted or if they should continue to exist.
+ * true indicates they should be deleted.
+ * false indicates they should be retained.
+ */
+static isc_result_t
+deleteit(dns_db_t *db, dns_dbversion_t *ver, const dns_name_t *name,
+ bool *yesno) {
+ isc_result_t result;
+ dns_fixedname_t foundname;
+ dns_fixedname_init(&foundname);
+
+ result = dns_db_find(db, name, ver, dns_rdatatype_any,
+ DNS_DBFIND_GLUEOK | DNS_DBFIND_NOWILD,
+ (isc_stdtime_t)0, NULL,
+ dns_fixedname_name(&foundname), NULL, NULL);
+ if (result == DNS_R_EMPTYNAME || result == ISC_R_SUCCESS ||
+ result == DNS_R_ZONECUT)
+ {
+ *yesno = false;
+ return (ISC_R_SUCCESS);
+ }
+ if (result == DNS_R_GLUE || result == DNS_R_DNAME ||
+ result == DNS_R_DELEGATION || result == DNS_R_NXDOMAIN)
+ {
+ *yesno = true;
+ return (ISC_R_SUCCESS);
+ }
+ /*
+ * Silence compiler.
+ */
+ *yesno = true;
+ return (result);
+}
+
+isc_result_t
+dns_nsec3_delnsec3(dns_db_t *db, dns_dbversion_t *version,
+ const dns_name_t *name,
+ const dns_rdata_nsec3param_t *nsec3param, dns_diff_t *diff) {
+ dns_dbiterator_t *dbit = NULL;
+ dns_dbnode_t *node = NULL;
+ dns_difftuple_t *tuple = NULL;
+ dns_fixedname_t fixed;
+ dns_fixedname_t fprev;
+ dns_hash_t hash;
+ dns_name_t *hashname;
+ dns_name_t *origin;
+ dns_name_t *prev;
+ dns_name_t empty;
+ dns_rdata_nsec3_t nsec3;
+ dns_rdata_t rdata = DNS_RDATA_INIT;
+ dns_rdataset_t rdataset;
+ int pass;
+ bool yesno;
+ isc_buffer_t buffer;
+ isc_result_t result;
+ unsigned char *salt;
+ unsigned char nexthash[NSEC3_MAX_HASH_LENGTH];
+ unsigned char nsec3buf[DNS_NSEC3_BUFFERSIZE];
+ unsigned int iterations;
+ unsigned int labels;
+ size_t next_length;
+ unsigned int salt_length;
+
+ hashname = dns_fixedname_initname(&fixed);
+ prev = dns_fixedname_initname(&fprev);
+
+ dns_rdataset_init(&rdataset);
+
+ origin = dns_db_origin(db);
+
+ /*
+ * Chain parameters.
+ */
+ hash = nsec3param->hash;
+ iterations = nsec3param->iterations;
+ salt_length = nsec3param->salt_length;
+ salt = nsec3param->salt;
+
+ /*
+ * If this is the first NSEC3 in the chain nexthash will
+ * remain pointing to itself.
+ */
+ next_length = sizeof(nexthash);
+ CHECK(dns_nsec3_hashname(&fixed, nexthash, &next_length, name, origin,
+ hash, iterations, salt, salt_length));
+
+ CHECK(dns_db_createiterator(db, DNS_DB_NSEC3ONLY, &dbit));
+
+ result = dns_dbiterator_seek(dbit, hashname);
+ if (result == ISC_R_NOTFOUND || result == DNS_R_PARTIALMATCH) {
+ goto cleanup_orphaned_ents;
+ }
+ if (result != ISC_R_SUCCESS) {
+ goto failure;
+ }
+
+ CHECK(dns_dbiterator_current(dbit, &node, NULL));
+ CHECK(dns_dbiterator_pause(dbit));
+ result = dns_db_findrdataset(db, node, version, dns_rdatatype_nsec3, 0,
+ (isc_stdtime_t)0, &rdataset, NULL);
+ dns_db_detachnode(db, &node);
+ if (result == ISC_R_NOTFOUND) {
+ goto cleanup_orphaned_ents;
+ }
+ if (result != ISC_R_SUCCESS) {
+ goto failure;
+ }
+
+ /*
+ * If we find a existing NSEC3 for this chain then save the
+ * next field.
+ */
+ result = find_nsec3(&nsec3, &rdataset, nsec3param);
+ if (result == ISC_R_SUCCESS) {
+ next_length = nsec3.next_length;
+ INSIST(next_length <= sizeof(nexthash));
+ memmove(nexthash, nsec3.next, next_length);
+ }
+ dns_rdataset_disassociate(&rdataset);
+ if (result == ISC_R_NOMORE) {
+ goto success;
+ }
+ if (result != ISC_R_SUCCESS) {
+ goto failure;
+ }
+
+ /*
+ * Find the previous NSEC3 and update it.
+ */
+ pass = 0;
+ do {
+ result = dns_dbiterator_prev(dbit);
+ if (result == ISC_R_NOMORE) {
+ pass++;
+ CHECK(dns_dbiterator_last(dbit));
+ }
+ CHECK(dns_dbiterator_current(dbit, &node, prev));
+ CHECK(dns_dbiterator_pause(dbit));
+ result = dns_db_findrdataset(db, node, version,
+ dns_rdatatype_nsec3, 0,
+ (isc_stdtime_t)0, &rdataset, NULL);
+ dns_db_detachnode(db, &node);
+ if (result != ISC_R_SUCCESS) {
+ continue;
+ }
+ result = find_nsec3(&nsec3, &rdataset, nsec3param);
+ if (result == ISC_R_NOMORE) {
+ dns_rdataset_disassociate(&rdataset);
+ continue;
+ }
+ if (result != ISC_R_SUCCESS) {
+ goto failure;
+ }
+
+ /*
+ * Delete the old previous NSEC3.
+ */
+ CHECK(delnsec3(db, version, prev, nsec3param, diff));
+
+ /*
+ * Fixup the previous NSEC3.
+ */
+ nsec3.next = nexthash;
+ nsec3.next_length = (unsigned char)next_length;
+ if (CREATE(nsec3param->flags)) {
+ nsec3.flags = nsec3param->flags & DNS_NSEC3FLAG_OPTOUT;
+ }
+ isc_buffer_init(&buffer, nsec3buf, sizeof(nsec3buf));
+ CHECK(dns_rdata_fromstruct(&rdata, rdataset.rdclass,
+ dns_rdatatype_nsec3, &nsec3,
+ &buffer));
+ CHECK(dns_difftuple_create(diff->mctx, DNS_DIFFOP_ADD, prev,
+ rdataset.ttl, &rdata, &tuple));
+ CHECK(do_one_tuple(&tuple, db, version, diff));
+ dns_rdata_reset(&rdata);
+ dns_rdataset_disassociate(&rdataset);
+ break;
+ } while (pass < 2);
+
+ /*
+ * Delete the old NSEC3 and record the change.
+ */
+ CHECK(delnsec3(db, version, hashname, nsec3param, diff));
+
+ /*
+ * Delete NSEC3 records for now non active nodes.
+ */
+cleanup_orphaned_ents:
+ dns_name_init(&empty, NULL);
+ dns_name_clone(name, &empty);
+ do {
+ labels = dns_name_countlabels(&empty) - 1;
+ if (labels <= dns_name_countlabels(origin)) {
+ break;
+ }
+ dns_name_getlabelsequence(&empty, 1, labels, &empty);
+ CHECK(deleteit(db, version, &empty, &yesno));
+ if (!yesno) {
+ break;
+ }
+
+ CHECK(dns_nsec3_hashname(&fixed, nexthash, &next_length, &empty,
+ origin, hash, iterations, salt,
+ salt_length));
+ result = dns_dbiterator_seek(dbit, hashname);
+ if (result == ISC_R_NOTFOUND || result == DNS_R_PARTIALMATCH) {
+ goto success;
+ }
+ if (result != ISC_R_SUCCESS) {
+ goto failure;
+ }
+
+ CHECK(dns_dbiterator_current(dbit, &node, NULL));
+ CHECK(dns_dbiterator_pause(dbit));
+ result = dns_db_findrdataset(db, node, version,
+ dns_rdatatype_nsec3, 0,
+ (isc_stdtime_t)0, &rdataset, NULL);
+ dns_db_detachnode(db, &node);
+ if (result == ISC_R_NOTFOUND) {
+ goto success;
+ }
+ if (result != ISC_R_SUCCESS) {
+ goto failure;
+ }
+
+ result = find_nsec3(&nsec3, &rdataset, nsec3param);
+ if (result == ISC_R_SUCCESS) {
+ next_length = nsec3.next_length;
+ INSIST(next_length <= sizeof(nexthash));
+ memmove(nexthash, nsec3.next, next_length);
+ }
+ dns_rdataset_disassociate(&rdataset);
+ if (result == ISC_R_NOMORE) {
+ goto success;
+ }
+ if (result != ISC_R_SUCCESS) {
+ goto failure;
+ }
+
+ pass = 0;
+ do {
+ result = dns_dbiterator_prev(dbit);
+ if (result == ISC_R_NOMORE) {
+ pass++;
+ CHECK(dns_dbiterator_last(dbit));
+ }
+ CHECK(dns_dbiterator_current(dbit, &node, prev));
+ CHECK(dns_dbiterator_pause(dbit));
+ result = dns_db_findrdataset(
+ db, node, version, dns_rdatatype_nsec3, 0,
+ (isc_stdtime_t)0, &rdataset, NULL);
+ dns_db_detachnode(db, &node);
+ if (result != ISC_R_SUCCESS) {
+ continue;
+ }
+ result = find_nsec3(&nsec3, &rdataset, nsec3param);
+ if (result == ISC_R_NOMORE) {
+ dns_rdataset_disassociate(&rdataset);
+ continue;
+ }
+ if (result != ISC_R_SUCCESS) {
+ goto failure;
+ }
+
+ /*
+ * Delete the old previous NSEC3.
+ */
+ CHECK(delnsec3(db, version, prev, nsec3param, diff));
+
+ /*
+ * Fixup the previous NSEC3.
+ */
+ nsec3.next = nexthash;
+ nsec3.next_length = (unsigned char)next_length;
+ isc_buffer_init(&buffer, nsec3buf, sizeof(nsec3buf));
+ CHECK(dns_rdata_fromstruct(&rdata, rdataset.rdclass,
+ dns_rdatatype_nsec3, &nsec3,
+ &buffer));
+ CHECK(dns_difftuple_create(diff->mctx, DNS_DIFFOP_ADD,
+ prev, rdataset.ttl, &rdata,
+ &tuple));
+ CHECK(do_one_tuple(&tuple, db, version, diff));
+ dns_rdata_reset(&rdata);
+ dns_rdataset_disassociate(&rdataset);
+ break;
+ } while (pass < 2);
+
+ INSIST(pass < 2);
+
+ /*
+ * Delete the old NSEC3 and record the change.
+ */
+ CHECK(delnsec3(db, version, hashname, nsec3param, diff));
+ } while (1);
+
+success:
+ result = ISC_R_SUCCESS;
+
+failure:
+ if (dbit != NULL) {
+ dns_dbiterator_destroy(&dbit);
+ }
+ if (dns_rdataset_isassociated(&rdataset)) {
+ dns_rdataset_disassociate(&rdataset);
+ }
+ if (node != NULL) {
+ dns_db_detachnode(db, &node);
+ }
+ return (result);
+}
+
+isc_result_t
+dns_nsec3_delnsec3s(dns_db_t *db, dns_dbversion_t *version,
+ const dns_name_t *name, dns_diff_t *diff) {
+ return (dns_nsec3_delnsec3sx(db, version, name, 0, diff));
+}
+
+isc_result_t
+dns_nsec3_delnsec3sx(dns_db_t *db, dns_dbversion_t *version,
+ const dns_name_t *name, dns_rdatatype_t privatetype,
+ dns_diff_t *diff) {
+ dns_dbnode_t *node = NULL;
+ dns_rdata_nsec3param_t nsec3param;
+ dns_rdataset_t rdataset;
+ isc_result_t result;
+
+ dns_rdataset_init(&rdataset);
+
+ /*
+ * Find the NSEC3 parameters for this zone.
+ */
+ result = dns_db_getoriginnode(db, &node);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+
+ result = dns_db_findrdataset(db, node, version,
+ dns_rdatatype_nsec3param, 0, 0, &rdataset,
+ NULL);
+ if (result == ISC_R_NOTFOUND) {
+ goto try_private;
+ }
+ if (result != ISC_R_SUCCESS) {
+ goto failure;
+ }
+
+ /*
+ * Update each active NSEC3 chain.
+ */
+ for (result = dns_rdataset_first(&rdataset); result == ISC_R_SUCCESS;
+ result = dns_rdataset_next(&rdataset))
+ {
+ dns_rdata_t rdata = DNS_RDATA_INIT;
+
+ dns_rdataset_current(&rdataset, &rdata);
+ CHECK(dns_rdata_tostruct(&rdata, &nsec3param, NULL));
+
+ if (nsec3param.flags != 0) {
+ continue;
+ }
+ /*
+ * We have a active chain. Update it.
+ */
+ CHECK(dns_nsec3_delnsec3(db, version, name, &nsec3param, diff));
+ }
+ dns_rdataset_disassociate(&rdataset);
+
+try_private:
+ if (privatetype == 0) {
+ goto success;
+ }
+ result = dns_db_findrdataset(db, node, version, privatetype, 0, 0,
+ &rdataset, NULL);
+ if (result == ISC_R_NOTFOUND) {
+ goto success;
+ }
+ if (result != ISC_R_SUCCESS) {
+ goto failure;
+ }
+
+ /*
+ * Update each NSEC3 chain being built.
+ */
+ for (result = dns_rdataset_first(&rdataset); result == ISC_R_SUCCESS;
+ result = dns_rdataset_next(&rdataset))
+ {
+ dns_rdata_t rdata1 = DNS_RDATA_INIT;
+ dns_rdata_t rdata2 = DNS_RDATA_INIT;
+ unsigned char buf[DNS_NSEC3PARAM_BUFFERSIZE];
+
+ dns_rdataset_current(&rdataset, &rdata1);
+ if (!dns_nsec3param_fromprivate(&rdata1, &rdata2, buf,
+ sizeof(buf)))
+ {
+ continue;
+ }
+ CHECK(dns_rdata_tostruct(&rdata2, &nsec3param, NULL));
+
+ if ((nsec3param.flags & DNS_NSEC3FLAG_REMOVE) != 0) {
+ continue;
+ }
+ if (better_param(&rdataset, &rdata2)) {
+ continue;
+ }
+
+ /*
+ * We have a active chain. Update it.
+ */
+ CHECK(dns_nsec3_delnsec3(db, version, name, &nsec3param, diff));
+ }
+ if (result == ISC_R_NOMORE) {
+ success:
+ result = ISC_R_SUCCESS;
+ }
+
+failure:
+ if (dns_rdataset_isassociated(&rdataset)) {
+ dns_rdataset_disassociate(&rdataset);
+ }
+ if (node != NULL) {
+ dns_db_detachnode(db, &node);
+ }
+
+ return (result);
+}
+
+isc_result_t
+dns_nsec3_active(dns_db_t *db, dns_dbversion_t *version, bool complete,
+ bool *answer) {
+ return (dns_nsec3_activex(db, version, complete, 0, answer));
+}
+
+isc_result_t
+dns_nsec3_activex(dns_db_t *db, dns_dbversion_t *version, bool complete,
+ dns_rdatatype_t privatetype, bool *answer) {
+ dns_dbnode_t *node = NULL;
+ dns_rdataset_t rdataset;
+ dns_rdata_nsec3param_t nsec3param;
+ isc_result_t result;
+
+ REQUIRE(answer != NULL);
+
+ dns_rdataset_init(&rdataset);
+
+ result = dns_db_getoriginnode(db, &node);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+
+ result = dns_db_findrdataset(db, node, version,
+ dns_rdatatype_nsec3param, 0, 0, &rdataset,
+ NULL);
+
+ if (result == ISC_R_NOTFOUND) {
+ goto try_private;
+ }
+
+ if (result != ISC_R_SUCCESS) {
+ dns_db_detachnode(db, &node);
+ return (result);
+ }
+ for (result = dns_rdataset_first(&rdataset); result == ISC_R_SUCCESS;
+ result = dns_rdataset_next(&rdataset))
+ {
+ dns_rdata_t rdata = DNS_RDATA_INIT;
+
+ dns_rdataset_current(&rdataset, &rdata);
+ result = dns_rdata_tostruct(&rdata, &nsec3param, NULL);
+ RUNTIME_CHECK(result == ISC_R_SUCCESS);
+
+ if (nsec3param.flags == 0) {
+ break;
+ }
+ }
+ dns_rdataset_disassociate(&rdataset);
+ if (result == ISC_R_SUCCESS) {
+ dns_db_detachnode(db, &node);
+ *answer = true;
+ return (ISC_R_SUCCESS);
+ }
+ if (result == ISC_R_NOMORE) {
+ *answer = false;
+ }
+
+try_private:
+ if (privatetype == 0 || complete) {
+ dns_db_detachnode(db, &node);
+ *answer = false;
+ return (ISC_R_SUCCESS);
+ }
+ result = dns_db_findrdataset(db, node, version, privatetype, 0, 0,
+ &rdataset, NULL);
+
+ dns_db_detachnode(db, &node);
+ if (result == ISC_R_NOTFOUND) {
+ *answer = false;
+ return (ISC_R_SUCCESS);
+ }
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+
+ for (result = dns_rdataset_first(&rdataset); result == ISC_R_SUCCESS;
+ result = dns_rdataset_next(&rdataset))
+ {
+ dns_rdata_t rdata1 = DNS_RDATA_INIT;
+ dns_rdata_t rdata2 = DNS_RDATA_INIT;
+ unsigned char buf[DNS_NSEC3PARAM_BUFFERSIZE];
+
+ dns_rdataset_current(&rdataset, &rdata1);
+ if (!dns_nsec3param_fromprivate(&rdata1, &rdata2, buf,
+ sizeof(buf)))
+ {
+ continue;
+ }
+ result = dns_rdata_tostruct(&rdata2, &nsec3param, NULL);
+ RUNTIME_CHECK(result == ISC_R_SUCCESS);
+
+ if (!complete && CREATE(nsec3param.flags)) {
+ break;
+ }
+ }
+ dns_rdataset_disassociate(&rdataset);
+ if (result == ISC_R_SUCCESS) {
+ *answer = true;
+ result = ISC_R_SUCCESS;
+ }
+ if (result == ISC_R_NOMORE) {
+ *answer = false;
+ result = ISC_R_SUCCESS;
+ }
+
+ return (result);
+}
+
+unsigned int
+dns_nsec3_maxiterations(void) {
+ return (DNS_NSEC3_MAXITERATIONS);
+}
+
+isc_result_t
+dns_nsec3_noexistnodata(dns_rdatatype_t type, const dns_name_t *name,
+ const dns_name_t *nsec3name, dns_rdataset_t *nsec3set,
+ dns_name_t *zonename, bool *exists, bool *data,
+ bool *optout, bool *unknown, bool *setclosest,
+ bool *setnearest, dns_name_t *closest,
+ dns_name_t *nearest, dns_nseclog_t logit, void *arg) {
+ char namebuf[DNS_NAME_FORMATSIZE];
+ dns_fixedname_t fzone;
+ dns_fixedname_t qfixed;
+ dns_label_t hashlabel;
+ dns_name_t *qname;
+ dns_name_t *zone;
+ dns_rdata_nsec3_t nsec3;
+ dns_rdata_t rdata = DNS_RDATA_INIT;
+ int order;
+ int scope;
+ bool atparent;
+ bool first;
+ bool ns;
+ bool soa;
+ isc_buffer_t buffer;
+ isc_result_t answer = ISC_R_IGNORE;
+ isc_result_t result;
+ unsigned char hash[NSEC3_MAX_HASH_LENGTH];
+ unsigned char owner[NSEC3_MAX_HASH_LENGTH];
+ unsigned int length;
+ unsigned int qlabels;
+ unsigned int zlabels;
+
+ REQUIRE((exists == NULL && data == NULL) ||
+ (exists != NULL && data != NULL));
+ REQUIRE(nsec3set != NULL && nsec3set->type == dns_rdatatype_nsec3);
+ REQUIRE((setclosest == NULL && closest == NULL) ||
+ (setclosest != NULL && closest != NULL));
+ REQUIRE((setnearest == NULL && nearest == NULL) ||
+ (setnearest != NULL && nearest != NULL));
+
+ result = dns_rdataset_first(nsec3set);
+ if (result != ISC_R_SUCCESS) {
+ (*logit)(arg, ISC_LOG_DEBUG(3), "failure processing NSEC3 set");
+ return (result);
+ }
+
+ dns_rdataset_current(nsec3set, &rdata);
+
+ result = dns_rdata_tostruct(&rdata, &nsec3, NULL);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+
+ (*logit)(arg, ISC_LOG_DEBUG(3), "looking for relevant NSEC3");
+
+ zone = dns_fixedname_initname(&fzone);
+ zlabels = dns_name_countlabels(nsec3name);
+
+ /*
+ * NSEC3 records must have two or more labels to be valid.
+ */
+ if (zlabels < 2) {
+ return (ISC_R_IGNORE);
+ }
+
+ /*
+ * Strip off the NSEC3 hash to get the zone.
+ */
+ zlabels--;
+ dns_name_split(nsec3name, zlabels, NULL, zone);
+
+ /*
+ * If not below the zone name we can ignore this record.
+ */
+ if (!dns_name_issubdomain(name, zone)) {
+ return (ISC_R_IGNORE);
+ }
+
+ /*
+ * Is this zone the same or deeper than the current zone?
+ */
+ if (dns_name_countlabels(zonename) == 0 ||
+ dns_name_issubdomain(zone, zonename))
+ {
+ dns_name_copy(zone, zonename);
+ }
+
+ if (!dns_name_equal(zone, zonename)) {
+ return (ISC_R_IGNORE);
+ }
+
+ /*
+ * Are we only looking for the most enclosing zone?
+ */
+ if (exists == NULL || data == NULL) {
+ return (ISC_R_SUCCESS);
+ }
+
+ /*
+ * Only set unknown once we are sure that this NSEC3 is from
+ * the deepest covering zone.
+ */
+ if (!dns_nsec3_supportedhash(nsec3.hash)) {
+ if (unknown != NULL) {
+ *unknown = true;
+ }
+ return (ISC_R_IGNORE);
+ }
+
+ /*
+ * Recover the hash from the first label.
+ */
+ dns_name_getlabel(nsec3name, 0, &hashlabel);
+ isc_region_consume(&hashlabel, 1);
+ isc_buffer_init(&buffer, owner, sizeof(owner));
+ result = isc_base32hex_decoderegion(&hashlabel, &buffer);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+
+ /*
+ * The hash lengths should match. If not ignore the record.
+ */
+ if (isc_buffer_usedlength(&buffer) != nsec3.next_length) {
+ return (ISC_R_IGNORE);
+ }
+
+ /*
+ * Work out what this NSEC3 covers.
+ * Inside (<0) or outside (>=0).
+ */
+ scope = memcmp(owner, nsec3.next, nsec3.next_length);
+
+ /*
+ * Prepare to compute all the hashes.
+ */
+ qname = dns_fixedname_initname(&qfixed);
+ dns_name_downcase(name, qname, NULL);
+ qlabels = dns_name_countlabels(qname);
+ first = true;
+
+ while (qlabels >= zlabels) {
+ /*
+ * If there are too many iterations reject the NSEC3 record.
+ */
+ if (nsec3.iterations > DNS_NSEC3_MAXITERATIONS) {
+ return (DNS_R_NSEC3ITERRANGE);
+ }
+
+ length = isc_iterated_hash(hash, nsec3.hash, nsec3.iterations,
+ nsec3.salt, nsec3.salt_length,
+ qname->ndata, qname->length);
+ /*
+ * The computed hash length should match.
+ */
+ if (length != nsec3.next_length) {
+ (*logit)(arg, ISC_LOG_DEBUG(3),
+ "ignoring NSEC bad length %u vs %u", length,
+ nsec3.next_length);
+ return (ISC_R_IGNORE);
+ }
+
+ order = memcmp(hash, owner, length);
+ if (first && order == 0) {
+ /*
+ * The hashes are the same.
+ */
+ atparent = dns_rdatatype_atparent(type);
+ ns = dns_nsec3_typepresent(&rdata, dns_rdatatype_ns);
+ soa = dns_nsec3_typepresent(&rdata, dns_rdatatype_soa);
+ if (ns && !soa) {
+ if (!atparent) {
+ /*
+ * This NSEC3 record is from somewhere
+ * higher in the DNS, and at the
+ * parent of a delegation. It can not
+ * be legitimately used here.
+ */
+ (*logit)(arg, ISC_LOG_DEBUG(3),
+ "ignoring parent NSEC3");
+ return (ISC_R_IGNORE);
+ }
+ } else if (atparent && ns && soa) {
+ /*
+ * This NSEC3 record is from the child.
+ * It can not be legitimately used here.
+ */
+ (*logit)(arg, ISC_LOG_DEBUG(3),
+ "ignoring child NSEC3");
+ return (ISC_R_IGNORE);
+ }
+ if (type == dns_rdatatype_cname ||
+ type == dns_rdatatype_nxt ||
+ type == dns_rdatatype_nsec ||
+ type == dns_rdatatype_key ||
+ !dns_nsec3_typepresent(&rdata, dns_rdatatype_cname))
+ {
+ *exists = true;
+ *data = dns_nsec3_typepresent(&rdata, type);
+ (*logit)(arg, ISC_LOG_DEBUG(3),
+ "NSEC3 proves name exists (owner) "
+ "data=%d",
+ *data);
+ return (ISC_R_SUCCESS);
+ }
+ (*logit)(arg, ISC_LOG_DEBUG(3),
+ "NSEC3 proves CNAME exists");
+ return (ISC_R_IGNORE);
+ }
+
+ if (order == 0 &&
+ dns_nsec3_typepresent(&rdata, dns_rdatatype_ns) &&
+ !dns_nsec3_typepresent(&rdata, dns_rdatatype_soa))
+ {
+ /*
+ * This NSEC3 record is from somewhere higher in
+ * the DNS, and at the parent of a delegation.
+ * It can not be legitimately used here.
+ */
+ (*logit)(arg, ISC_LOG_DEBUG(3),
+ "ignoring parent NSEC3");
+ return (ISC_R_IGNORE);
+ }
+
+ /*
+ * Potential closest encloser.
+ */
+ if (order == 0) {
+ if (closest != NULL &&
+ (dns_name_countlabels(closest) == 0 ||
+ dns_name_issubdomain(qname, closest)) &&
+ !dns_nsec3_typepresent(&rdata, dns_rdatatype_ds) &&
+ !dns_nsec3_typepresent(&rdata,
+ dns_rdatatype_dname) &&
+ (dns_nsec3_typepresent(&rdata, dns_rdatatype_soa) ||
+ !dns_nsec3_typepresent(&rdata, dns_rdatatype_ns)))
+ {
+ dns_name_format(qname, namebuf,
+ sizeof(namebuf));
+ (*logit)(arg, ISC_LOG_DEBUG(3),
+ "NSEC3 indicates potential closest "
+ "encloser: '%s'",
+ namebuf);
+ dns_name_copy(qname, closest);
+ *setclosest = true;
+ }
+ dns_name_format(qname, namebuf, sizeof(namebuf));
+ (*logit)(arg, ISC_LOG_DEBUG(3),
+ "NSEC3 at super-domain %s", namebuf);
+ return (answer);
+ }
+
+ /*
+ * Find if the name does not exist.
+ *
+ * We continue as we need to find the name closest to the
+ * closest encloser that doesn't exist.
+ *
+ * We also need to continue to ensure that we are not
+ * proving the non-existence of a record in a sub-zone.
+ * If that would be the case we will return ISC_R_IGNORE
+ * above.
+ */
+ if ((scope < 0 && order > 0 &&
+ memcmp(hash, nsec3.next, length) < 0) ||
+ (scope >= 0 &&
+ (order > 0 || memcmp(hash, nsec3.next, length) < 0)))
+ {
+ dns_name_format(qname, namebuf, sizeof(namebuf));
+ (*logit)(arg, ISC_LOG_DEBUG(3),
+ "NSEC3 proves "
+ "name does not exist: '%s'",
+ namebuf);
+ if (nearest != NULL &&
+ (dns_name_countlabels(nearest) == 0 ||
+ dns_name_issubdomain(nearest, qname)))
+ {
+ dns_name_copy(qname, nearest);
+ *setnearest = true;
+ }
+
+ *exists = false;
+ *data = false;
+ if (optout != NULL) {
+ *optout = ((nsec3.flags &
+ DNS_NSEC3FLAG_OPTOUT) != 0);
+ (*logit)(arg, ISC_LOG_DEBUG(3),
+ (*optout ? "NSEC3 indicates optout"
+ : "NSEC3 indicates secure "
+ "range"));
+ }
+ answer = ISC_R_SUCCESS;
+ }
+
+ qlabels--;
+ if (qlabels > 0) {
+ dns_name_split(qname, qlabels, NULL, qname);
+ }
+ first = false;
+ }
+ return (answer);
+}
diff --git a/lib/dns/nta.c b/lib/dns/nta.c
new file mode 100644
index 0000000..5614eb8
--- /dev/null
+++ b/lib/dns/nta.c
@@ -0,0 +1,703 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/*! \file */
+
+#include <inttypes.h>
+#include <stdbool.h>
+
+#include <isc/buffer.h>
+#include <isc/log.h>
+#include <isc/mem.h>
+#include <isc/print.h>
+#include <isc/result.h>
+#include <isc/rwlock.h>
+#include <isc/string.h>
+#include <isc/task.h>
+#include <isc/time.h>
+#include <isc/timer.h>
+#include <isc/util.h>
+
+#include <dns/db.h>
+#include <dns/fixedname.h>
+#include <dns/log.h>
+#include <dns/name.h>
+#include <dns/nta.h>
+#include <dns/rbt.h>
+#include <dns/rdataset.h>
+#include <dns/resolver.h>
+#include <dns/time.h>
+
+struct dns_nta {
+ unsigned int magic;
+ isc_refcount_t refcount;
+ dns_ntatable_t *ntatable;
+ bool forced;
+ isc_timer_t *timer;
+ dns_fetch_t *fetch;
+ dns_rdataset_t rdataset;
+ dns_rdataset_t sigrdataset;
+ dns_fixedname_t fn;
+ dns_name_t *name;
+ isc_stdtime_t expiry;
+};
+
+#define NTA_MAGIC ISC_MAGIC('N', 'T', 'A', 'n')
+#define VALID_NTA(nn) ISC_MAGIC_VALID(nn, NTA_MAGIC)
+
+/*
+ * Obtain a reference to the nta object. Released by
+ * nta_detach.
+ */
+static void
+nta_ref(dns_nta_t *nta) {
+ isc_refcount_increment(&nta->refcount);
+}
+
+static void
+nta_detach(isc_mem_t *mctx, dns_nta_t **ntap) {
+ REQUIRE(ntap != NULL && VALID_NTA(*ntap));
+ dns_nta_t *nta = *ntap;
+ *ntap = NULL;
+
+ if (isc_refcount_decrement(&nta->refcount) == 1) {
+ isc_refcount_destroy(&nta->refcount);
+ nta->magic = 0;
+ if (nta->timer != NULL) {
+ (void)isc_timer_reset(nta->timer,
+ isc_timertype_inactive, NULL,
+ NULL, true);
+ isc_timer_destroy(&nta->timer);
+ }
+ if (dns_rdataset_isassociated(&nta->rdataset)) {
+ dns_rdataset_disassociate(&nta->rdataset);
+ }
+ if (dns_rdataset_isassociated(&nta->sigrdataset)) {
+ dns_rdataset_disassociate(&nta->sigrdataset);
+ }
+ if (nta->fetch != NULL) {
+ dns_resolver_cancelfetch(nta->fetch);
+ dns_resolver_destroyfetch(&nta->fetch);
+ }
+ isc_mem_put(mctx, nta, sizeof(dns_nta_t));
+ }
+}
+
+static void
+free_nta(void *data, void *arg) {
+ dns_nta_t *nta = (dns_nta_t *)data;
+ isc_mem_t *mctx = (isc_mem_t *)arg;
+
+ nta_detach(mctx, &nta);
+}
+
+isc_result_t
+dns_ntatable_create(dns_view_t *view, isc_taskmgr_t *taskmgr,
+ isc_timermgr_t *timermgr, dns_ntatable_t **ntatablep) {
+ dns_ntatable_t *ntatable;
+ isc_result_t result;
+
+ REQUIRE(ntatablep != NULL && *ntatablep == NULL);
+
+ ntatable = isc_mem_get(view->mctx, sizeof(*ntatable));
+
+ ntatable->task = NULL;
+ result = isc_task_create(taskmgr, 0, &ntatable->task);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup_ntatable;
+ }
+ isc_task_setname(ntatable->task, "ntatable", ntatable);
+
+ ntatable->table = NULL;
+ result = dns_rbt_create(view->mctx, free_nta, view->mctx,
+ &ntatable->table);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup_task;
+ }
+
+ isc_rwlock_init(&ntatable->rwlock, 0, 0);
+
+ ntatable->shuttingdown = false;
+ ntatable->timermgr = timermgr;
+ ntatable->taskmgr = taskmgr;
+
+ ntatable->view = view;
+ isc_refcount_init(&ntatable->references, 1);
+
+ ntatable->magic = NTATABLE_MAGIC;
+ *ntatablep = ntatable;
+
+ return (ISC_R_SUCCESS);
+
+cleanup_task:
+ isc_task_detach(&ntatable->task);
+
+cleanup_ntatable:
+ isc_mem_put(view->mctx, ntatable, sizeof(*ntatable));
+
+ return (result);
+}
+
+void
+dns_ntatable_attach(dns_ntatable_t *source, dns_ntatable_t **targetp) {
+ REQUIRE(VALID_NTATABLE(source));
+ REQUIRE(targetp != NULL && *targetp == NULL);
+
+ isc_refcount_increment(&source->references);
+
+ *targetp = source;
+}
+
+void
+dns_ntatable_detach(dns_ntatable_t **ntatablep) {
+ dns_ntatable_t *ntatable;
+
+ REQUIRE(ntatablep != NULL && VALID_NTATABLE(*ntatablep));
+
+ ntatable = *ntatablep;
+ *ntatablep = NULL;
+
+ if (isc_refcount_decrement(&ntatable->references) == 1) {
+ dns_rbt_destroy(&ntatable->table);
+ isc_rwlock_destroy(&ntatable->rwlock);
+ isc_refcount_destroy(&ntatable->references);
+ if (ntatable->task != NULL) {
+ isc_task_detach(&ntatable->task);
+ }
+ ntatable->timermgr = NULL;
+ ntatable->taskmgr = NULL;
+ ntatable->magic = 0;
+ isc_mem_put(ntatable->view->mctx, ntatable, sizeof(*ntatable));
+ }
+}
+
+static void
+fetch_done(isc_task_t *task, isc_event_t *event) {
+ dns_fetchevent_t *devent = (dns_fetchevent_t *)event;
+ dns_nta_t *nta = devent->ev_arg;
+ isc_result_t eresult = devent->result;
+ dns_ntatable_t *ntatable = nta->ntatable;
+ dns_view_t *view = ntatable->view;
+ isc_stdtime_t now;
+
+ UNUSED(task);
+
+ if (dns_rdataset_isassociated(&nta->rdataset)) {
+ dns_rdataset_disassociate(&nta->rdataset);
+ }
+ if (dns_rdataset_isassociated(&nta->sigrdataset)) {
+ dns_rdataset_disassociate(&nta->sigrdataset);
+ }
+ if (nta->fetch == devent->fetch) {
+ nta->fetch = NULL;
+ }
+ dns_resolver_destroyfetch(&devent->fetch);
+
+ if (devent->node != NULL) {
+ dns_db_detachnode(devent->db, &devent->node);
+ }
+ if (devent->db != NULL) {
+ dns_db_detach(&devent->db);
+ }
+
+ isc_event_free(&event);
+ isc_stdtime_get(&now);
+
+ switch (eresult) {
+ case ISC_R_SUCCESS:
+ case DNS_R_NCACHENXDOMAIN:
+ case DNS_R_NXDOMAIN:
+ case DNS_R_NCACHENXRRSET:
+ case DNS_R_NXRRSET:
+ if (nta->expiry > now) {
+ nta->expiry = now;
+ }
+ break;
+ default:
+ break;
+ }
+
+ /*
+ * If we're expiring before the next recheck, we might
+ * as well stop the timer now.
+ */
+ if (nta->timer != NULL && nta->expiry - now < view->nta_recheck) {
+ (void)isc_timer_reset(nta->timer, isc_timertype_inactive, NULL,
+ NULL, true);
+ }
+ nta_detach(view->mctx, &nta);
+ dns_view_weakdetach(&view);
+}
+
+static void
+checkbogus(isc_task_t *task, isc_event_t *event) {
+ dns_nta_t *nta = event->ev_arg;
+ dns_ntatable_t *ntatable = nta->ntatable;
+ dns_view_t *view = NULL;
+ isc_result_t result;
+
+ if (nta->fetch != NULL) {
+ dns_resolver_cancelfetch(nta->fetch);
+ nta->fetch = NULL;
+ }
+ if (dns_rdataset_isassociated(&nta->rdataset)) {
+ dns_rdataset_disassociate(&nta->rdataset);
+ }
+ if (dns_rdataset_isassociated(&nta->sigrdataset)) {
+ dns_rdataset_disassociate(&nta->sigrdataset);
+ }
+
+ isc_event_free(&event);
+
+ nta_ref(nta);
+ dns_view_weakattach(ntatable->view, &view);
+ result = dns_resolver_createfetch(
+ view->resolver, nta->name, dns_rdatatype_nsec, NULL, NULL, NULL,
+ NULL, 0, DNS_FETCHOPT_NONTA, 0, NULL, task, fetch_done, nta,
+ &nta->rdataset, &nta->sigrdataset, &nta->fetch);
+ if (result != ISC_R_SUCCESS) {
+ nta_detach(view->mctx, &nta);
+ dns_view_weakdetach(&view);
+ }
+}
+
+static isc_result_t
+settimer(dns_ntatable_t *ntatable, dns_nta_t *nta, uint32_t lifetime) {
+ isc_result_t result;
+ isc_interval_t interval;
+ dns_view_t *view;
+
+ REQUIRE(VALID_NTATABLE(ntatable));
+ REQUIRE(VALID_NTA(nta));
+
+ if (ntatable->timermgr == NULL) {
+ return (ISC_R_SUCCESS);
+ }
+
+ view = ntatable->view;
+ if (view->nta_recheck == 0 || lifetime <= view->nta_recheck) {
+ return (ISC_R_SUCCESS);
+ }
+
+ isc_interval_set(&interval, view->nta_recheck, 0);
+ result = isc_timer_create(ntatable->timermgr, isc_timertype_ticker,
+ NULL, &interval, ntatable->task, checkbogus,
+ nta, &nta->timer);
+ if (result != ISC_R_SUCCESS) {
+ isc_timer_destroy(&nta->timer);
+ }
+ return (result);
+}
+
+static isc_result_t
+nta_create(dns_ntatable_t *ntatable, const dns_name_t *name,
+ dns_nta_t **target) {
+ dns_nta_t *nta = NULL;
+ dns_view_t *view;
+
+ REQUIRE(VALID_NTATABLE(ntatable));
+ REQUIRE(target != NULL && *target == NULL);
+
+ view = ntatable->view;
+
+ nta = isc_mem_get(view->mctx, sizeof(dns_nta_t));
+
+ nta->ntatable = ntatable;
+ nta->expiry = 0;
+ nta->timer = NULL;
+ nta->fetch = NULL;
+ dns_rdataset_init(&nta->rdataset);
+ dns_rdataset_init(&nta->sigrdataset);
+
+ isc_refcount_init(&nta->refcount, 1);
+
+ nta->name = dns_fixedname_initname(&nta->fn);
+ dns_name_copy(name, nta->name);
+
+ nta->magic = NTA_MAGIC;
+
+ *target = nta;
+ return (ISC_R_SUCCESS);
+}
+
+isc_result_t
+dns_ntatable_add(dns_ntatable_t *ntatable, const dns_name_t *name, bool force,
+ isc_stdtime_t now, uint32_t lifetime) {
+ isc_result_t result = ISC_R_SUCCESS;
+ dns_nta_t *nta = NULL;
+ dns_rbtnode_t *node;
+ dns_view_t *view;
+
+ REQUIRE(VALID_NTATABLE(ntatable));
+
+ view = ntatable->view;
+
+ RWLOCK(&ntatable->rwlock, isc_rwlocktype_write);
+
+ if (ntatable->shuttingdown) {
+ goto unlock;
+ }
+
+ result = nta_create(ntatable, name, &nta);
+ if (result != ISC_R_SUCCESS) {
+ goto unlock;
+ }
+
+ nta->expiry = now + lifetime;
+ nta->forced = force;
+
+ node = NULL;
+ result = dns_rbt_addnode(ntatable->table, name, &node);
+ if (result == ISC_R_SUCCESS) {
+ if (!force) {
+ (void)settimer(ntatable, nta, lifetime);
+ }
+ node->data = nta;
+ nta = NULL;
+ } else if (result == ISC_R_EXISTS) {
+ dns_nta_t *n = node->data;
+ if (n == NULL) {
+ if (!force) {
+ (void)settimer(ntatable, nta, lifetime);
+ }
+ node->data = nta;
+ nta = NULL;
+ } else {
+ n->expiry = nta->expiry;
+ nta_detach(view->mctx, &nta);
+ }
+ result = ISC_R_SUCCESS;
+ }
+
+unlock:
+ RWUNLOCK(&ntatable->rwlock, isc_rwlocktype_write);
+
+ if (nta != NULL) {
+ nta_detach(view->mctx, &nta);
+ }
+
+ return (result);
+}
+
+/*
+ * Caller must hold a write lock on rwlock.
+ */
+static isc_result_t
+deletenode(dns_ntatable_t *ntatable, const dns_name_t *name) {
+ isc_result_t result;
+ dns_rbtnode_t *node = NULL;
+
+ REQUIRE(VALID_NTATABLE(ntatable));
+ REQUIRE(name != NULL);
+
+ result = dns_rbt_findnode(ntatable->table, name, NULL, &node, NULL,
+ DNS_RBTFIND_NOOPTIONS, NULL, NULL);
+ if (result == ISC_R_SUCCESS) {
+ if (node->data != NULL) {
+ result = dns_rbt_deletenode(ntatable->table, node,
+ false);
+ } else {
+ result = ISC_R_NOTFOUND;
+ }
+ } else if (result == DNS_R_PARTIALMATCH) {
+ result = ISC_R_NOTFOUND;
+ }
+
+ return (result);
+}
+
+isc_result_t
+dns_ntatable_delete(dns_ntatable_t *ntatable, const dns_name_t *name) {
+ isc_result_t result;
+
+ RWLOCK(&ntatable->rwlock, isc_rwlocktype_write);
+ result = deletenode(ntatable, name);
+ RWUNLOCK(&ntatable->rwlock, isc_rwlocktype_write);
+
+ return (result);
+}
+
+bool
+dns_ntatable_covered(dns_ntatable_t *ntatable, isc_stdtime_t now,
+ const dns_name_t *name, const dns_name_t *anchor) {
+ isc_result_t result;
+ dns_fixedname_t fn;
+ dns_rbtnode_t *node;
+ dns_name_t *foundname;
+ dns_nta_t *nta = NULL;
+ bool answer = false;
+ isc_rwlocktype_t locktype = isc_rwlocktype_read;
+
+ REQUIRE(ntatable == NULL || VALID_NTATABLE(ntatable));
+ REQUIRE(dns_name_isabsolute(name));
+
+ if (ntatable == NULL) {
+ return (false);
+ }
+
+ foundname = dns_fixedname_initname(&fn);
+
+relock:
+ RWLOCK(&ntatable->rwlock, locktype);
+again:
+ node = NULL;
+ result = dns_rbt_findnode(ntatable->table, name, foundname, &node, NULL,
+ DNS_RBTFIND_NOOPTIONS, NULL, NULL);
+ if (result == DNS_R_PARTIALMATCH) {
+ if (dns_name_issubdomain(foundname, anchor)) {
+ result = ISC_R_SUCCESS;
+ }
+ }
+ if (result == ISC_R_SUCCESS) {
+ nta = (dns_nta_t *)node->data;
+ answer = (nta->expiry > now);
+ }
+
+ /* Deal with expired NTA */
+ if (result == ISC_R_SUCCESS && !answer) {
+ char nb[DNS_NAME_FORMATSIZE];
+
+ if (locktype == isc_rwlocktype_read) {
+ RWUNLOCK(&ntatable->rwlock, locktype);
+ locktype = isc_rwlocktype_write;
+ goto relock;
+ }
+
+ dns_name_format(foundname, nb, sizeof(nb));
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_DNSSEC,
+ DNS_LOGMODULE_NTA, ISC_LOG_INFO,
+ "deleting expired NTA at %s", nb);
+
+ if (nta->timer != NULL) {
+ (void)isc_timer_reset(nta->timer,
+ isc_timertype_inactive, NULL,
+ NULL, true);
+ isc_timer_destroy(&nta->timer);
+ }
+
+ result = deletenode(ntatable, foundname);
+ if (result != ISC_R_SUCCESS) {
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_DNSSEC,
+ DNS_LOGMODULE_NTA, ISC_LOG_INFO,
+ "deleting NTA failed: %s",
+ isc_result_totext(result));
+ }
+ goto again;
+ }
+ RWUNLOCK(&ntatable->rwlock, locktype);
+
+ return (answer);
+}
+
+static isc_result_t
+putstr(isc_buffer_t **b, const char *str) {
+ isc_result_t result;
+
+ result = isc_buffer_reserve(b, strlen(str));
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+
+ isc_buffer_putstr(*b, str);
+ return (ISC_R_SUCCESS);
+}
+
+isc_result_t
+dns_ntatable_totext(dns_ntatable_t *ntatable, const char *view,
+ isc_buffer_t **buf) {
+ isc_result_t result;
+ dns_rbtnode_t *node;
+ dns_rbtnodechain_t chain;
+ bool first = true;
+ isc_stdtime_t now;
+
+ REQUIRE(VALID_NTATABLE(ntatable));
+
+ isc_stdtime_get(&now);
+
+ RWLOCK(&ntatable->rwlock, isc_rwlocktype_read);
+ dns_rbtnodechain_init(&chain);
+ result = dns_rbtnodechain_first(&chain, ntatable->table, NULL, NULL);
+ if (result != ISC_R_SUCCESS && result != DNS_R_NEWORIGIN) {
+ if (result == ISC_R_NOTFOUND) {
+ result = ISC_R_SUCCESS;
+ }
+ goto cleanup;
+ }
+ for (;;) {
+ dns_rbtnodechain_current(&chain, NULL, NULL, &node);
+ if (node->data != NULL) {
+ dns_nta_t *n = (dns_nta_t *)node->data;
+ char nbuf[DNS_NAME_FORMATSIZE];
+ char tbuf[ISC_FORMATHTTPTIMESTAMP_SIZE];
+ char obuf[DNS_NAME_FORMATSIZE +
+ ISC_FORMATHTTPTIMESTAMP_SIZE +
+ sizeof("expired: \n")];
+ dns_fixedname_t fn;
+ dns_name_t *name;
+ isc_time_t t;
+
+ name = dns_fixedname_initname(&fn);
+ dns_rbt_fullnamefromnode(node, name);
+ dns_name_format(name, nbuf, sizeof(nbuf));
+
+ if (n->expiry != 0xffffffffU) {
+ /* Normal NTA entries */
+ isc_time_set(&t, n->expiry, 0);
+ isc_time_formattimestamp(&t, tbuf,
+ sizeof(tbuf));
+
+ snprintf(obuf, sizeof(obuf), "%s%s%s%s: %s %s",
+ first ? "" : "\n", nbuf,
+ view != NULL ? "/" : "",
+ view != NULL ? view : "",
+ n->expiry <= now ? "expired"
+ : "expiry",
+ tbuf);
+ } else {
+ /* "validate-except" entries */
+ snprintf(obuf, sizeof(obuf), "%s%s%s%s: %s",
+ first ? "" : "\n", nbuf,
+ view != NULL ? "/" : "",
+ view != NULL ? view : "", "permanent");
+ }
+
+ first = false;
+ result = putstr(buf, obuf);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+ }
+ result = dns_rbtnodechain_next(&chain, NULL, NULL);
+ if (result != ISC_R_SUCCESS && result != DNS_R_NEWORIGIN) {
+ if (result == ISC_R_NOMORE) {
+ result = ISC_R_SUCCESS;
+ }
+ break;
+ }
+ }
+
+cleanup:
+ dns_rbtnodechain_invalidate(&chain);
+ RWUNLOCK(&ntatable->rwlock, isc_rwlocktype_read);
+ return (result);
+}
+
+isc_result_t
+dns_ntatable_save(dns_ntatable_t *ntatable, FILE *fp) {
+ isc_result_t result;
+ dns_rbtnode_t *node;
+ dns_rbtnodechain_t chain;
+ isc_stdtime_t now;
+ bool written = false;
+
+ REQUIRE(VALID_NTATABLE(ntatable));
+
+ isc_stdtime_get(&now);
+
+ RWLOCK(&ntatable->rwlock, isc_rwlocktype_read);
+ dns_rbtnodechain_init(&chain);
+ result = dns_rbtnodechain_first(&chain, ntatable->table, NULL, NULL);
+ if (result != ISC_R_SUCCESS && result != DNS_R_NEWORIGIN) {
+ goto cleanup;
+ }
+
+ for (;;) {
+ dns_rbtnodechain_current(&chain, NULL, NULL, &node);
+ if (node->data != NULL) {
+ isc_buffer_t b;
+ char nbuf[DNS_NAME_FORMATSIZE + 1], tbuf[80];
+ dns_fixedname_t fn;
+ dns_name_t *name;
+ dns_nta_t *n = (dns_nta_t *)node->data;
+
+ /*
+ * Skip this node if the expiry is already in the
+ * past, or if this is a "validate-except" entry.
+ */
+ if (n->expiry <= now || n->expiry == 0xffffffffU) {
+ goto skip;
+ }
+
+ name = dns_fixedname_initname(&fn);
+ dns_rbt_fullnamefromnode(node, name);
+
+ isc_buffer_init(&b, nbuf, sizeof(nbuf));
+ result = dns_name_totext(name, false, &b);
+ if (result != ISC_R_SUCCESS) {
+ goto skip;
+ }
+
+ /* Zero terminate. */
+ isc_buffer_putuint8(&b, 0);
+
+ isc_buffer_init(&b, tbuf, sizeof(tbuf));
+ dns_time32_totext(n->expiry, &b);
+
+ /* Zero terminate. */
+ isc_buffer_putuint8(&b, 0);
+
+ fprintf(fp, "%s %s %s\n", nbuf,
+ n->forced ? "forced" : "regular", tbuf);
+ written = true;
+ }
+ skip:
+ result = dns_rbtnodechain_next(&chain, NULL, NULL);
+ if (result != ISC_R_SUCCESS && result != DNS_R_NEWORIGIN) {
+ if (result == ISC_R_NOMORE) {
+ result = ISC_R_SUCCESS;
+ }
+ break;
+ }
+ }
+
+cleanup:
+ dns_rbtnodechain_invalidate(&chain);
+ RWUNLOCK(&ntatable->rwlock, isc_rwlocktype_read);
+
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ } else {
+ return (written ? ISC_R_SUCCESS : ISC_R_NOTFOUND);
+ }
+}
+
+void
+dns_ntatable_shutdown(dns_ntatable_t *ntatable) {
+ isc_result_t result;
+ dns_rbtnode_t *node;
+ dns_rbtnodechain_t chain;
+
+ REQUIRE(VALID_NTATABLE(ntatable));
+
+ RWLOCK(&ntatable->rwlock, isc_rwlocktype_write);
+ ntatable->shuttingdown = true;
+
+ dns_rbtnodechain_init(&chain);
+ result = dns_rbtnodechain_first(&chain, ntatable->table, NULL, NULL);
+ while (result == ISC_R_SUCCESS || result == DNS_R_NEWORIGIN) {
+ dns_rbtnodechain_current(&chain, NULL, NULL, &node);
+ if (node->data != NULL) {
+ dns_nta_t *nta = (dns_nta_t *)node->data;
+ if (nta->timer != NULL) {
+ (void)isc_timer_reset(nta->timer,
+ isc_timertype_inactive,
+ NULL, NULL, true);
+ }
+ }
+ result = dns_rbtnodechain_next(&chain, NULL, NULL);
+ }
+
+ dns_rbtnodechain_invalidate(&chain);
+ RWUNLOCK(&ntatable->rwlock, isc_rwlocktype_write);
+}
diff --git a/lib/dns/openssl_link.c b/lib/dns/openssl_link.c
new file mode 100644
index 0000000..addf16a
--- /dev/null
+++ b/lib/dns/openssl_link.c
@@ -0,0 +1,223 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0 AND ISC
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/*
+ * Copyright (C) Network Associates, Inc.
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND ISC AND NETWORK ASSOCIATES DISCLAIMS
+ * ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE
+ * FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR
+ * IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <isc/mem.h>
+#include <isc/mutex.h>
+#include <isc/mutexblock.h>
+#include <isc/result.h>
+#include <isc/string.h>
+#include <isc/thread.h>
+#include <isc/tls.h>
+#include <isc/util.h>
+
+#include <dns/log.h>
+
+#include "dst_internal.h"
+#include "dst_openssl.h"
+
+#if !defined(OPENSSL_NO_ENGINE) && OPENSSL_API_LEVEL < 30000
+#include <openssl/engine.h>
+#endif /* if !defined(OPENSSL_NO_ENGINE) && OPENSSL_API_LEVEL < 30000 */
+
+#include "openssl_shim.h"
+
+#if !defined(OPENSSL_NO_ENGINE) && OPENSSL_API_LEVEL < 30000
+static ENGINE *e = NULL;
+#endif /* if !defined(OPENSSL_NO_ENGINE) && OPENSSL_API_LEVEL < 30000 */
+
+static void
+enable_fips_mode(void) {
+#ifdef HAVE_FIPS_MODE
+ if (FIPS_mode() != 0) {
+ /*
+ * FIPS mode is already enabled.
+ */
+ return;
+ }
+
+ if (FIPS_mode_set(1) == 0) {
+ dst__openssl_toresult2("FIPS_mode_set", DST_R_OPENSSLFAILURE);
+ exit(1);
+ }
+#endif /* HAVE_FIPS_MODE */
+}
+
+isc_result_t
+dst__openssl_init(const char *engine) {
+ isc_result_t result = ISC_R_SUCCESS;
+
+ enable_fips_mode();
+
+#if !defined(OPENSSL_NO_ENGINE) && OPENSSL_API_LEVEL < 30000
+ if (engine != NULL && *engine == '\0') {
+ engine = NULL;
+ }
+
+ if (engine != NULL) {
+ e = ENGINE_by_id(engine);
+ if (e == NULL) {
+ result = DST_R_NOENGINE;
+ goto cleanup_rm;
+ }
+ if (!ENGINE_init(e)) {
+ result = DST_R_NOENGINE;
+ goto cleanup_rm;
+ }
+ /* This will init the engine. */
+ if (!ENGINE_set_default(e, ENGINE_METHOD_ALL)) {
+ result = DST_R_NOENGINE;
+ goto cleanup_init;
+ }
+ }
+
+ return (ISC_R_SUCCESS);
+cleanup_init:
+ ENGINE_finish(e);
+cleanup_rm:
+ if (e != NULL) {
+ ENGINE_free(e);
+ }
+ e = NULL;
+ ERR_clear_error();
+#else
+ UNUSED(engine);
+#endif /* if !defined(OPENSSL_NO_ENGINE) && OPENSSL_API_LEVEL < 30000 */
+ return (result);
+}
+
+void
+dst__openssl_destroy(void) {
+#if !defined(OPENSSL_NO_ENGINE) && OPENSSL_API_LEVEL < 30000
+ if (e != NULL) {
+ ENGINE_finish(e);
+ ENGINE_free(e);
+ }
+ e = NULL;
+#endif /* if !defined(OPENSSL_NO_ENGINE) && OPENSSL_API_LEVEL < 30000 */
+}
+
+static isc_result_t
+toresult(isc_result_t fallback) {
+ isc_result_t result = fallback;
+ unsigned long err = ERR_peek_error();
+#if defined(ECDSA_R_RANDOM_NUMBER_GENERATION_FAILED)
+ int lib = ERR_GET_LIB(err);
+#endif /* if defined(ECDSA_R_RANDOM_NUMBER_GENERATION_FAILED) */
+ int reason = ERR_GET_REASON(err);
+
+ switch (reason) {
+ /*
+ * ERR_* errors are globally unique; others
+ * are unique per sublibrary
+ */
+ case ERR_R_MALLOC_FAILURE:
+ result = ISC_R_NOMEMORY;
+ break;
+ default:
+#if defined(ECDSA_R_RANDOM_NUMBER_GENERATION_FAILED)
+ if (lib == ERR_R_ECDSA_LIB &&
+ reason == ECDSA_R_RANDOM_NUMBER_GENERATION_FAILED)
+ {
+ result = ISC_R_NOENTROPY;
+ break;
+ }
+#endif /* if defined(ECDSA_R_RANDOM_NUMBER_GENERATION_FAILED) */
+ break;
+ }
+
+ return (result);
+}
+
+isc_result_t
+dst__openssl_toresult(isc_result_t fallback) {
+ isc_result_t result;
+
+ result = toresult(fallback);
+
+ ERR_clear_error();
+ return (result);
+}
+
+isc_result_t
+dst__openssl_toresult2(const char *funcname, isc_result_t fallback) {
+ return (dst__openssl_toresult3(DNS_LOGCATEGORY_GENERAL, funcname,
+ fallback));
+}
+
+isc_result_t
+dst__openssl_toresult3(isc_logcategory_t *category, const char *funcname,
+ isc_result_t fallback) {
+ isc_result_t result;
+ unsigned long err;
+ const char *file, *func, *data;
+ int line, flags;
+ char buf[256];
+
+ result = toresult(fallback);
+
+ isc_log_write(dns_lctx, category, DNS_LOGMODULE_CRYPTO, ISC_LOG_WARNING,
+ "%s failed (%s)", funcname, isc_result_totext(result));
+
+ if (result == ISC_R_NOMEMORY) {
+ goto done;
+ }
+
+ for (;;) {
+ err = ERR_get_error_all(&file, &line, &func, &data, &flags);
+ if (err == 0U) {
+ goto done;
+ }
+ ERR_error_string_n(err, buf, sizeof(buf));
+ isc_log_write(dns_lctx, category, DNS_LOGMODULE_CRYPTO,
+ ISC_LOG_INFO, "%s:%s:%d:%s", buf, file, line,
+ ((flags & ERR_TXT_STRING) != 0) ? data : "");
+ }
+
+done:
+ ERR_clear_error();
+ return (result);
+}
+
+#if !defined(OPENSSL_NO_ENGINE) && OPENSSL_API_LEVEL < 30000
+ENGINE *
+dst__openssl_getengine(const char *engine) {
+ if (engine == NULL) {
+ return (NULL);
+ }
+ if (e == NULL) {
+ return (NULL);
+ }
+ if (strcmp(engine, ENGINE_get_id(e)) == 0) {
+ return (e);
+ }
+ return (NULL);
+}
+#endif /* if !defined(OPENSSL_NO_ENGINE) && OPENSSL_API_LEVEL < 30000 */
+
+/*! \file */
diff --git a/lib/dns/openssl_shim.c b/lib/dns/openssl_shim.c
new file mode 100644
index 0000000..816813a
--- /dev/null
+++ b/lib/dns/openssl_shim.c
@@ -0,0 +1,252 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#include "openssl_shim.h"
+
+#if !HAVE_RSA_SET0_KEY && OPENSSL_VERSION_NUMBER < 0x30000000L
+/* From OpenSSL 1.1.0 */
+int
+RSA_set0_key(RSA *r, BIGNUM *n, BIGNUM *e, BIGNUM *d) {
+ /*
+ * If the fields n and e in r are NULL, the corresponding input
+ * parameters MUST be non-NULL for n and e. d may be
+ * left NULL (in case only the public key is used).
+ */
+ if ((r->n == NULL && n == NULL) || (r->e == NULL && e == NULL)) {
+ return (0);
+ }
+
+ if (n != NULL) {
+ BN_free(r->n);
+ r->n = n;
+ }
+ if (e != NULL) {
+ BN_free(r->e);
+ r->e = e;
+ }
+ if (d != NULL) {
+ BN_clear_free(r->d);
+ r->d = d;
+ }
+
+ return (1);
+}
+
+int
+RSA_set0_factors(RSA *r, BIGNUM *p, BIGNUM *q) {
+ /*
+ * If the fields p and q in r are NULL, the corresponding input
+ * parameters MUST be non-NULL.
+ */
+ if ((r->p == NULL && p == NULL) || (r->q == NULL && q == NULL)) {
+ return (0);
+ }
+
+ if (p != NULL) {
+ BN_clear_free(r->p);
+ r->p = p;
+ }
+ if (q != NULL) {
+ BN_clear_free(r->q);
+ r->q = q;
+ }
+
+ return (1);
+}
+
+int
+RSA_set0_crt_params(RSA *r, BIGNUM *dmp1, BIGNUM *dmq1, BIGNUM *iqmp) {
+ /*
+ * If the fields dmp1, dmq1 and iqmp in r are NULL, the
+ * corresponding input parameters MUST be non-NULL.
+ */
+ if ((r->dmp1 == NULL && dmp1 == NULL) ||
+ (r->dmq1 == NULL && dmq1 == NULL) ||
+ (r->iqmp == NULL && iqmp == NULL))
+ {
+ return (0);
+ }
+
+ if (dmp1 != NULL) {
+ BN_clear_free(r->dmp1);
+ r->dmp1 = dmp1;
+ }
+ if (dmq1 != NULL) {
+ BN_clear_free(r->dmq1);
+ r->dmq1 = dmq1;
+ }
+ if (iqmp != NULL) {
+ BN_clear_free(r->iqmp);
+ r->iqmp = iqmp;
+ }
+
+ return (1);
+}
+
+void
+RSA_get0_key(const RSA *r, const BIGNUM **n, const BIGNUM **e,
+ const BIGNUM **d) {
+ if (n != NULL) {
+ *n = r->n;
+ }
+ if (e != NULL) {
+ *e = r->e;
+ }
+ if (d != NULL) {
+ *d = r->d;
+ }
+}
+
+void
+RSA_get0_factors(const RSA *r, const BIGNUM **p, const BIGNUM **q) {
+ if (p != NULL) {
+ *p = r->p;
+ }
+ if (q != NULL) {
+ *q = r->q;
+ }
+}
+
+void
+RSA_get0_crt_params(const RSA *r, const BIGNUM **dmp1, const BIGNUM **dmq1,
+ const BIGNUM **iqmp) {
+ if (dmp1 != NULL) {
+ *dmp1 = r->dmp1;
+ }
+ if (dmq1 != NULL) {
+ *dmq1 = r->dmq1;
+ }
+ if (iqmp != NULL) {
+ *iqmp = r->iqmp;
+ }
+}
+
+int
+RSA_test_flags(const RSA *r, int flags) {
+ return (r->flags & flags);
+}
+#endif /* !HAVE_RSA_SET0_KEY && OPENSSL_VERSION_NUMBER < 0x30000000L */
+
+#if !HAVE_ECDSA_SIG_GET0
+/* From OpenSSL 1.1 */
+void
+ECDSA_SIG_get0(const ECDSA_SIG *sig, const BIGNUM **pr, const BIGNUM **ps) {
+ if (pr != NULL) {
+ *pr = sig->r;
+ }
+ if (ps != NULL) {
+ *ps = sig->s;
+ }
+}
+
+int
+ECDSA_SIG_set0(ECDSA_SIG *sig, BIGNUM *r, BIGNUM *s) {
+ if (r == NULL || s == NULL) {
+ return (0);
+ }
+
+ BN_clear_free(sig->r);
+ BN_clear_free(sig->s);
+ sig->r = r;
+ sig->s = s;
+
+ return (1);
+}
+#endif /* !HAVE_ECDSA_SIG_GET0 */
+
+#if !HAVE_DH_GET0_KEY && OPENSSL_VERSION_NUMBER < 0x30000000L
+/*
+ * DH_get0_key, DH_set0_key, DH_get0_pqg and DH_set0_pqg
+ * are from OpenSSL 1.1.0.
+ */
+void
+DH_get0_key(const DH *dh, const BIGNUM **pub_key, const BIGNUM **priv_key) {
+ if (pub_key != NULL) {
+ *pub_key = dh->pub_key;
+ }
+ if (priv_key != NULL) {
+ *priv_key = dh->priv_key;
+ }
+}
+
+int
+DH_set0_key(DH *dh, BIGNUM *pub_key, BIGNUM *priv_key) {
+ if (pub_key != NULL) {
+ BN_free(dh->pub_key);
+ dh->pub_key = pub_key;
+ }
+
+ if (priv_key != NULL) {
+ BN_free(dh->priv_key);
+ dh->priv_key = priv_key;
+ }
+
+ return (1);
+}
+
+void
+DH_get0_pqg(const DH *dh, const BIGNUM **p, const BIGNUM **q,
+ const BIGNUM **g) {
+ if (p != NULL) {
+ *p = dh->p;
+ }
+ if (q != NULL) {
+ *q = dh->q;
+ }
+ if (g != NULL) {
+ *g = dh->g;
+ }
+}
+
+int
+DH_set0_pqg(DH *dh, BIGNUM *p, BIGNUM *q, BIGNUM *g) {
+ /* If the fields p and g in d are NULL, the corresponding input
+ * parameters MUST be non-NULL. q may remain NULL.
+ */
+ if ((dh->p == NULL && p == NULL) || (dh->g == NULL && g == NULL)) {
+ return (0);
+ }
+
+ if (p != NULL) {
+ BN_free(dh->p);
+ dh->p = p;
+ }
+ if (q != NULL) {
+ BN_free(dh->q);
+ dh->q = q;
+ }
+ if (g != NULL) {
+ BN_free(dh->g);
+ dh->g = g;
+ }
+
+ if (q != NULL) {
+ dh->length = BN_num_bits(q);
+ }
+
+ return (1);
+}
+#endif /* !HAVE_DH_GET0_KEY && OPENSSL_VERSION_NUMBER < 0x30000000L */
+
+#if !HAVE_ERR_GET_ERROR_ALL
+static const char err_empty_string = '\0';
+
+unsigned long
+ERR_get_error_all(const char **file, int *line, const char **func,
+ const char **data, int *flags) {
+ if (func != NULL) {
+ *func = &err_empty_string;
+ }
+ return (ERR_get_error_line_data(file, line, data, flags));
+}
+#endif /* if !HAVE_ERR_GET_ERROR_ALL */
diff --git a/lib/dns/openssl_shim.h b/lib/dns/openssl_shim.h
new file mode 100644
index 0000000..439d9f9
--- /dev/null
+++ b/lib/dns/openssl_shim.h
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#pragma once
+
+#include <openssl/bn.h>
+#include <openssl/dh.h>
+#include <openssl/ecdsa.h>
+#include <openssl/err.h>
+#include <openssl/opensslv.h>
+#include <openssl/rsa.h>
+
+/*
+ * Limit the size of public exponents.
+ */
+#ifndef RSA_MAX_PUBEXP_BITS
+#define RSA_MAX_PUBEXP_BITS 35
+#endif /* ifndef RSA_MAX_PUBEXP_BITS */
+
+#if !HAVE_RSA_SET0_KEY && OPENSSL_VERSION_NUMBER < 0x30000000L
+int
+RSA_set0_key(RSA *r, BIGNUM *n, BIGNUM *e, BIGNUM *d);
+
+int
+RSA_set0_factors(RSA *r, BIGNUM *p, BIGNUM *q);
+
+int
+RSA_set0_crt_params(RSA *r, BIGNUM *dmp1, BIGNUM *dmq1, BIGNUM *iqmp);
+
+void
+RSA_get0_key(const RSA *r, const BIGNUM **n, const BIGNUM **e,
+ const BIGNUM **d);
+
+void
+RSA_get0_factors(const RSA *r, const BIGNUM **p, const BIGNUM **q);
+
+void
+RSA_get0_crt_params(const RSA *r, const BIGNUM **dmp1, const BIGNUM **dmq1,
+ const BIGNUM **iqmp);
+
+int
+RSA_test_flags(const RSA *r, int flags);
+#endif /* !HAVE_RSA_SET0_KEY && OPENSSL_VERSION_NUMBER < 0x30000000L */
+
+#if !HAVE_ECDSA_SIG_GET0
+void
+ECDSA_SIG_get0(const ECDSA_SIG *sig, const BIGNUM **pr, const BIGNUM **ps);
+
+int
+ECDSA_SIG_set0(ECDSA_SIG *sig, BIGNUM *r, BIGNUM *s);
+#endif /* !HAVE_ECDSA_SIG_GET0 */
+
+#if !HAVE_DH_GET0_KEY
+void
+DH_get0_key(const DH *dh, const BIGNUM **pub_key, const BIGNUM **priv_key);
+
+int
+DH_set0_key(DH *dh, BIGNUM *pub_key, BIGNUM *priv_key);
+
+void
+DH_get0_pqg(const DH *dh, const BIGNUM **p, const BIGNUM **q, const BIGNUM **g);
+
+int
+DH_set0_pqg(DH *dh, BIGNUM *p, BIGNUM *q, BIGNUM *g);
+
+#define DH_clear_flags(d, f) ((d)->flags &= ~(f))
+#endif /* !HAVE_DH_GET0_KEY */
+
+#if !HAVE_ERR_GET_ERROR_ALL
+unsigned long
+ERR_get_error_all(const char **file, int *line, const char **func,
+ const char **data, int *flags);
+#endif /* if !HAVE_ERR_GET_ERROR_ALL */
+
+#if !HAVE_EVP_PKEY_EQ
+#define EVP_PKEY_eq EVP_PKEY_cmp
+#endif
diff --git a/lib/dns/openssldh_link.c b/lib/dns/openssldh_link.c
new file mode 100644
index 0000000..00950b7
--- /dev/null
+++ b/lib/dns/openssldh_link.c
@@ -0,0 +1,1336 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0 AND ISC
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/*
+ * Copyright (C) Network Associates, Inc.
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND ISC AND NETWORK ASSOCIATES DISCLAIMS
+ * ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE
+ * FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR
+ * IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+/*! \file */
+
+#include <ctype.h>
+#include <inttypes.h>
+#include <stdbool.h>
+
+#include <openssl/bn.h>
+#include <openssl/opensslv.h>
+#if OPENSSL_VERSION_NUMBER >= 0x30000000L
+#include <openssl/core_names.h>
+#endif
+#include <openssl/err.h>
+#include <openssl/objects.h>
+#if OPENSSL_VERSION_NUMBER >= 0x30000000L
+#include <openssl/param_build.h>
+#endif
+#include <openssl/dh.h>
+
+#include <isc/mem.h>
+#include <isc/result.h>
+#include <isc/safe.h>
+#include <isc/string.h>
+#include <isc/util.h>
+
+#include "dst_internal.h"
+#include "dst_openssl.h"
+#include "dst_parse.h"
+#include "openssl_shim.h"
+
+#define PRIME2 "02"
+
+#define PRIME768 \
+ "FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088" \
+ "A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25" \
+ "F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A63A3620FFFFFFFFFFFFFFF" \
+ "F"
+
+#define PRIME1024 \
+ "FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E08" \
+ "8A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF2" \
+ "5F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406" \
+ "B7EDEE386BFB5A899FA5AE9F24117C4B1FE649286651ECE65381FFFFFFFFFFFFFFFF"
+
+#define PRIME1536 \
+ "FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD1" \
+ "29024E088A67CC74020BBEA63B139B22514A08798E3404DD" \
+ "EF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245" \
+ "E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7ED" \
+ "EE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3D" \
+ "C2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F" \
+ "83655D23DCA3AD961C62F356208552BB9ED529077096966D" \
+ "670C354E4ABC9804F1746C08CA237327FFFFFFFFFFFFFFFF"
+
+#define DST_RET(a) \
+ { \
+ ret = a; \
+ goto err; \
+ }
+
+static BIGNUM *bn2 = NULL, *bn768 = NULL, *bn1024 = NULL, *bn1536 = NULL;
+
+static isc_result_t
+openssldh_computesecret(const dst_key_t *pub, const dst_key_t *priv,
+ isc_buffer_t *secret) {
+#if OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000
+ DH *dhpub, *dhpriv;
+ const BIGNUM *pub_key = NULL;
+ int secret_len = 0;
+#else
+ EVP_PKEY_CTX *ctx = NULL;
+ EVP_PKEY *dhpub, *dhpriv;
+ size_t secret_len = 0;
+#endif /* OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000 */
+ isc_region_t r;
+ unsigned int len;
+
+#if OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000
+ REQUIRE(pub->keydata.dh != NULL);
+ REQUIRE(priv->keydata.dh != NULL);
+
+ dhpub = pub->keydata.dh;
+ dhpriv = priv->keydata.dh;
+
+ len = DH_size(dhpriv);
+#else
+ REQUIRE(pub->keydata.pkey != NULL);
+ REQUIRE(priv->keydata.pkey != NULL);
+
+ dhpub = pub->keydata.pkey;
+ dhpriv = priv->keydata.pkey;
+
+ len = EVP_PKEY_get_size(dhpriv);
+#endif /* OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000 */
+
+ isc_buffer_availableregion(secret, &r);
+ if (r.length < len) {
+ return (ISC_R_NOSPACE);
+ }
+
+#if OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000
+ DH_get0_key(dhpub, &pub_key, NULL);
+ secret_len = DH_compute_key(r.base, pub_key, dhpriv);
+ if (secret_len <= 0) {
+ return (dst__openssl_toresult2("DH_compute_key",
+ DST_R_COMPUTESECRETFAILURE));
+ }
+#else
+ ctx = EVP_PKEY_CTX_new_from_pkey(NULL, dhpriv, NULL);
+ if (ctx == NULL) {
+ return (dst__openssl_toresult2("EVP_PKEY_CTX_new_from_pkey",
+ DST_R_OPENSSLFAILURE));
+ }
+ if (EVP_PKEY_derive_init(ctx) != 1) {
+ EVP_PKEY_CTX_free(ctx);
+ return (dst__openssl_toresult2("EVP_PKEY_derive_init",
+ DST_R_OPENSSLFAILURE));
+ }
+ if (EVP_PKEY_derive_set_peer(ctx, dhpub) != 1) {
+ EVP_PKEY_CTX_free(ctx);
+ return (dst__openssl_toresult2("EVP_PKEY_derive_set_peer",
+ DST_R_OPENSSLFAILURE));
+ }
+ secret_len = r.length;
+ if (EVP_PKEY_derive(ctx, r.base, &secret_len) != 1 || secret_len == 0) {
+ EVP_PKEY_CTX_free(ctx);
+ return (dst__openssl_toresult2("EVP_PKEY_derive",
+ DST_R_COMPUTESECRETFAILURE));
+ }
+ EVP_PKEY_CTX_free(ctx);
+#endif /* OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000 */
+
+ isc_buffer_add(secret, (unsigned int)secret_len);
+
+ return (ISC_R_SUCCESS);
+}
+
+static bool
+openssldh_compare(const dst_key_t *key1, const dst_key_t *key2) {
+ bool ret = true;
+#if OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000
+ DH *dh1, *dh2;
+ const BIGNUM *pub_key1 = NULL, *pub_key2 = NULL;
+ const BIGNUM *priv_key1 = NULL, *priv_key2 = NULL;
+ const BIGNUM *p1 = NULL, *g1 = NULL, *p2 = NULL, *g2 = NULL;
+#else
+ EVP_PKEY *pkey1, *pkey2;
+ BIGNUM *pub_key1 = NULL, *pub_key2 = NULL;
+ BIGNUM *priv_key1 = NULL, *priv_key2 = NULL;
+ BIGNUM *p1 = NULL, *g1 = NULL, *p2 = NULL, *g2 = NULL;
+#endif /* OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000 */
+
+#if OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000
+ dh1 = key1->keydata.dh;
+ dh2 = key2->keydata.dh;
+
+ if (dh1 == NULL && dh2 == NULL) {
+ return (true);
+ } else if (dh1 == NULL || dh2 == NULL) {
+ return (false);
+ }
+
+ DH_get0_key(dh1, &pub_key1, &priv_key1);
+ DH_get0_key(dh2, &pub_key2, &priv_key2);
+ DH_get0_pqg(dh1, &p1, NULL, &g1);
+ DH_get0_pqg(dh2, &p2, NULL, &g2);
+#else
+ pkey1 = key1->keydata.pkey;
+ pkey2 = key2->keydata.pkey;
+
+ if (pkey1 == NULL && pkey2 == NULL) {
+ return (true);
+ } else if (pkey1 == NULL || pkey2 == NULL) {
+ return (false);
+ }
+
+ EVP_PKEY_get_bn_param(pkey1, OSSL_PKEY_PARAM_FFC_P, &p1);
+ EVP_PKEY_get_bn_param(pkey2, OSSL_PKEY_PARAM_FFC_P, &p2);
+ EVP_PKEY_get_bn_param(pkey1, OSSL_PKEY_PARAM_FFC_G, &g1);
+ EVP_PKEY_get_bn_param(pkey2, OSSL_PKEY_PARAM_FFC_G, &g2);
+ EVP_PKEY_get_bn_param(pkey1, OSSL_PKEY_PARAM_PUB_KEY, &pub_key1);
+ EVP_PKEY_get_bn_param(pkey2, OSSL_PKEY_PARAM_PUB_KEY, &pub_key2);
+ EVP_PKEY_get_bn_param(pkey1, OSSL_PKEY_PARAM_PRIV_KEY, &priv_key1);
+ EVP_PKEY_get_bn_param(pkey2, OSSL_PKEY_PARAM_PRIV_KEY, &priv_key2);
+#endif /* OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000*/
+
+ if (BN_cmp(p1, p2) != 0 || BN_cmp(g1, g2) != 0 ||
+ BN_cmp(pub_key1, pub_key2) != 0)
+ {
+ DST_RET(false);
+ }
+
+ if (priv_key1 != NULL || priv_key2 != NULL) {
+ if (priv_key1 == NULL || priv_key2 == NULL ||
+ BN_cmp(priv_key1, priv_key2) != 0)
+ {
+ DST_RET(false);
+ }
+ }
+
+err:
+#if OPENSSL_VERSION_NUMBER >= 0x30000000L && OPENSSL_API_LEVEL >= 30000
+ if (p1 != NULL) {
+ BN_free(p1);
+ }
+ if (p2 != NULL) {
+ BN_free(p2);
+ }
+ if (g1 != NULL) {
+ BN_free(g1);
+ }
+ if (g2 != NULL) {
+ BN_free(g2);
+ }
+ if (pub_key1 != NULL) {
+ BN_free(pub_key1);
+ }
+ if (pub_key2 != NULL) {
+ BN_free(pub_key2);
+ }
+ if (priv_key1 != NULL) {
+ BN_clear_free(priv_key1);
+ }
+ if (priv_key2 != NULL) {
+ BN_clear_free(priv_key2);
+ }
+#endif /* OPENSSL_VERSION_NUMBER >= 0x30000000L && OPENSSL_API_LEVEL >= 30000 \
+ */
+
+ return (ret);
+}
+
+static bool
+openssldh_paramcompare(const dst_key_t *key1, const dst_key_t *key2) {
+ bool ret = true;
+#if OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000
+ DH *dh1, *dh2;
+ const BIGNUM *p1 = NULL, *g1 = NULL, *p2 = NULL, *g2 = NULL;
+#else
+ EVP_PKEY *pkey1, *pkey2;
+ BIGNUM *p1 = NULL, *g1 = NULL, *p2 = NULL, *g2 = NULL;
+#endif /* OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000 */
+
+#if OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000
+ dh1 = key1->keydata.dh;
+ dh2 = key2->keydata.dh;
+
+ if (dh1 == NULL && dh2 == NULL) {
+ return (true);
+ } else if (dh1 == NULL || dh2 == NULL) {
+ return (false);
+ }
+
+ DH_get0_pqg(dh1, &p1, NULL, &g1);
+ DH_get0_pqg(dh2, &p2, NULL, &g2);
+#else
+ pkey1 = key1->keydata.pkey;
+ pkey2 = key2->keydata.pkey;
+
+ if (pkey1 == NULL && pkey2 == NULL) {
+ return (true);
+ } else if (pkey1 == NULL || pkey2 == NULL) {
+ return (false);
+ }
+
+ EVP_PKEY_get_bn_param(pkey1, OSSL_PKEY_PARAM_FFC_P, &p1);
+ EVP_PKEY_get_bn_param(pkey2, OSSL_PKEY_PARAM_FFC_P, &p2);
+ EVP_PKEY_get_bn_param(pkey1, OSSL_PKEY_PARAM_FFC_G, &g1);
+ EVP_PKEY_get_bn_param(pkey2, OSSL_PKEY_PARAM_FFC_G, &g2);
+#endif /* OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000 */
+
+ if (BN_cmp(p1, p2) != 0 || BN_cmp(g1, g2) != 0) {
+ DST_RET(false);
+ }
+
+err:
+#if OPENSSL_VERSION_NUMBER >= 0x30000000L && OPENSSL_API_LEVEL >= 30000
+ if (p1 != NULL) {
+ BN_free(p1);
+ }
+ if (p2 != NULL) {
+ BN_free(p2);
+ }
+ if (g1 != NULL) {
+ BN_free(g1);
+ }
+ if (g2 != NULL) {
+ BN_free(g2);
+ }
+#endif /* OPENSSL_VERSION_NUMBER >= 0x30000000L && OPENSSL_API_LEVEL >= 30000 \
+ */
+
+ return (ret);
+}
+
+#if OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000
+static int
+progress_cb(int p, int n, BN_GENCB *cb) {
+ union {
+ void *dptr;
+ void (*fptr)(int);
+ } u;
+
+ UNUSED(n);
+
+ u.dptr = BN_GENCB_get_arg(cb);
+ if (u.fptr != NULL) {
+ u.fptr(p);
+ }
+ return (1);
+}
+#else
+static int
+progress_cb(EVP_PKEY_CTX *ctx) {
+ union {
+ void *dptr;
+ void (*fptr)(int);
+ } u;
+
+ u.dptr = EVP_PKEY_CTX_get_app_data(ctx);
+ if (u.fptr != NULL) {
+ int p = EVP_PKEY_CTX_get_keygen_info(ctx, 0);
+ u.fptr(p);
+ }
+ return (1);
+}
+#endif /* OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000 */
+
+static isc_result_t
+openssldh_generate(dst_key_t *key, int generator, void (*callback)(int)) {
+ isc_result_t ret;
+ union {
+ void *dptr;
+ void (*fptr)(int);
+ } u;
+ BIGNUM *p = NULL, *g = NULL;
+#if OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000
+ DH *dh = NULL;
+ BN_GENCB *cb = NULL;
+#if !HAVE_BN_GENCB_NEW
+ BN_GENCB _cb;
+#endif /* !HAVE_BN_GENCB_NEW */
+#else
+ OSSL_PARAM_BLD *bld = NULL;
+ OSSL_PARAM *params = NULL;
+ EVP_PKEY_CTX *param_ctx = NULL;
+ EVP_PKEY_CTX *ctx = NULL;
+ EVP_PKEY *param_pkey = NULL;
+ EVP_PKEY *pkey = NULL;
+#endif /* OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000 */
+
+#if OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000
+ dh = DH_new();
+ if (dh == NULL) {
+ DST_RET(dst__openssl_toresult(ISC_R_NOMEMORY));
+ }
+#else
+ bld = OSSL_PARAM_BLD_new();
+ if (bld == NULL) {
+ DST_RET(dst__openssl_toresult(DST_R_OPENSSLFAILURE));
+ }
+ param_ctx = EVP_PKEY_CTX_new_from_name(NULL, "DH", NULL);
+ if (param_ctx == NULL) {
+ DST_RET(dst__openssl_toresult(DST_R_OPENSSLFAILURE));
+ }
+#endif /* OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000 */
+
+ if (generator == 0) {
+ /*
+ * When `generator` is 0, we have three pre-computed `p` and `g`
+ * static parameters which we can use.
+ */
+ if (key->key_size == 768 || key->key_size == 1024 ||
+ key->key_size == 1536)
+ {
+ if (key->key_size == 768) {
+ p = BN_dup(bn768);
+ } else if (key->key_size == 1024) {
+ p = BN_dup(bn1024);
+ } else {
+ p = BN_dup(bn1536);
+ }
+ g = BN_dup(bn2);
+ if (p == NULL || g == NULL) {
+ DST_RET(dst__openssl_toresult(ISC_R_NOMEMORY));
+ }
+#if OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000
+ if (DH_set0_pqg(dh, p, NULL, g) != 1) {
+ DST_RET(dst__openssl_toresult2(
+ "DH_set0_pqg", DST_R_OPENSSLFAILURE));
+ }
+#else
+ if (OSSL_PARAM_BLD_push_uint(bld,
+ OSSL_PKEY_PARAM_FFC_PBITS,
+ key->key_size) != 1)
+ {
+ DST_RET(dst__openssl_toresult2(
+ "OSSL_PARAM_BLD_push_uint",
+ DST_R_OPENSSLFAILURE));
+ }
+ if (OSSL_PARAM_BLD_push_BN(bld, OSSL_PKEY_PARAM_FFC_P,
+ p) != 1 ||
+ OSSL_PARAM_BLD_push_BN(bld, OSSL_PKEY_PARAM_FFC_G,
+ g) != 1)
+ {
+ DST_RET(dst__openssl_toresult2(
+ "OSSL_PARAM_BLD_push_BN",
+ DST_R_OPENSSLFAILURE));
+ }
+ params = OSSL_PARAM_BLD_to_param(bld);
+#endif /* OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000 */
+
+ } else {
+ /*
+ * If the requested size is not present in our
+ * pre-computed set, we will use `generator` 2 to
+ * generate new parameters.
+ */
+ generator = 2;
+ }
+ }
+
+ if (generator != 0) {
+#if OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000
+ cb = BN_GENCB_new();
+#if OPENSSL_VERSION_NUMBER >= 0x10100000L && !defined(LIBRESSL_VERSION_NUMBER)
+ if (cb == NULL) {
+ DST_RET(dst__openssl_toresult(ISC_R_NOMEMORY));
+ }
+#endif /* if OPENSSL_VERSION_NUMBER >= 0x10100000L && \
+ * !defined(LIBRESSL_VERSION_NUMBER) */
+ if (callback == NULL) {
+ BN_GENCB_set_old(cb, NULL, NULL);
+ } else {
+ u.fptr = callback;
+ BN_GENCB_set(cb, progress_cb, u.dptr);
+ }
+
+ if (!DH_generate_parameters_ex(dh, key->key_size, generator,
+ cb))
+ {
+ DST_RET(dst__openssl_toresult2("DH_generate_parameters_"
+ "ex",
+ DST_R_OPENSSLFAILURE));
+ }
+#else
+ if (OSSL_PARAM_BLD_push_int(bld, OSSL_PKEY_PARAM_DH_GENERATOR,
+ generator) != 1)
+ {
+ DST_RET(dst__openssl_toresult2("OSSL_PARAM_BLD_push_"
+ "int",
+ DST_R_OPENSSLFAILURE));
+ }
+ if (OSSL_PARAM_BLD_push_utf8_string(
+ bld, OSSL_PKEY_PARAM_FFC_TYPE, "generator", 0) != 1)
+ {
+ DST_RET(dst__openssl_toresult2("OSSL_PARAM_BLD_push_"
+ "utf8_string",
+ DST_R_OPENSSLFAILURE));
+ }
+ if (OSSL_PARAM_BLD_push_uint(bld, OSSL_PKEY_PARAM_FFC_PBITS,
+ key->key_size) != 1)
+ {
+ DST_RET(dst__openssl_toresult2("OSSL_PARAM_BLD_push_"
+ "uint",
+ DST_R_OPENSSLFAILURE));
+ }
+ params = OSSL_PARAM_BLD_to_param(bld);
+#endif /* OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000 */
+ }
+
+#if OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000
+ if (DH_generate_key(dh) == 0) {
+ DST_RET(dst__openssl_toresult2("DH_generate_key",
+ DST_R_OPENSSLFAILURE));
+ }
+ DH_clear_flags(dh, DH_FLAG_CACHE_MONT_P);
+ key->keydata.dh = dh;
+ dh = NULL;
+#else
+ if (params == NULL) {
+ DST_RET(dst__openssl_toresult(DST_R_OPENSSLFAILURE));
+ }
+
+ if (generator == 0) {
+ if (EVP_PKEY_fromdata_init(param_ctx) != 1) {
+ DST_RET(dst__openssl_toresult2("EVP_PKEY_fromdata_init",
+ DST_R_OPENSSLFAILURE));
+ }
+ if (EVP_PKEY_fromdata(param_ctx, &param_pkey,
+ OSSL_KEYMGMT_SELECT_ALL, params) != 1 ||
+ param_pkey == NULL)
+ {
+ DST_RET(dst__openssl_toresult2("EVP_PKEY_fromdata",
+ DST_R_OPENSSLFAILURE));
+ }
+ } else {
+ if (EVP_PKEY_paramgen_init(param_ctx) != 1) {
+ DST_RET(dst__openssl_toresult2("EVP_PKEY_paramgen_init",
+ DST_R_OPENSSLFAILURE));
+ }
+ if (EVP_PKEY_CTX_set_params(param_ctx, params) != 1) {
+ DST_RET(dst__openssl_toresult2("EVP_PKEY_CTX_set_"
+ "params",
+ DST_R_OPENSSLFAILURE));
+ }
+ if (EVP_PKEY_paramgen(param_ctx, &param_pkey) != 1 ||
+ param_pkey == NULL)
+ {
+ DST_RET(dst__openssl_toresult2("EVP_PKEY_paramgen",
+ DST_R_OPENSSLFAILURE));
+ }
+ }
+
+ /*
+ * Now `param_pkey` holds the DH parameters (either pre-coumputed or
+ * newly generated) so we will generate a new public/private key-pair
+ * using those parameters and put it into `pkey`.
+ */
+ ctx = EVP_PKEY_CTX_new_from_pkey(NULL, param_pkey, NULL);
+ if (ctx == NULL) {
+ DST_RET(dst__openssl_toresult2("EVP_PKEY_CTX_new_from_pkey",
+ DST_R_OPENSSLFAILURE));
+ }
+ if (callback != NULL) {
+ u.fptr = callback;
+ EVP_PKEY_CTX_set_app_data(ctx, u.dptr);
+ EVP_PKEY_CTX_set_cb(ctx, progress_cb);
+ }
+ if (EVP_PKEY_keygen_init(ctx) != 1) {
+ DST_RET(dst__openssl_toresult2("EVP_PKEY_keygen_init",
+ DST_R_OPENSSLFAILURE));
+ }
+ if (EVP_PKEY_keygen(ctx, &pkey) != 1 || pkey == NULL) {
+ DST_RET(dst__openssl_toresult2("EVP_PKEY_keygen",
+ DST_R_OPENSSLFAILURE));
+ }
+
+ key->keydata.pkey = pkey;
+ pkey = NULL;
+#endif /* OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000 */
+
+ ret = ISC_R_SUCCESS;
+
+err:
+#if OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000
+ if (dh != NULL) {
+ DH_free(dh);
+ }
+ if (cb != NULL) {
+ BN_GENCB_free(cb);
+ }
+#else
+ if (param_pkey != NULL) {
+ EVP_PKEY_free(param_pkey);
+ }
+ if (pkey != NULL) {
+ EVP_PKEY_free(pkey);
+ }
+ if (param_ctx != NULL) {
+ EVP_PKEY_CTX_free(param_ctx);
+ }
+ if (ctx != NULL) {
+ EVP_PKEY_CTX_free(ctx);
+ }
+ if (params != NULL) {
+ OSSL_PARAM_free(params);
+ }
+ if (bld != NULL) {
+ OSSL_PARAM_BLD_free(bld);
+ }
+ if (p != NULL) {
+ BN_free(p);
+ }
+ if (g != NULL) {
+ BN_free(g);
+ }
+#endif /* OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000 */
+
+ return (ret);
+}
+
+static bool
+openssldh_isprivate(const dst_key_t *key) {
+#if OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000
+ DH *dh = key->keydata.dh;
+ const BIGNUM *priv_key = NULL;
+
+ DH_get0_key(dh, NULL, &priv_key);
+
+ return (dh != NULL && priv_key != NULL);
+#else
+ bool ret;
+ EVP_PKEY *pkey;
+ BIGNUM *priv_key = NULL;
+
+ pkey = key->keydata.pkey;
+ if (pkey == NULL) {
+ return (false);
+ }
+
+ ret = (EVP_PKEY_get_bn_param(pkey, OSSL_PKEY_PARAM_PRIV_KEY,
+ &priv_key) == 1 &&
+ priv_key != NULL);
+ if (priv_key != NULL) {
+ BN_clear_free(priv_key);
+ }
+
+ return (ret);
+#endif /* OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000 */
+}
+
+static void
+openssldh_destroy(dst_key_t *key) {
+#if OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000
+ DH *dh = key->keydata.dh;
+
+ if (dh == NULL) {
+ return;
+ }
+
+ DH_free(dh);
+ key->keydata.dh = NULL;
+#else
+ EVP_PKEY *pkey = key->keydata.pkey;
+
+ if (pkey == NULL) {
+ return;
+ }
+
+ EVP_PKEY_free(pkey);
+ key->keydata.pkey = NULL;
+#endif /* OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000 */
+}
+
+static void
+uint16_toregion(uint16_t val, isc_region_t *region) {
+ *region->base = (val & 0xff00) >> 8;
+ isc_region_consume(region, 1);
+ *region->base = (val & 0x00ff);
+ isc_region_consume(region, 1);
+}
+
+static uint16_t
+uint16_fromregion(isc_region_t *region) {
+ uint16_t val;
+ unsigned char *cp = region->base;
+
+ val = ((unsigned int)(cp[0])) << 8;
+ val |= ((unsigned int)(cp[1]));
+
+ isc_region_consume(region, 2);
+
+ return (val);
+}
+
+static isc_result_t
+openssldh_todns(const dst_key_t *key, isc_buffer_t *data) {
+ isc_result_t ret = ISC_R_SUCCESS;
+#if OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000
+ DH *dh;
+ const BIGNUM *pub_key = NULL, *p = NULL, *g = NULL;
+#else
+ EVP_PKEY *pkey;
+ BIGNUM *pub_key = NULL, *p = NULL, *g = NULL;
+#endif /* OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000 */
+ isc_region_t r;
+ uint16_t dnslen, plen, glen, publen;
+
+#if OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000
+ REQUIRE(key->keydata.dh != NULL);
+
+ dh = key->keydata.dh;
+ DH_get0_pqg(dh, &p, NULL, &g);
+ DH_get0_key(dh, &pub_key, NULL);
+#else
+ REQUIRE(key->keydata.pkey != NULL);
+
+ pkey = key->keydata.pkey;
+ EVP_PKEY_get_bn_param(pkey, OSSL_PKEY_PARAM_FFC_P, &p);
+ EVP_PKEY_get_bn_param(pkey, OSSL_PKEY_PARAM_FFC_G, &g);
+ EVP_PKEY_get_bn_param(pkey, OSSL_PKEY_PARAM_PUB_KEY, &pub_key);
+#endif /* OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000 */
+
+ isc_buffer_availableregion(data, &r);
+
+ if (BN_cmp(g, bn2) == 0 &&
+ (BN_cmp(p, bn768) == 0 || BN_cmp(p, bn1024) == 0 ||
+ BN_cmp(p, bn1536) == 0))
+ {
+ plen = 1;
+ glen = 0;
+ } else {
+ plen = BN_num_bytes(p);
+ glen = BN_num_bytes(g);
+ }
+
+ publen = BN_num_bytes(pub_key);
+ dnslen = plen + glen + publen + 6;
+ if (r.length < (unsigned int)dnslen) {
+ DST_RET(ISC_R_NOSPACE);
+ }
+
+ uint16_toregion(plen, &r);
+ if (plen == 1) {
+ if (BN_cmp(p, bn768) == 0) {
+ *r.base = 1;
+ } else if (BN_cmp(p, bn1024) == 0) {
+ *r.base = 2;
+ } else {
+ *r.base = 3;
+ }
+ } else {
+ BN_bn2bin(p, r.base);
+ }
+ isc_region_consume(&r, plen);
+
+ uint16_toregion(glen, &r);
+ if (glen > 0) {
+ BN_bn2bin(g, r.base);
+ }
+ isc_region_consume(&r, glen);
+
+ uint16_toregion(publen, &r);
+ BN_bn2bin(pub_key, r.base);
+ isc_region_consume(&r, publen);
+
+ isc_buffer_add(data, dnslen);
+
+err:
+#if OPENSSL_VERSION_NUMBER >= 0x30000000L && OPENSSL_API_LEVEL >= 30000
+ if (p != NULL) {
+ BN_free(p);
+ }
+ if (g != NULL) {
+ BN_free(g);
+ }
+ if (pub_key != NULL) {
+ BN_free(pub_key);
+ }
+#endif /* OPENSSL_VERSION_NUMBER >= 0x30000000L && OPENSSL_API_LEVEL >= 30000 \
+ */
+
+ return (ret);
+}
+
+static isc_result_t
+openssldh_fromdns(dst_key_t *key, isc_buffer_t *data) {
+ isc_result_t ret;
+#if OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000
+ DH *dh;
+#else
+ OSSL_PARAM_BLD *bld = NULL;
+ OSSL_PARAM *params = NULL;
+ EVP_PKEY_CTX *ctx = NULL;
+ EVP_PKEY *pkey = NULL;
+#endif /* OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000 */
+ BIGNUM *pub_key = NULL, *p = NULL, *g = NULL;
+ int key_size;
+ isc_region_t r;
+ uint16_t plen, glen, publen;
+ int special = 0;
+
+ isc_buffer_remainingregion(data, &r);
+ if (r.length == 0) {
+ return (ISC_R_SUCCESS);
+ }
+
+#if OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000
+ dh = DH_new();
+ if (dh == NULL) {
+ DST_RET(dst__openssl_toresult(ISC_R_NOMEMORY));
+ }
+ DH_clear_flags(dh, DH_FLAG_CACHE_MONT_P);
+#else
+ bld = OSSL_PARAM_BLD_new();
+ if (bld == NULL) {
+ DST_RET(dst__openssl_toresult(DST_R_OPENSSLFAILURE));
+ }
+ ctx = EVP_PKEY_CTX_new_from_name(NULL, "DH", NULL);
+ if (ctx == NULL) {
+ DST_RET(dst__openssl_toresult(DST_R_OPENSSLFAILURE));
+ }
+#endif /* OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000 */
+
+ /*
+ * Read the prime length. 1 & 2 are table entries, > 16 means a
+ * prime follows, otherwise an error.
+ */
+ if (r.length < 2) {
+ DST_RET(DST_R_INVALIDPUBLICKEY);
+ }
+ plen = uint16_fromregion(&r);
+ if (plen < 16 && plen != 1 && plen != 2) {
+ DST_RET(DST_R_INVALIDPUBLICKEY);
+ }
+ if (r.length < plen) {
+ DST_RET(DST_R_INVALIDPUBLICKEY);
+ }
+ if (plen == 1 || plen == 2) {
+ if (plen == 1) {
+ special = *r.base;
+ isc_region_consume(&r, 1);
+ } else {
+ special = uint16_fromregion(&r);
+ }
+ switch (special) {
+ case 1:
+ p = BN_dup(bn768);
+ break;
+ case 2:
+ p = BN_dup(bn1024);
+ break;
+ case 3:
+ p = BN_dup(bn1536);
+ break;
+ default:
+ DST_RET(DST_R_INVALIDPUBLICKEY);
+ }
+ } else {
+ p = BN_bin2bn(r.base, plen, NULL);
+ isc_region_consume(&r, plen);
+ }
+
+ /*
+ * Read the generator length. This should be 0 if the prime was
+ * special, but it might not be. If it's 0 and the prime is not
+ * special, we have a problem.
+ */
+ if (r.length < 2) {
+ DST_RET(DST_R_INVALIDPUBLICKEY);
+ }
+ glen = uint16_fromregion(&r);
+ if (r.length < glen) {
+ DST_RET(DST_R_INVALIDPUBLICKEY);
+ }
+ if (special != 0) {
+ if (glen == 0) {
+ g = BN_dup(bn2);
+ } else {
+ g = BN_bin2bn(r.base, glen, NULL);
+ if (g != NULL && BN_cmp(g, bn2) != 0) {
+ DST_RET(DST_R_INVALIDPUBLICKEY);
+ }
+ }
+ } else {
+ if (glen == 0) {
+ DST_RET(DST_R_INVALIDPUBLICKEY);
+ }
+ g = BN_bin2bn(r.base, glen, NULL);
+ }
+ isc_region_consume(&r, glen);
+
+ if (p == NULL || g == NULL) {
+ DST_RET(dst__openssl_toresult(ISC_R_NOMEMORY));
+ }
+
+ key_size = BN_num_bits(p);
+
+#if OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000
+ if (DH_set0_pqg(dh, p, NULL, g) != 1) {
+ DST_RET(dst__openssl_toresult2("DH_set0_pqg",
+ DST_R_OPENSSLFAILURE));
+ }
+
+ /* These are now managed by OpenSSL */
+ p = NULL;
+ g = NULL;
+#else
+ if (OSSL_PARAM_BLD_push_BN(bld, OSSL_PKEY_PARAM_FFC_P, p) != 1 ||
+ OSSL_PARAM_BLD_push_BN(bld, OSSL_PKEY_PARAM_FFC_G, g) != 1)
+ {
+ DST_RET(dst__openssl_toresult2("OSSL_PARAM_BLD_push_BN",
+ DST_R_OPENSSLFAILURE));
+ }
+#endif /* OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000 */
+
+ if (r.length < 2) {
+ DST_RET(DST_R_INVALIDPUBLICKEY);
+ }
+ publen = uint16_fromregion(&r);
+ if (r.length < publen) {
+ DST_RET(DST_R_INVALIDPUBLICKEY);
+ }
+ pub_key = BN_bin2bn(r.base, publen, NULL);
+ if (pub_key == NULL) {
+ DST_RET(dst__openssl_toresult(ISC_R_NOMEMORY));
+ }
+
+ isc_region_consume(&r, publen);
+
+ isc_buffer_forward(data, plen + glen + publen + 6);
+
+#if OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000
+#if (LIBRESSL_VERSION_NUMBER >= 0x2070000fL) && \
+ (LIBRESSL_VERSION_NUMBER <= 0x2070200fL)
+ /*
+ * LibreSSL << 2.7.3 DH_get0_key requires priv_key to be set when
+ * DH structure is empty, hence we cannot use DH_get0_key().
+ */
+ dh->pub_key = pub_key;
+#else /* LIBRESSL_VERSION_NUMBER */
+ if (DH_set0_key(dh, pub_key, NULL) != 1) {
+ DST_RET(dst__openssl_toresult2("DH_set0_key",
+ DST_R_OPENSSLFAILURE));
+ }
+#endif /* LIBRESSL_VERSION_NUMBER */
+
+ /* This is now managed by OpenSSL */
+ pub_key = NULL;
+
+ key->keydata.dh = dh;
+ dh = NULL;
+#else
+ if (OSSL_PARAM_BLD_push_BN(bld, OSSL_PKEY_PARAM_PUB_KEY, pub_key) != 1)
+ {
+ DST_RET(dst__openssl_toresult2("OSSL_PARAM_BLD_push_BN",
+ DST_R_OPENSSLFAILURE));
+ }
+ params = OSSL_PARAM_BLD_to_param(bld);
+ if (params == NULL) {
+ DST_RET(dst__openssl_toresult(DST_R_OPENSSLFAILURE));
+ }
+ if (EVP_PKEY_fromdata_init(ctx) != 1) {
+ DST_RET(dst__openssl_toresult2("EVP_PKEY_fromdata_init",
+ DST_R_OPENSSLFAILURE));
+ }
+ if (EVP_PKEY_fromdata(ctx, &pkey, OSSL_KEYMGMT_SELECT_ALL, params) !=
+ 1 ||
+ pkey == NULL)
+ {
+ DST_RET(dst__openssl_toresult2("EVP_PKEY_fromdata",
+ DST_R_OPENSSLFAILURE));
+ }
+
+ key->keydata.pkey = pkey;
+ pkey = NULL;
+#endif /* OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000 */
+
+ key->key_size = (unsigned int)key_size;
+
+ ret = ISC_R_SUCCESS;
+
+err:
+#if OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000
+ if (dh != NULL) {
+ DH_free(dh);
+ }
+#else
+ if (pkey != NULL) {
+ EVP_PKEY_free(pkey);
+ }
+ if (ctx != NULL) {
+ EVP_PKEY_CTX_free(ctx);
+ }
+ if (params != NULL) {
+ OSSL_PARAM_free(params);
+ }
+ if (bld != NULL) {
+ OSSL_PARAM_BLD_free(bld);
+ }
+#endif /* OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000 */
+ if (p != NULL) {
+ BN_free(p);
+ }
+ if (g != NULL) {
+ BN_free(g);
+ }
+ if (pub_key != NULL) {
+ BN_free(pub_key);
+ }
+
+ return (ret);
+}
+
+static isc_result_t
+openssldh_tofile(const dst_key_t *key, const char *directory) {
+#if OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000
+ DH *dh;
+ const BIGNUM *pub_key = NULL, *priv_key = NULL, *p = NULL, *g = NULL;
+#else
+ EVP_PKEY *pkey;
+ BIGNUM *pub_key = NULL, *priv_key = NULL, *p = NULL, *g = NULL;
+#endif /* OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000 */
+ dst_private_t priv;
+ unsigned char *bufs[4] = { NULL };
+ unsigned short i = 0;
+ isc_result_t result;
+
+ if (key->external) {
+ return (DST_R_EXTERNALKEY);
+ }
+
+#if OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000
+ if (key->keydata.dh == NULL) {
+ return (DST_R_NULLKEY);
+ }
+
+ dh = key->keydata.dh;
+ DH_get0_key(dh, &pub_key, &priv_key);
+ DH_get0_pqg(dh, &p, NULL, &g);
+#else
+ if (key->keydata.pkey == NULL) {
+ return (DST_R_NULLKEY);
+ }
+
+ pkey = key->keydata.pkey;
+ EVP_PKEY_get_bn_param(pkey, OSSL_PKEY_PARAM_FFC_P, &p);
+ EVP_PKEY_get_bn_param(pkey, OSSL_PKEY_PARAM_FFC_G, &g);
+ EVP_PKEY_get_bn_param(pkey, OSSL_PKEY_PARAM_PUB_KEY, &pub_key);
+ EVP_PKEY_get_bn_param(pkey, OSSL_PKEY_PARAM_PRIV_KEY, &priv_key);
+#endif /* OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000 */
+
+ priv.elements[i].tag = TAG_DH_PRIME;
+ priv.elements[i].length = BN_num_bytes(p);
+ bufs[i] = isc_mem_get(key->mctx, priv.elements[i].length);
+ BN_bn2bin(p, bufs[i]);
+ priv.elements[i].data = bufs[i];
+ i++;
+
+ priv.elements[i].tag = TAG_DH_GENERATOR;
+ priv.elements[i].length = BN_num_bytes(g);
+ bufs[i] = isc_mem_get(key->mctx, priv.elements[i].length);
+ BN_bn2bin(g, bufs[i]);
+ priv.elements[i].data = bufs[i];
+ i++;
+
+ priv.elements[i].tag = TAG_DH_PRIVATE;
+ priv.elements[i].length = BN_num_bytes(priv_key);
+ bufs[i] = isc_mem_get(key->mctx, priv.elements[i].length);
+ BN_bn2bin(priv_key, bufs[i]);
+ priv.elements[i].data = bufs[i];
+ i++;
+
+ priv.elements[i].tag = TAG_DH_PUBLIC;
+ priv.elements[i].length = BN_num_bytes(pub_key);
+ bufs[i] = isc_mem_get(key->mctx, priv.elements[i].length);
+ BN_bn2bin(pub_key, bufs[i]);
+ priv.elements[i].data = bufs[i];
+ i++;
+
+ priv.nelements = i;
+ result = dst__privstruct_writefile(key, &priv, directory);
+
+ while (i--) {
+ if (bufs[i] != NULL) {
+ isc_mem_put(key->mctx, bufs[i],
+ priv.elements[i].length);
+ }
+ }
+
+#if OPENSSL_VERSION_NUMBER >= 0x30000000L && OPENSSL_API_LEVEL >= 30000
+ if (p != NULL) {
+ BN_free(p);
+ }
+ if (g != NULL) {
+ BN_free(g);
+ }
+ if (pub_key != NULL) {
+ BN_free(pub_key);
+ }
+ if (priv_key != NULL) {
+ BN_clear_free(priv_key);
+ }
+#endif /* OPENSSL_VERSION_NUMBER >= 0x30000000L && OPENSSL_API_LEVEL >= 30000 \
+ */
+
+ return (result);
+}
+
+static isc_result_t
+openssldh_parse(dst_key_t *key, isc_lex_t *lexer, dst_key_t *pub) {
+ dst_private_t priv;
+ isc_result_t ret;
+ int i;
+#if OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000
+ DH *dh = NULL;
+#else
+ OSSL_PARAM_BLD *bld = NULL;
+ OSSL_PARAM *params = NULL;
+ EVP_PKEY_CTX *ctx = NULL;
+ EVP_PKEY *pkey = NULL;
+#endif /* OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000 */
+ BIGNUM *pub_key = NULL, *priv_key = NULL, *p = NULL, *g = NULL;
+ int key_size = 0;
+ isc_mem_t *mctx;
+
+ UNUSED(pub);
+ mctx = key->mctx;
+
+ /* read private key file */
+ ret = dst__privstruct_parse(key, DST_ALG_DH, lexer, mctx, &priv);
+ if (ret != ISC_R_SUCCESS) {
+ return (ret);
+ }
+
+ if (key->external) {
+ DST_RET(DST_R_EXTERNALKEY);
+ }
+
+#if OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000
+ dh = DH_new();
+ if (dh == NULL) {
+ DST_RET(ISC_R_NOMEMORY);
+ }
+ DH_clear_flags(dh, DH_FLAG_CACHE_MONT_P);
+#else
+ bld = OSSL_PARAM_BLD_new();
+ if (bld == NULL) {
+ DST_RET(dst__openssl_toresult(DST_R_OPENSSLFAILURE));
+ }
+ ctx = EVP_PKEY_CTX_new_from_name(NULL, "DH", NULL);
+ if (ctx == NULL) {
+ DST_RET(dst__openssl_toresult(DST_R_OPENSSLFAILURE));
+ }
+#endif /* OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000 */
+
+ for (i = 0; i < priv.nelements; i++) {
+ BIGNUM *bn;
+ bn = BN_bin2bn(priv.elements[i].data, priv.elements[i].length,
+ NULL);
+ if (bn == NULL) {
+ DST_RET(ISC_R_NOMEMORY);
+ }
+
+ switch (priv.elements[i].tag) {
+ case TAG_DH_PRIME:
+ p = bn;
+ key_size = BN_num_bits(p);
+ break;
+ case TAG_DH_GENERATOR:
+ g = bn;
+ break;
+ case TAG_DH_PRIVATE:
+ priv_key = bn;
+ break;
+ case TAG_DH_PUBLIC:
+ pub_key = bn;
+ break;
+ }
+ }
+
+#if OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000
+ if (DH_set0_key(dh, pub_key, priv_key) != 1) {
+ DST_RET(dst__openssl_toresult2("DH_set0_key",
+ DST_R_OPENSSLFAILURE));
+ }
+ if (DH_set0_pqg(dh, p, NULL, g) != 1) {
+ DST_RET(dst__openssl_toresult2("DH_set0_pqg",
+ DST_R_OPENSSLFAILURE));
+ }
+
+ /* These are now managed by OpenSSL */
+ pub_key = NULL;
+ priv_key = NULL;
+ p = NULL;
+ g = NULL;
+
+ key->keydata.dh = dh;
+ dh = NULL;
+#else
+ if (OSSL_PARAM_BLD_push_BN(bld, OSSL_PKEY_PARAM_PUB_KEY, pub_key) !=
+ 1 ||
+ OSSL_PARAM_BLD_push_BN(bld, OSSL_PKEY_PARAM_PRIV_KEY, priv_key) !=
+ 1 ||
+ OSSL_PARAM_BLD_push_BN(bld, OSSL_PKEY_PARAM_FFC_P, p) != 1 ||
+ OSSL_PARAM_BLD_push_BN(bld, OSSL_PKEY_PARAM_FFC_G, g) != 1)
+ {
+ DST_RET(dst__openssl_toresult2("OSSL_PARAM_BLD_push_BN",
+ DST_R_OPENSSLFAILURE));
+ }
+ params = OSSL_PARAM_BLD_to_param(bld);
+ if (params == NULL) {
+ DST_RET(dst__openssl_toresult(DST_R_OPENSSLFAILURE));
+ }
+ if (EVP_PKEY_fromdata_init(ctx) != 1) {
+ DST_RET(dst__openssl_toresult2("EVP_PKEY_fromdata_init",
+ DST_R_OPENSSLFAILURE));
+ }
+ if (EVP_PKEY_fromdata(ctx, &pkey, OSSL_KEYMGMT_SELECT_ALL, params) !=
+ 1 ||
+ pkey == NULL)
+ {
+ DST_RET(dst__openssl_toresult2("EVP_PKEY_fromdata",
+ DST_R_OPENSSLFAILURE));
+ }
+
+ key->keydata.pkey = pkey;
+ pkey = NULL;
+#endif /* OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000 */
+
+ key->key_size = (unsigned int)key_size;
+ ret = ISC_R_SUCCESS;
+
+err:
+#if OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000
+ if (dh != NULL) {
+ DH_free(dh);
+ }
+#else
+ if (pkey != NULL) {
+ EVP_PKEY_free(pkey);
+ }
+ if (ctx != NULL) {
+ EVP_PKEY_CTX_free(ctx);
+ }
+ if (params != NULL) {
+ OSSL_PARAM_free(params);
+ }
+ if (bld != NULL) {
+ OSSL_PARAM_BLD_free(bld);
+ }
+#endif /* OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000 */
+ if (p != NULL) {
+ BN_free(p);
+ }
+ if (g != NULL) {
+ BN_free(g);
+ }
+ if (pub_key != NULL) {
+ BN_free(pub_key);
+ }
+ if (priv_key != NULL) {
+ BN_clear_free(priv_key);
+ }
+ if (ret != ISC_R_SUCCESS) {
+ openssldh_destroy(key);
+ }
+ dst__privstruct_free(&priv, mctx);
+ isc_safe_memwipe(&priv, sizeof(priv));
+
+ return (ret);
+}
+
+static void
+openssldh_cleanup(void) {
+ BN_free(bn2);
+ bn2 = NULL;
+
+ BN_free(bn768);
+ bn768 = NULL;
+
+ BN_free(bn1024);
+ bn1024 = NULL;
+
+ BN_free(bn1536);
+ bn1536 = NULL;
+}
+
+static dst_func_t openssldh_functions = {
+ NULL, /*%< createctx */
+ NULL, /*%< createctx2 */
+ NULL, /*%< destroyctx */
+ NULL, /*%< adddata */
+ NULL, /*%< openssldh_sign */
+ NULL, /*%< openssldh_verify */
+ NULL, /*%< openssldh_verify2 */
+ openssldh_computesecret,
+ openssldh_compare,
+ openssldh_paramcompare,
+ openssldh_generate,
+ openssldh_isprivate,
+ openssldh_destroy,
+ openssldh_todns,
+ openssldh_fromdns,
+ openssldh_tofile,
+ openssldh_parse,
+ openssldh_cleanup,
+ NULL, /*%< fromlabel */
+ NULL, /*%< dump */
+ NULL, /*%< restore */
+};
+
+isc_result_t
+dst__openssldh_init(dst_func_t **funcp) {
+ REQUIRE(funcp != NULL);
+ if (*funcp == NULL) {
+ if (BN_hex2bn(&bn2, PRIME2) == 0 || bn2 == NULL) {
+ goto cleanup;
+ }
+ if (BN_hex2bn(&bn768, PRIME768) == 0 || bn768 == NULL) {
+ goto cleanup;
+ }
+ if (BN_hex2bn(&bn1024, PRIME1024) == 0 || bn1024 == NULL) {
+ goto cleanup;
+ }
+ if (BN_hex2bn(&bn1536, PRIME1536) == 0 || bn1536 == NULL) {
+ goto cleanup;
+ }
+ *funcp = &openssldh_functions;
+ }
+ return (ISC_R_SUCCESS);
+
+cleanup:
+ if (bn2 != NULL) {
+ BN_free(bn2);
+ }
+ if (bn768 != NULL) {
+ BN_free(bn768);
+ }
+ if (bn1024 != NULL) {
+ BN_free(bn1024);
+ }
+ if (bn1536 != NULL) {
+ BN_free(bn1536);
+ }
+ return (ISC_R_NOMEMORY);
+}
diff --git a/lib/dns/opensslecdsa_link.c b/lib/dns/opensslecdsa_link.c
new file mode 100644
index 0000000..b80d637
--- /dev/null
+++ b/lib/dns/opensslecdsa_link.c
@@ -0,0 +1,1453 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/*! \file */
+
+#include <stdbool.h>
+
+#include <openssl/bn.h>
+#include <openssl/opensslv.h>
+#if OPENSSL_VERSION_NUMBER >= 0x30000000L && OPENSSL_API_LEVEL >= 30000
+#include <openssl/core_names.h>
+#endif
+#include <openssl/ecdsa.h>
+#include <openssl/err.h>
+#include <openssl/evp.h>
+#include <openssl/objects.h>
+#if OPENSSL_VERSION_NUMBER >= 0x30000000L && OPENSSL_API_LEVEL >= 30000
+#include <openssl/param_build.h>
+#endif
+#if !defined(OPENSSL_NO_ENGINE) && OPENSSL_API_LEVEL < 30000
+#include <openssl/engine.h>
+#endif
+
+#include <isc/mem.h>
+#include <isc/result.h>
+#include <isc/safe.h>
+#include <isc/string.h>
+#include <isc/util.h>
+
+#include <dns/keyvalues.h>
+
+#include "dst_internal.h"
+#include "dst_openssl.h"
+#include "dst_parse.h"
+#include "openssl_shim.h"
+
+#ifndef NID_X9_62_prime256v1
+#error "P-256 group is not known (NID_X9_62_prime256v1)"
+#endif /* ifndef NID_X9_62_prime256v1 */
+#ifndef NID_secp384r1
+#error "P-384 group is not known (NID_secp384r1)"
+#endif /* ifndef NID_secp384r1 */
+
+#define DST_RET(a) \
+ { \
+ ret = a; \
+ goto err; \
+ }
+
+#if OPENSSL_VERSION_NUMBER >= 0x30000000L && OPENSSL_API_LEVEL >= 30000
+static isc_result_t
+raw_key_to_ossl(unsigned int key_alg, int private, const unsigned char *key,
+ size_t key_len, EVP_PKEY **pkey) {
+ isc_result_t ret;
+ int status;
+ const char *groupname;
+ OSSL_PARAM_BLD *bld = NULL;
+ OSSL_PARAM *params = NULL;
+ EVP_PKEY_CTX *ctx = NULL;
+ BIGNUM *priv = NULL;
+ unsigned char buf[DNS_KEY_ECDSA384SIZE + 1];
+
+ if (key_alg == DST_ALG_ECDSA256) {
+ groupname = "P-256";
+ } else if (key_alg == DST_ALG_ECDSA384) {
+ groupname = "P-384";
+ } else {
+ DST_RET(ISC_R_NOTIMPLEMENTED);
+ }
+
+ bld = OSSL_PARAM_BLD_new();
+ if (bld == NULL) {
+ DST_RET(dst__openssl_toresult2("OSSL_PARAM_BLD_new",
+ DST_R_OPENSSLFAILURE));
+ }
+ status = OSSL_PARAM_BLD_push_utf8_string(
+ bld, OSSL_PKEY_PARAM_GROUP_NAME, groupname, 0);
+ if (status != 1) {
+ DST_RET(dst__openssl_toresult2("OSSL_PARAM_BLD_push_"
+ "utf8_string",
+ DST_R_OPENSSLFAILURE));
+ }
+
+ if (private) {
+ priv = BN_bin2bn(key, key_len, NULL);
+ if (priv == NULL) {
+ DST_RET(dst__openssl_toresult2("BN_bin2bn",
+ DST_R_OPENSSLFAILURE));
+ }
+
+ status = OSSL_PARAM_BLD_push_BN(bld, OSSL_PKEY_PARAM_PRIV_KEY,
+ priv);
+ if (status != 1) {
+ DST_RET(dst__openssl_toresult2("OSSL_PARAM_BLD_push_BN",
+ DST_R_OPENSSLFAILURE));
+ }
+ } else {
+ INSIST(key_len < sizeof(buf));
+ buf[0] = POINT_CONVERSION_UNCOMPRESSED;
+ memmove(buf + 1, key, key_len);
+
+ status = OSSL_PARAM_BLD_push_octet_string(
+ bld, OSSL_PKEY_PARAM_PUB_KEY, buf, 1 + key_len);
+ if (status != 1) {
+ DST_RET(dst__openssl_toresult2("OSSL_PARAM_BLD_push_"
+ "octet_string",
+ DST_R_OPENSSLFAILURE));
+ }
+ }
+
+ params = OSSL_PARAM_BLD_to_param(bld);
+ if (params == NULL) {
+ DST_RET(dst__openssl_toresult2("OSSL_PARAM_BLD_to_param",
+ DST_R_OPENSSLFAILURE));
+ }
+ ctx = EVP_PKEY_CTX_new_from_name(NULL, "EC", NULL);
+ if (ctx == NULL) {
+ DST_RET(dst__openssl_toresult2("EVP_PKEY_CTX_new_from_name",
+ DST_R_OPENSSLFAILURE));
+ }
+ status = EVP_PKEY_fromdata_init(ctx);
+ if (status != 1) {
+ DST_RET(dst__openssl_toresult2("EVP_PKEY_fromdata_init",
+ DST_R_OPENSSLFAILURE));
+ }
+ status = EVP_PKEY_fromdata(
+ ctx, pkey, private ? EVP_PKEY_KEYPAIR : EVP_PKEY_PUBLIC_KEY,
+ params);
+ if (status != 1 || *pkey == NULL) {
+ DST_RET(dst__openssl_toresult2("EVP_PKEY_fromdata",
+ DST_R_OPENSSLFAILURE));
+ }
+
+ ret = ISC_R_SUCCESS;
+
+err:
+ if (params != NULL) {
+ OSSL_PARAM_free(params);
+ }
+ if (bld != NULL) {
+ OSSL_PARAM_BLD_free(bld);
+ }
+ if (ctx != NULL) {
+ EVP_PKEY_CTX_free(ctx);
+ }
+ if (priv != NULL) {
+ BN_clear_free(priv);
+ }
+
+ return (ret);
+}
+#endif /* OPENSSL_VERSION_NUMBER >= 0x30000000L && OPENSSL_API_LEVEL >= 30000 \
+ */
+
+static isc_result_t
+opensslecdsa_createctx(dst_key_t *key, dst_context_t *dctx) {
+ isc_result_t ret = ISC_R_SUCCESS;
+ EVP_MD_CTX *evp_md_ctx;
+ const EVP_MD *type = NULL;
+
+ UNUSED(key);
+ REQUIRE(dctx->key->key_alg == DST_ALG_ECDSA256 ||
+ dctx->key->key_alg == DST_ALG_ECDSA384);
+ REQUIRE(dctx->use == DO_SIGN || dctx->use == DO_VERIFY);
+
+ evp_md_ctx = EVP_MD_CTX_create();
+ if (evp_md_ctx == NULL) {
+ DST_RET(dst__openssl_toresult(ISC_R_NOMEMORY));
+ }
+ if (dctx->key->key_alg == DST_ALG_ECDSA256) {
+ type = EVP_sha256();
+ } else {
+ type = EVP_sha384();
+ }
+
+ if (dctx->use == DO_SIGN) {
+ if (EVP_DigestSignInit(evp_md_ctx, NULL, type, NULL,
+ dctx->key->keydata.pkey) != 1)
+ {
+ EVP_MD_CTX_destroy(evp_md_ctx);
+ DST_RET(dst__openssl_toresult3(dctx->category,
+ "EVP_DigestSignInit",
+ ISC_R_FAILURE));
+ }
+ } else {
+ if (EVP_DigestVerifyInit(evp_md_ctx, NULL, type, NULL,
+ dctx->key->keydata.pkey) != 1)
+ {
+ EVP_MD_CTX_destroy(evp_md_ctx);
+ DST_RET(dst__openssl_toresult3(dctx->category,
+ "EVP_DigestVerifyInit",
+ ISC_R_FAILURE));
+ }
+ }
+
+ dctx->ctxdata.evp_md_ctx = evp_md_ctx;
+
+err:
+ return (ret);
+}
+
+static void
+opensslecdsa_destroyctx(dst_context_t *dctx) {
+ EVP_MD_CTX *evp_md_ctx = dctx->ctxdata.evp_md_ctx;
+
+ REQUIRE(dctx->key->key_alg == DST_ALG_ECDSA256 ||
+ dctx->key->key_alg == DST_ALG_ECDSA384);
+ REQUIRE(dctx->use == DO_SIGN || dctx->use == DO_VERIFY);
+
+ if (evp_md_ctx != NULL) {
+ EVP_MD_CTX_destroy(evp_md_ctx);
+ dctx->ctxdata.evp_md_ctx = NULL;
+ }
+}
+
+static isc_result_t
+opensslecdsa_adddata(dst_context_t *dctx, const isc_region_t *data) {
+ isc_result_t ret = ISC_R_SUCCESS;
+ EVP_MD_CTX *evp_md_ctx = dctx->ctxdata.evp_md_ctx;
+
+ REQUIRE(dctx->key->key_alg == DST_ALG_ECDSA256 ||
+ dctx->key->key_alg == DST_ALG_ECDSA384);
+ REQUIRE(dctx->use == DO_SIGN || dctx->use == DO_VERIFY);
+
+ if (dctx->use == DO_SIGN) {
+ if (EVP_DigestSignUpdate(evp_md_ctx, data->base,
+ data->length) != 1)
+ {
+ DST_RET(dst__openssl_toresult3(dctx->category,
+ "EVP_DigestSignUpdate",
+ ISC_R_FAILURE));
+ }
+ } else {
+ if (EVP_DigestVerifyUpdate(evp_md_ctx, data->base,
+ data->length) != 1)
+ {
+ DST_RET(dst__openssl_toresult3(dctx->category,
+ "EVP_DigestVerifyUpdate",
+ ISC_R_FAILURE));
+ }
+ }
+
+err:
+ return (ret);
+}
+
+static int
+BN_bn2bin_fixed(const BIGNUM *bn, unsigned char *buf, int size) {
+ int bytes = size - BN_num_bytes(bn);
+
+ INSIST(bytes >= 0);
+
+ while (bytes-- > 0) {
+ *buf++ = 0;
+ }
+ BN_bn2bin(bn, buf);
+ return (size);
+}
+
+static isc_result_t
+opensslecdsa_sign(dst_context_t *dctx, isc_buffer_t *sig) {
+ isc_result_t ret;
+ dst_key_t *key = dctx->key;
+ isc_region_t region;
+ EVP_MD_CTX *evp_md_ctx = dctx->ctxdata.evp_md_ctx;
+ ECDSA_SIG *ecdsasig = NULL;
+ size_t siglen, sigder_len = 0, sigder_alloced = 0;
+ unsigned char *sigder = NULL;
+ const unsigned char *sigder_copy;
+ const BIGNUM *r, *s;
+
+ REQUIRE(key->key_alg == DST_ALG_ECDSA256 ||
+ key->key_alg == DST_ALG_ECDSA384);
+ REQUIRE(dctx->use == DO_SIGN);
+
+ if (key->key_alg == DST_ALG_ECDSA256) {
+ siglen = DNS_SIG_ECDSA256SIZE;
+ } else {
+ siglen = DNS_SIG_ECDSA384SIZE;
+ }
+
+ isc_buffer_availableregion(sig, &region);
+ if (region.length < siglen) {
+ DST_RET(ISC_R_NOSPACE);
+ }
+
+ if (EVP_DigestSignFinal(evp_md_ctx, NULL, &sigder_len) != 1) {
+ DST_RET(dst__openssl_toresult3(
+ dctx->category, "EVP_DigestSignFinal", ISC_R_FAILURE));
+ }
+ if (sigder_len == 0) {
+ DST_RET(ISC_R_FAILURE);
+ }
+ sigder = isc_mem_get(dctx->mctx, sigder_len);
+ sigder_alloced = sigder_len;
+ if (EVP_DigestSignFinal(evp_md_ctx, sigder, &sigder_len) != 1) {
+ DST_RET(dst__openssl_toresult3(
+ dctx->category, "EVP_DigestSignFinal", ISC_R_FAILURE));
+ }
+ sigder_copy = sigder;
+ if (d2i_ECDSA_SIG(&ecdsasig, &sigder_copy, sigder_len) == NULL) {
+ DST_RET(dst__openssl_toresult3(dctx->category, "d2i_ECDSA_SIG",
+ ISC_R_FAILURE));
+ }
+
+ ECDSA_SIG_get0(ecdsasig, &r, &s);
+ BN_bn2bin_fixed(r, region.base, siglen / 2);
+ isc_region_consume(&region, siglen / 2);
+ BN_bn2bin_fixed(s, region.base, siglen / 2);
+ isc_region_consume(&region, siglen / 2);
+ ECDSA_SIG_free(ecdsasig);
+ isc_buffer_add(sig, siglen);
+ ret = ISC_R_SUCCESS;
+
+err:
+ if (sigder != NULL && sigder_alloced != 0) {
+ isc_mem_put(dctx->mctx, sigder, sigder_alloced);
+ }
+
+ return (ret);
+}
+
+static isc_result_t
+opensslecdsa_verify(dst_context_t *dctx, const isc_region_t *sig) {
+ isc_result_t ret;
+ dst_key_t *key = dctx->key;
+ int status;
+ unsigned char *cp = sig->base;
+ ECDSA_SIG *ecdsasig = NULL;
+ EVP_MD_CTX *evp_md_ctx = dctx->ctxdata.evp_md_ctx;
+ size_t siglen, sigder_len = 0, sigder_alloced = 0;
+ unsigned char *sigder = NULL;
+ unsigned char *sigder_copy;
+ BIGNUM *r = NULL, *s = NULL;
+
+ REQUIRE(key->key_alg == DST_ALG_ECDSA256 ||
+ key->key_alg == DST_ALG_ECDSA384);
+ REQUIRE(dctx->use == DO_VERIFY);
+
+ if (key->key_alg == DST_ALG_ECDSA256) {
+ siglen = DNS_SIG_ECDSA256SIZE;
+ } else {
+ siglen = DNS_SIG_ECDSA384SIZE;
+ }
+
+ if (sig->length != siglen) {
+ DST_RET(DST_R_VERIFYFAILURE);
+ }
+
+ ecdsasig = ECDSA_SIG_new();
+ if (ecdsasig == NULL) {
+ DST_RET(dst__openssl_toresult(ISC_R_NOMEMORY));
+ }
+ r = BN_bin2bn(cp, siglen / 2, NULL);
+ cp += siglen / 2;
+ s = BN_bin2bn(cp, siglen / 2, NULL);
+ /* cp += siglen / 2; */
+ ECDSA_SIG_set0(ecdsasig, r, s);
+
+ status = i2d_ECDSA_SIG(ecdsasig, NULL);
+ if (status < 0) {
+ DST_RET(dst__openssl_toresult3(dctx->category, "i2d_ECDSA_SIG",
+ DST_R_VERIFYFAILURE));
+ }
+
+ sigder_len = (size_t)status;
+ sigder = isc_mem_get(dctx->mctx, sigder_len);
+ sigder_alloced = sigder_len;
+
+ sigder_copy = sigder;
+ status = i2d_ECDSA_SIG(ecdsasig, &sigder_copy);
+ if (status < 0) {
+ DST_RET(dst__openssl_toresult3(dctx->category, "i2d_ECDSA_SIG",
+ DST_R_VERIFYFAILURE));
+ }
+
+ status = EVP_DigestVerifyFinal(evp_md_ctx, sigder, sigder_len);
+
+ switch (status) {
+ case 1:
+ ret = ISC_R_SUCCESS;
+ break;
+ case 0:
+ ret = dst__openssl_toresult(DST_R_VERIFYFAILURE);
+ break;
+ default:
+ ret = dst__openssl_toresult3(dctx->category,
+ "EVP_DigestVerifyFinal",
+ DST_R_VERIFYFAILURE);
+ break;
+ }
+
+err:
+ if (ecdsasig != NULL) {
+ ECDSA_SIG_free(ecdsasig);
+ }
+ if (sigder != NULL && sigder_alloced != 0) {
+ isc_mem_put(dctx->mctx, sigder, sigder_alloced);
+ }
+
+ return (ret);
+}
+
+static bool
+opensslecdsa_compare(const dst_key_t *key1, const dst_key_t *key2) {
+ bool ret;
+ EVP_PKEY *pkey1 = key1->keydata.pkey;
+ EVP_PKEY *pkey2 = key2->keydata.pkey;
+#if OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000
+ EC_KEY *eckey1 = NULL;
+ EC_KEY *eckey2 = NULL;
+ const BIGNUM *priv1;
+ const BIGNUM *priv2;
+#else
+ BIGNUM *priv1 = NULL;
+ BIGNUM *priv2 = NULL;
+#endif /* OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000 */
+
+ if (pkey1 == NULL && pkey2 == NULL) {
+ return (true);
+ } else if (pkey1 == NULL || pkey2 == NULL) {
+ return (false);
+ }
+
+ /* `EVP_PKEY_eq` checks only the public key components and paramters. */
+ if (EVP_PKEY_eq(pkey1, pkey2) != 1) {
+ DST_RET(false);
+ }
+
+#if OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000
+ eckey1 = EVP_PKEY_get1_EC_KEY(pkey1);
+ eckey2 = EVP_PKEY_get1_EC_KEY(pkey2);
+ if (eckey1 == NULL && eckey2 == NULL) {
+ ERR_clear_error();
+ DST_RET(true);
+ } else if (eckey1 == NULL || eckey2 == NULL) {
+ ERR_clear_error();
+ DST_RET(false);
+ }
+ priv1 = EC_KEY_get0_private_key(eckey1);
+ priv2 = EC_KEY_get0_private_key(eckey2);
+#else
+ EVP_PKEY_get_bn_param(pkey1, OSSL_PKEY_PARAM_PRIV_KEY, &priv1);
+ EVP_PKEY_get_bn_param(pkey2, OSSL_PKEY_PARAM_PRIV_KEY, &priv2);
+#endif /* OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000 */
+
+ if (priv1 != NULL || priv2 != NULL) {
+ if (priv1 == NULL || priv2 == NULL || BN_cmp(priv1, priv2) != 0)
+ {
+ ERR_clear_error();
+ DST_RET(false);
+ }
+ } else {
+ ERR_clear_error();
+ }
+
+ ret = true;
+
+err:
+#if OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000
+ if (eckey1 != NULL) {
+ EC_KEY_free(eckey1);
+ }
+ if (eckey2 != NULL) {
+ EC_KEY_free(eckey2);
+ }
+#else
+ if (priv1 != NULL) {
+ BN_clear_free(priv1);
+ }
+ if (priv2 != NULL) {
+ BN_clear_free(priv2);
+ }
+#endif /* OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000 */
+
+ return (ret);
+}
+
+static isc_result_t
+opensslecdsa_generate(dst_key_t *key, int unused, void (*callback)(int)) {
+ isc_result_t ret;
+ int status;
+ EVP_PKEY *pkey = NULL;
+#if OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000
+ EC_KEY *eckey = NULL;
+#else
+ EVP_PKEY_CTX *ctx = NULL;
+ EVP_PKEY *params_pkey = NULL;
+#endif /* OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000 */
+ int group_nid;
+
+ REQUIRE(key->key_alg == DST_ALG_ECDSA256 ||
+ key->key_alg == DST_ALG_ECDSA384);
+ UNUSED(unused);
+ UNUSED(callback);
+
+ if (key->key_alg == DST_ALG_ECDSA256) {
+ group_nid = NID_X9_62_prime256v1;
+ key->key_size = DNS_KEY_ECDSA256SIZE * 4;
+ } else {
+ group_nid = NID_secp384r1;
+ key->key_size = DNS_KEY_ECDSA384SIZE * 4;
+ }
+
+#if OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000
+ eckey = EC_KEY_new_by_curve_name(group_nid);
+ if (eckey == NULL) {
+ DST_RET(dst__openssl_toresult2("EC_KEY_new_by_curve_name",
+ DST_R_OPENSSLFAILURE));
+ }
+
+ status = EC_KEY_generate_key(eckey);
+ if (status != 1) {
+ DST_RET(dst__openssl_toresult2("EC_KEY_generate_key",
+ DST_R_OPENSSLFAILURE));
+ }
+
+ pkey = EVP_PKEY_new();
+ if (pkey == NULL) {
+ DST_RET(dst__openssl_toresult(ISC_R_NOMEMORY));
+ }
+ if (!EVP_PKEY_set1_EC_KEY(pkey, eckey)) {
+ DST_RET(ISC_R_FAILURE);
+ }
+#else
+ /* Generate the key's parameters. */
+ ctx = EVP_PKEY_CTX_new_from_name(NULL, "EC", NULL);
+ if (ctx == NULL) {
+ DST_RET(dst__openssl_toresult2("EVP_PKEY_CTX_new_from_name",
+ DST_R_OPENSSLFAILURE));
+ }
+ status = EVP_PKEY_paramgen_init(ctx);
+ if (status != 1) {
+ DST_RET(dst__openssl_toresult2("EVP_PKEY_paramgen_init",
+ DST_R_OPENSSLFAILURE));
+ }
+ status = EVP_PKEY_CTX_set_ec_paramgen_curve_nid(ctx, group_nid);
+ if (status != 1) {
+ DST_RET(dst__openssl_toresult2("EVP_PKEY_CTX_set_ec_paramgen_"
+ "curve_nid",
+ DST_R_OPENSSLFAILURE));
+ }
+ status = EVP_PKEY_paramgen(ctx, &params_pkey);
+ if (status != 1 || params_pkey == NULL) {
+ DST_RET(dst__openssl_toresult2("EVP_PKEY_paramgen",
+ DST_R_OPENSSLFAILURE));
+ }
+ EVP_PKEY_CTX_free(ctx);
+
+ /* Generate the key. */
+ ctx = EVP_PKEY_CTX_new(params_pkey, NULL);
+ if (ctx == NULL) {
+ DST_RET(dst__openssl_toresult2("EVP_PKEY_CTX_new",
+ DST_R_OPENSSLFAILURE));
+ }
+ status = EVP_PKEY_keygen_init(ctx);
+ if (status != 1) {
+ DST_RET(dst__openssl_toresult2("EVP_PKEY_keygen_init",
+ DST_R_OPENSSLFAILURE));
+ }
+ status = EVP_PKEY_keygen(ctx, &pkey);
+ if (status != 1 || pkey == NULL) {
+ DST_RET(dst__openssl_toresult2("EVP_PKEY_keygen",
+ DST_R_OPENSSLFAILURE));
+ }
+#endif /* OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000 */
+
+ key->keydata.pkey = pkey;
+ pkey = NULL;
+ ret = ISC_R_SUCCESS;
+
+err:
+ if (pkey != NULL) {
+ EVP_PKEY_free(pkey);
+ }
+#if OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000
+ if (eckey != NULL) {
+ EC_KEY_free(eckey);
+ }
+#else
+ if (params_pkey != NULL) {
+ EVP_PKEY_free(params_pkey);
+ }
+ if (ctx != NULL) {
+ EVP_PKEY_CTX_free(ctx);
+ }
+#endif /* OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000 */
+
+ return (ret);
+}
+
+static bool
+opensslecdsa_isprivate(const dst_key_t *key) {
+ bool ret;
+ EVP_PKEY *pkey;
+#if OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000
+ EC_KEY *eckey;
+#else
+ BIGNUM *priv = NULL;
+#endif /* OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000 */
+
+ REQUIRE(key->key_alg == DST_ALG_ECDSA256 ||
+ key->key_alg == DST_ALG_ECDSA384);
+
+ pkey = key->keydata.pkey;
+ if (pkey == NULL) {
+ return (false);
+ }
+
+#if OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000
+ eckey = EVP_PKEY_get1_EC_KEY(pkey);
+
+ ret = (eckey != NULL && EC_KEY_get0_private_key(eckey) != NULL);
+ if (eckey != NULL) {
+ EC_KEY_free(eckey);
+ } else {
+ ERR_clear_error();
+ }
+#else
+ ret = (EVP_PKEY_get_bn_param(pkey, OSSL_PKEY_PARAM_PRIV_KEY, &priv) ==
+ 1 &&
+ priv != NULL);
+ if (priv != NULL) {
+ BN_clear_free(priv);
+ }
+#endif /* OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000 */
+
+ return (ret);
+}
+
+static void
+opensslecdsa_destroy(dst_key_t *key) {
+ EVP_PKEY *pkey = key->keydata.pkey;
+
+ if (pkey != NULL) {
+ EVP_PKEY_free(pkey);
+ key->keydata.pkey = NULL;
+ }
+}
+
+static isc_result_t
+opensslecdsa_todns(const dst_key_t *key, isc_buffer_t *data) {
+ isc_result_t ret;
+ EVP_PKEY *pkey;
+#if OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000
+ EC_KEY *eckey = NULL;
+ int len;
+ unsigned char *cp;
+#else
+ int status;
+ BIGNUM *x = NULL;
+ BIGNUM *y = NULL;
+ size_t keysize = 0;
+ size_t len = 0;
+#endif /* OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000 */
+ isc_region_t r;
+ unsigned char buf[DNS_KEY_ECDSA384SIZE + 1];
+
+ REQUIRE(key->keydata.pkey != NULL);
+
+ pkey = key->keydata.pkey;
+
+#if OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000
+ eckey = EVP_PKEY_get1_EC_KEY(pkey);
+ if (eckey == NULL) {
+ DST_RET(dst__openssl_toresult(ISC_R_FAILURE));
+ }
+ len = i2o_ECPublicKey(eckey, NULL);
+
+ /* skip form */
+ len--;
+#else
+ if (key->key_alg == DST_ALG_ECDSA256) {
+ keysize = DNS_KEY_ECDSA256SIZE;
+ } else if (key->key_alg == DST_ALG_ECDSA384) {
+ keysize = DNS_KEY_ECDSA384SIZE;
+ } else {
+ DST_RET(ISC_R_NOTIMPLEMENTED);
+ }
+
+ len = keysize;
+#endif /* OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000 */
+
+ isc_buffer_availableregion(data, &r);
+ if (r.length < (unsigned int)len) {
+ DST_RET(ISC_R_NOSPACE);
+ }
+
+#if OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000
+ cp = buf;
+ if (!i2o_ECPublicKey(eckey, &cp)) {
+ DST_RET(dst__openssl_toresult(ISC_R_FAILURE));
+ }
+ memmove(r.base, buf + 1, len);
+#else
+ status = EVP_PKEY_get_bn_param(pkey, OSSL_PKEY_PARAM_EC_PUB_X, &x);
+ if (status != 1 || x == NULL) {
+ DST_RET(dst__openssl_toresult2("EVP_PKEY_get_bn_param",
+ DST_R_OPENSSLFAILURE));
+ }
+ status = EVP_PKEY_get_bn_param(pkey, OSSL_PKEY_PARAM_EC_PUB_Y, &y);
+ if (status != 1 || y == NULL) {
+ DST_RET(dst__openssl_toresult2("EVP_PKEY_get_bn_param",
+ DST_R_OPENSSLFAILURE));
+ }
+ BN_bn2bin_fixed(x, &buf[0], keysize / 2);
+ BN_bn2bin_fixed(y, &buf[keysize / 2], keysize / 2);
+ memmove(r.base, buf, len);
+#endif /* OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000 */
+
+ isc_buffer_add(data, len);
+ ret = ISC_R_SUCCESS;
+
+err:
+#if OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000
+ if (eckey != NULL) {
+ EC_KEY_free(eckey);
+ }
+#else
+ if (x != NULL) {
+ BN_clear_free(x);
+ }
+ if (y != NULL) {
+ BN_clear_free(y);
+ }
+#endif /* OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000 */
+
+ return (ret);
+}
+
+static isc_result_t
+opensslecdsa_fromdns(dst_key_t *key, isc_buffer_t *data) {
+ isc_result_t ret;
+ EVP_PKEY *pkey = NULL;
+ isc_region_t r;
+#if OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000
+ EC_KEY *eckey = NULL;
+ const unsigned char *cp;
+ unsigned int len;
+ unsigned char buf[DNS_KEY_ECDSA384SIZE + 1];
+ int group_nid;
+#else
+ size_t len;
+#endif /* OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000 */
+
+ REQUIRE(key->key_alg == DST_ALG_ECDSA256 ||
+ key->key_alg == DST_ALG_ECDSA384);
+
+ if (key->key_alg == DST_ALG_ECDSA256) {
+ len = DNS_KEY_ECDSA256SIZE;
+ } else {
+ len = DNS_KEY_ECDSA384SIZE;
+ }
+
+ isc_buffer_remainingregion(data, &r);
+ if (r.length == 0) {
+ DST_RET(ISC_R_SUCCESS);
+ }
+ if (r.length != len) {
+ DST_RET(DST_R_INVALIDPUBLICKEY);
+ }
+
+#if OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000
+ if (key->key_alg == DST_ALG_ECDSA256) {
+ group_nid = NID_X9_62_prime256v1;
+ } else {
+ group_nid = NID_secp384r1;
+ }
+
+ eckey = EC_KEY_new_by_curve_name(group_nid);
+ if (eckey == NULL) {
+ DST_RET(dst__openssl_toresult(DST_R_OPENSSLFAILURE));
+ }
+
+ buf[0] = POINT_CONVERSION_UNCOMPRESSED;
+ memmove(buf + 1, r.base, len);
+ cp = buf;
+ if (o2i_ECPublicKey(&eckey, (const unsigned char **)&cp,
+ (long)len + 1) == NULL)
+ {
+ DST_RET(dst__openssl_toresult(DST_R_INVALIDPUBLICKEY));
+ }
+ if (EC_KEY_check_key(eckey) != 1) {
+ DST_RET(dst__openssl_toresult(DST_R_INVALIDPUBLICKEY));
+ }
+
+ pkey = EVP_PKEY_new();
+ if (pkey == NULL) {
+ DST_RET(dst__openssl_toresult(ISC_R_NOMEMORY));
+ }
+ if (!EVP_PKEY_set1_EC_KEY(pkey, eckey)) {
+ EVP_PKEY_free(pkey);
+ DST_RET(dst__openssl_toresult(ISC_R_FAILURE));
+ }
+#else
+ ret = raw_key_to_ossl(key->key_alg, 0, r.base, len, &pkey);
+ if (ret != ISC_R_SUCCESS) {
+ DST_RET(ret);
+ }
+#endif /* OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000 */
+
+ isc_buffer_forward(data, len);
+ key->keydata.pkey = pkey;
+ key->key_size = len * 4;
+ ret = ISC_R_SUCCESS;
+
+err:
+#if OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000
+ if (eckey != NULL) {
+ EC_KEY_free(eckey);
+ }
+#endif /* OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000 */
+ return (ret);
+}
+
+static isc_result_t
+opensslecdsa_tofile(const dst_key_t *key, const char *directory) {
+ isc_result_t ret;
+ EVP_PKEY *pkey;
+#if OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000
+ EC_KEY *eckey = NULL;
+ const BIGNUM *privkey = NULL;
+#else
+ int status;
+ BIGNUM *privkey = NULL;
+#endif /* OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000 */
+ dst_private_t priv;
+ unsigned char *buf = NULL;
+ unsigned short i;
+
+ if (key->keydata.pkey == NULL) {
+ DST_RET(DST_R_NULLKEY);
+ }
+
+ if (key->external) {
+ priv.nelements = 0;
+ DST_RET(dst__privstruct_writefile(key, &priv, directory));
+ }
+
+ pkey = key->keydata.pkey;
+#if OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000
+ eckey = EVP_PKEY_get1_EC_KEY(pkey);
+ if (eckey == NULL) {
+ DST_RET(dst__openssl_toresult2("EVP_PKEY_get1_EC_KEY",
+ DST_R_OPENSSLFAILURE));
+ }
+ privkey = EC_KEY_get0_private_key(eckey);
+ if (privkey == NULL) {
+ DST_RET(dst__openssl_toresult2("EC_KEY_get0_private_key",
+ DST_R_OPENSSLFAILURE));
+ }
+#else
+ status = EVP_PKEY_get_bn_param(pkey, OSSL_PKEY_PARAM_PRIV_KEY,
+ &privkey);
+ if (status != 1 || privkey == NULL) {
+ DST_RET(dst__openssl_toresult2("EVP_PKEY_get_bn_param",
+ DST_R_OPENSSLFAILURE));
+ }
+#endif /* OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000 */
+
+ buf = isc_mem_get(key->mctx, BN_num_bytes(privkey));
+
+ i = 0;
+
+ priv.elements[i].tag = TAG_ECDSA_PRIVATEKEY;
+ priv.elements[i].length = BN_num_bytes(privkey);
+ BN_bn2bin(privkey, buf);
+ priv.elements[i].data = buf;
+ i++;
+
+ if (key->engine != NULL) {
+ priv.elements[i].tag = TAG_ECDSA_ENGINE;
+ priv.elements[i].length = (unsigned short)strlen(key->engine) +
+ 1;
+ priv.elements[i].data = (unsigned char *)key->engine;
+ i++;
+ }
+
+ if (key->label != NULL) {
+ priv.elements[i].tag = TAG_ECDSA_LABEL;
+ priv.elements[i].length = (unsigned short)strlen(key->label) +
+ 1;
+ priv.elements[i].data = (unsigned char *)key->label;
+ i++;
+ }
+
+ priv.nelements = i;
+ ret = dst__privstruct_writefile(key, &priv, directory);
+
+err:
+ if (buf != NULL && privkey != NULL) {
+ isc_mem_put(key->mctx, buf, BN_num_bytes(privkey));
+ }
+#if OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000
+ if (eckey != NULL) {
+ EC_KEY_free(eckey);
+ }
+#else
+ if (privkey != NULL) {
+ BN_clear_free(privkey);
+ }
+#endif /* OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000 */
+
+ return (ret);
+}
+
+#if OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000
+static isc_result_t
+ecdsa_check(EC_KEY *eckey, EC_KEY *pubeckey) {
+ const EC_POINT *pubkey;
+
+ pubkey = EC_KEY_get0_public_key(eckey);
+ if (pubkey != NULL) {
+ return (ISC_R_SUCCESS);
+ } else if (pubeckey != NULL) {
+ pubkey = EC_KEY_get0_public_key(pubeckey);
+ if (pubkey == NULL) {
+ return (ISC_R_SUCCESS);
+ }
+ if (EC_KEY_set_public_key(eckey, pubkey) != 1) {
+ return (ISC_R_SUCCESS);
+ }
+ }
+ if (EC_KEY_check_key(eckey) == 1) {
+ return (ISC_R_SUCCESS);
+ }
+
+ return (ISC_R_FAILURE);
+}
+#else
+static isc_result_t
+ecdsa_check(EVP_PKEY **pkey, EVP_PKEY *pubpkey) {
+ isc_result_t ret = ISC_R_FAILURE;
+ int status;
+ size_t pkey_len = 0;
+ BIGNUM *x = NULL;
+ BIGNUM *y = NULL;
+ BIGNUM *priv = NULL;
+ char groupname[80];
+ unsigned char buf[DNS_KEY_ECDSA384SIZE + 1];
+ size_t keysize;
+ OSSL_PARAM_BLD *bld = NULL;
+ OSSL_PARAM *params = NULL;
+ EVP_PKEY_CTX *ctx = NULL;
+ EVP_PKEY *pkey_new = NULL;
+
+ /* Check if `pkey` has a public key. */
+ status = EVP_PKEY_get_octet_string_param(*pkey, OSSL_PKEY_PARAM_PUB_KEY,
+ NULL, 0, &pkey_len);
+
+ /* Check if `pubpkey` exists and that we can extract its public key. */
+ if (pubpkey == NULL ||
+ EVP_PKEY_get_bn_param(pubpkey, OSSL_PKEY_PARAM_EC_PUB_X, &x) != 1 ||
+ x == NULL ||
+ EVP_PKEY_get_bn_param(pubpkey, OSSL_PKEY_PARAM_EC_PUB_Y, &y) != 1 ||
+ y == NULL)
+ {
+ if (status != 1 || pkey_len == 0) {
+ /* No public key both in `pkey` and in `pubpkey` */
+ DST_RET(DST_R_INVALIDPRIVATEKEY);
+ } else {
+ /*
+ * `pkey` has a public key, but there is no public key
+ * in `pubpkey` to check against.
+ */
+ DST_RET(ISC_R_SUCCESS);
+ }
+ }
+
+ /*
+ * If `pkey` doesn't have a public key then we will copy it from
+ * `pubpkey`.
+ */
+ if (status != 1 || pkey_len == 0) {
+ /*
+ * We can't (?) add a public key to an existing PKEY, so we
+ * have to create a new PKEY.
+ */
+
+ keysize = (EVP_PKEY_bits(*pkey) + 7) / 8;
+ /*
+ * The "raw" public key is created by combining the "x" and "y"
+ * parts.
+ */
+ keysize *= 2;
+ buf[0] = POINT_CONVERSION_UNCOMPRESSED;
+ BN_bn2bin_fixed(x, &buf[1], keysize / 2);
+ BN_bn2bin_fixed(y, &buf[1 + keysize / 2], keysize / 2);
+
+ groupname[0] = '\0';
+ status = EVP_PKEY_get_utf8_string_param(
+ *pkey, OSSL_PKEY_PARAM_GROUP_NAME, groupname,
+ sizeof groupname, NULL);
+ if (status != 1 || strlen(groupname) == 0) {
+ DST_RET(ISC_R_FAILURE);
+ }
+ status = EVP_PKEY_get_bn_param(*pkey, OSSL_PKEY_PARAM_PRIV_KEY,
+ &priv);
+ if (status != 1) {
+ DST_RET(ISC_R_FAILURE);
+ }
+
+ bld = OSSL_PARAM_BLD_new();
+ if (bld == NULL) {
+ DST_RET(ISC_R_FAILURE);
+ }
+ if (OSSL_PARAM_BLD_push_utf8_string(
+ bld, OSSL_PKEY_PARAM_GROUP_NAME, groupname, 0) != 1)
+ {
+ DST_RET(ISC_R_FAILURE);
+ }
+ if (OSSL_PARAM_BLD_push_BN(bld, OSSL_PKEY_PARAM_PRIV_KEY,
+ priv) != 1)
+ {
+ DST_RET(ISC_R_FAILURE);
+ }
+ if (OSSL_PARAM_BLD_push_octet_string(bld,
+ OSSL_PKEY_PARAM_PUB_KEY,
+ buf, 1 + keysize) != 1)
+ {
+ DST_RET(ISC_R_FAILURE);
+ }
+ params = OSSL_PARAM_BLD_to_param(bld);
+ if (params == NULL) {
+ DST_RET(ISC_R_FAILURE);
+ }
+
+ ctx = EVP_PKEY_CTX_new_from_name(NULL, "EC", NULL);
+ if (ctx == NULL) {
+ DST_RET(ISC_R_FAILURE);
+ }
+ if (EVP_PKEY_fromdata_init(ctx) != 1) {
+ DST_RET(ISC_R_FAILURE);
+ }
+ status = EVP_PKEY_fromdata(ctx, &pkey_new, EVP_PKEY_KEYPAIR,
+ params);
+ if (status != 1 || pkey_new == NULL) {
+ DST_RET(ISC_R_FAILURE);
+ }
+
+ /* Replace the old key with the new one. */
+ EVP_PKEY_free(*pkey);
+ *pkey = pkey_new;
+ }
+
+ if (EVP_PKEY_eq(*pkey, pubpkey) == 1) {
+ DST_RET(ISC_R_SUCCESS);
+ }
+
+err:
+ if (ctx != NULL) {
+ EVP_PKEY_CTX_free(ctx);
+ }
+ if (params != NULL) {
+ OSSL_PARAM_free(params);
+ }
+ if (bld != NULL) {
+ OSSL_PARAM_BLD_free(bld);
+ }
+ if (priv != NULL) {
+ BN_clear_free(priv);
+ }
+ if (x != NULL) {
+ BN_clear_free(x);
+ }
+ if (y != NULL) {
+ BN_clear_free(y);
+ }
+
+ return (ret);
+}
+#endif /* OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000 */
+
+#if OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000
+static isc_result_t
+load_privkey_from_privstruct(EC_KEY *eckey, dst_private_t *priv,
+ int privkey_index) {
+ BIGNUM *privkey = BN_bin2bn(priv->elements[privkey_index].data,
+ priv->elements[privkey_index].length, NULL);
+ isc_result_t result = ISC_R_SUCCESS;
+
+ if (privkey == NULL) {
+ return (ISC_R_NOMEMORY);
+ }
+
+ if (!EC_KEY_set_private_key(eckey, privkey)) {
+ result = ISC_R_NOMEMORY;
+ }
+
+ BN_clear_free(privkey);
+ return (result);
+}
+
+static isc_result_t
+eckey_to_pkey(EC_KEY *eckey, EVP_PKEY **pkey) {
+ REQUIRE(pkey != NULL && *pkey == NULL);
+
+ *pkey = EVP_PKEY_new();
+ if (*pkey == NULL) {
+ return (dst__openssl_toresult(ISC_R_NOMEMORY));
+ }
+ if (!EVP_PKEY_set1_EC_KEY(*pkey, eckey)) {
+ EVP_PKEY_free(*pkey);
+ *pkey = NULL;
+ return (dst__openssl_toresult(DST_R_OPENSSLFAILURE));
+ }
+ return (ISC_R_SUCCESS);
+}
+#endif /* OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000 */
+
+static isc_result_t
+finalize_eckey(dst_key_t *key,
+#if OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000
+ EC_KEY *eckey,
+#endif
+ const char *engine, const char *label) {
+ isc_result_t result = ISC_R_SUCCESS;
+#if OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000
+ EVP_PKEY *pkey = NULL;
+
+ REQUIRE(eckey != NULL);
+
+ result = eckey_to_pkey(eckey, &pkey);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+
+ key->keydata.pkey = pkey;
+#endif /* OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000 */
+
+ if (label != NULL) {
+ key->label = isc_mem_strdup(key->mctx, label);
+ key->engine = isc_mem_strdup(key->mctx, engine);
+ }
+
+ if (key->key_alg == DST_ALG_ECDSA256) {
+ key->key_size = DNS_KEY_ECDSA256SIZE * 4;
+ } else {
+ key->key_size = DNS_KEY_ECDSA384SIZE * 4;
+ }
+
+ return (result);
+}
+
+#if OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000
+static isc_result_t
+dst__key_to_eckey(dst_key_t *key, EC_KEY **eckey) {
+ int group_nid;
+
+ REQUIRE(eckey != NULL && *eckey == NULL);
+
+ switch (key->key_alg) {
+ case DST_ALG_ECDSA256:
+ group_nid = NID_X9_62_prime256v1;
+ break;
+ case DST_ALG_ECDSA384:
+ group_nid = NID_secp384r1;
+ break;
+ default:
+ UNREACHABLE();
+ }
+
+ *eckey = EC_KEY_new_by_curve_name(group_nid);
+ if (*eckey == NULL) {
+ return (dst__openssl_toresult(DST_R_OPENSSLFAILURE));
+ }
+
+ return (ISC_R_SUCCESS);
+}
+#endif /* OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000 */
+
+static isc_result_t
+opensslecdsa_fromlabel(dst_key_t *key, const char *engine, const char *label,
+ const char *pin);
+
+static isc_result_t
+opensslecdsa_parse(dst_key_t *key, isc_lex_t *lexer, dst_key_t *pub) {
+ dst_private_t priv;
+ isc_result_t ret;
+#if OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000
+ EC_KEY *eckey = NULL;
+ EC_KEY *pubeckey = NULL;
+#endif /* OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000 */
+ const char *engine = NULL;
+ const char *label = NULL;
+ int i, privkey_index = -1;
+ bool finalize_key = false;
+
+ REQUIRE(key->key_alg == DST_ALG_ECDSA256 ||
+ key->key_alg == DST_ALG_ECDSA384);
+
+ /* read private key file */
+ ret = dst__privstruct_parse(key, DST_ALG_ECDSA256, lexer, key->mctx,
+ &priv);
+ if (ret != ISC_R_SUCCESS) {
+ goto err;
+ }
+
+ if (key->external) {
+ if (priv.nelements != 0 || pub == NULL) {
+ DST_RET(dst__openssl_toresult(DST_R_INVALIDPRIVATEKEY));
+ }
+ key->keydata.pkey = pub->keydata.pkey;
+ pub->keydata.pkey = NULL;
+ DST_RET(ISC_R_SUCCESS);
+ }
+
+ for (i = 0; i < priv.nelements; i++) {
+ switch (priv.elements[i].tag) {
+ case TAG_ECDSA_ENGINE:
+ engine = (char *)priv.elements[i].data;
+ break;
+ case TAG_ECDSA_LABEL:
+ label = (char *)priv.elements[i].data;
+ break;
+ case TAG_ECDSA_PRIVATEKEY:
+ privkey_index = i;
+ break;
+ default:
+ break;
+ }
+ }
+
+ if (privkey_index < 0) {
+ DST_RET(dst__openssl_toresult(DST_R_INVALIDPRIVATEKEY));
+ }
+
+ if (label != NULL) {
+ ret = opensslecdsa_fromlabel(key, engine, label, NULL);
+ if (ret != ISC_R_SUCCESS) {
+ goto err;
+ }
+
+#if OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000
+ eckey = EVP_PKEY_get1_EC_KEY(key->keydata.pkey);
+ if (eckey == NULL) {
+ DST_RET(dst__openssl_toresult(DST_R_OPENSSLFAILURE));
+ }
+#endif /* OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000 */
+ } else {
+#if OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000
+ ret = dst__key_to_eckey(key, &eckey);
+ if (ret != ISC_R_SUCCESS) {
+ goto err;
+ }
+
+ ret = load_privkey_from_privstruct(eckey, &priv, privkey_index);
+#else
+ if (key->keydata.pkey != NULL) {
+ EVP_PKEY_free(key->keydata.pkey);
+ key->keydata.pkey = NULL;
+ }
+
+ ret = raw_key_to_ossl(key->key_alg, 1,
+ priv.elements[privkey_index].data,
+ priv.elements[privkey_index].length,
+ &key->keydata.pkey);
+#endif /* OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000 */
+
+ if (ret != ISC_R_SUCCESS) {
+ goto err;
+ }
+
+ finalize_key = true;
+ }
+
+#if OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000
+ if (pub != NULL && pub->keydata.pkey != NULL) {
+ pubeckey = EVP_PKEY_get1_EC_KEY(pub->keydata.pkey);
+ }
+
+ if (ecdsa_check(eckey, pubeckey) != ISC_R_SUCCESS) {
+ DST_RET(dst__openssl_toresult(DST_R_INVALIDPRIVATEKEY));
+ }
+
+ if (finalize_key) {
+ ret = finalize_eckey(key, eckey, engine, label);
+ }
+#else
+ if (ecdsa_check(&key->keydata.pkey,
+ pub == NULL ? NULL : pub->keydata.pkey) !=
+ ISC_R_SUCCESS)
+ {
+ DST_RET(dst__openssl_toresult(DST_R_INVALIDPRIVATEKEY));
+ }
+
+ if (finalize_key) {
+ ret = finalize_eckey(key, engine, label);
+ }
+#endif /* OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000 */
+
+err:
+#if OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000
+ if (pubeckey != NULL) {
+ EC_KEY_free(pubeckey);
+ }
+ if (eckey != NULL) {
+ EC_KEY_free(eckey);
+ }
+#endif /* OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000 */
+ if (ret != ISC_R_SUCCESS) {
+ key->keydata.generic = NULL;
+ }
+
+ dst__privstruct_free(&priv, key->mctx);
+ isc_safe_memwipe(&priv, sizeof(priv));
+
+ return (ret);
+}
+
+static isc_result_t
+opensslecdsa_fromlabel(dst_key_t *key, const char *engine, const char *label,
+ const char *pin) {
+#if !defined(OPENSSL_NO_ENGINE) && OPENSSL_API_LEVEL < 30000
+ isc_result_t ret = ISC_R_SUCCESS;
+ ENGINE *e;
+ EC_KEY *eckey = NULL;
+ EC_KEY *pubeckey = NULL;
+ int group_nid;
+ EVP_PKEY *pkey = NULL;
+ EVP_PKEY *pubpkey = NULL;
+
+ REQUIRE(key->key_alg == DST_ALG_ECDSA256 ||
+ key->key_alg == DST_ALG_ECDSA384);
+
+ UNUSED(pin);
+
+ if (engine == NULL || label == NULL) {
+ return (DST_R_NOENGINE);
+ }
+ e = dst__openssl_getengine(engine);
+ if (e == NULL) {
+ DST_RET(DST_R_NOENGINE);
+ }
+
+ if (key->key_alg == DST_ALG_ECDSA256) {
+ group_nid = NID_X9_62_prime256v1;
+ } else {
+ group_nid = NID_secp384r1;
+ }
+
+ /* Load private key. */
+ pkey = ENGINE_load_private_key(e, label, NULL, NULL);
+ if (pkey == NULL) {
+ DST_RET(dst__openssl_toresult2("ENGINE_load_private_key",
+ DST_R_OPENSSLFAILURE));
+ }
+ /* Check base id, group nid */
+ if (EVP_PKEY_base_id(pkey) != EVP_PKEY_EC) {
+ DST_RET(DST_R_INVALIDPRIVATEKEY);
+ }
+ eckey = EVP_PKEY_get1_EC_KEY(pkey);
+ if (eckey == NULL) {
+ DST_RET(dst__openssl_toresult(DST_R_OPENSSLFAILURE));
+ }
+ if (EC_GROUP_get_curve_name(EC_KEY_get0_group(eckey)) != group_nid) {
+ DST_RET(DST_R_INVALIDPRIVATEKEY);
+ }
+
+ /* Load public key. */
+ pubpkey = ENGINE_load_public_key(e, label, NULL, NULL);
+ if (pubpkey == NULL) {
+ DST_RET(dst__openssl_toresult2("ENGINE_load_public_key",
+ DST_R_OPENSSLFAILURE));
+ }
+ /* Check base id, group nid */
+ if (EVP_PKEY_base_id(pubpkey) != EVP_PKEY_EC) {
+ DST_RET(DST_R_INVALIDPUBLICKEY);
+ }
+ pubeckey = EVP_PKEY_get1_EC_KEY(pubpkey);
+ if (pubeckey == NULL) {
+ DST_RET(dst__openssl_toresult(DST_R_OPENSSLFAILURE));
+ }
+ if (EC_GROUP_get_curve_name(EC_KEY_get0_group(pubeckey)) != group_nid) {
+ DST_RET(DST_R_INVALIDPUBLICKEY);
+ }
+
+ if (ecdsa_check(eckey, pubeckey) != ISC_R_SUCCESS) {
+ DST_RET(dst__openssl_toresult(DST_R_INVALIDPRIVATEKEY));
+ }
+
+ key->label = isc_mem_strdup(key->mctx, label);
+ key->engine = isc_mem_strdup(key->mctx, engine);
+ key->key_size = EVP_PKEY_bits(pkey);
+ key->keydata.pkey = pkey;
+ pkey = NULL;
+
+err:
+ if (pubpkey != NULL) {
+ EVP_PKEY_free(pubpkey);
+ }
+ if (pkey != NULL) {
+ EVP_PKEY_free(pkey);
+ }
+ if (pubeckey != NULL) {
+ EC_KEY_free(pubeckey);
+ }
+ if (eckey != NULL) {
+ EC_KEY_free(eckey);
+ }
+
+ return (ret);
+#else
+ UNUSED(key);
+ UNUSED(engine);
+ UNUSED(label);
+ UNUSED(pin);
+ return (DST_R_NOENGINE);
+#endif /* !defined(OPENSSL_NO_ENGINE) && OPENSSL_API_LEVEL < 30000 */
+}
+
+static dst_func_t opensslecdsa_functions = {
+ opensslecdsa_createctx,
+ NULL, /*%< createctx2 */
+ opensslecdsa_destroyctx,
+ opensslecdsa_adddata,
+ opensslecdsa_sign,
+ opensslecdsa_verify,
+ NULL, /*%< verify2 */
+ NULL, /*%< computesecret */
+ opensslecdsa_compare,
+ NULL, /*%< paramcompare */
+ opensslecdsa_generate,
+ opensslecdsa_isprivate,
+ opensslecdsa_destroy,
+ opensslecdsa_todns,
+ opensslecdsa_fromdns,
+ opensslecdsa_tofile,
+ opensslecdsa_parse,
+ NULL, /*%< cleanup */
+ opensslecdsa_fromlabel, /*%< fromlabel */
+ NULL, /*%< dump */
+ NULL, /*%< restore */
+};
+
+isc_result_t
+dst__opensslecdsa_init(dst_func_t **funcp) {
+ REQUIRE(funcp != NULL);
+ if (*funcp == NULL) {
+ *funcp = &opensslecdsa_functions;
+ }
+ return (ISC_R_SUCCESS);
+}
diff --git a/lib/dns/openssleddsa_link.c b/lib/dns/openssleddsa_link.c
new file mode 100644
index 0000000..0fddfd4
--- /dev/null
+++ b/lib/dns/openssleddsa_link.c
@@ -0,0 +1,702 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/*! \file */
+
+#if HAVE_OPENSSL_ED25519 || HAVE_OPENSSL_ED448
+
+#include <stdbool.h>
+
+#include <openssl/err.h>
+#include <openssl/evp.h>
+#include <openssl/objects.h>
+#include <openssl/x509.h>
+#if !defined(OPENSSL_NO_ENGINE) && OPENSSL_API_LEVEL < 30000
+#include <openssl/engine.h>
+#endif /* if !defined(OPENSSL_NO_ENGINE) && OPENSSL_API_LEVEL < 30000 */
+
+#include <isc/mem.h>
+#include <isc/result.h>
+#include <isc/safe.h>
+#include <isc/string.h>
+#include <isc/util.h>
+
+#include <dns/keyvalues.h>
+
+#include "dst_internal.h"
+#include "dst_openssl.h"
+#include "dst_parse.h"
+#include "openssl_shim.h"
+
+#define DST_RET(a) \
+ { \
+ ret = a; \
+ goto err; \
+ }
+
+#if HAVE_OPENSSL_ED25519
+#ifndef NID_ED25519
+#error "Ed25519 group is not known (NID_ED25519)"
+#endif /* ifndef NID_ED25519 */
+#endif /* HAVE_OPENSSL_ED25519 */
+
+#if HAVE_OPENSSL_ED448
+#ifndef NID_ED448
+#error "Ed448 group is not known (NID_ED448)"
+#endif /* ifndef NID_ED448 */
+#endif /* HAVE_OPENSSL_ED448 */
+
+static isc_result_t
+raw_key_to_ossl(unsigned int key_alg, int private, const unsigned char *key,
+ size_t *key_len, EVP_PKEY **pkey) {
+ isc_result_t ret;
+ int pkey_type = EVP_PKEY_NONE;
+ size_t len = 0;
+
+#if HAVE_OPENSSL_ED25519
+ if (key_alg == DST_ALG_ED25519) {
+ pkey_type = EVP_PKEY_ED25519;
+ len = DNS_KEY_ED25519SIZE;
+ }
+#endif /* HAVE_OPENSSL_ED25519 */
+#if HAVE_OPENSSL_ED448
+ if (key_alg == DST_ALG_ED448) {
+ pkey_type = EVP_PKEY_ED448;
+ len = DNS_KEY_ED448SIZE;
+ }
+#endif /* HAVE_OPENSSL_ED448 */
+ if (pkey_type == EVP_PKEY_NONE) {
+ return (ISC_R_NOTIMPLEMENTED);
+ }
+
+ ret = (private ? DST_R_INVALIDPRIVATEKEY : DST_R_INVALIDPUBLICKEY);
+ if (*key_len < len) {
+ return (ret);
+ }
+
+ if (private) {
+ *pkey = EVP_PKEY_new_raw_private_key(pkey_type, NULL, key, len);
+ } else {
+ *pkey = EVP_PKEY_new_raw_public_key(pkey_type, NULL, key, len);
+ }
+ if (*pkey == NULL) {
+ return (dst__openssl_toresult(ret));
+ }
+
+ *key_len = len;
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+openssleddsa_fromlabel(dst_key_t *key, const char *engine, const char *label,
+ const char *pin);
+
+static isc_result_t
+openssleddsa_createctx(dst_key_t *key, dst_context_t *dctx) {
+ isc_buffer_t *buf = NULL;
+
+ UNUSED(key);
+ REQUIRE(dctx->key->key_alg == DST_ALG_ED25519 ||
+ dctx->key->key_alg == DST_ALG_ED448);
+
+ isc_buffer_allocate(dctx->mctx, &buf, 64);
+ dctx->ctxdata.generic = buf;
+
+ return (ISC_R_SUCCESS);
+}
+
+static void
+openssleddsa_destroyctx(dst_context_t *dctx) {
+ isc_buffer_t *buf = (isc_buffer_t *)dctx->ctxdata.generic;
+
+ REQUIRE(dctx->key->key_alg == DST_ALG_ED25519 ||
+ dctx->key->key_alg == DST_ALG_ED448);
+ if (buf != NULL) {
+ isc_buffer_free(&buf);
+ }
+ dctx->ctxdata.generic = NULL;
+}
+
+static isc_result_t
+openssleddsa_adddata(dst_context_t *dctx, const isc_region_t *data) {
+ isc_buffer_t *buf = (isc_buffer_t *)dctx->ctxdata.generic;
+ isc_buffer_t *nbuf = NULL;
+ isc_region_t r;
+ unsigned int length;
+ isc_result_t result;
+
+ REQUIRE(dctx->key->key_alg == DST_ALG_ED25519 ||
+ dctx->key->key_alg == DST_ALG_ED448);
+
+ result = isc_buffer_copyregion(buf, data);
+ if (result == ISC_R_SUCCESS) {
+ return (ISC_R_SUCCESS);
+ }
+
+ length = isc_buffer_length(buf) + data->length + 64;
+ isc_buffer_allocate(dctx->mctx, &nbuf, length);
+ isc_buffer_usedregion(buf, &r);
+ (void)isc_buffer_copyregion(nbuf, &r);
+ (void)isc_buffer_copyregion(nbuf, data);
+ isc_buffer_free(&buf);
+ dctx->ctxdata.generic = nbuf;
+
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+openssleddsa_sign(dst_context_t *dctx, isc_buffer_t *sig) {
+ isc_result_t ret;
+ dst_key_t *key = dctx->key;
+ isc_region_t tbsreg;
+ isc_region_t sigreg;
+ EVP_PKEY *pkey = key->keydata.pkey;
+ EVP_MD_CTX *ctx = EVP_MD_CTX_new();
+ isc_buffer_t *buf = (isc_buffer_t *)dctx->ctxdata.generic;
+ size_t siglen;
+
+ REQUIRE(key->key_alg == DST_ALG_ED25519 ||
+ key->key_alg == DST_ALG_ED448);
+
+ if (ctx == NULL) {
+ return (ISC_R_NOMEMORY);
+ }
+
+ if (key->key_alg == DST_ALG_ED25519) {
+ siglen = DNS_SIG_ED25519SIZE;
+ } else {
+ siglen = DNS_SIG_ED448SIZE;
+ }
+
+ isc_buffer_availableregion(sig, &sigreg);
+ if (sigreg.length < (unsigned int)siglen) {
+ DST_RET(ISC_R_NOSPACE);
+ }
+
+ isc_buffer_usedregion(buf, &tbsreg);
+
+ if (EVP_DigestSignInit(ctx, NULL, NULL, NULL, pkey) != 1) {
+ DST_RET(dst__openssl_toresult3(
+ dctx->category, "EVP_DigestSignInit", ISC_R_FAILURE));
+ }
+ if (EVP_DigestSign(ctx, sigreg.base, &siglen, tbsreg.base,
+ tbsreg.length) != 1)
+ {
+ DST_RET(dst__openssl_toresult3(dctx->category, "EVP_DigestSign",
+ DST_R_SIGNFAILURE));
+ }
+ isc_buffer_add(sig, (unsigned int)siglen);
+ ret = ISC_R_SUCCESS;
+
+err:
+ EVP_MD_CTX_free(ctx);
+ isc_buffer_free(&buf);
+ dctx->ctxdata.generic = NULL;
+
+ return (ret);
+}
+
+static isc_result_t
+openssleddsa_verify(dst_context_t *dctx, const isc_region_t *sig) {
+ isc_result_t ret;
+ dst_key_t *key = dctx->key;
+ int status;
+ isc_region_t tbsreg;
+ EVP_PKEY *pkey = key->keydata.pkey;
+ EVP_MD_CTX *ctx = EVP_MD_CTX_new();
+ isc_buffer_t *buf = (isc_buffer_t *)dctx->ctxdata.generic;
+ unsigned int siglen = 0;
+
+ REQUIRE(key->key_alg == DST_ALG_ED25519 ||
+ key->key_alg == DST_ALG_ED448);
+
+ if (ctx == NULL) {
+ return (dst__openssl_toresult(ISC_R_NOMEMORY));
+ }
+
+#if HAVE_OPENSSL_ED25519
+ if (key->key_alg == DST_ALG_ED25519) {
+ siglen = DNS_SIG_ED25519SIZE;
+ }
+#endif /* if HAVE_OPENSSL_ED25519 */
+#if HAVE_OPENSSL_ED448
+ if (key->key_alg == DST_ALG_ED448) {
+ siglen = DNS_SIG_ED448SIZE;
+ }
+#endif /* if HAVE_OPENSSL_ED448 */
+ if (siglen == 0) {
+ DST_RET(ISC_R_NOTIMPLEMENTED);
+ }
+
+ if (sig->length != siglen) {
+ DST_RET(DST_R_VERIFYFAILURE);
+ }
+
+ isc_buffer_usedregion(buf, &tbsreg);
+
+ if (EVP_DigestVerifyInit(ctx, NULL, NULL, NULL, pkey) != 1) {
+ DST_RET(dst__openssl_toresult3(
+ dctx->category, "EVP_DigestVerifyInit", ISC_R_FAILURE));
+ }
+
+ status = EVP_DigestVerify(ctx, sig->base, siglen, tbsreg.base,
+ tbsreg.length);
+
+ switch (status) {
+ case 1:
+ ret = ISC_R_SUCCESS;
+ break;
+ case 0:
+ ret = dst__openssl_toresult(DST_R_VERIFYFAILURE);
+ break;
+ default:
+ ret = dst__openssl_toresult3(dctx->category, "EVP_DigestVerify",
+ DST_R_VERIFYFAILURE);
+ break;
+ }
+
+err:
+ EVP_MD_CTX_free(ctx);
+ isc_buffer_free(&buf);
+ dctx->ctxdata.generic = NULL;
+
+ return (ret);
+}
+
+static bool
+openssleddsa_compare(const dst_key_t *key1, const dst_key_t *key2) {
+ int status;
+ EVP_PKEY *pkey1 = key1->keydata.pkey;
+ EVP_PKEY *pkey2 = key2->keydata.pkey;
+
+ if (pkey1 == NULL && pkey2 == NULL) {
+ return (true);
+ } else if (pkey1 == NULL || pkey2 == NULL) {
+ return (false);
+ }
+
+ status = EVP_PKEY_eq(pkey1, pkey2);
+ if (status == 1) {
+ return (true);
+ }
+ return (false);
+}
+
+static isc_result_t
+openssleddsa_generate(dst_key_t *key, int unused, void (*callback)(int)) {
+ isc_result_t ret;
+ EVP_PKEY *pkey = NULL;
+ EVP_PKEY_CTX *ctx = NULL;
+ int nid = 0, status;
+
+ REQUIRE(key->key_alg == DST_ALG_ED25519 ||
+ key->key_alg == DST_ALG_ED448);
+ UNUSED(unused);
+ UNUSED(callback);
+
+#if HAVE_OPENSSL_ED25519
+ if (key->key_alg == DST_ALG_ED25519) {
+ nid = NID_ED25519;
+ key->key_size = DNS_KEY_ED25519SIZE * 8;
+ }
+#endif /* if HAVE_OPENSSL_ED25519 */
+#if HAVE_OPENSSL_ED448
+ if (key->key_alg == DST_ALG_ED448) {
+ nid = NID_ED448;
+ key->key_size = DNS_KEY_ED448SIZE * 8;
+ }
+#endif /* if HAVE_OPENSSL_ED448 */
+ if (nid == 0) {
+ return (ISC_R_NOTIMPLEMENTED);
+ }
+
+ ctx = EVP_PKEY_CTX_new_id(nid, NULL);
+ if (ctx == NULL) {
+ return (dst__openssl_toresult2("EVP_PKEY_CTX_new_id",
+ DST_R_OPENSSLFAILURE));
+ }
+
+ status = EVP_PKEY_keygen_init(ctx);
+ if (status != 1) {
+ DST_RET(dst__openssl_toresult2("EVP_PKEY_keygen_init",
+ DST_R_OPENSSLFAILURE));
+ }
+
+ status = EVP_PKEY_keygen(ctx, &pkey);
+ if (status != 1) {
+ DST_RET(dst__openssl_toresult2("EVP_PKEY_keygen",
+ DST_R_OPENSSLFAILURE));
+ }
+
+ key->keydata.pkey = pkey;
+ ret = ISC_R_SUCCESS;
+
+err:
+ EVP_PKEY_CTX_free(ctx);
+ return (ret);
+}
+
+static bool
+openssleddsa_isprivate(const dst_key_t *key) {
+ EVP_PKEY *pkey = key->keydata.pkey;
+ size_t len;
+
+ if (pkey == NULL) {
+ return (false);
+ }
+
+ if (EVP_PKEY_get_raw_private_key(pkey, NULL, &len) == 1 && len > 0) {
+ return (true);
+ }
+ /* can check if first error is EC_R_INVALID_PRIVATE_KEY */
+ while (ERR_get_error() != 0) {
+ /**/
+ }
+
+ return (false);
+}
+
+static void
+openssleddsa_destroy(dst_key_t *key) {
+ EVP_PKEY *pkey = key->keydata.pkey;
+
+ EVP_PKEY_free(pkey);
+ key->keydata.pkey = NULL;
+}
+
+static isc_result_t
+openssleddsa_todns(const dst_key_t *key, isc_buffer_t *data) {
+ EVP_PKEY *pkey = key->keydata.pkey;
+ isc_region_t r;
+ size_t len;
+
+ REQUIRE(pkey != NULL);
+ REQUIRE(key->key_alg == DST_ALG_ED25519 ||
+ key->key_alg == DST_ALG_ED448);
+
+ if (key->key_alg == DST_ALG_ED25519) {
+ len = DNS_KEY_ED25519SIZE;
+ } else {
+ len = DNS_KEY_ED448SIZE;
+ }
+
+ isc_buffer_availableregion(data, &r);
+ if (r.length < len) {
+ return (ISC_R_NOSPACE);
+ }
+
+ if (EVP_PKEY_get_raw_public_key(pkey, r.base, &len) != 1) {
+ return (dst__openssl_toresult(ISC_R_FAILURE));
+ }
+
+ isc_buffer_add(data, len);
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+openssleddsa_fromdns(dst_key_t *key, isc_buffer_t *data) {
+ isc_result_t ret;
+ isc_region_t r;
+ size_t len;
+ EVP_PKEY *pkey;
+
+ REQUIRE(key->key_alg == DST_ALG_ED25519 ||
+ key->key_alg == DST_ALG_ED448);
+
+ isc_buffer_remainingregion(data, &r);
+ if (r.length == 0) {
+ return (ISC_R_SUCCESS);
+ }
+
+ len = r.length;
+ ret = raw_key_to_ossl(key->key_alg, 0, r.base, &len, &pkey);
+ if (ret != ISC_R_SUCCESS) {
+ return ret;
+ }
+
+ isc_buffer_forward(data, len);
+ key->keydata.pkey = pkey;
+ key->key_size = len * 8;
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+openssleddsa_tofile(const dst_key_t *key, const char *directory) {
+ isc_result_t ret;
+ dst_private_t priv;
+ unsigned char *buf = NULL;
+ size_t len;
+ int i;
+
+ REQUIRE(key->key_alg == DST_ALG_ED25519 ||
+ key->key_alg == DST_ALG_ED448);
+
+ if (key->keydata.pkey == NULL) {
+ return (DST_R_NULLKEY);
+ }
+
+ if (key->external) {
+ priv.nelements = 0;
+ return (dst__privstruct_writefile(key, &priv, directory));
+ }
+
+ i = 0;
+
+ if (openssleddsa_isprivate(key)) {
+ if (key->key_alg == DST_ALG_ED25519) {
+ len = DNS_KEY_ED25519SIZE;
+ } else {
+ len = DNS_KEY_ED448SIZE;
+ }
+ buf = isc_mem_get(key->mctx, len);
+ if (EVP_PKEY_get_raw_private_key(key->keydata.pkey, buf,
+ &len) != 1)
+ {
+ DST_RET(dst__openssl_toresult(ISC_R_FAILURE));
+ }
+ priv.elements[i].tag = TAG_EDDSA_PRIVATEKEY;
+ priv.elements[i].length = len;
+ priv.elements[i].data = buf;
+ i++;
+ }
+ if (key->engine != NULL) {
+ priv.elements[i].tag = TAG_EDDSA_ENGINE;
+ priv.elements[i].length = (unsigned short)strlen(key->engine) +
+ 1;
+ priv.elements[i].data = (unsigned char *)key->engine;
+ i++;
+ }
+ if (key->label != NULL) {
+ priv.elements[i].tag = TAG_EDDSA_LABEL;
+ priv.elements[i].length = (unsigned short)strlen(key->label) +
+ 1;
+ priv.elements[i].data = (unsigned char *)key->label;
+ i++;
+ }
+
+ priv.nelements = i;
+ ret = dst__privstruct_writefile(key, &priv, directory);
+
+err:
+ if (buf != NULL) {
+ isc_mem_put(key->mctx, buf, len);
+ }
+ return (ret);
+}
+
+static isc_result_t
+eddsa_check(EVP_PKEY *pkey, EVP_PKEY *pubpkey) {
+ if (pubpkey == NULL) {
+ return (ISC_R_SUCCESS);
+ }
+ if (EVP_PKEY_eq(pkey, pubpkey) == 1) {
+ return (ISC_R_SUCCESS);
+ }
+ return (ISC_R_FAILURE);
+}
+
+static isc_result_t
+openssleddsa_parse(dst_key_t *key, isc_lex_t *lexer, dst_key_t *pub) {
+ dst_private_t priv;
+ isc_result_t ret;
+ int i, privkey_index = -1;
+ const char *engine = NULL, *label = NULL;
+ EVP_PKEY *pkey = NULL, *pubpkey = NULL;
+ size_t len;
+ isc_mem_t *mctx = key->mctx;
+
+ REQUIRE(key->key_alg == DST_ALG_ED25519 ||
+ key->key_alg == DST_ALG_ED448);
+
+ /* read private key file */
+ ret = dst__privstruct_parse(key, DST_ALG_ED25519, lexer, mctx, &priv);
+ if (ret != ISC_R_SUCCESS) {
+ goto err;
+ }
+
+ if (key->external) {
+ if (priv.nelements != 0) {
+ DST_RET(DST_R_INVALIDPRIVATEKEY);
+ }
+ if (pub == NULL) {
+ DST_RET(DST_R_INVALIDPRIVATEKEY);
+ }
+ key->keydata.pkey = pub->keydata.pkey;
+ pub->keydata.pkey = NULL;
+ dst__privstruct_free(&priv, mctx);
+ isc_safe_memwipe(&priv, sizeof(priv));
+ return (ISC_R_SUCCESS);
+ }
+
+ if (pub != NULL) {
+ pubpkey = pub->keydata.pkey;
+ }
+
+ for (i = 0; i < priv.nelements; i++) {
+ switch (priv.elements[i].tag) {
+ case TAG_EDDSA_ENGINE:
+ engine = (char *)priv.elements[i].data;
+ break;
+ case TAG_EDDSA_LABEL:
+ label = (char *)priv.elements[i].data;
+ break;
+ case TAG_EDDSA_PRIVATEKEY:
+ privkey_index = i;
+ break;
+ default:
+ break;
+ }
+ }
+
+ if (label != NULL) {
+ ret = openssleddsa_fromlabel(key, engine, label, NULL);
+ if (ret != ISC_R_SUCCESS) {
+ goto err;
+ }
+ if (eddsa_check(key->keydata.pkey, pubpkey) != ISC_R_SUCCESS) {
+ DST_RET(DST_R_INVALIDPRIVATEKEY);
+ }
+ DST_RET(ISC_R_SUCCESS);
+ }
+
+ if (privkey_index < 0) {
+ DST_RET(DST_R_INVALIDPRIVATEKEY);
+ }
+
+ len = priv.elements[privkey_index].length;
+ ret = raw_key_to_ossl(key->key_alg, 1,
+ priv.elements[privkey_index].data, &len, &pkey);
+ if (ret != ISC_R_SUCCESS) {
+ goto err;
+ }
+ if (eddsa_check(pkey, pubpkey) != ISC_R_SUCCESS) {
+ EVP_PKEY_free(pkey);
+ DST_RET(DST_R_INVALIDPRIVATEKEY);
+ }
+ key->keydata.pkey = pkey;
+ key->key_size = len * 8;
+ ret = ISC_R_SUCCESS;
+
+err:
+ dst__privstruct_free(&priv, mctx);
+ isc_safe_memwipe(&priv, sizeof(priv));
+ return (ret);
+}
+
+static isc_result_t
+openssleddsa_fromlabel(dst_key_t *key, const char *engine, const char *label,
+ const char *pin) {
+#if !defined(OPENSSL_NO_ENGINE) && OPENSSL_API_LEVEL < 30000
+ isc_result_t ret;
+ ENGINE *e;
+ EVP_PKEY *pkey = NULL, *pubpkey = NULL;
+ int baseid = EVP_PKEY_NONE;
+
+ UNUSED(pin);
+
+ REQUIRE(key->key_alg == DST_ALG_ED25519 ||
+ key->key_alg == DST_ALG_ED448);
+
+#if HAVE_OPENSSL_ED25519
+ if (key->key_alg == DST_ALG_ED25519) {
+ baseid = EVP_PKEY_ED25519;
+ }
+#endif /* if HAVE_OPENSSL_ED25519 */
+#if HAVE_OPENSSL_ED448
+ if (key->key_alg == DST_ALG_ED448) {
+ baseid = EVP_PKEY_ED448;
+ }
+#endif /* if HAVE_OPENSSL_ED448 */
+ if (baseid == EVP_PKEY_NONE) {
+ return (ISC_R_NOTIMPLEMENTED);
+ }
+
+ if (engine == NULL) {
+ return (DST_R_NOENGINE);
+ }
+ e = dst__openssl_getengine(engine);
+ if (e == NULL) {
+ return (DST_R_NOENGINE);
+ }
+ pkey = ENGINE_load_private_key(e, label, NULL, NULL);
+ if (pkey == NULL) {
+ return (dst__openssl_toresult2("ENGINE_load_private_key",
+ ISC_R_NOTFOUND));
+ }
+ if (EVP_PKEY_base_id(pkey) != baseid) {
+ DST_RET(DST_R_INVALIDPRIVATEKEY);
+ }
+
+ pubpkey = ENGINE_load_public_key(e, label, NULL, NULL);
+ if (eddsa_check(pkey, pubpkey) != ISC_R_SUCCESS) {
+ DST_RET(DST_R_INVALIDPRIVATEKEY);
+ }
+
+ key->engine = isc_mem_strdup(key->mctx, engine);
+ key->label = isc_mem_strdup(key->mctx, label);
+ key->key_size = EVP_PKEY_bits(pkey);
+ key->keydata.pkey = pkey;
+ pkey = NULL;
+ ret = ISC_R_SUCCESS;
+
+err:
+ if (pubpkey != NULL) {
+ EVP_PKEY_free(pubpkey);
+ }
+ if (pkey != NULL) {
+ EVP_PKEY_free(pkey);
+ }
+ return (ret);
+#else /* if !defined(OPENSSL_NO_ENGINE) && OPENSSL_API_LEVEL < 30000 */
+ UNUSED(key);
+ UNUSED(engine);
+ UNUSED(label);
+ UNUSED(pin);
+ return (DST_R_NOENGINE);
+#endif /* if !defined(OPENSSL_NO_ENGINE) && OPENSSL_API_LEVEL < 30000 */
+}
+
+static dst_func_t openssleddsa_functions = {
+ openssleddsa_createctx,
+ NULL, /*%< createctx2 */
+ openssleddsa_destroyctx,
+ openssleddsa_adddata,
+ openssleddsa_sign,
+ openssleddsa_verify,
+ NULL, /*%< verify2 */
+ NULL, /*%< computesecret */
+ openssleddsa_compare,
+ NULL, /*%< paramcompare */
+ openssleddsa_generate,
+ openssleddsa_isprivate,
+ openssleddsa_destroy,
+ openssleddsa_todns,
+ openssleddsa_fromdns,
+ openssleddsa_tofile,
+ openssleddsa_parse,
+ NULL, /*%< cleanup */
+ openssleddsa_fromlabel,
+ NULL, /*%< dump */
+ NULL, /*%< restore */
+};
+
+isc_result_t
+dst__openssleddsa_init(dst_func_t **funcp) {
+ REQUIRE(funcp != NULL);
+ if (*funcp == NULL) {
+ *funcp = &openssleddsa_functions;
+ }
+ return (ISC_R_SUCCESS);
+}
+
+#endif /* HAVE_OPENSSL_ED25519 || HAVE_OPENSSL_ED448 */
diff --git a/lib/dns/opensslrsa_link.c b/lib/dns/opensslrsa_link.c
new file mode 100644
index 0000000..dc7382c
--- /dev/null
+++ b/lib/dns/opensslrsa_link.c
@@ -0,0 +1,1816 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/*! \file */
+
+#include <inttypes.h>
+#include <stdbool.h>
+
+#include <openssl/bn.h>
+#include <openssl/opensslv.h>
+#if OPENSSL_VERSION_NUMBER >= 0x30000000L && OPENSSL_API_LEVEL >= 30000
+#include <openssl/core_names.h>
+#endif
+#if !defined(OPENSSL_NO_ENGINE) && OPENSSL_API_LEVEL < 30000
+#include <openssl/engine.h>
+#endif /* if !defined(OPENSSL_NO_ENGINE) && OPENSSL_API_LEVEL < 30000 */
+#include <openssl/err.h>
+#include <openssl/objects.h>
+#if OPENSSL_VERSION_NUMBER >= 0x30000000L && OPENSSL_API_LEVEL >= 30000
+#include <openssl/param_build.h>
+#endif
+#include <openssl/rsa.h>
+
+#include <isc/mem.h>
+#include <isc/result.h>
+#include <isc/safe.h>
+#include <isc/string.h>
+#include <isc/util.h>
+
+#include "dst_internal.h"
+#include "dst_openssl.h"
+#include "dst_parse.h"
+#include "openssl_shim.h"
+
+#define DST_RET(a) \
+ { \
+ ret = a; \
+ goto err; \
+ }
+
+static isc_result_t
+opensslrsa_createctx(dst_key_t *key, dst_context_t *dctx) {
+ EVP_MD_CTX *evp_md_ctx;
+ const EVP_MD *type = NULL;
+
+ UNUSED(key);
+ REQUIRE(dctx != NULL && dctx->key != NULL);
+ REQUIRE(dctx->key->key_alg == DST_ALG_RSASHA1 ||
+ dctx->key->key_alg == DST_ALG_NSEC3RSASHA1 ||
+ dctx->key->key_alg == DST_ALG_RSASHA256 ||
+ dctx->key->key_alg == DST_ALG_RSASHA512);
+
+ /*
+ * Reject incorrect RSA key lengths.
+ */
+ switch (dctx->key->key_alg) {
+ case DST_ALG_RSASHA1:
+ case DST_ALG_NSEC3RSASHA1:
+ /* From RFC 3110 */
+ if (dctx->key->key_size > 4096) {
+ return (ISC_R_FAILURE);
+ }
+ break;
+ case DST_ALG_RSASHA256:
+ /* From RFC 5702 */
+ if (dctx->key->key_size < 512 || dctx->key->key_size > 4096) {
+ return (ISC_R_FAILURE);
+ }
+ break;
+ case DST_ALG_RSASHA512:
+ /* From RFC 5702 */
+ if (dctx->key->key_size < 1024 || dctx->key->key_size > 4096) {
+ return (ISC_R_FAILURE);
+ }
+ break;
+ default:
+ UNREACHABLE();
+ }
+
+ evp_md_ctx = EVP_MD_CTX_create();
+ if (evp_md_ctx == NULL) {
+ return (dst__openssl_toresult(ISC_R_NOMEMORY));
+ }
+
+ switch (dctx->key->key_alg) {
+ case DST_ALG_RSASHA1:
+ case DST_ALG_NSEC3RSASHA1:
+ type = EVP_sha1(); /* SHA1 + RSA */
+ break;
+ case DST_ALG_RSASHA256:
+ type = EVP_sha256(); /* SHA256 + RSA */
+ break;
+ case DST_ALG_RSASHA512:
+ type = EVP_sha512();
+ break;
+ default:
+ UNREACHABLE();
+ }
+
+ if (!EVP_DigestInit_ex(evp_md_ctx, type, NULL)) {
+ EVP_MD_CTX_destroy(evp_md_ctx);
+ return (dst__openssl_toresult3(
+ dctx->category, "EVP_DigestInit_ex", ISC_R_FAILURE));
+ }
+ dctx->ctxdata.evp_md_ctx = evp_md_ctx;
+
+ return (ISC_R_SUCCESS);
+}
+
+static void
+opensslrsa_destroyctx(dst_context_t *dctx) {
+ EVP_MD_CTX *evp_md_ctx = NULL;
+
+ REQUIRE(dctx != NULL && dctx->key != NULL);
+ REQUIRE(dctx->key->key_alg == DST_ALG_RSASHA1 ||
+ dctx->key->key_alg == DST_ALG_NSEC3RSASHA1 ||
+ dctx->key->key_alg == DST_ALG_RSASHA256 ||
+ dctx->key->key_alg == DST_ALG_RSASHA512);
+
+ evp_md_ctx = dctx->ctxdata.evp_md_ctx;
+
+ if (evp_md_ctx != NULL) {
+ EVP_MD_CTX_destroy(evp_md_ctx);
+ dctx->ctxdata.evp_md_ctx = NULL;
+ }
+}
+
+static isc_result_t
+opensslrsa_adddata(dst_context_t *dctx, const isc_region_t *data) {
+ EVP_MD_CTX *evp_md_ctx = NULL;
+
+ REQUIRE(dctx != NULL && dctx->key != NULL);
+ REQUIRE(dctx->key->key_alg == DST_ALG_RSASHA1 ||
+ dctx->key->key_alg == DST_ALG_NSEC3RSASHA1 ||
+ dctx->key->key_alg == DST_ALG_RSASHA256 ||
+ dctx->key->key_alg == DST_ALG_RSASHA512);
+
+ evp_md_ctx = dctx->ctxdata.evp_md_ctx;
+
+ if (!EVP_DigestUpdate(evp_md_ctx, data->base, data->length)) {
+ return (dst__openssl_toresult3(
+ dctx->category, "EVP_DigestUpdate", ISC_R_FAILURE));
+ }
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+opensslrsa_sign(dst_context_t *dctx, isc_buffer_t *sig) {
+ dst_key_t *key = NULL;
+ isc_region_t r;
+ unsigned int siglen = 0;
+ EVP_MD_CTX *evp_md_ctx = NULL;
+ EVP_PKEY *pkey = NULL;
+
+ REQUIRE(dctx != NULL && dctx->key != NULL);
+ REQUIRE(dctx->key->key_alg == DST_ALG_RSASHA1 ||
+ dctx->key->key_alg == DST_ALG_NSEC3RSASHA1 ||
+ dctx->key->key_alg == DST_ALG_RSASHA256 ||
+ dctx->key->key_alg == DST_ALG_RSASHA512);
+
+ key = dctx->key;
+ evp_md_ctx = dctx->ctxdata.evp_md_ctx;
+ pkey = key->keydata.pkey;
+
+ isc_buffer_availableregion(sig, &r);
+
+ if (r.length < (unsigned int)EVP_PKEY_size(pkey)) {
+ return (ISC_R_NOSPACE);
+ }
+
+ if (!EVP_SignFinal(evp_md_ctx, r.base, &siglen, pkey)) {
+ return (dst__openssl_toresult3(dctx->category, "EVP_SignFinal",
+ ISC_R_FAILURE));
+ }
+
+ isc_buffer_add(sig, siglen);
+
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+opensslrsa_verify2(dst_context_t *dctx, int maxbits, const isc_region_t *sig) {
+ dst_key_t *key = NULL;
+ int status = 0;
+#if OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000
+ RSA *rsa;
+ const BIGNUM *e = NULL;
+#else
+ BIGNUM *e = NULL;
+#endif /* OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000 */
+ EVP_MD_CTX *evp_md_ctx = NULL;
+ EVP_PKEY *pkey = NULL;
+ int bits;
+
+ REQUIRE(dctx != NULL && dctx->key != NULL);
+ REQUIRE(dctx->key->key_alg == DST_ALG_RSASHA1 ||
+ dctx->key->key_alg == DST_ALG_NSEC3RSASHA1 ||
+ dctx->key->key_alg == DST_ALG_RSASHA256 ||
+ dctx->key->key_alg == DST_ALG_RSASHA512);
+
+ key = dctx->key;
+ evp_md_ctx = dctx->ctxdata.evp_md_ctx;
+ pkey = key->keydata.pkey;
+
+#if OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000
+ rsa = EVP_PKEY_get1_RSA(pkey);
+ if (rsa == NULL) {
+ return (dst__openssl_toresult(DST_R_OPENSSLFAILURE));
+ }
+ RSA_get0_key(rsa, NULL, &e, NULL);
+ if (e == NULL) {
+ RSA_free(rsa);
+ return (dst__openssl_toresult(DST_R_VERIFYFAILURE));
+ }
+ bits = BN_num_bits(e);
+ RSA_free(rsa);
+#else
+ EVP_PKEY_get_bn_param(pkey, OSSL_PKEY_PARAM_RSA_E, &e);
+ if (e == NULL) {
+ return (dst__openssl_toresult(DST_R_VERIFYFAILURE));
+ }
+ bits = BN_num_bits(e);
+ BN_free(e);
+#endif /* OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000 */
+
+ if (bits > maxbits && maxbits != 0) {
+ return (DST_R_VERIFYFAILURE);
+ }
+
+ status = EVP_VerifyFinal(evp_md_ctx, sig->base, sig->length, pkey);
+ switch (status) {
+ case 1:
+ return (ISC_R_SUCCESS);
+ case 0:
+ return (dst__openssl_toresult(DST_R_VERIFYFAILURE));
+ default:
+ return (dst__openssl_toresult3(dctx->category,
+ "EVP_VerifyFinal",
+ DST_R_VERIFYFAILURE));
+ }
+}
+
+static isc_result_t
+opensslrsa_verify(dst_context_t *dctx, const isc_region_t *sig) {
+ return (opensslrsa_verify2(dctx, 0, sig));
+}
+
+static bool
+opensslrsa_compare(const dst_key_t *key1, const dst_key_t *key2) {
+ bool ret;
+ int status;
+ EVP_PKEY *pkey1 = key1->keydata.pkey;
+ EVP_PKEY *pkey2 = key2->keydata.pkey;
+#if OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000
+ RSA *rsa1 = NULL;
+ RSA *rsa2 = NULL;
+ const BIGNUM *d1 = NULL, *d2 = NULL;
+ const BIGNUM *p1 = NULL, *p2 = NULL;
+ const BIGNUM *q1 = NULL, *q2 = NULL;
+#else
+ BIGNUM *d1 = NULL, *d2 = NULL;
+ BIGNUM *p1 = NULL, *p2 = NULL;
+ BIGNUM *q1 = NULL, *q2 = NULL;
+#endif /* OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000 */
+
+ if (pkey1 == NULL && pkey2 == NULL) {
+ return (true);
+ } else if (pkey1 == NULL || pkey2 == NULL) {
+ return (false);
+ }
+
+ /* `EVP_PKEY_eq` checks only the public key components and paramters. */
+ status = EVP_PKEY_eq(pkey1, pkey2);
+ if (status != 1) {
+ DST_RET(false);
+ }
+
+#if OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000
+ rsa1 = EVP_PKEY_get1_RSA(pkey1);
+ rsa2 = EVP_PKEY_get1_RSA(pkey2);
+ if (rsa1 == NULL && rsa2 == NULL) {
+ DST_RET(true);
+ } else if (rsa1 == NULL || rsa2 == NULL) {
+ DST_RET(false);
+ }
+ RSA_get0_key(rsa1, NULL, NULL, &d1);
+ RSA_get0_key(rsa2, NULL, NULL, &d2);
+#else
+ EVP_PKEY_get_bn_param(pkey1, OSSL_PKEY_PARAM_RSA_D, &d1);
+ EVP_PKEY_get_bn_param(pkey2, OSSL_PKEY_PARAM_RSA_D, &d2);
+ ERR_clear_error();
+#endif /* OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000 */
+
+ if (d1 != NULL || d2 != NULL) {
+ if (d1 == NULL || d2 == NULL) {
+ DST_RET(false);
+ }
+
+#if OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000
+ RSA_get0_factors(rsa1, &p1, &q1);
+ RSA_get0_factors(rsa2, &p2, &q2);
+#else
+ EVP_PKEY_get_bn_param(pkey1, OSSL_PKEY_PARAM_RSA_FACTOR1, &p1);
+ EVP_PKEY_get_bn_param(pkey1, OSSL_PKEY_PARAM_RSA_FACTOR2, &q1);
+ EVP_PKEY_get_bn_param(pkey2, OSSL_PKEY_PARAM_RSA_FACTOR1, &p2);
+ EVP_PKEY_get_bn_param(pkey2, OSSL_PKEY_PARAM_RSA_FACTOR2, &q2);
+ ERR_clear_error();
+#endif /* OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000 */
+
+ if (BN_cmp(d1, d2) != 0 || BN_cmp(p1, p2) != 0 ||
+ BN_cmp(q1, q2) != 0)
+ {
+ DST_RET(false);
+ }
+ }
+
+ ret = true;
+
+err:
+#if OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000
+ if (rsa1 != NULL) {
+ RSA_free(rsa1);
+ }
+ if (rsa2 != NULL) {
+ RSA_free(rsa2);
+ }
+#else
+ if (d1 != NULL) {
+ BN_clear_free(d1);
+ }
+ if (d2 != NULL) {
+ BN_clear_free(d2);
+ }
+ if (p1 != NULL) {
+ BN_clear_free(p1);
+ }
+ if (p2 != NULL) {
+ BN_clear_free(p2);
+ }
+ if (q1 != NULL) {
+ BN_clear_free(q1);
+ }
+ if (q2 != NULL) {
+ BN_clear_free(q2);
+ }
+#endif /* OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000 */
+
+ return (ret);
+}
+
+#if OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000
+static int
+progress_cb(int p, int n, BN_GENCB *cb) {
+ union {
+ void *dptr;
+ void (*fptr)(int);
+ } u;
+
+ UNUSED(n);
+
+ u.dptr = BN_GENCB_get_arg(cb);
+ if (u.fptr != NULL) {
+ u.fptr(p);
+ }
+ return (1);
+}
+#else
+static int
+progress_cb(EVP_PKEY_CTX *ctx) {
+ union {
+ void *dptr;
+ void (*fptr)(int);
+ } u;
+
+ u.dptr = EVP_PKEY_CTX_get_app_data(ctx);
+ if (u.fptr != NULL) {
+ int p = EVP_PKEY_CTX_get_keygen_info(ctx, 0);
+ u.fptr(p);
+ }
+ return (1);
+}
+#endif /* OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000 */
+
+static isc_result_t
+opensslrsa_generate(dst_key_t *key, int exp, void (*callback)(int)) {
+ isc_result_t ret;
+ union {
+ void *dptr;
+ void (*fptr)(int);
+ } u;
+ BIGNUM *e = BN_new();
+#if OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000
+ RSA *rsa = RSA_new();
+ EVP_PKEY *pkey = EVP_PKEY_new();
+#if !HAVE_BN_GENCB_NEW
+ BN_GENCB _cb;
+#endif /* !HAVE_BN_GENCB_NEW */
+ BN_GENCB *cb = BN_GENCB_new();
+#else
+ EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new_from_name(NULL, "RSA", NULL);
+ EVP_PKEY *pkey = NULL;
+#endif /* OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000 */
+
+#if OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000
+ if (e == NULL || rsa == NULL || pkey == NULL || cb == NULL) {
+ DST_RET(dst__openssl_toresult(DST_R_OPENSSLFAILURE));
+ }
+#else
+ if (e == NULL || ctx == NULL) {
+ DST_RET(dst__openssl_toresult(DST_R_OPENSSLFAILURE));
+ }
+#endif /* OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000 */
+
+ /*
+ * Reject incorrect RSA key lengths.
+ */
+ switch (key->key_alg) {
+ case DST_ALG_RSASHA1:
+ case DST_ALG_NSEC3RSASHA1:
+ /* From RFC 3110 */
+ if (key->key_size > 4096) {
+ DST_RET(DST_R_INVALIDPARAM);
+ }
+ break;
+ case DST_ALG_RSASHA256:
+ /* From RFC 5702 */
+ if (key->key_size < 512 || key->key_size > 4096) {
+ DST_RET(DST_R_INVALIDPARAM);
+ }
+ break;
+ case DST_ALG_RSASHA512:
+ /* From RFC 5702 */
+ if (key->key_size < 1024 || key->key_size > 4096) {
+ DST_RET(DST_R_INVALIDPARAM);
+ }
+ break;
+ default:
+ UNREACHABLE();
+ }
+
+ if (exp == 0) {
+ /* RSA_F4 0x10001 */
+ BN_set_bit(e, 0);
+ BN_set_bit(e, 16);
+ } else {
+ /* (phased-out) F5 0x100000001 */
+ BN_set_bit(e, 0);
+ BN_set_bit(e, 32);
+ }
+
+#if OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000
+ if (EVP_PKEY_set1_RSA(pkey, rsa) != 1) {
+ DST_RET(dst__openssl_toresult(DST_R_OPENSSLFAILURE));
+ }
+
+ if (callback == NULL) {
+ BN_GENCB_set_old(cb, NULL, NULL);
+ } else {
+ u.fptr = callback;
+ BN_GENCB_set(cb, progress_cb, u.dptr);
+ }
+
+ if (RSA_generate_key_ex(rsa, key->key_size, e, cb) != 1) {
+ DST_RET(dst__openssl_toresult2("RSA_generate_key_ex",
+ DST_R_OPENSSLFAILURE));
+ }
+#else
+ if (EVP_PKEY_keygen_init(ctx) != 1) {
+ DST_RET(dst__openssl_toresult(DST_R_OPENSSLFAILURE));
+ }
+
+ if (EVP_PKEY_CTX_set_rsa_keygen_bits(ctx, (int)key->key_size) != 1) {
+ DST_RET(dst__openssl_toresult(DST_R_OPENSSLFAILURE));
+ }
+
+ if (EVP_PKEY_CTX_set1_rsa_keygen_pubexp(ctx, e) != 1) {
+ DST_RET(dst__openssl_toresult(DST_R_OPENSSLFAILURE));
+ }
+
+ if (callback != NULL) {
+ u.fptr = callback;
+ EVP_PKEY_CTX_set_app_data(ctx, u.dptr);
+ EVP_PKEY_CTX_set_cb(ctx, progress_cb);
+ }
+
+ if (EVP_PKEY_keygen(ctx, &pkey) != 1 || pkey == NULL) {
+ DST_RET(dst__openssl_toresult2("EVP_PKEY_keygen",
+ DST_R_OPENSSLFAILURE));
+ }
+#endif /* OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000 */
+
+ key->keydata.pkey = pkey;
+ pkey = NULL;
+ ret = ISC_R_SUCCESS;
+
+err:
+ if (pkey != NULL) {
+ EVP_PKEY_free(pkey);
+ }
+#if OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000
+ if (rsa != NULL) {
+ RSA_free(rsa);
+ }
+ if (cb != NULL) {
+ BN_GENCB_free(cb);
+ }
+#else
+ if (ctx != NULL) {
+ EVP_PKEY_CTX_free(ctx);
+ }
+#endif /* OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000 */
+ if (e != NULL) {
+ BN_free(e);
+ }
+ return (ret);
+}
+
+static bool
+opensslrsa_isprivate(const dst_key_t *key) {
+ bool ret;
+ EVP_PKEY *pkey;
+#if OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000
+ RSA *rsa;
+ const BIGNUM *d = NULL;
+#else
+ BIGNUM *d = NULL;
+#endif /* OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000 */
+
+ REQUIRE(key->key_alg == DST_ALG_RSASHA1 ||
+ key->key_alg == DST_ALG_NSEC3RSASHA1 ||
+ key->key_alg == DST_ALG_RSASHA256 ||
+ key->key_alg == DST_ALG_RSASHA512);
+
+ pkey = key->keydata.pkey;
+ if (pkey == NULL) {
+ return (false);
+ }
+
+#if OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000
+ rsa = EVP_PKEY_get1_RSA(pkey);
+ INSIST(rsa != NULL);
+
+ if (RSA_test_flags(rsa, RSA_FLAG_EXT_PKEY) != 0) {
+ ret = true;
+ } else {
+ RSA_get0_key(rsa, NULL, NULL, &d);
+ ret = (d != NULL);
+ }
+ RSA_free(rsa);
+#else
+ ret = (EVP_PKEY_get_bn_param(pkey, OSSL_PKEY_PARAM_RSA_D, &d) == 1 &&
+ d != NULL);
+ if (d != NULL) {
+ BN_clear_free(d);
+ } else {
+ ERR_clear_error();
+ }
+#endif /* OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000 */
+
+ return (ret);
+}
+
+static void
+opensslrsa_destroy(dst_key_t *key) {
+ EVP_PKEY *pkey = key->keydata.pkey;
+
+ if (pkey != NULL) {
+ EVP_PKEY_free(pkey);
+ key->keydata.pkey = NULL;
+ }
+}
+
+static isc_result_t
+opensslrsa_todns(const dst_key_t *key, isc_buffer_t *data) {
+ isc_region_t r;
+ unsigned int e_bytes;
+ unsigned int mod_bytes;
+ isc_result_t ret;
+ EVP_PKEY *pkey;
+#if OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000
+ RSA *rsa;
+ const BIGNUM *e = NULL, *n = NULL;
+#else
+ BIGNUM *e = NULL, *n = NULL;
+#endif /* OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000 */
+
+ REQUIRE(key->keydata.pkey != NULL);
+
+ pkey = key->keydata.pkey;
+ isc_buffer_availableregion(data, &r);
+
+#if OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000
+ rsa = EVP_PKEY_get1_RSA(pkey);
+ if (rsa == NULL) {
+ DST_RET(dst__openssl_toresult(DST_R_OPENSSLFAILURE));
+ }
+ RSA_get0_key(rsa, &n, &e, NULL);
+#else
+ EVP_PKEY_get_bn_param(pkey, OSSL_PKEY_PARAM_RSA_E, &e);
+ EVP_PKEY_get_bn_param(pkey, OSSL_PKEY_PARAM_RSA_N, &n);
+#endif /* OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000 */
+ if (e == NULL || n == NULL) {
+ DST_RET(dst__openssl_toresult(DST_R_OPENSSLFAILURE));
+ }
+
+ mod_bytes = BN_num_bytes(n);
+ e_bytes = BN_num_bytes(e);
+
+ if (e_bytes < 256) { /*%< key exponent is <= 2040 bits */
+ if (r.length < 1) {
+ DST_RET(ISC_R_NOSPACE);
+ }
+ isc_buffer_putuint8(data, (uint8_t)e_bytes);
+ isc_region_consume(&r, 1);
+ } else {
+ if (r.length < 3) {
+ DST_RET(ISC_R_NOSPACE);
+ }
+ isc_buffer_putuint8(data, 0);
+ isc_buffer_putuint16(data, (uint16_t)e_bytes);
+ isc_region_consume(&r, 3);
+ }
+
+ if (r.length < e_bytes + mod_bytes) {
+ DST_RET(ISC_R_NOSPACE);
+ }
+
+ BN_bn2bin(e, r.base);
+ isc_region_consume(&r, e_bytes);
+ BN_bn2bin(n, r.base);
+ isc_region_consume(&r, mod_bytes);
+
+ isc_buffer_add(data, e_bytes + mod_bytes);
+
+ ret = ISC_R_SUCCESS;
+err:
+#if OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000
+ if (rsa != NULL) {
+ RSA_free(rsa);
+ }
+#else
+ if (e != NULL) {
+ BN_free(e);
+ }
+ if (n != NULL) {
+ BN_free(n);
+ }
+#endif /* OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000 */
+ return (ret);
+}
+
+static isc_result_t
+opensslrsa_fromdns(dst_key_t *key, isc_buffer_t *data) {
+ isc_result_t ret;
+ int status;
+ isc_region_t r;
+ unsigned int e_bytes;
+ unsigned int length;
+#if OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000
+ RSA *rsa = NULL;
+#else
+ OSSL_PARAM_BLD *bld = NULL;
+ OSSL_PARAM *params = NULL;
+ EVP_PKEY_CTX *ctx = NULL;
+#endif /* OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000 */
+ EVP_PKEY *pkey = NULL;
+ BIGNUM *e = NULL, *n = NULL;
+
+ REQUIRE(key->key_alg == DST_ALG_RSASHA1 ||
+ key->key_alg == DST_ALG_NSEC3RSASHA1 ||
+ key->key_alg == DST_ALG_RSASHA256 ||
+ key->key_alg == DST_ALG_RSASHA512);
+
+ isc_buffer_remainingregion(data, &r);
+ if (r.length == 0) {
+ DST_RET(ISC_R_SUCCESS);
+ }
+ length = r.length;
+ if (r.length < 1) {
+ DST_RET(DST_R_INVALIDPUBLICKEY);
+ }
+
+ e_bytes = *r.base;
+ isc_region_consume(&r, 1);
+
+ if (e_bytes == 0) {
+ if (r.length < 2) {
+ DST_RET(DST_R_INVALIDPUBLICKEY);
+ }
+ e_bytes = (*r.base) << 8;
+ isc_region_consume(&r, 1);
+ e_bytes += *r.base;
+ isc_region_consume(&r, 1);
+ }
+
+ if (r.length < e_bytes) {
+ DST_RET(DST_R_INVALIDPUBLICKEY);
+ }
+ e = BN_bin2bn(r.base, e_bytes, NULL);
+ isc_region_consume(&r, e_bytes);
+ n = BN_bin2bn(r.base, r.length, NULL);
+ if (e == NULL || n == NULL) {
+ DST_RET(ISC_R_NOMEMORY);
+ }
+
+ key->key_size = BN_num_bits(n);
+
+ isc_buffer_forward(data, length);
+
+#if OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000
+ rsa = RSA_new();
+ if (rsa == NULL) {
+ DST_RET(dst__openssl_toresult2("RSA_new",
+ DST_R_OPENSSLFAILURE));
+ }
+ status = RSA_set0_key(rsa, n, e, NULL);
+ if (status != 1) {
+ DST_RET(dst__openssl_toresult2("RSA_set0_key",
+ DST_R_OPENSSLFAILURE));
+ }
+
+ /* These are now managed by OpenSSL. */
+ n = NULL;
+ e = NULL;
+
+ pkey = EVP_PKEY_new();
+ if (pkey == NULL) {
+ DST_RET(dst__openssl_toresult2("EVP_PKEY_new",
+ DST_R_OPENSSLFAILURE));
+ }
+ status = EVP_PKEY_set1_RSA(pkey, rsa);
+ if (status != 1) {
+ DST_RET(dst__openssl_toresult2("EVP_PKEY_set1_RSA",
+ DST_R_OPENSSLFAILURE));
+ }
+#else
+ bld = OSSL_PARAM_BLD_new();
+ if (bld == NULL) {
+ DST_RET(dst__openssl_toresult2("OSSL_PARAM_BLD_new",
+ DST_R_OPENSSLFAILURE));
+ }
+ if (OSSL_PARAM_BLD_push_BN(bld, OSSL_PKEY_PARAM_RSA_N, n) != 1 ||
+ OSSL_PARAM_BLD_push_BN(bld, OSSL_PKEY_PARAM_RSA_E, e) != 1)
+ {
+ DST_RET(dst__openssl_toresult2("OSSL_PARAM_BLD_push_BN",
+ DST_R_OPENSSLFAILURE));
+ }
+ params = OSSL_PARAM_BLD_to_param(bld);
+ if (params == NULL) {
+ DST_RET(dst__openssl_toresult2("OSSL_PARAM_BLD_to_param",
+ DST_R_OPENSSLFAILURE));
+ }
+ ctx = EVP_PKEY_CTX_new_from_name(NULL, "RSA", NULL);
+ if (ctx == NULL) {
+ DST_RET(dst__openssl_toresult2("EVP_PKEY_CTX_new_from_name",
+ DST_R_OPENSSLFAILURE));
+ }
+ status = EVP_PKEY_fromdata_init(ctx);
+ if (status != 1) {
+ DST_RET(dst__openssl_toresult2("EVP_PKEY_fromdata_init",
+ DST_R_OPENSSLFAILURE));
+ }
+ status = EVP_PKEY_fromdata(ctx, &pkey, EVP_PKEY_PUBLIC_KEY, params);
+ if (status != 1 || pkey == NULL) {
+ DST_RET(dst__openssl_toresult2("EVP_PKEY_fromdata",
+ DST_R_OPENSSLFAILURE));
+ }
+#endif /* OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000 */
+
+ key->keydata.pkey = pkey;
+ pkey = NULL;
+ ret = ISC_R_SUCCESS;
+
+err:
+
+#if OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000
+ if (rsa != NULL) {
+ RSA_free(rsa);
+ }
+#else
+ if (ctx != NULL) {
+ EVP_PKEY_CTX_free(ctx);
+ }
+ if (params != NULL) {
+ OSSL_PARAM_free(params);
+ }
+ if (bld != NULL) {
+ OSSL_PARAM_BLD_free(bld);
+ }
+#endif /* OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000 */
+ if (n != NULL) {
+ BN_free(n);
+ }
+ if (e != NULL) {
+ BN_free(e);
+ }
+ if (pkey != NULL) {
+ EVP_PKEY_free(pkey);
+ }
+
+ return (ret);
+}
+
+static isc_result_t
+opensslrsa_tofile(const dst_key_t *key, const char *directory) {
+ isc_result_t ret;
+ dst_private_t priv = { 0 };
+ unsigned char *bufs[8] = { NULL };
+ unsigned short i = 0;
+ EVP_PKEY *pkey;
+#if OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000
+ RSA *rsa = NULL;
+ const BIGNUM *n = NULL, *e = NULL, *d = NULL;
+ const BIGNUM *p = NULL, *q = NULL;
+ const BIGNUM *dmp1 = NULL, *dmq1 = NULL, *iqmp = NULL;
+#else
+ BIGNUM *n = NULL, *e = NULL, *d = NULL;
+ BIGNUM *p = NULL, *q = NULL;
+ BIGNUM *dmp1 = NULL, *dmq1 = NULL, *iqmp = NULL;
+#endif /* OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000 */
+
+ if (key->keydata.pkey == NULL) {
+ DST_RET(DST_R_NULLKEY);
+ }
+
+ if (key->external) {
+ return (dst__privstruct_writefile(key, &priv, directory));
+ }
+
+ pkey = key->keydata.pkey;
+#if OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000
+ rsa = EVP_PKEY_get1_RSA(pkey);
+ if (rsa == NULL) {
+ DST_RET(dst__openssl_toresult(DST_R_OPENSSLFAILURE));
+ }
+ RSA_get0_key(rsa, &n, &e, &d);
+ RSA_get0_factors(rsa, &p, &q);
+ RSA_get0_crt_params(rsa, &dmp1, &dmq1, &iqmp);
+#else
+ EVP_PKEY_get_bn_param(pkey, OSSL_PKEY_PARAM_RSA_N, &n);
+ EVP_PKEY_get_bn_param(pkey, OSSL_PKEY_PARAM_RSA_E, &e);
+ EVP_PKEY_get_bn_param(pkey, OSSL_PKEY_PARAM_RSA_D, &d);
+ EVP_PKEY_get_bn_param(pkey, OSSL_PKEY_PARAM_RSA_FACTOR1, &p);
+ EVP_PKEY_get_bn_param(pkey, OSSL_PKEY_PARAM_RSA_FACTOR2, &q);
+ EVP_PKEY_get_bn_param(pkey, OSSL_PKEY_PARAM_RSA_EXPONENT1, &dmp1);
+ EVP_PKEY_get_bn_param(pkey, OSSL_PKEY_PARAM_RSA_EXPONENT2, &dmq1);
+ EVP_PKEY_get_bn_param(pkey, OSSL_PKEY_PARAM_RSA_COEFFICIENT1, &iqmp);
+ ERR_clear_error();
+#endif /* OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000 */
+
+ if (n == NULL || e == NULL) {
+ DST_RET(dst__openssl_toresult(DST_R_OPENSSLFAILURE));
+ }
+
+ priv.elements[i].tag = TAG_RSA_MODULUS;
+ priv.elements[i].length = BN_num_bytes(n);
+ bufs[i] = isc_mem_get(key->mctx, priv.elements[i].length);
+ BN_bn2bin(n, bufs[i]);
+ priv.elements[i].data = bufs[i];
+ i++;
+
+ priv.elements[i].tag = TAG_RSA_PUBLICEXPONENT;
+ priv.elements[i].length = BN_num_bytes(e);
+ bufs[i] = isc_mem_get(key->mctx, priv.elements[i].length);
+ BN_bn2bin(e, bufs[i]);
+ priv.elements[i].data = bufs[i];
+ i++;
+
+ if (d != NULL) {
+ priv.elements[i].tag = TAG_RSA_PRIVATEEXPONENT;
+ priv.elements[i].length = BN_num_bytes(d);
+ INSIST(i < ARRAY_SIZE(bufs));
+ bufs[i] = isc_mem_get(key->mctx, priv.elements[i].length);
+ BN_bn2bin(d, bufs[i]);
+ priv.elements[i].data = bufs[i];
+ i++;
+ }
+
+ if (p != NULL) {
+ priv.elements[i].tag = TAG_RSA_PRIME1;
+ priv.elements[i].length = BN_num_bytes(p);
+ INSIST(i < ARRAY_SIZE(bufs));
+ bufs[i] = isc_mem_get(key->mctx, priv.elements[i].length);
+ BN_bn2bin(p, bufs[i]);
+ priv.elements[i].data = bufs[i];
+ i++;
+ }
+
+ if (q != NULL) {
+ priv.elements[i].tag = TAG_RSA_PRIME2;
+ priv.elements[i].length = BN_num_bytes(q);
+ INSIST(i < ARRAY_SIZE(bufs));
+ bufs[i] = isc_mem_get(key->mctx, priv.elements[i].length);
+ BN_bn2bin(q, bufs[i]);
+ priv.elements[i].data = bufs[i];
+ i++;
+ }
+
+ if (dmp1 != NULL) {
+ priv.elements[i].tag = TAG_RSA_EXPONENT1;
+ priv.elements[i].length = BN_num_bytes(dmp1);
+ INSIST(i < ARRAY_SIZE(bufs));
+ bufs[i] = isc_mem_get(key->mctx, priv.elements[i].length);
+ BN_bn2bin(dmp1, bufs[i]);
+ priv.elements[i].data = bufs[i];
+ i++;
+ }
+
+ if (dmq1 != NULL) {
+ priv.elements[i].tag = TAG_RSA_EXPONENT2;
+ priv.elements[i].length = BN_num_bytes(dmq1);
+ INSIST(i < ARRAY_SIZE(bufs));
+ bufs[i] = isc_mem_get(key->mctx, priv.elements[i].length);
+ BN_bn2bin(dmq1, bufs[i]);
+ priv.elements[i].data = bufs[i];
+ i++;
+ }
+
+ if (iqmp != NULL) {
+ priv.elements[i].tag = TAG_RSA_COEFFICIENT;
+ priv.elements[i].length = BN_num_bytes(iqmp);
+ INSIST(i < ARRAY_SIZE(bufs));
+ bufs[i] = isc_mem_get(key->mctx, priv.elements[i].length);
+ BN_bn2bin(iqmp, bufs[i]);
+ priv.elements[i].data = bufs[i];
+ i++;
+ }
+
+ if (key->engine != NULL) {
+ priv.elements[i].tag = TAG_RSA_ENGINE;
+ priv.elements[i].length = (unsigned short)strlen(key->engine) +
+ 1;
+ priv.elements[i].data = (unsigned char *)key->engine;
+ i++;
+ }
+
+ if (key->label != NULL) {
+ priv.elements[i].tag = TAG_RSA_LABEL;
+ priv.elements[i].length = (unsigned short)strlen(key->label) +
+ 1;
+ priv.elements[i].data = (unsigned char *)key->label;
+ i++;
+ }
+
+ priv.nelements = i;
+ ret = dst__privstruct_writefile(key, &priv, directory);
+
+err:
+ for (i = 0; i < ARRAY_SIZE(bufs); i++) {
+ if (bufs[i] != NULL) {
+ isc_mem_put(key->mctx, bufs[i],
+ priv.elements[i].length);
+ }
+ }
+#if OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000
+ RSA_free(rsa);
+#else
+ if (n != NULL) {
+ BN_free(n);
+ }
+ if (e != NULL) {
+ BN_free(e);
+ }
+ if (d != NULL) {
+ BN_clear_free(d);
+ }
+ if (p != NULL) {
+ BN_clear_free(p);
+ }
+ if (q != NULL) {
+ BN_clear_free(q);
+ }
+ if (dmp1 != NULL) {
+ BN_clear_free(dmp1);
+ }
+ if (dmq1 != NULL) {
+ BN_clear_free(dmq1);
+ }
+ if (iqmp != NULL) {
+ BN_clear_free(iqmp);
+ }
+#endif /* OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000 */
+
+ return (ret);
+}
+
+#if OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000
+static isc_result_t
+rsa_check(RSA *rsa, RSA *pub) {
+ const BIGNUM *n1 = NULL, *n2 = NULL;
+ const BIGNUM *e1 = NULL, *e2 = NULL;
+ BIGNUM *n = NULL, *e = NULL;
+
+ /*
+ * Public parameters should be the same but if they are not set
+ * copy them from the public key.
+ */
+ if (pub != NULL) {
+ RSA_get0_key(rsa, &n1, &e1, NULL);
+ RSA_get0_key(pub, &n2, &e2, NULL);
+ if (n1 != NULL) {
+ if (BN_cmp(n1, n2) != 0) {
+ return (DST_R_INVALIDPRIVATEKEY);
+ }
+ } else {
+ n = BN_dup(n2);
+ if (n == NULL) {
+ return (ISC_R_NOMEMORY);
+ }
+ }
+ if (e1 != NULL) {
+ if (BN_cmp(e1, e2) != 0) {
+ if (n != NULL) {
+ BN_free(n);
+ }
+ return (DST_R_INVALIDPRIVATEKEY);
+ }
+ } else {
+ e = BN_dup(e2);
+ if (e == NULL) {
+ if (n != NULL) {
+ BN_free(n);
+ }
+ return (ISC_R_NOMEMORY);
+ }
+ }
+ if (RSA_set0_key(rsa, n, e, NULL) == 0) {
+ if (n != NULL) {
+ BN_free(n);
+ }
+ if (e != NULL) {
+ BN_free(e);
+ }
+ }
+ }
+
+ RSA_get0_key(rsa, &n1, &e1, NULL);
+ if (n1 == NULL || e1 == NULL) {
+ return (DST_R_INVALIDPRIVATEKEY);
+ }
+
+ return (ISC_R_SUCCESS);
+}
+#else
+static isc_result_t
+rsa_check(EVP_PKEY *pkey, EVP_PKEY *pubpkey) {
+ isc_result_t ret = ISC_R_FAILURE;
+ int status;
+ BIGNUM *n1 = NULL, *n2 = NULL;
+ BIGNUM *e1 = NULL, *e2 = NULL;
+
+ /* Try to get the public key from pkey. */
+ EVP_PKEY_get_bn_param(pkey, OSSL_PKEY_PARAM_RSA_N, &n1);
+ EVP_PKEY_get_bn_param(pkey, OSSL_PKEY_PARAM_RSA_E, &e1);
+
+ /* Check if `pubpkey` exists and that we can extract its public key. */
+ if (pubpkey == NULL ||
+ EVP_PKEY_get_bn_param(pubpkey, OSSL_PKEY_PARAM_RSA_N, &n2) != 1 ||
+ n2 == NULL ||
+ EVP_PKEY_get_bn_param(pubpkey, OSSL_PKEY_PARAM_RSA_E, &e2) != 1 ||
+ e2 == NULL)
+ {
+ if (n1 == NULL || e1 == NULL) {
+ /* No public key both in `pkey` and in `pubpkey`. */
+ DST_RET(DST_R_INVALIDPRIVATEKEY);
+ } else {
+ /*
+ * `pkey` has a public key, but there is no public key
+ * in `pubpkey` to check against.
+ */
+ DST_RET(ISC_R_SUCCESS);
+ }
+ }
+
+ /*
+ * If `pkey` doesn't have a public key then we will copy it from
+ * `pubpkey`.
+ */
+ if (n1 == NULL || e1 == NULL) {
+ status = EVP_PKEY_set_bn_param(pkey, OSSL_PKEY_PARAM_RSA_N, n2);
+ if (status != 1) {
+ DST_RET(ISC_R_FAILURE);
+ }
+
+ status = EVP_PKEY_set_bn_param(pkey, OSSL_PKEY_PARAM_RSA_E, e2);
+ if (status != 1) {
+ DST_RET(ISC_R_FAILURE);
+ }
+ }
+
+ if (EVP_PKEY_eq(pkey, pubpkey) == 1) {
+ DST_RET(ISC_R_SUCCESS);
+ }
+
+err:
+ if (n1 != NULL) {
+ BN_free(n1);
+ }
+ if (n2 != NULL) {
+ BN_free(n2);
+ }
+ if (e1 != NULL) {
+ BN_free(e1);
+ }
+ if (e2 != NULL) {
+ BN_free(e2);
+ }
+
+ return (ret);
+}
+#endif /* OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000 */
+
+static isc_result_t
+opensslrsa_parse(dst_key_t *key, isc_lex_t *lexer, dst_key_t *pub) {
+ dst_private_t priv;
+ isc_result_t ret;
+ int i;
+#if OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000
+ RSA *rsa = NULL, *pubrsa = NULL;
+#else
+ OSSL_PARAM_BLD *bld = NULL;
+ OSSL_PARAM *params = NULL;
+ EVP_PKEY_CTX *ctx = NULL;
+#endif /* OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000 */
+#if !defined(OPENSSL_NO_ENGINE) && OPENSSL_API_LEVEL < 30000
+ const BIGNUM *ex = NULL;
+ ENGINE *ep = NULL;
+#endif /* if !defined(OPENSSL_NO_ENGINE) && OPENSSL_API_LEVEL < 30000 */
+ isc_mem_t *mctx = NULL;
+ const char *engine = NULL, *label = NULL;
+ EVP_PKEY *pkey = NULL;
+ BIGNUM *n = NULL, *e = NULL, *d = NULL;
+ BIGNUM *p = NULL, *q = NULL;
+ BIGNUM *dmp1 = NULL, *dmq1 = NULL, *iqmp = NULL;
+
+ REQUIRE(key != NULL);
+ REQUIRE(key->key_alg == DST_ALG_RSASHA1 ||
+ key->key_alg == DST_ALG_NSEC3RSASHA1 ||
+ key->key_alg == DST_ALG_RSASHA256 ||
+ key->key_alg == DST_ALG_RSASHA512);
+
+ mctx = key->mctx;
+
+ /* read private key file */
+ ret = dst__privstruct_parse(key, DST_ALG_RSA, lexer, mctx, &priv);
+ if (ret != ISC_R_SUCCESS) {
+ goto err;
+ }
+
+ if (key->external) {
+ if (priv.nelements != 0 || pub == NULL) {
+ DST_RET(DST_R_INVALIDPRIVATEKEY);
+ }
+ key->keydata.pkey = pub->keydata.pkey;
+ pub->keydata.pkey = NULL;
+ key->key_size = pub->key_size;
+ DST_RET(ISC_R_SUCCESS);
+ }
+
+#if OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000
+ if (pub != NULL && pub->keydata.pkey != NULL) {
+ pubrsa = EVP_PKEY_get1_RSA(pub->keydata.pkey);
+ }
+#endif /* OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000 */
+
+ for (i = 0; i < priv.nelements; i++) {
+ switch (priv.elements[i].tag) {
+ case TAG_RSA_ENGINE:
+ engine = (char *)priv.elements[i].data;
+ break;
+ case TAG_RSA_LABEL:
+ label = (char *)priv.elements[i].data;
+ break;
+ default:
+ break;
+ }
+ }
+
+ /*
+ * Is this key stored in a HSM?
+ * See if we can fetch it.
+ */
+ if (label != NULL) {
+#if !defined(OPENSSL_NO_ENGINE) && OPENSSL_API_LEVEL < 30000
+ if (engine == NULL) {
+ DST_RET(DST_R_NOENGINE);
+ }
+ ep = dst__openssl_getengine(engine);
+ if (ep == NULL) {
+ DST_RET(dst__openssl_toresult(DST_R_NOENGINE));
+ }
+ pkey = ENGINE_load_private_key(ep, label, NULL, NULL);
+ if (pkey == NULL) {
+ DST_RET(dst__openssl_toresult2("ENGINE_load_private_"
+ "key",
+ ISC_R_NOTFOUND));
+ }
+ key->engine = isc_mem_strdup(key->mctx, engine);
+ key->label = isc_mem_strdup(key->mctx, label);
+
+ rsa = EVP_PKEY_get1_RSA(pkey);
+ if (rsa == NULL) {
+ DST_RET(dst__openssl_toresult(DST_R_OPENSSLFAILURE));
+ }
+ if (rsa_check(rsa, pubrsa) != ISC_R_SUCCESS) {
+ DST_RET(dst__openssl_toresult(DST_R_INVALIDPRIVATEKEY));
+ }
+ RSA_get0_key(rsa, NULL, &ex, NULL);
+
+ if (ex == NULL) {
+ DST_RET(dst__openssl_toresult(DST_R_INVALIDPRIVATEKEY));
+ }
+ if (BN_num_bits(ex) > RSA_MAX_PUBEXP_BITS) {
+ DST_RET(ISC_R_RANGE);
+ }
+
+ key->key_size = EVP_PKEY_bits(pkey);
+ key->keydata.pkey = pkey;
+ pkey = NULL;
+ DST_RET(ISC_R_SUCCESS);
+#else /* if !defined(OPENSSL_NO_ENGINE) && OPENSSL_API_LEVEL < 30000 */
+ UNUSED(engine);
+ DST_RET(DST_R_NOENGINE);
+#endif /* if !defined(OPENSSL_NO_ENGINE) && OPENSSL_API_LEVEL < 30000 */
+ }
+
+ for (i = 0; i < priv.nelements; i++) {
+ BIGNUM *bn;
+ switch (priv.elements[i].tag) {
+ case TAG_RSA_ENGINE:
+ continue;
+ case TAG_RSA_LABEL:
+ continue;
+ default:
+ bn = BN_bin2bn(priv.elements[i].data,
+ priv.elements[i].length, NULL);
+ if (bn == NULL) {
+ DST_RET(ISC_R_NOMEMORY);
+ }
+ switch (priv.elements[i].tag) {
+ case TAG_RSA_MODULUS:
+ n = bn;
+ break;
+ case TAG_RSA_PUBLICEXPONENT:
+ e = bn;
+ break;
+ case TAG_RSA_PRIVATEEXPONENT:
+ d = bn;
+ break;
+ case TAG_RSA_PRIME1:
+ p = bn;
+ break;
+ case TAG_RSA_PRIME2:
+ q = bn;
+ break;
+ case TAG_RSA_EXPONENT1:
+ dmp1 = bn;
+ break;
+ case TAG_RSA_EXPONENT2:
+ dmq1 = bn;
+ break;
+ case TAG_RSA_COEFFICIENT:
+ iqmp = bn;
+ break;
+ default:
+ BN_clear_free(bn);
+ }
+ }
+ }
+
+#if OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000
+ rsa = RSA_new();
+ if (rsa == NULL) {
+ DST_RET(ISC_R_NOMEMORY);
+ }
+ pkey = EVP_PKEY_new();
+ if (pkey == NULL) {
+ DST_RET(ISC_R_NOMEMORY);
+ }
+ if (EVP_PKEY_set1_RSA(pkey, rsa) != 1) {
+ DST_RET(dst__openssl_toresult(DST_R_OPENSSLFAILURE));
+ }
+ if (RSA_set0_key(rsa, n, e, d) == 0) {
+ if (n != NULL) {
+ BN_free(n);
+ }
+ if (e != NULL) {
+ BN_free(e);
+ }
+ if (d != NULL) {
+ BN_clear_free(d);
+ }
+ }
+ if (RSA_set0_factors(rsa, p, q) == 0) {
+ if (p != NULL) {
+ BN_clear_free(p);
+ }
+ if (q != NULL) {
+ BN_clear_free(q);
+ }
+ }
+ if (RSA_set0_crt_params(rsa, dmp1, dmq1, iqmp) == 0) {
+ if (dmp1 != NULL) {
+ BN_clear_free(dmp1);
+ }
+ if (dmq1 != NULL) {
+ BN_clear_free(dmq1);
+ }
+ if (iqmp != NULL) {
+ BN_clear_free(iqmp);
+ }
+ }
+ if (rsa_check(rsa, pubrsa) != ISC_R_SUCCESS) {
+ DST_RET(dst__openssl_toresult(DST_R_INVALIDPRIVATEKEY));
+ }
+#else
+ bld = OSSL_PARAM_BLD_new();
+ if (bld == NULL) {
+ DST_RET(dst__openssl_toresult(DST_R_OPENSSLFAILURE));
+ }
+
+ if (n != NULL &&
+ OSSL_PARAM_BLD_push_BN(bld, OSSL_PKEY_PARAM_RSA_N, n) != 1)
+ {
+ DST_RET(dst__openssl_toresult(DST_R_OPENSSLFAILURE));
+ }
+ if (e != NULL &&
+ OSSL_PARAM_BLD_push_BN(bld, OSSL_PKEY_PARAM_RSA_E, e) != 1)
+ {
+ DST_RET(dst__openssl_toresult(DST_R_OPENSSLFAILURE));
+ }
+ if (d != NULL &&
+ OSSL_PARAM_BLD_push_BN(bld, OSSL_PKEY_PARAM_RSA_D, d) != 1)
+ {
+ DST_RET(dst__openssl_toresult(DST_R_OPENSSLFAILURE));
+ }
+ if (p != NULL &&
+ OSSL_PARAM_BLD_push_BN(bld, OSSL_PKEY_PARAM_RSA_FACTOR1, p) != 1)
+ {
+ DST_RET(dst__openssl_toresult(DST_R_OPENSSLFAILURE));
+ }
+ if (q != NULL &&
+ OSSL_PARAM_BLD_push_BN(bld, OSSL_PKEY_PARAM_RSA_FACTOR2, q) != 1)
+ {
+ DST_RET(dst__openssl_toresult(DST_R_OPENSSLFAILURE));
+ }
+ if (dmp1 != NULL &&
+ OSSL_PARAM_BLD_push_BN(bld, OSSL_PKEY_PARAM_RSA_EXPONENT1, dmp1) !=
+ 1)
+ {
+ DST_RET(dst__openssl_toresult(DST_R_OPENSSLFAILURE));
+ }
+ if (dmq1 != NULL &&
+ OSSL_PARAM_BLD_push_BN(bld, OSSL_PKEY_PARAM_RSA_EXPONENT2, dmq1) !=
+ 1)
+ {
+ DST_RET(dst__openssl_toresult(DST_R_OPENSSLFAILURE));
+ }
+ if (iqmp != NULL &&
+ OSSL_PARAM_BLD_push_BN(bld, OSSL_PKEY_PARAM_RSA_COEFFICIENT1,
+ iqmp) != 1)
+ {
+ DST_RET(dst__openssl_toresult(DST_R_OPENSSLFAILURE));
+ }
+
+ params = OSSL_PARAM_BLD_to_param(bld);
+ if (params == NULL) {
+ DST_RET(dst__openssl_toresult(DST_R_OPENSSLFAILURE));
+ }
+
+ ctx = EVP_PKEY_CTX_new_from_name(NULL, "RSA", NULL);
+ if (ctx == NULL || EVP_PKEY_fromdata_init(ctx) != 1) {
+ DST_RET(dst__openssl_toresult(DST_R_OPENSSLFAILURE));
+ }
+
+ if (EVP_PKEY_fromdata(ctx, &pkey, EVP_PKEY_KEYPAIR, params) != 1 ||
+ pkey == NULL)
+ {
+ DST_RET(dst__openssl_toresult(DST_R_OPENSSLFAILURE));
+ }
+
+ if (rsa_check(pkey, pub != NULL ? pub->keydata.pkey : NULL) !=
+ ISC_R_SUCCESS)
+ {
+ DST_RET(dst__openssl_toresult(DST_R_INVALIDPRIVATEKEY));
+ }
+#endif /* OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000 */
+
+ if (BN_num_bits(e) > RSA_MAX_PUBEXP_BITS) {
+ DST_RET(ISC_R_RANGE);
+ }
+
+ key->key_size = BN_num_bits(n);
+ key->keydata.pkey = pkey;
+ pkey = NULL;
+
+err:
+ if (pkey != NULL) {
+ EVP_PKEY_free(pkey);
+ }
+#if OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000
+ if (rsa != NULL) {
+ RSA_free(rsa);
+ }
+ if (pubrsa != NULL) {
+ RSA_free(pubrsa);
+ }
+#else
+ if (ctx != NULL) {
+ EVP_PKEY_CTX_free(ctx);
+ }
+ if (params != NULL) {
+ OSSL_PARAM_free(params);
+ }
+ if (bld != NULL) {
+ OSSL_PARAM_BLD_free(bld);
+ }
+ if (e != NULL) {
+ BN_free(e);
+ }
+ if (n != NULL) {
+ BN_free(n);
+ }
+ if (d != NULL) {
+ BN_clear_free(d);
+ }
+ if (p != NULL) {
+ BN_clear_free(p);
+ }
+ if (q != NULL) {
+ BN_clear_free(q);
+ }
+ if (dmp1 != NULL) {
+ BN_clear_free(dmp1);
+ }
+ if (dmq1 != NULL) {
+ BN_clear_free(dmq1);
+ }
+ if (iqmp != NULL) {
+ BN_clear_free(iqmp);
+ }
+#endif /* OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000 */
+ if (ret != ISC_R_SUCCESS) {
+ key->keydata.generic = NULL;
+ }
+
+ dst__privstruct_free(&priv, mctx);
+ isc_safe_memwipe(&priv, sizeof(priv));
+
+ return (ret);
+}
+
+static isc_result_t
+opensslrsa_fromlabel(dst_key_t *key, const char *engine, const char *label,
+ const char *pin) {
+#if !defined(OPENSSL_NO_ENGINE) && OPENSSL_API_LEVEL < 30000
+ ENGINE *e = NULL;
+ isc_result_t ret = ISC_R_SUCCESS;
+ EVP_PKEY *pkey = NULL, *pubpkey = NULL;
+ RSA *rsa = NULL, *pubrsa = NULL;
+ const BIGNUM *ex = NULL;
+
+ UNUSED(pin);
+
+ if (engine == NULL) {
+ DST_RET(DST_R_NOENGINE);
+ }
+ e = dst__openssl_getengine(engine);
+ if (e == NULL) {
+ DST_RET(dst__openssl_toresult(DST_R_NOENGINE));
+ }
+
+ pubpkey = ENGINE_load_public_key(e, label, NULL, NULL);
+ if (pubpkey == NULL) {
+ DST_RET(dst__openssl_toresult2("ENGINE_load_public_key",
+ DST_R_OPENSSLFAILURE));
+ }
+ pubrsa = EVP_PKEY_get1_RSA(pubpkey);
+ if (pubrsa == NULL) {
+ DST_RET(dst__openssl_toresult(DST_R_OPENSSLFAILURE));
+ }
+
+ pkey = ENGINE_load_private_key(e, label, NULL, NULL);
+ if (pkey == NULL) {
+ DST_RET(dst__openssl_toresult2("ENGINE_load_private_key",
+ DST_R_OPENSSLFAILURE));
+ }
+
+ key->engine = isc_mem_strdup(key->mctx, engine);
+ key->label = isc_mem_strdup(key->mctx, label);
+
+ rsa = EVP_PKEY_get1_RSA(pkey);
+ if (rsa == NULL) {
+ DST_RET(dst__openssl_toresult(DST_R_OPENSSLFAILURE));
+ }
+ if (rsa_check(rsa, pubrsa) != ISC_R_SUCCESS) {
+ DST_RET(dst__openssl_toresult(DST_R_INVALIDPRIVATEKEY));
+ }
+ RSA_get0_key(rsa, NULL, &ex, NULL);
+
+ if (ex == NULL) {
+ DST_RET(dst__openssl_toresult(DST_R_INVALIDPRIVATEKEY));
+ }
+ if (BN_num_bits(ex) > RSA_MAX_PUBEXP_BITS) {
+ DST_RET(ISC_R_RANGE);
+ }
+
+ key->key_size = EVP_PKEY_bits(pkey);
+ key->keydata.pkey = pkey;
+ pkey = NULL;
+
+err:
+ if (rsa != NULL) {
+ RSA_free(rsa);
+ }
+ if (pubrsa != NULL) {
+ RSA_free(pubrsa);
+ }
+ if (pkey != NULL) {
+ EVP_PKEY_free(pkey);
+ }
+ if (pubpkey != NULL) {
+ EVP_PKEY_free(pubpkey);
+ }
+ return (ret);
+#else /* if !defined(OPENSSL_NO_ENGINE) && OPENSSL_API_LEVEL < 30000 */
+ UNUSED(key);
+ UNUSED(engine);
+ UNUSED(label);
+ UNUSED(pin);
+ return (DST_R_NOENGINE);
+#endif /* if !defined(OPENSSL_NO_ENGINE) && OPENSSL_API_LEVEL < 30000 */
+}
+
+static dst_func_t opensslrsa_functions = {
+ opensslrsa_createctx,
+ NULL, /*%< createctx2 */
+ opensslrsa_destroyctx,
+ opensslrsa_adddata,
+ opensslrsa_sign,
+ opensslrsa_verify,
+ opensslrsa_verify2,
+ NULL, /*%< computesecret */
+ opensslrsa_compare,
+ NULL, /*%< paramcompare */
+ opensslrsa_generate,
+ opensslrsa_isprivate,
+ opensslrsa_destroy,
+ opensslrsa_todns,
+ opensslrsa_fromdns,
+ opensslrsa_tofile,
+ opensslrsa_parse,
+ NULL, /*%< cleanup */
+ opensslrsa_fromlabel,
+ NULL, /*%< dump */
+ NULL, /*%< restore */
+};
+
+/*
+ * An RSA public key with 2048 bits
+ */
+static const unsigned char e_bytes[] = "\x01\x00\x01";
+static const unsigned char n_bytes[] =
+ "\xc3\x90\x07\xbe\xf1\x85\xfc\x1a\x43\xb1\xa5\x15\xce\x71\x34\xfc\xc1"
+ "\x87\x27\x28\x38\xa4\xcf\x7c\x1a\x82\xa8\xdc\x04\x14\xd0\x3f\xb4\xfe"
+ "\x20\x4a\xdd\xd9\x0d\xd7\xcd\x61\x8c\xbd\x61\xa8\x10\xb5\x63\x1c\x29"
+ "\x15\xcb\x41\xee\x43\x91\x7f\xeb\xa5\x2c\xab\x81\x75\x0d\xa3\x3d\xe4"
+ "\xc8\x49\xb9\xca\x5a\x55\xa1\xbb\x09\xd1\xfb\xcd\xa2\xd2\x12\xa4\x85"
+ "\xdf\xa5\x65\xc9\x27\x2d\x8b\xd7\x8b\xfe\x6d\xc4\xd1\xd9\x83\x1c\x91"
+ "\x7d\x3d\xd0\xa4\xcd\xe1\xe7\xb9\x7a\x11\x38\xf9\x8b\x3c\xec\x30\xb6"
+ "\x36\xb9\x92\x64\x81\x56\x3c\xbc\xf9\x49\xfb\xba\x82\xb7\xa0\xfa\x65"
+ "\x79\x83\xb9\x4c\xa7\xfd\x53\x0b\x5a\xe4\xde\xf9\xfc\x38\x7e\xb5\x2c"
+ "\xa0\xc3\xb2\xfc\x7c\x38\xb0\x63\x50\xaf\x00\xaa\xb2\xad\x49\x54\x1e"
+ "\x8b\x11\x88\x9b\x6e\xae\x3b\x23\xa3\xdd\x53\x51\x80\x7a\x0b\x91\x4e"
+ "\x6d\x32\x01\xbd\x17\x81\x12\x64\x9f\x84\xae\x76\x53\x1a\x63\xa0\xda"
+ "\xcc\x45\x04\x72\xb0\xa7\xfb\xfa\x02\x39\x53\xc1\x83\x1f\x88\x54\x47"
+ "\x88\x63\x20\x71\x5d\xe2\xaa\x7c\x53\x39\x5e\x35\x25\xee\xe6\x5c\x15"
+ "\x5e\x14\xbe\x99\xde\x25\x19\xe7\x13\xdb\xce\xa3\xd3\x6c\x5c\xbb\x0e"
+ "\x6b";
+
+static const unsigned char sha1_sig[] =
+ "\x69\x99\x89\x28\xe0\x38\x34\x91\x29\xb6\xac\x4b\xe9\x51\xbd\xbe\xc8"
+ "\x1a\x2d\xb6\xca\x99\xa3\x9f\x6a\x8b\x94\x5a\x51\x37\xd5\x8d\xae\x87"
+ "\xed\xbc\x8e\xb8\xa3\x60\x6b\xf6\xe6\x72\xfc\x26\x2a\x39\x2b\xfe\x88"
+ "\x1a\xa9\xd1\x93\xc7\xb9\xf8\xb6\x45\xa1\xf9\xa1\x56\x78\x7b\x00\xec"
+ "\x33\x83\xd4\x93\x25\x48\xb3\x50\x09\xd0\xbc\x7f\xac\x67\xc7\xa2\x7f"
+ "\xfc\xf6\x5a\xef\xf8\x5a\xad\x52\x74\xf5\x71\x34\xd9\x3d\x33\x8b\x4d"
+ "\x99\x64\x7e\x14\x59\xbe\xdf\x26\x8a\x67\x96\x6c\x1f\x79\x85\x10\x0d"
+ "\x7f\xd6\xa4\xba\x57\x41\x03\x71\x4e\x8c\x17\xd5\xc4\xfb\x4a\xbe\x66"
+ "\x45\x15\x45\x0c\x02\xe0\x10\xe1\xbb\x33\x8d\x90\x34\x3c\x94\xa4\x4c"
+ "\x7c\xd0\x5e\x90\x76\x80\x59\xb2\xfa\x54\xbf\xa9\x86\xb8\x84\x1e\x28"
+ "\x48\x60\x2f\x9e\xa4\xbc\xd4\x9c\x20\x27\x16\xac\x33\xcb\xcf\xab\x93"
+ "\x7a\x3b\x74\xa0\x18\x92\xa1\x4f\xfc\x52\x19\xee\x7a\x13\x73\xba\x36"
+ "\xaf\x78\x5d\xb6\x1f\x96\x76\x15\x73\xee\x04\xa8\x70\x27\xf7\xe7\xfa"
+ "\xe8\xf6\xc8\x5f\x4a\x81\x56\x0a\x94\xf3\xc6\x98\xd2\x93\xc4\x0b\x49"
+ "\x6b\x44\xd3\x73\xa2\xe3\xef\x5d\x9e\x68\xac\xa7\x42\xb1\xbb\x65\xbe"
+ "\x59";
+
+static const unsigned char sha256_sig[] =
+ "\x0f\x8c\xdb\xe6\xb6\x21\xc8\xc5\x28\x76\x7d\xf6\xf2\x3b\x78\x47\x77"
+ "\x03\x34\xc5\x5e\xc0\xda\x42\x41\xc0\x0f\x97\xd3\xd0\x53\xa1\xd6\x87"
+ "\xe4\x16\x29\x9a\xa5\x59\xf4\x01\xad\xc9\x04\xe7\x61\xe2\xcb\x79\x73"
+ "\xce\xe0\xa6\x85\xe5\x10\x8c\x4b\xc5\x68\x3b\x96\x42\x3f\x56\xb3\x6d"
+ "\x89\xc4\xff\x72\x36\xf2\x3f\xed\xe9\xb8\xe3\xae\xab\x3c\xb7\xaa\xf7"
+ "\x1f\x8f\x26\x6b\xee\xc1\xac\x72\x89\x23\x8b\x7a\xd7\x8c\x84\xf3\xf5"
+ "\x97\xa8\x8d\xd3\xef\xb2\x5e\x06\x04\x21\xdd\x28\xa2\x28\x83\x68\x9b"
+ "\xac\x34\xdd\x36\x33\xda\xdd\xa4\x59\xc7\x5a\x4d\xf3\x83\x06\xd5\xc0"
+ "\x0d\x1f\x4f\x47\x2f\x9f\xcc\xc2\x0d\x21\x1e\x82\xb9\x3d\xf3\xa4\x1a"
+ "\xa6\xd8\x0e\x72\x1d\x71\x17\x1c\x54\xad\x37\x3e\xa4\x0e\x70\x86\x53"
+ "\xfb\x40\xad\xb9\x14\xf8\x8d\x93\xbb\xd7\xe7\x31\xce\xe0\x98\xda\x27"
+ "\x1c\x18\x8e\xd8\x85\xcb\xa7\xb1\x18\xac\x8c\xa8\x9d\xa9\xe2\xf6\x30"
+ "\x95\xa4\x81\xf4\x1c\xa0\x31\xd5\xc7\x9d\x28\x33\xee\x7f\x08\x4f\xcb"
+ "\xd1\x14\x17\xdf\xd0\x88\x78\x47\x29\xaf\x6c\xb2\x62\xa6\x30\x87\x29"
+ "\xaa\x80\x19\x7d\x2f\x05\xe3\x7e\x23\x73\x88\x08\xcc\xbd\x50\x46\x09"
+ "\x2a";
+
+static const unsigned char sha512_sig[] =
+ "\x15\xda\x87\x87\x1f\x76\x08\xd3\x9d\x3a\xb9\xd2\x6a\x0e\x3b\x7d\xdd"
+ "\xec\x7d\xc4\x6d\x26\xf5\x04\xd3\x76\xc7\x83\xc4\x81\x69\x35\xe9\x47"
+ "\xbf\x49\xd1\xc0\xf9\x01\x4e\x0a\x34\x5b\xd0\xec\x6e\xe2\x2e\xe9\x2d"
+ "\x00\xfd\xe0\xa0\x28\x54\x53\x19\x49\x6d\xd2\x58\xb9\x47\xfa\x45\xad"
+ "\xd2\x1d\x52\xac\x80\xcb\xfc\x91\x97\x84\x58\x5f\xab\x21\x62\x60\x79"
+ "\xb8\x8a\x83\xe1\xf1\xcb\x05\x4c\x92\x56\x62\xd9\xbf\xa7\x81\x34\x23"
+ "\xdf\xd7\xa7\xc4\xdf\xde\x96\x00\x57\x4b\x78\x85\xb9\x3b\xdd\x3f\x98"
+ "\x88\x59\x1d\x48\xcf\x5a\xa8\xb7\x2a\x8b\x77\x93\x8e\x38\x3a\x0c\xa7"
+ "\x8a\x5f\xe6\x9f\xcb\xf0\x9a\x6b\xb6\x91\x04\x8b\x69\x6a\x37\xee\xa2"
+ "\xad\x5f\x31\x20\x96\xd6\x51\x80\xbf\x62\x48\xb8\xe4\x94\x10\x86\x4e"
+ "\xf2\x22\x1e\xa4\xd5\x54\xfe\xe1\x35\x49\xaf\xf8\x62\xfc\x11\xeb\xf7"
+ "\x3d\xd5\x5e\xaf\x11\xbd\x3d\xa9\x3a\x9f\x7f\xe8\xb4\x0d\xa2\xbb\x1c"
+ "\xbd\x4c\xed\x9e\x81\xb1\xec\xd3\xea\xaa\x03\xe3\x14\xdf\x8c\xb3\x78"
+ "\x85\x5e\x87\xad\xec\x41\x1a\xa9\x4f\xd2\xe6\xc6\xbe\xfa\xb8\x10\xea"
+ "\x74\x25\x36\x0c\x23\xe2\x24\xb7\x21\xb7\x0d\xaf\xf6\xb4\x31\xf5\x75"
+ "\xf1";
+
+static isc_result_t
+check_algorithm(unsigned char algorithm) {
+ BIGNUM *n = NULL, *e = NULL;
+ EVP_MD_CTX *evp_md_ctx = EVP_MD_CTX_create();
+ EVP_PKEY *pkey = NULL;
+ const EVP_MD *type = NULL;
+ const unsigned char *sig = NULL;
+ int status;
+ isc_result_t ret = ISC_R_SUCCESS;
+ size_t len;
+#if OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000
+ RSA *rsa = NULL;
+#else
+ OSSL_PARAM *params = NULL;
+ OSSL_PARAM_BLD *bld = NULL;
+ EVP_PKEY_CTX *ctx = NULL;
+#endif
+
+ if (evp_md_ctx == NULL) {
+ DST_RET(ISC_R_NOMEMORY);
+ }
+
+ switch (algorithm) {
+ case DST_ALG_RSASHA1:
+ case DST_ALG_NSEC3RSASHA1:
+ type = EVP_sha1(); /* SHA1 + RSA */
+ sig = sha1_sig;
+ len = sizeof(sha1_sig) - 1;
+ break;
+ case DST_ALG_RSASHA256:
+ type = EVP_sha256(); /* SHA256 + RSA */
+ sig = sha256_sig;
+ len = sizeof(sha256_sig) - 1;
+ break;
+ case DST_ALG_RSASHA512:
+ type = EVP_sha512();
+ sig = sha512_sig;
+ len = sizeof(sha512_sig) - 1;
+ break;
+ default:
+ DST_RET(ISC_R_NOTIMPLEMENTED);
+ }
+
+ if (type == NULL) {
+ DST_RET(ISC_R_NOTIMPLEMENTED);
+ }
+
+ /*
+ * Construct pkey.
+ */
+ e = BN_bin2bn(e_bytes, sizeof(e_bytes) - 1, NULL);
+ n = BN_bin2bn(n_bytes, sizeof(n_bytes) - 1, NULL);
+ if (e == NULL || n == NULL) {
+ DST_RET(ISC_R_NOMEMORY);
+ }
+
+#if OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000
+ rsa = RSA_new();
+ if (rsa == NULL) {
+ DST_RET(dst__openssl_toresult2("RSA_new",
+ DST_R_OPENSSLFAILURE));
+ }
+ status = RSA_set0_key(rsa, n, e, NULL);
+ if (status != 1) {
+ DST_RET(dst__openssl_toresult2("RSA_set0_key",
+ DST_R_OPENSSLFAILURE));
+ }
+
+ /* These are now managed by OpenSSL. */
+ n = NULL;
+ e = NULL;
+
+ pkey = EVP_PKEY_new();
+ if (pkey == NULL) {
+ DST_RET(dst__openssl_toresult2("EVP_PKEY_new",
+ DST_R_OPENSSLFAILURE));
+ }
+ status = EVP_PKEY_set1_RSA(pkey, rsa);
+ if (status != 1) {
+ DST_RET(dst__openssl_toresult2("EVP_PKEY_set1_RSA",
+ DST_R_OPENSSLFAILURE));
+ }
+#else
+ bld = OSSL_PARAM_BLD_new();
+ if (bld == NULL) {
+ DST_RET(dst__openssl_toresult2("OSSL_PARAM_BLD_new",
+ DST_R_OPENSSLFAILURE));
+ }
+ if (OSSL_PARAM_BLD_push_BN(bld, OSSL_PKEY_PARAM_RSA_N, n) != 1 ||
+ OSSL_PARAM_BLD_push_BN(bld, OSSL_PKEY_PARAM_RSA_E, e) != 1)
+ {
+ DST_RET(dst__openssl_toresult2("OSSL_PARAM_BLD_push_BN",
+ DST_R_OPENSSLFAILURE));
+ }
+ params = OSSL_PARAM_BLD_to_param(bld);
+ if (params == NULL) {
+ DST_RET(dst__openssl_toresult2("OSSL_PARAM_BLD_to_param",
+ DST_R_OPENSSLFAILURE));
+ }
+ ctx = EVP_PKEY_CTX_new_from_name(NULL, "RSA", NULL);
+ if (ctx == NULL) {
+ DST_RET(dst__openssl_toresult2("EVP_PKEY_CTX_new_from_name",
+ DST_R_OPENSSLFAILURE));
+ }
+ status = EVP_PKEY_fromdata_init(ctx);
+ if (status != 1) {
+ DST_RET(dst__openssl_toresult2("EVP_PKEY_fromdata_init",
+ DST_R_OPENSSLFAILURE));
+ }
+ status = EVP_PKEY_fromdata(ctx, &pkey, EVP_PKEY_PUBLIC_KEY, params);
+ if (status != 1 || pkey == NULL) {
+ DST_RET(dst__openssl_toresult2("EVP_PKEY_fromdata",
+ DST_R_OPENSSLFAILURE));
+ }
+#endif
+
+ /*
+ * Check that we can verify the signature.
+ */
+ if (EVP_DigestInit_ex(evp_md_ctx, type, NULL) != 1 ||
+ EVP_DigestUpdate(evp_md_ctx, "test", 4) != 1 ||
+ EVP_VerifyFinal(evp_md_ctx, sig, len, pkey) != 1)
+ {
+ DST_RET(ISC_R_NOTIMPLEMENTED);
+ }
+
+err:
+ BN_free(e);
+ BN_free(n);
+#if OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000
+ if (rsa != NULL) {
+ RSA_free(rsa);
+ }
+#else
+ if (bld != NULL) {
+ OSSL_PARAM_BLD_free(bld);
+ }
+ if (ctx != NULL) {
+ EVP_PKEY_CTX_free(ctx);
+ }
+ if (params != NULL) {
+ OSSL_PARAM_free(params);
+ }
+#endif
+ if (pkey != NULL) {
+ EVP_PKEY_free(pkey);
+ }
+ if (evp_md_ctx != NULL) {
+ EVP_MD_CTX_destroy(evp_md_ctx);
+ }
+ ERR_clear_error();
+ return (ret);
+}
+
+isc_result_t
+dst__opensslrsa_init(dst_func_t **funcp, unsigned char algorithm) {
+ isc_result_t result;
+
+ REQUIRE(funcp != NULL);
+
+ result = check_algorithm(algorithm);
+
+ if (result == ISC_R_SUCCESS) {
+ if (*funcp == NULL) {
+ *funcp = &opensslrsa_functions;
+ }
+ } else if (result == ISC_R_NOTIMPLEMENTED) {
+ result = ISC_R_SUCCESS;
+ }
+
+ return (result);
+}
diff --git a/lib/dns/order.c b/lib/dns/order.c
new file mode 100644
index 0000000..477576c
--- /dev/null
+++ b/lib/dns/order.c
@@ -0,0 +1,150 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/*! \file */
+
+#include <stdbool.h>
+
+#include <isc/magic.h>
+#include <isc/mem.h>
+#include <isc/refcount.h>
+#include <isc/types.h>
+#include <isc/util.h>
+
+#include <dns/fixedname.h>
+#include <dns/name.h>
+#include <dns/order.h>
+#include <dns/rdataset.h>
+#include <dns/types.h>
+
+typedef struct dns_order_ent dns_order_ent_t;
+struct dns_order_ent {
+ dns_fixedname_t name;
+ dns_rdataclass_t rdclass;
+ dns_rdatatype_t rdtype;
+ unsigned int mode;
+ ISC_LINK(dns_order_ent_t) link;
+};
+
+struct dns_order {
+ unsigned int magic;
+ isc_refcount_t references;
+ ISC_LIST(dns_order_ent_t) ents;
+ isc_mem_t *mctx;
+};
+
+#define DNS_ORDER_MAGIC ISC_MAGIC('O', 'r', 'd', 'r')
+#define DNS_ORDER_VALID(order) ISC_MAGIC_VALID(order, DNS_ORDER_MAGIC)
+
+isc_result_t
+dns_order_create(isc_mem_t *mctx, dns_order_t **orderp) {
+ dns_order_t *order;
+
+ REQUIRE(orderp != NULL && *orderp == NULL);
+
+ order = isc_mem_get(mctx, sizeof(*order));
+
+ ISC_LIST_INIT(order->ents);
+
+ /* Implicit attach. */
+ isc_refcount_init(&order->references, 1);
+
+ order->mctx = NULL;
+ isc_mem_attach(mctx, &order->mctx);
+ order->magic = DNS_ORDER_MAGIC;
+ *orderp = order;
+ return (ISC_R_SUCCESS);
+}
+
+isc_result_t
+dns_order_add(dns_order_t *order, const dns_name_t *name,
+ dns_rdatatype_t rdtype, dns_rdataclass_t rdclass,
+ unsigned int mode) {
+ dns_order_ent_t *ent;
+
+ REQUIRE(DNS_ORDER_VALID(order));
+ REQUIRE(mode == DNS_RDATASETATTR_RANDOMIZE ||
+ mode == DNS_RDATASETATTR_FIXEDORDER ||
+ mode == DNS_RDATASETATTR_CYCLIC ||
+ mode == DNS_RDATASETATTR_NONE);
+
+ ent = isc_mem_get(order->mctx, sizeof(*ent));
+
+ dns_fixedname_init(&ent->name);
+ dns_name_copy(name, dns_fixedname_name(&ent->name));
+ ent->rdtype = rdtype;
+ ent->rdclass = rdclass;
+ ent->mode = mode;
+ ISC_LINK_INIT(ent, link);
+ ISC_LIST_INITANDAPPEND(order->ents, ent, link);
+ return (ISC_R_SUCCESS);
+}
+
+static bool
+match(const dns_name_t *name1, const dns_name_t *name2) {
+ if (dns_name_iswildcard(name2)) {
+ return (dns_name_matcheswildcard(name1, name2));
+ }
+ return (dns_name_equal(name1, name2));
+}
+
+unsigned int
+dns_order_find(dns_order_t *order, const dns_name_t *name,
+ dns_rdatatype_t rdtype, dns_rdataclass_t rdclass) {
+ dns_order_ent_t *ent;
+ REQUIRE(DNS_ORDER_VALID(order));
+
+ for (ent = ISC_LIST_HEAD(order->ents); ent != NULL;
+ ent = ISC_LIST_NEXT(ent, link))
+ {
+ if (ent->rdtype != rdtype && ent->rdtype != dns_rdatatype_any) {
+ continue;
+ }
+ if (ent->rdclass != rdclass &&
+ ent->rdclass != dns_rdataclass_any)
+ {
+ continue;
+ }
+ if (match(name, dns_fixedname_name(&ent->name))) {
+ return (ent->mode);
+ }
+ }
+ return (DNS_RDATASETATTR_NONE);
+}
+
+void
+dns_order_attach(dns_order_t *source, dns_order_t **target) {
+ REQUIRE(DNS_ORDER_VALID(source));
+ REQUIRE(target != NULL && *target == NULL);
+ isc_refcount_increment(&source->references);
+ *target = source;
+}
+
+void
+dns_order_detach(dns_order_t **orderp) {
+ REQUIRE(orderp != NULL && DNS_ORDER_VALID(*orderp));
+ dns_order_t *order;
+ order = *orderp;
+ *orderp = NULL;
+
+ if (isc_refcount_decrement(&order->references) == 1) {
+ isc_refcount_destroy(&order->references);
+ order->magic = 0;
+ dns_order_ent_t *ent;
+ while ((ent = ISC_LIST_HEAD(order->ents)) != NULL) {
+ ISC_LIST_UNLINK(order->ents, ent, link);
+ isc_mem_put(order->mctx, ent, sizeof(*ent));
+ }
+ isc_mem_putanddetach(&order->mctx, order, sizeof(*order));
+ }
+}
diff --git a/lib/dns/peer.c b/lib/dns/peer.c
new file mode 100644
index 0000000..e4d2fcb
--- /dev/null
+++ b/lib/dns/peer.c
@@ -0,0 +1,898 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/*! \file */
+
+#include <inttypes.h>
+#include <stdbool.h>
+
+#include <isc/mem.h>
+#include <isc/sockaddr.h>
+#include <isc/string.h>
+#include <isc/util.h>
+
+#include <dns/bit.h>
+#include <dns/fixedname.h>
+#include <dns/name.h>
+#include <dns/peer.h>
+
+/***
+ *** Types
+ ***/
+
+struct dns_peerlist {
+ unsigned int magic;
+ isc_refcount_t refs;
+
+ isc_mem_t *mem;
+
+ ISC_LIST(dns_peer_t) elements;
+};
+
+struct dns_peer {
+ unsigned int magic;
+ isc_refcount_t refs;
+
+ isc_mem_t *mem;
+
+ isc_netaddr_t address;
+ unsigned int prefixlen;
+ bool bogus;
+ dns_transfer_format_t transfer_format;
+ uint32_t transfers;
+ bool support_ixfr;
+ bool provide_ixfr;
+ bool request_ixfr;
+ bool support_edns;
+ bool request_nsid;
+ bool send_cookie;
+ bool request_expire;
+ bool force_tcp;
+ bool tcp_keepalive;
+ bool check_axfr_id;
+ dns_name_t *key;
+ isc_sockaddr_t *transfer_source;
+ isc_sockaddr_t *notify_source;
+ isc_sockaddr_t *query_source;
+ uint16_t udpsize; /* receive size */
+ uint16_t maxudp; /* transmit size */
+ uint16_t padding; /* pad block size */
+ uint8_t ednsversion; /* edns version */
+
+ uint32_t bitflags;
+
+ ISC_LINK(dns_peer_t) next;
+};
+
+/*%
+ * Bit positions in the dns_peer_t structure flags field
+ */
+#define BOGUS_BIT 0
+#define SERVER_TRANSFER_FORMAT_BIT 1
+#define TRANSFERS_BIT 2
+#define PROVIDE_IXFR_BIT 3
+#define REQUEST_IXFR_BIT 4
+#define SUPPORT_EDNS_BIT 5
+#define SERVER_UDPSIZE_BIT 6
+#define SERVER_MAXUDP_BIT 7
+#define REQUEST_NSID_BIT 8
+#define SEND_COOKIE_BIT 9
+#define REQUEST_EXPIRE_BIT 10
+#define EDNS_VERSION_BIT 11
+#define FORCE_TCP_BIT 12
+#define SERVER_PADDING_BIT 13
+#define REQUEST_TCP_KEEPALIVE_BIT 14
+
+static void
+peerlist_delete(dns_peerlist_t **list);
+
+static void
+peer_delete(dns_peer_t **peer);
+
+isc_result_t
+dns_peerlist_new(isc_mem_t *mem, dns_peerlist_t **list) {
+ dns_peerlist_t *l;
+
+ REQUIRE(list != NULL);
+
+ l = isc_mem_get(mem, sizeof(*l));
+
+ ISC_LIST_INIT(l->elements);
+ l->mem = mem;
+ isc_refcount_init(&l->refs, 1);
+ l->magic = DNS_PEERLIST_MAGIC;
+
+ *list = l;
+
+ return (ISC_R_SUCCESS);
+}
+
+void
+dns_peerlist_attach(dns_peerlist_t *source, dns_peerlist_t **target) {
+ REQUIRE(DNS_PEERLIST_VALID(source));
+ REQUIRE(target != NULL);
+ REQUIRE(*target == NULL);
+
+ isc_refcount_increment(&source->refs);
+
+ *target = source;
+}
+
+void
+dns_peerlist_detach(dns_peerlist_t **list) {
+ dns_peerlist_t *plist;
+
+ REQUIRE(list != NULL);
+ REQUIRE(*list != NULL);
+ REQUIRE(DNS_PEERLIST_VALID(*list));
+
+ plist = *list;
+ *list = NULL;
+
+ if (isc_refcount_decrement(&plist->refs) == 1) {
+ peerlist_delete(&plist);
+ }
+}
+
+static void
+peerlist_delete(dns_peerlist_t **list) {
+ dns_peerlist_t *l;
+ dns_peer_t *server, *stmp;
+
+ REQUIRE(list != NULL);
+ REQUIRE(DNS_PEERLIST_VALID(*list));
+
+ l = *list;
+ *list = NULL;
+
+ isc_refcount_destroy(&l->refs);
+
+ server = ISC_LIST_HEAD(l->elements);
+ while (server != NULL) {
+ stmp = ISC_LIST_NEXT(server, next);
+ ISC_LIST_UNLINK(l->elements, server, next);
+ dns_peer_detach(&server);
+ server = stmp;
+ }
+
+ l->magic = 0;
+ isc_mem_put(l->mem, l, sizeof(*l));
+}
+
+void
+dns_peerlist_addpeer(dns_peerlist_t *peers, dns_peer_t *peer) {
+ dns_peer_t *p = NULL;
+
+ dns_peer_attach(peer, &p);
+
+ /*
+ * More specifics to front of list.
+ */
+ for (p = ISC_LIST_HEAD(peers->elements); p != NULL;
+ p = ISC_LIST_NEXT(p, next))
+ {
+ if (p->prefixlen < peer->prefixlen) {
+ break;
+ }
+ }
+
+ if (p != NULL) {
+ ISC_LIST_INSERTBEFORE(peers->elements, p, peer, next);
+ } else {
+ ISC_LIST_APPEND(peers->elements, peer, next);
+ }
+}
+
+isc_result_t
+dns_peerlist_peerbyaddr(dns_peerlist_t *servers, const isc_netaddr_t *addr,
+ dns_peer_t **retval) {
+ dns_peer_t *server;
+ isc_result_t res;
+
+ REQUIRE(retval != NULL);
+ REQUIRE(DNS_PEERLIST_VALID(servers));
+
+ server = ISC_LIST_HEAD(servers->elements);
+ while (server != NULL) {
+ if (isc_netaddr_eqprefix(addr, &server->address,
+ server->prefixlen))
+ {
+ break;
+ }
+
+ server = ISC_LIST_NEXT(server, next);
+ }
+
+ if (server != NULL) {
+ *retval = server;
+ res = ISC_R_SUCCESS;
+ } else {
+ res = ISC_R_NOTFOUND;
+ }
+
+ return (res);
+}
+
+isc_result_t
+dns_peerlist_currpeer(dns_peerlist_t *peers, dns_peer_t **retval) {
+ dns_peer_t *p = NULL;
+
+ p = ISC_LIST_TAIL(peers->elements);
+
+ dns_peer_attach(p, retval);
+
+ return (ISC_R_SUCCESS);
+}
+
+isc_result_t
+dns_peer_new(isc_mem_t *mem, const isc_netaddr_t *addr, dns_peer_t **peerptr) {
+ unsigned int prefixlen = 0;
+
+ REQUIRE(peerptr != NULL);
+ switch (addr->family) {
+ case AF_INET:
+ prefixlen = 32;
+ break;
+ case AF_INET6:
+ prefixlen = 128;
+ break;
+ default:
+ UNREACHABLE();
+ }
+
+ return (dns_peer_newprefix(mem, addr, prefixlen, peerptr));
+}
+
+isc_result_t
+dns_peer_newprefix(isc_mem_t *mem, const isc_netaddr_t *addr,
+ unsigned int prefixlen, dns_peer_t **peerptr) {
+ dns_peer_t *peer;
+
+ REQUIRE(peerptr != NULL && *peerptr == NULL);
+
+ peer = isc_mem_get(mem, sizeof(*peer));
+
+ *peer = (dns_peer_t){
+ .magic = DNS_PEER_MAGIC,
+ .address = *addr,
+ .prefixlen = prefixlen,
+ .mem = mem,
+ .transfer_format = dns_one_answer,
+ };
+
+ isc_refcount_init(&peer->refs, 1);
+
+ ISC_LINK_INIT(peer, next);
+
+ *peerptr = peer;
+
+ return (ISC_R_SUCCESS);
+}
+
+void
+dns_peer_attach(dns_peer_t *source, dns_peer_t **target) {
+ REQUIRE(DNS_PEER_VALID(source));
+ REQUIRE(target != NULL);
+ REQUIRE(*target == NULL);
+
+ isc_refcount_increment(&source->refs);
+
+ *target = source;
+}
+
+void
+dns_peer_detach(dns_peer_t **peer) {
+ dns_peer_t *p;
+
+ REQUIRE(peer != NULL);
+ REQUIRE(*peer != NULL);
+ REQUIRE(DNS_PEER_VALID(*peer));
+
+ p = *peer;
+ *peer = NULL;
+
+ if (isc_refcount_decrement(&p->refs) == 1) {
+ peer_delete(&p);
+ }
+}
+
+static void
+peer_delete(dns_peer_t **peer) {
+ dns_peer_t *p;
+ isc_mem_t *mem;
+
+ REQUIRE(peer != NULL);
+ REQUIRE(DNS_PEER_VALID(*peer));
+
+ p = *peer;
+ *peer = NULL;
+
+ isc_refcount_destroy(&p->refs);
+
+ mem = p->mem;
+ p->mem = NULL;
+ p->magic = 0;
+
+ if (p->key != NULL) {
+ dns_name_free(p->key, mem);
+ isc_mem_put(mem, p->key, sizeof(dns_name_t));
+ }
+
+ if (p->query_source != NULL) {
+ isc_mem_put(mem, p->query_source, sizeof(*p->query_source));
+ }
+
+ if (p->notify_source != NULL) {
+ isc_mem_put(mem, p->notify_source, sizeof(*p->notify_source));
+ }
+
+ if (p->transfer_source != NULL) {
+ isc_mem_put(mem, p->transfer_source,
+ sizeof(*p->transfer_source));
+ }
+
+ isc_mem_put(mem, p, sizeof(*p));
+}
+
+isc_result_t
+dns_peer_setbogus(dns_peer_t *peer, bool newval) {
+ bool existed;
+
+ REQUIRE(DNS_PEER_VALID(peer));
+
+ existed = DNS_BIT_CHECK(BOGUS_BIT, &peer->bitflags);
+
+ peer->bogus = newval;
+ DNS_BIT_SET(BOGUS_BIT, &peer->bitflags);
+
+ return (existed ? ISC_R_EXISTS : ISC_R_SUCCESS);
+}
+
+isc_result_t
+dns_peer_getbogus(dns_peer_t *peer, bool *retval) {
+ REQUIRE(DNS_PEER_VALID(peer));
+ REQUIRE(retval != NULL);
+
+ if (DNS_BIT_CHECK(BOGUS_BIT, &peer->bitflags)) {
+ *retval = peer->bogus;
+ return (ISC_R_SUCCESS);
+ } else {
+ return (ISC_R_NOTFOUND);
+ }
+}
+
+isc_result_t
+dns_peer_setprovideixfr(dns_peer_t *peer, bool newval) {
+ bool existed;
+
+ REQUIRE(DNS_PEER_VALID(peer));
+
+ existed = DNS_BIT_CHECK(PROVIDE_IXFR_BIT, &peer->bitflags);
+
+ peer->provide_ixfr = newval;
+ DNS_BIT_SET(PROVIDE_IXFR_BIT, &peer->bitflags);
+
+ return (existed ? ISC_R_EXISTS : ISC_R_SUCCESS);
+}
+
+isc_result_t
+dns_peer_getprovideixfr(dns_peer_t *peer, bool *retval) {
+ REQUIRE(DNS_PEER_VALID(peer));
+ REQUIRE(retval != NULL);
+
+ if (DNS_BIT_CHECK(PROVIDE_IXFR_BIT, &peer->bitflags)) {
+ *retval = peer->provide_ixfr;
+ return (ISC_R_SUCCESS);
+ } else {
+ return (ISC_R_NOTFOUND);
+ }
+}
+
+isc_result_t
+dns_peer_setrequestixfr(dns_peer_t *peer, bool newval) {
+ bool existed;
+
+ REQUIRE(DNS_PEER_VALID(peer));
+
+ existed = DNS_BIT_CHECK(REQUEST_IXFR_BIT, &peer->bitflags);
+
+ peer->request_ixfr = newval;
+ DNS_BIT_SET(REQUEST_IXFR_BIT, &peer->bitflags);
+
+ return (existed ? ISC_R_EXISTS : ISC_R_SUCCESS);
+}
+
+isc_result_t
+dns_peer_getrequestixfr(dns_peer_t *peer, bool *retval) {
+ REQUIRE(DNS_PEER_VALID(peer));
+ REQUIRE(retval != NULL);
+
+ if (DNS_BIT_CHECK(REQUEST_IXFR_BIT, &peer->bitflags)) {
+ *retval = peer->request_ixfr;
+ return (ISC_R_SUCCESS);
+ } else {
+ return (ISC_R_NOTFOUND);
+ }
+}
+
+isc_result_t
+dns_peer_setsupportedns(dns_peer_t *peer, bool newval) {
+ bool existed;
+
+ REQUIRE(DNS_PEER_VALID(peer));
+
+ existed = DNS_BIT_CHECK(SUPPORT_EDNS_BIT, &peer->bitflags);
+
+ peer->support_edns = newval;
+ DNS_BIT_SET(SUPPORT_EDNS_BIT, &peer->bitflags);
+
+ return (existed ? ISC_R_EXISTS : ISC_R_SUCCESS);
+}
+
+isc_result_t
+dns_peer_getsupportedns(dns_peer_t *peer, bool *retval) {
+ REQUIRE(DNS_PEER_VALID(peer));
+ REQUIRE(retval != NULL);
+
+ if (DNS_BIT_CHECK(SUPPORT_EDNS_BIT, &peer->bitflags)) {
+ *retval = peer->support_edns;
+ return (ISC_R_SUCCESS);
+ } else {
+ return (ISC_R_NOTFOUND);
+ }
+}
+
+isc_result_t
+dns_peer_setrequestnsid(dns_peer_t *peer, bool newval) {
+ bool existed;
+
+ REQUIRE(DNS_PEER_VALID(peer));
+
+ existed = DNS_BIT_CHECK(REQUEST_NSID_BIT, &peer->bitflags);
+
+ peer->request_nsid = newval;
+ DNS_BIT_SET(REQUEST_NSID_BIT, &peer->bitflags);
+
+ return (existed ? ISC_R_EXISTS : ISC_R_SUCCESS);
+}
+
+isc_result_t
+dns_peer_getrequestnsid(dns_peer_t *peer, bool *retval) {
+ REQUIRE(DNS_PEER_VALID(peer));
+ REQUIRE(retval != NULL);
+
+ if (DNS_BIT_CHECK(REQUEST_NSID_BIT, &peer->bitflags)) {
+ *retval = peer->request_nsid;
+ return (ISC_R_SUCCESS);
+ } else {
+ return (ISC_R_NOTFOUND);
+ }
+}
+
+isc_result_t
+dns_peer_setsendcookie(dns_peer_t *peer, bool newval) {
+ bool existed;
+
+ REQUIRE(DNS_PEER_VALID(peer));
+
+ existed = DNS_BIT_CHECK(SEND_COOKIE_BIT, &peer->bitflags);
+
+ peer->send_cookie = newval;
+ DNS_BIT_SET(SEND_COOKIE_BIT, &peer->bitflags);
+
+ return (existed ? ISC_R_EXISTS : ISC_R_SUCCESS);
+}
+
+isc_result_t
+dns_peer_getsendcookie(dns_peer_t *peer, bool *retval) {
+ REQUIRE(DNS_PEER_VALID(peer));
+ REQUIRE(retval != NULL);
+
+ if (DNS_BIT_CHECK(SEND_COOKIE_BIT, &peer->bitflags)) {
+ *retval = peer->send_cookie;
+ return (ISC_R_SUCCESS);
+ } else {
+ return (ISC_R_NOTFOUND);
+ }
+}
+
+isc_result_t
+dns_peer_setrequestexpire(dns_peer_t *peer, bool newval) {
+ bool existed;
+
+ REQUIRE(DNS_PEER_VALID(peer));
+
+ existed = DNS_BIT_CHECK(REQUEST_EXPIRE_BIT, &peer->bitflags);
+
+ peer->request_expire = newval;
+ DNS_BIT_SET(REQUEST_EXPIRE_BIT, &peer->bitflags);
+
+ return (existed ? ISC_R_EXISTS : ISC_R_SUCCESS);
+}
+
+isc_result_t
+dns_peer_getrequestexpire(dns_peer_t *peer, bool *retval) {
+ REQUIRE(DNS_PEER_VALID(peer));
+ REQUIRE(retval != NULL);
+
+ if (DNS_BIT_CHECK(REQUEST_EXPIRE_BIT, &peer->bitflags)) {
+ *retval = peer->request_expire;
+ return (ISC_R_SUCCESS);
+ } else {
+ return (ISC_R_NOTFOUND);
+ }
+}
+
+isc_result_t
+dns_peer_setforcetcp(dns_peer_t *peer, bool newval) {
+ bool existed;
+
+ REQUIRE(DNS_PEER_VALID(peer));
+
+ existed = DNS_BIT_CHECK(FORCE_TCP_BIT, &peer->bitflags);
+
+ peer->force_tcp = newval;
+ DNS_BIT_SET(FORCE_TCP_BIT, &peer->bitflags);
+
+ return (existed ? ISC_R_EXISTS : ISC_R_SUCCESS);
+}
+
+isc_result_t
+dns_peer_getforcetcp(dns_peer_t *peer, bool *retval) {
+ REQUIRE(DNS_PEER_VALID(peer));
+ REQUIRE(retval != NULL);
+
+ if (DNS_BIT_CHECK(FORCE_TCP_BIT, &peer->bitflags)) {
+ *retval = peer->force_tcp;
+ return (ISC_R_SUCCESS);
+ } else {
+ return (ISC_R_NOTFOUND);
+ }
+}
+
+isc_result_t
+dns_peer_settcpkeepalive(dns_peer_t *peer, bool newval) {
+ bool existed;
+
+ REQUIRE(DNS_PEER_VALID(peer));
+
+ existed = DNS_BIT_CHECK(REQUEST_TCP_KEEPALIVE_BIT, &peer->bitflags);
+
+ peer->tcp_keepalive = newval;
+ DNS_BIT_SET(REQUEST_TCP_KEEPALIVE_BIT, &peer->bitflags);
+
+ return (existed ? ISC_R_EXISTS : ISC_R_SUCCESS);
+}
+
+isc_result_t
+dns_peer_gettcpkeepalive(dns_peer_t *peer, bool *retval) {
+ REQUIRE(DNS_PEER_VALID(peer));
+ REQUIRE(retval != NULL);
+
+ if (DNS_BIT_CHECK(REQUEST_TCP_KEEPALIVE_BIT, &peer->bitflags)) {
+ *retval = peer->tcp_keepalive;
+ return (ISC_R_SUCCESS);
+ } else {
+ return (ISC_R_NOTFOUND);
+ }
+}
+
+isc_result_t
+dns_peer_settransfers(dns_peer_t *peer, uint32_t newval) {
+ bool existed;
+
+ REQUIRE(DNS_PEER_VALID(peer));
+
+ existed = DNS_BIT_CHECK(TRANSFERS_BIT, &peer->bitflags);
+
+ peer->transfers = newval;
+ DNS_BIT_SET(TRANSFERS_BIT, &peer->bitflags);
+
+ return (existed ? ISC_R_EXISTS : ISC_R_SUCCESS);
+}
+
+isc_result_t
+dns_peer_gettransfers(dns_peer_t *peer, uint32_t *retval) {
+ REQUIRE(DNS_PEER_VALID(peer));
+ REQUIRE(retval != NULL);
+
+ if (DNS_BIT_CHECK(TRANSFERS_BIT, &peer->bitflags)) {
+ *retval = peer->transfers;
+ return (ISC_R_SUCCESS);
+ } else {
+ return (ISC_R_NOTFOUND);
+ }
+}
+
+isc_result_t
+dns_peer_settransferformat(dns_peer_t *peer, dns_transfer_format_t newval) {
+ bool existed;
+
+ REQUIRE(DNS_PEER_VALID(peer));
+
+ existed = DNS_BIT_CHECK(SERVER_TRANSFER_FORMAT_BIT, &peer->bitflags);
+
+ peer->transfer_format = newval;
+ DNS_BIT_SET(SERVER_TRANSFER_FORMAT_BIT, &peer->bitflags);
+
+ return (existed ? ISC_R_EXISTS : ISC_R_SUCCESS);
+}
+
+isc_result_t
+dns_peer_gettransferformat(dns_peer_t *peer, dns_transfer_format_t *retval) {
+ REQUIRE(DNS_PEER_VALID(peer));
+ REQUIRE(retval != NULL);
+
+ if (DNS_BIT_CHECK(SERVER_TRANSFER_FORMAT_BIT, &peer->bitflags)) {
+ *retval = peer->transfer_format;
+ return (ISC_R_SUCCESS);
+ } else {
+ return (ISC_R_NOTFOUND);
+ }
+}
+
+isc_result_t
+dns_peer_getkey(dns_peer_t *peer, dns_name_t **retval) {
+ REQUIRE(DNS_PEER_VALID(peer));
+ REQUIRE(retval != NULL);
+
+ if (peer->key != NULL) {
+ *retval = peer->key;
+ }
+
+ return (peer->key == NULL ? ISC_R_NOTFOUND : ISC_R_SUCCESS);
+}
+
+isc_result_t
+dns_peer_setkey(dns_peer_t *peer, dns_name_t **keyval) {
+ bool exists = false;
+
+ if (peer->key != NULL) {
+ dns_name_free(peer->key, peer->mem);
+ isc_mem_put(peer->mem, peer->key, sizeof(dns_name_t));
+ exists = true;
+ }
+
+ peer->key = *keyval;
+ *keyval = NULL;
+
+ return (exists ? ISC_R_EXISTS : ISC_R_SUCCESS);
+}
+
+isc_result_t
+dns_peer_setkeybycharp(dns_peer_t *peer, const char *keyval) {
+ isc_buffer_t b;
+ dns_fixedname_t fname;
+ dns_name_t *name;
+ isc_result_t result;
+
+ dns_fixedname_init(&fname);
+ isc_buffer_constinit(&b, keyval, strlen(keyval));
+ isc_buffer_add(&b, strlen(keyval));
+ result = dns_name_fromtext(dns_fixedname_name(&fname), &b, dns_rootname,
+ 0, NULL);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+
+ name = isc_mem_get(peer->mem, sizeof(dns_name_t));
+
+ dns_name_init(name, NULL);
+ dns_name_dup(dns_fixedname_name(&fname), peer->mem, name);
+
+ result = dns_peer_setkey(peer, &name);
+ if (result != ISC_R_SUCCESS) {
+ isc_mem_put(peer->mem, name, sizeof(dns_name_t));
+ }
+
+ return (result);
+}
+
+isc_result_t
+dns_peer_settransfersource(dns_peer_t *peer,
+ const isc_sockaddr_t *transfer_source) {
+ REQUIRE(DNS_PEER_VALID(peer));
+
+ if (peer->transfer_source != NULL) {
+ isc_mem_put(peer->mem, peer->transfer_source,
+ sizeof(*peer->transfer_source));
+ peer->transfer_source = NULL;
+ }
+ if (transfer_source != NULL) {
+ peer->transfer_source =
+ isc_mem_get(peer->mem, sizeof(*peer->transfer_source));
+
+ *peer->transfer_source = *transfer_source;
+ }
+ return (ISC_R_SUCCESS);
+}
+
+isc_result_t
+dns_peer_gettransfersource(dns_peer_t *peer, isc_sockaddr_t *transfer_source) {
+ REQUIRE(DNS_PEER_VALID(peer));
+ REQUIRE(transfer_source != NULL);
+
+ if (peer->transfer_source == NULL) {
+ return (ISC_R_NOTFOUND);
+ }
+ *transfer_source = *peer->transfer_source;
+ return (ISC_R_SUCCESS);
+}
+
+isc_result_t
+dns_peer_setnotifysource(dns_peer_t *peer,
+ const isc_sockaddr_t *notify_source) {
+ REQUIRE(DNS_PEER_VALID(peer));
+
+ if (peer->notify_source != NULL) {
+ isc_mem_put(peer->mem, peer->notify_source,
+ sizeof(*peer->notify_source));
+ peer->notify_source = NULL;
+ }
+ if (notify_source != NULL) {
+ peer->notify_source = isc_mem_get(peer->mem,
+ sizeof(*peer->notify_source));
+
+ *peer->notify_source = *notify_source;
+ }
+ return (ISC_R_SUCCESS);
+}
+
+isc_result_t
+dns_peer_getnotifysource(dns_peer_t *peer, isc_sockaddr_t *notify_source) {
+ REQUIRE(DNS_PEER_VALID(peer));
+ REQUIRE(notify_source != NULL);
+
+ if (peer->notify_source == NULL) {
+ return (ISC_R_NOTFOUND);
+ }
+ *notify_source = *peer->notify_source;
+ return (ISC_R_SUCCESS);
+}
+
+isc_result_t
+dns_peer_setquerysource(dns_peer_t *peer, const isc_sockaddr_t *query_source) {
+ REQUIRE(DNS_PEER_VALID(peer));
+
+ if (peer->query_source != NULL) {
+ isc_mem_put(peer->mem, peer->query_source,
+ sizeof(*peer->query_source));
+ peer->query_source = NULL;
+ }
+ if (query_source != NULL) {
+ peer->query_source = isc_mem_get(peer->mem,
+ sizeof(*peer->query_source));
+
+ *peer->query_source = *query_source;
+ }
+ return (ISC_R_SUCCESS);
+}
+
+isc_result_t
+dns_peer_getquerysource(dns_peer_t *peer, isc_sockaddr_t *query_source) {
+ REQUIRE(DNS_PEER_VALID(peer));
+ REQUIRE(query_source != NULL);
+
+ if (peer->query_source == NULL) {
+ return (ISC_R_NOTFOUND);
+ }
+ *query_source = *peer->query_source;
+ return (ISC_R_SUCCESS);
+}
+
+isc_result_t
+dns_peer_setudpsize(dns_peer_t *peer, uint16_t udpsize) {
+ bool existed;
+
+ REQUIRE(DNS_PEER_VALID(peer));
+
+ existed = DNS_BIT_CHECK(SERVER_UDPSIZE_BIT, &peer->bitflags);
+
+ peer->udpsize = udpsize;
+ DNS_BIT_SET(SERVER_UDPSIZE_BIT, &peer->bitflags);
+
+ return (existed ? ISC_R_EXISTS : ISC_R_SUCCESS);
+}
+
+isc_result_t
+dns_peer_getudpsize(dns_peer_t *peer, uint16_t *udpsize) {
+ REQUIRE(DNS_PEER_VALID(peer));
+ REQUIRE(udpsize != NULL);
+
+ if (DNS_BIT_CHECK(SERVER_UDPSIZE_BIT, &peer->bitflags)) {
+ *udpsize = peer->udpsize;
+ return (ISC_R_SUCCESS);
+ } else {
+ return (ISC_R_NOTFOUND);
+ }
+}
+
+isc_result_t
+dns_peer_setmaxudp(dns_peer_t *peer, uint16_t maxudp) {
+ bool existed;
+
+ REQUIRE(DNS_PEER_VALID(peer));
+
+ existed = DNS_BIT_CHECK(SERVER_MAXUDP_BIT, &peer->bitflags);
+
+ peer->maxudp = maxudp;
+ DNS_BIT_SET(SERVER_MAXUDP_BIT, &peer->bitflags);
+
+ return (existed ? ISC_R_EXISTS : ISC_R_SUCCESS);
+}
+
+isc_result_t
+dns_peer_getmaxudp(dns_peer_t *peer, uint16_t *maxudp) {
+ REQUIRE(DNS_PEER_VALID(peer));
+ REQUIRE(maxudp != NULL);
+
+ if (DNS_BIT_CHECK(SERVER_MAXUDP_BIT, &peer->bitflags)) {
+ *maxudp = peer->maxudp;
+ return (ISC_R_SUCCESS);
+ } else {
+ return (ISC_R_NOTFOUND);
+ }
+}
+
+isc_result_t
+dns_peer_setpadding(dns_peer_t *peer, uint16_t padding) {
+ bool existed;
+
+ REQUIRE(DNS_PEER_VALID(peer));
+
+ existed = DNS_BIT_CHECK(SERVER_PADDING_BIT, &peer->bitflags);
+
+ if (padding > 512) {
+ padding = 512;
+ }
+ peer->padding = padding;
+ DNS_BIT_SET(SERVER_PADDING_BIT, &peer->bitflags);
+
+ return (existed ? ISC_R_EXISTS : ISC_R_SUCCESS);
+}
+
+isc_result_t
+dns_peer_getpadding(dns_peer_t *peer, uint16_t *padding) {
+ REQUIRE(DNS_PEER_VALID(peer));
+ REQUIRE(padding != NULL);
+
+ if (DNS_BIT_CHECK(SERVER_PADDING_BIT, &peer->bitflags)) {
+ *padding = peer->padding;
+ return (ISC_R_SUCCESS);
+ } else {
+ return (ISC_R_NOTFOUND);
+ }
+}
+
+isc_result_t
+dns_peer_setednsversion(dns_peer_t *peer, uint8_t ednsversion) {
+ REQUIRE(DNS_PEER_VALID(peer));
+
+ peer->ednsversion = ednsversion;
+ DNS_BIT_SET(EDNS_VERSION_BIT, &peer->bitflags);
+
+ return (ISC_R_SUCCESS);
+}
+
+isc_result_t
+dns_peer_getednsversion(dns_peer_t *peer, uint8_t *ednsversion) {
+ REQUIRE(DNS_PEER_VALID(peer));
+ REQUIRE(ednsversion != NULL);
+
+ if (DNS_BIT_CHECK(EDNS_VERSION_BIT, &peer->bitflags)) {
+ *ednsversion = peer->ednsversion;
+ return (ISC_R_SUCCESS);
+ } else {
+ return (ISC_R_NOTFOUND);
+ }
+}
diff --git a/lib/dns/private.c b/lib/dns/private.c
new file mode 100644
index 0000000..56573b3
--- /dev/null
+++ b/lib/dns/private.c
@@ -0,0 +1,417 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#include <stdbool.h>
+
+#include <isc/base64.h>
+#include <isc/print.h>
+#include <isc/result.h>
+#include <isc/string.h>
+#include <isc/types.h>
+#include <isc/util.h>
+
+#include <dns/nsec3.h>
+#include <dns/private.h>
+
+/*
+ * 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], &param->data[5], param->data[4]))
+ {
+ continue;
+ }
+ /*
+ * The removal of this NSEC3 chain does NOT cause a
+ * NSEC chain to be created so we don't need to tell
+ * the caller that it will be removed.
+ */
+ if (NONSEC(rdata.data[1])) {
+ return (false);
+ }
+ return (true);
+ }
+ return (false);
+}
+
+isc_result_t
+dns_private_chains(dns_db_t *db, dns_dbversion_t *ver,
+ dns_rdatatype_t privatetype, bool *build_nsec,
+ bool *build_nsec3) {
+ dns_dbnode_t *node;
+ dns_rdataset_t nsecset, nsec3paramset, privateset;
+ bool nsec3chain;
+ bool signing;
+ isc_result_t result;
+ unsigned char buf[DNS_NSEC3PARAM_BUFFERSIZE];
+ unsigned int count;
+
+ node = NULL;
+ dns_rdataset_init(&nsecset);
+ dns_rdataset_init(&nsec3paramset);
+ dns_rdataset_init(&privateset);
+
+ CHECK(dns_db_getoriginnode(db, &node));
+
+ result = dns_db_findrdataset(db, node, ver, dns_rdatatype_nsec, 0,
+ (isc_stdtime_t)0, &nsecset, NULL);
+
+ if (result != ISC_R_SUCCESS && result != ISC_R_NOTFOUND) {
+ goto failure;
+ }
+
+ result = dns_db_findrdataset(db, node, ver, dns_rdatatype_nsec3param, 0,
+ (isc_stdtime_t)0, &nsec3paramset, NULL);
+ if (result != ISC_R_SUCCESS && result != ISC_R_NOTFOUND) {
+ goto failure;
+ }
+
+ if (dns_rdataset_isassociated(&nsecset) &&
+ dns_rdataset_isassociated(&nsec3paramset))
+ {
+ if (build_nsec != NULL) {
+ *build_nsec = true;
+ }
+ if (build_nsec3 != NULL) {
+ *build_nsec3 = true;
+ }
+ goto success;
+ }
+
+ if (privatetype != (dns_rdatatype_t)0) {
+ result = dns_db_findrdataset(db, node, ver, privatetype, 0,
+ (isc_stdtime_t)0, &privateset,
+ NULL);
+ if (result != ISC_R_SUCCESS && result != ISC_R_NOTFOUND) {
+ goto failure;
+ }
+ }
+
+ /*
+ * Look to see if we also need to be creating a NSEC3 chain.
+ */
+ if (dns_rdataset_isassociated(&nsecset)) {
+ if (build_nsec != NULL) {
+ *build_nsec = true;
+ }
+ if (build_nsec3 != NULL) {
+ *build_nsec3 = false;
+ }
+ if (!dns_rdataset_isassociated(&privateset)) {
+ goto success;
+ }
+ for (result = dns_rdataset_first(&privateset);
+ result == ISC_R_SUCCESS;
+ result = dns_rdataset_next(&privateset))
+ {
+ dns_rdata_t private = DNS_RDATA_INIT;
+ dns_rdata_t rdata = DNS_RDATA_INIT;
+
+ dns_rdataset_current(&privateset, &private);
+ if (!dns_nsec3param_fromprivate(&private, &rdata, buf,
+ sizeof(buf)))
+ {
+ continue;
+ }
+ if (REMOVE(rdata.data[1])) {
+ continue;
+ }
+ if (build_nsec3 != NULL) {
+ *build_nsec3 = true;
+ }
+ break;
+ }
+ goto success;
+ }
+
+ if (dns_rdataset_isassociated(&nsec3paramset)) {
+ if (build_nsec3 != NULL) {
+ *build_nsec3 = true;
+ }
+ if (build_nsec != NULL) {
+ *build_nsec = false;
+ }
+ if (!dns_rdataset_isassociated(&privateset)) {
+ goto success;
+ }
+ /*
+ * If we are in the process of building a new NSEC3 chain
+ * then we don't need to build a NSEC chain.
+ */
+ for (result = dns_rdataset_first(&privateset);
+ result == ISC_R_SUCCESS;
+ result = dns_rdataset_next(&privateset))
+ {
+ dns_rdata_t private = DNS_RDATA_INIT;
+ dns_rdata_t rdata = DNS_RDATA_INIT;
+
+ dns_rdataset_current(&privateset, &private);
+ if (!dns_nsec3param_fromprivate(&private, &rdata, buf,
+ sizeof(buf)))
+ {
+ continue;
+ }
+ if (CREATE(rdata.data[1])) {
+ goto success;
+ }
+ }
+
+ /*
+ * Check to see if there will be a active NSEC3CHAIN once
+ * the changes queued complete.
+ */
+ count = 0;
+ for (result = dns_rdataset_first(&nsec3paramset);
+ result == ISC_R_SUCCESS;
+ result = dns_rdataset_next(&nsec3paramset))
+ {
+ dns_rdata_t rdata = DNS_RDATA_INIT;
+
+ /*
+ * If there is more that one NSEC3 chain present then
+ * we don't need to construct a NSEC chain.
+ */
+ if (++count > 1) {
+ goto success;
+ }
+ dns_rdataset_current(&nsec3paramset, &rdata);
+ if (ignore(&rdata, &privateset)) {
+ continue;
+ }
+ /*
+ * We still have a good NSEC3 chain or we are
+ * not creating a NSEC chain as NONSEC is set.
+ */
+ goto success;
+ }
+
+ /*
+ * The last NSEC3 chain is being removed and does not have
+ * have NONSEC set.
+ */
+ if (build_nsec != NULL) {
+ *build_nsec = true;
+ }
+ goto success;
+ }
+
+ if (build_nsec != NULL) {
+ *build_nsec = false;
+ }
+ if (build_nsec3 != NULL) {
+ *build_nsec3 = false;
+ }
+ if (!dns_rdataset_isassociated(&privateset)) {
+ goto success;
+ }
+
+ signing = false;
+ nsec3chain = false;
+
+ for (result = dns_rdataset_first(&privateset); result == ISC_R_SUCCESS;
+ result = dns_rdataset_next(&privateset))
+ {
+ dns_rdata_t rdata = DNS_RDATA_INIT;
+ dns_rdata_t private = DNS_RDATA_INIT;
+
+ dns_rdataset_current(&privateset, &private);
+ if (!dns_nsec3param_fromprivate(&private, &rdata, buf,
+ sizeof(buf)))
+ {
+ /*
+ * Look for record that says we are signing the
+ * zone with a key.
+ */
+ if (private.length == 5 && private.data[0] != 0 &&
+ private.data[3] == 0 && private.data[4] == 0)
+ {
+ signing = true;
+ }
+ } else {
+ if (CREATE(rdata.data[1])) {
+ nsec3chain = true;
+ }
+ }
+ }
+
+ if (signing) {
+ if (nsec3chain) {
+ if (build_nsec3 != NULL) {
+ *build_nsec3 = true;
+ }
+ } else {
+ if (build_nsec != NULL) {
+ *build_nsec = true;
+ }
+ }
+ }
+
+success:
+ result = ISC_R_SUCCESS;
+failure:
+ if (dns_rdataset_isassociated(&nsecset)) {
+ dns_rdataset_disassociate(&nsecset);
+ }
+ if (dns_rdataset_isassociated(&nsec3paramset)) {
+ dns_rdataset_disassociate(&nsec3paramset);
+ }
+ if (dns_rdataset_isassociated(&privateset)) {
+ dns_rdataset_disassociate(&privateset);
+ }
+ if (node != NULL) {
+ dns_db_detachnode(db, &node);
+ }
+ return (result);
+}
+
+isc_result_t
+dns_private_totext(dns_rdata_t *private, isc_buffer_t *buf) {
+ isc_result_t result;
+
+ if (private->length < 5) {
+ return (ISC_R_NOTFOUND);
+ }
+
+ if (private->data[0] == 0) {
+ unsigned char nsec3buf[DNS_NSEC3PARAM_BUFFERSIZE];
+ unsigned char newbuf[DNS_NSEC3PARAM_BUFFERSIZE];
+ dns_rdata_t rdata = DNS_RDATA_INIT;
+ dns_rdata_nsec3param_t nsec3param;
+ bool del, init, nonsec;
+ isc_buffer_t b;
+
+ if (!dns_nsec3param_fromprivate(private, &rdata, nsec3buf,
+ sizeof(nsec3buf)))
+ {
+ CHECK(ISC_R_FAILURE);
+ }
+
+ CHECK(dns_rdata_tostruct(&rdata, &nsec3param, NULL));
+
+ del = ((nsec3param.flags & DNS_NSEC3FLAG_REMOVE) != 0);
+ init = ((nsec3param.flags & DNS_NSEC3FLAG_INITIAL) != 0);
+ nonsec = ((nsec3param.flags & DNS_NSEC3FLAG_NONSEC) != 0);
+
+ nsec3param.flags &=
+ ~(DNS_NSEC3FLAG_CREATE | DNS_NSEC3FLAG_REMOVE |
+ DNS_NSEC3FLAG_INITIAL | DNS_NSEC3FLAG_NONSEC);
+
+ if (init) {
+ isc_buffer_putstr(buf, "Pending NSEC3 chain ");
+ } else if (del) {
+ isc_buffer_putstr(buf, "Removing NSEC3 chain ");
+ } else {
+ isc_buffer_putstr(buf, "Creating NSEC3 chain ");
+ }
+
+ dns_rdata_reset(&rdata);
+ isc_buffer_init(&b, newbuf, sizeof(newbuf));
+ CHECK(dns_rdata_fromstruct(&rdata, dns_rdataclass_in,
+ dns_rdatatype_nsec3param,
+ &nsec3param, &b));
+
+ CHECK(dns_rdata_totext(&rdata, NULL, buf));
+
+ if (del && !nonsec) {
+ isc_buffer_putstr(buf, " / creating NSEC chain");
+ }
+ } else if (private->length == 5) {
+ unsigned char alg = private->data[0];
+ dns_keytag_t keyid = (private->data[2] | private->data[1] << 8);
+ char keybuf[DNS_SECALG_FORMATSIZE + BUFSIZ],
+ algbuf[DNS_SECALG_FORMATSIZE];
+ bool del = private->data[3];
+ bool complete = private->data[4];
+
+ if (del && complete) {
+ isc_buffer_putstr(buf, "Done removing signatures for ");
+ } else if (del) {
+ isc_buffer_putstr(buf, "Removing signatures for ");
+ } else if (complete) {
+ isc_buffer_putstr(buf, "Done signing with ");
+ } else {
+ isc_buffer_putstr(buf, "Signing with ");
+ }
+
+ dns_secalg_format(alg, algbuf, sizeof(algbuf));
+ snprintf(keybuf, sizeof(keybuf), "key %d/%s", keyid, algbuf);
+ isc_buffer_putstr(buf, keybuf);
+ } else {
+ return (ISC_R_NOTFOUND);
+ }
+
+ isc_buffer_putuint8(buf, 0);
+ result = ISC_R_SUCCESS;
+failure:
+ return (result);
+}
diff --git a/lib/dns/rbt.c b/lib/dns/rbt.c
new file mode 100644
index 0000000..57dcd54
--- /dev/null
+++ b/lib/dns/rbt.c
@@ -0,0 +1,3124 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/*! \file */
+
+#include <inttypes.h>
+#include <stdbool.h>
+#include <sys/stat.h>
+
+#include <isc/crc64.h>
+#include <isc/file.h>
+#include <isc/hex.h>
+#include <isc/mem.h>
+#include <isc/once.h>
+#include <isc/print.h>
+#include <isc/refcount.h>
+#include <isc/stdio.h>
+#include <isc/string.h>
+#include <isc/util.h>
+
+/*%
+ * 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 <unistd.h>
+
+#include <isc/result.h>
+
+#include <dns/fixedname.h>
+#include <dns/log.h>
+#include <dns/rbt.h>
+
+#define CHECK(x) \
+ do { \
+ result = (x); \
+ if (result != ISC_R_SUCCESS) \
+ goto cleanup; \
+ } while (0)
+
+#define RBT_MAGIC ISC_MAGIC('R', 'B', 'T', '+')
+#define VALID_RBT(rbt) ISC_MAGIC_VALID(rbt, RBT_MAGIC)
+
+/*
+ * XXXDCL Since parent pointers were added in again, I could remove all of the
+ * chain junk, and replace with dns_rbt_firstnode, _previousnode, _nextnode,
+ * _lastnode. This would involve pretty major change to the API.
+ */
+#define CHAIN_MAGIC ISC_MAGIC('0', '-', '0', '-')
+#define VALID_CHAIN(chain) ISC_MAGIC_VALID(chain, CHAIN_MAGIC)
+
+#define RBT_HASH_NO_BITS 0
+#define RBT_HASH_MIN_BITS 4
+#define RBT_HASH_MAX_BITS 32
+#define RBT_HASH_OVERCOMMIT 3
+
+#define RBT_HASH_NEXTTABLE(hindex) ((hindex == 0) ? 1 : 0)
+
+#define GOLDEN_RATIO_32 0x61C88647
+
+#define HASHSIZE(bits) (UINT64_C(1) << (bits))
+
+static uint32_t
+hash_32(uint32_t val, unsigned int bits) {
+ REQUIRE(bits <= RBT_HASH_MAX_BITS);
+ /* High bits are more random. */
+ return (val * GOLDEN_RATIO_32 >> (32 - bits));
+}
+
+struct dns_rbt {
+ unsigned int magic;
+ isc_mem_t *mctx;
+ dns_rbtnode_t *root;
+ void (*data_deleter)(void *, void *);
+ void *deleter_arg;
+ unsigned int nodecount;
+ uint8_t hashbits[2];
+ dns_rbtnode_t **hashtable[2];
+ uint8_t hindex;
+ uint32_t hiter;
+};
+
+#define RED 0
+#define BLACK 1
+
+/*%
+ * Elements of the rbtnode structure.
+ */
+#define PARENT(node) ((node)->parent)
+#define LEFT(node) ((node)->left)
+#define RIGHT(node) ((node)->right)
+#define DOWN(node) ((node)->down)
+#define UPPERNODE(node) ((node)->uppernode)
+#define DATA(node) ((node)->data)
+#define IS_EMPTY(node) ((node)->data == NULL)
+#define HASHNEXT(node) ((node)->hashnext)
+#define HASHVAL(node) ((node)->hashval)
+#define COLOR(node) ((node)->color)
+#define NAMELEN(node) ((node)->namelen)
+#define OLDNAMELEN(node) ((node)->oldnamelen)
+#define OFFSETLEN(node) ((node)->offsetlen)
+#define ATTRS(node) ((node)->attributes)
+#define IS_ROOT(node) ((node)->is_root)
+#define FINDCALLBACK(node) ((node)->find_callback)
+
+#define WANTEMPTYDATA_OR_DATA(options, node) \
+ ((options & DNS_RBTFIND_EMPTYDATA) != 0 || DATA(node) != NULL)
+
+/*%
+ * Structure elements from the rbtdb.c, not
+ * used as part of the rbt.c algorithms.
+ */
+#define DIRTY(node) ((node)->dirty)
+#define WILD(node) ((node)->wild)
+#define LOCKNUM(node) ((node)->locknum)
+
+/*%
+ * The variable length stuff stored after the node has the following
+ * structure.
+ *
+ * &lt;name_data&gt;{1..255}&lt;oldoffsetlen&gt;{1}&lt;offsets&gt;{1..128}
+ *
+ * &lt;name_data&gt; contains the name of the node when it was created.
+ * &lt;oldoffsetlen&gt; contains the length of &lt;offsets&gt; when the node
+ * was created.
+ * &lt;offsets&gt; contains the offsets into name for each label when the node
+ * was created.
+ */
+
+#define NAME(node) ((unsigned char *)((node) + 1))
+#define OFFSETS(node) (NAME(node) + OLDNAMELEN(node) + 1)
+#define OLDOFFSETLEN(node) (OFFSETS(node)[-1])
+
+#define NODE_SIZE(node) \
+ (sizeof(*node) + OLDNAMELEN(node) + OLDOFFSETLEN(node) + 1)
+
+/*%
+ * Color management.
+ */
+#define IS_RED(node) ((node) != NULL && (node)->color == RED)
+#define IS_BLACK(node) ((node) == NULL || (node)->color == BLACK)
+#define MAKE_RED(node) ((node)->color = RED)
+#define MAKE_BLACK(node) ((node)->color = BLACK)
+
+/*%
+ * Chain management.
+ *
+ * The "ancestors" member of chains were removed, with their job now
+ * being wholly handled by parent pointers (which didn't exist, because
+ * of memory concerns, when chains were first implemented).
+ */
+#define ADD_LEVEL(chain, node) \
+ do { \
+ INSIST((chain)->level_count < DNS_RBT_LEVELBLOCK); \
+ (chain)->levels[(chain)->level_count++] = (node); \
+ } while (0)
+
+/*%
+ * The following macros directly access normally private name variables.
+ * These macros are used to avoid a lot of function calls in the critical
+ * path of the tree traversal code.
+ */
+
+static void
+NODENAME(dns_rbtnode_t *node, dns_name_t *name) {
+ name->length = NAMELEN(node);
+ name->labels = OFFSETLEN(node);
+ name->ndata = NAME(node);
+ name->offsets = OFFSETS(node);
+ name->attributes = ATTRS(node);
+ name->attributes |= DNS_NAMEATTR_READONLY;
+}
+
+#ifdef DEBUG
+/*
+ * A little something to help out in GDB.
+ */
+dns_name_t
+Name(dns_rbtnode_t *node);
+dns_name_t
+Name(dns_rbtnode_t *node) {
+ dns_name_t name;
+
+ dns_name_init(&name, NULL);
+ if (node != NULL) {
+ NODENAME(node, &name);
+ }
+
+ return (name);
+}
+#endif /* DEBUG */
+
+/*
+ * Upper node is the parent of the root of the passed node's
+ * subtree. The passed node must not be NULL.
+ */
+static dns_rbtnode_t *
+get_upper_node(dns_rbtnode_t *node) {
+ return (UPPERNODE(node));
+}
+
+size_t
+dns__rbtnode_getdistance(dns_rbtnode_t *node) {
+ size_t nodes = 1;
+
+ while (node != NULL) {
+ if (IS_ROOT(node)) {
+ break;
+ }
+ nodes++;
+ node = PARENT(node);
+ }
+
+ return (nodes);
+}
+
+/*
+ * Forward declarations.
+ */
+static isc_result_t
+create_node(isc_mem_t *mctx, const dns_name_t *name, dns_rbtnode_t **nodep);
+
+static void
+hashtable_new(dns_rbt_t *rbt, uint8_t index, uint8_t bits);
+static void
+hashtable_free(dns_rbt_t *rbt, uint8_t index);
+
+static void
+hash_node(dns_rbt_t *rbt, dns_rbtnode_t *node, const dns_name_t *name);
+
+static void
+unhash_node(dns_rbt_t *rbt, dns_rbtnode_t *node);
+
+static uint32_t
+rehash_bits(dns_rbt_t *rbt, size_t newcount);
+static void
+hashtable_rehash(dns_rbt_t *rbt, uint32_t newbits);
+static void
+hashtable_rehash_one(dns_rbt_t *rbt);
+static void
+maybe_rehash(dns_rbt_t *rbt, size_t size);
+static bool
+rehashing_in_progress(dns_rbt_t *rbt);
+
+#define TRY_NEXTTABLE(hindex, rbt) \
+ (hindex == rbt->hindex && rehashing_in_progress(rbt))
+
+static void
+rotate_left(dns_rbtnode_t *node, dns_rbtnode_t **rootp);
+static void
+rotate_right(dns_rbtnode_t *node, dns_rbtnode_t **rootp);
+
+static void
+addonlevel(dns_rbtnode_t *node, dns_rbtnode_t *current, int order,
+ dns_rbtnode_t **rootp);
+
+static void
+deletefromlevel(dns_rbtnode_t *item, dns_rbtnode_t **rootp);
+
+static void
+deletetreeflat(dns_rbt_t *rbt, unsigned int quantum, bool unhash,
+ dns_rbtnode_t **nodep);
+
+static void
+printnodename(dns_rbtnode_t *node, bool quoted, FILE *f);
+
+static void
+freenode(dns_rbt_t *rbt, dns_rbtnode_t **nodep);
+
+unsigned int
+dns__rbtnode_namelen(dns_rbtnode_t *node) {
+ dns_name_t current;
+ unsigned int len = 0;
+
+ REQUIRE(DNS_RBTNODE_VALID(node));
+
+ dns_name_init(&current, NULL);
+
+ do {
+ if (node != NULL) {
+ NODENAME(node, &current);
+ len += current.length;
+ } else {
+ len += 1;
+ break;
+ }
+
+ node = get_upper_node(node);
+ } while (!dns_name_isabsolute(&current));
+
+ return (len);
+}
+
+/*
+ * Initialize a red/black tree of trees.
+ */
+isc_result_t
+dns_rbt_create(isc_mem_t *mctx, dns_rbtdeleter_t deleter, void *deleter_arg,
+ dns_rbt_t **rbtp) {
+ dns_rbt_t *rbt;
+
+ REQUIRE(mctx != NULL);
+ REQUIRE(rbtp != NULL && *rbtp == NULL);
+ REQUIRE(deleter == NULL ? deleter_arg == NULL : 1);
+
+ rbt = isc_mem_get(mctx, sizeof(*rbt));
+ *rbt = (dns_rbt_t){
+ .data_deleter = deleter,
+ .deleter_arg = deleter_arg,
+ };
+
+ isc_mem_attach(mctx, &rbt->mctx);
+
+ hashtable_new(rbt, 0, RBT_HASH_MIN_BITS);
+
+ rbt->magic = RBT_MAGIC;
+
+ *rbtp = rbt;
+
+ return (ISC_R_SUCCESS);
+}
+
+/*
+ * Deallocate a red/black tree of trees.
+ */
+void
+dns_rbt_destroy(dns_rbt_t **rbtp) {
+ RUNTIME_CHECK(dns_rbt_destroy2(rbtp, 0) == ISC_R_SUCCESS);
+}
+
+isc_result_t
+dns_rbt_destroy2(dns_rbt_t **rbtp, unsigned int quantum) {
+ dns_rbt_t *rbt;
+
+ REQUIRE(rbtp != NULL && VALID_RBT(*rbtp));
+
+ rbt = *rbtp;
+
+ deletetreeflat(rbt, quantum, false, &rbt->root);
+ if (rbt->root != NULL) {
+ return (ISC_R_QUOTA);
+ }
+
+ *rbtp = NULL;
+
+ INSIST(rbt->nodecount == 0);
+
+ if (rbt->hashtable[0] != NULL) {
+ hashtable_free(rbt, 0);
+ }
+ if (rbt->hashtable[1] != NULL) {
+ hashtable_free(rbt, 1);
+ }
+
+ rbt->magic = 0;
+
+ isc_mem_putanddetach(&rbt->mctx, rbt, sizeof(*rbt));
+ return (ISC_R_SUCCESS);
+}
+
+unsigned int
+dns_rbt_nodecount(dns_rbt_t *rbt) {
+ REQUIRE(VALID_RBT(rbt));
+
+ return (rbt->nodecount);
+}
+
+size_t
+dns_rbt_hashsize(dns_rbt_t *rbt) {
+ REQUIRE(VALID_RBT(rbt));
+
+ uint8_t hashbits = (rbt->hashbits[0] > rbt->hashbits[1])
+ ? rbt->hashbits[0]
+ : rbt->hashbits[1];
+
+ return (1 << hashbits);
+}
+
+static isc_result_t
+chain_name(dns_rbtnodechain_t *chain, dns_name_t *name,
+ bool include_chain_end) {
+ dns_name_t nodename;
+ isc_result_t result = ISC_R_SUCCESS;
+ int i;
+
+ dns_name_init(&nodename, NULL);
+
+ if (include_chain_end && chain->end != NULL) {
+ NODENAME(chain->end, &nodename);
+ dns_name_copy(&nodename, name);
+ } else {
+ dns_name_reset(name);
+ }
+
+ for (i = (int)chain->level_count - 1; i >= 0; i--) {
+ NODENAME(chain->levels[i], &nodename);
+ result = dns_name_concatenate(name, &nodename, name, NULL);
+
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+ }
+ return (result);
+}
+
+static isc_result_t
+move_chain_to_last(dns_rbtnodechain_t *chain, dns_rbtnode_t *node) {
+ do {
+ /*
+ * Go as far right and then down as much as possible,
+ * as long as the rightmost node has a down pointer.
+ */
+ while (RIGHT(node) != NULL) {
+ node = RIGHT(node);
+ }
+
+ if (DOWN(node) == NULL) {
+ break;
+ }
+
+ ADD_LEVEL(chain, node);
+ node = DOWN(node);
+ } while (1);
+
+ chain->end = node;
+
+ return (ISC_R_SUCCESS);
+}
+
+/*
+ * Add 'name' to tree, initializing its data pointer with 'data'.
+ */
+
+isc_result_t
+dns_rbt_addnode(dns_rbt_t *rbt, const dns_name_t *name, dns_rbtnode_t **nodep) {
+ /*
+ * Does this thing have too many variables or what?
+ */
+ dns_rbtnode_t **root, *parent, *child, *current, *new_current;
+ dns_name_t *add_name, *new_name, current_name, *prefix, *suffix;
+ dns_fixedname_t fixedcopy, fixedprefix, fixedsuffix, fnewname;
+ dns_offsets_t current_offsets;
+ dns_namereln_t compared;
+ isc_result_t result = ISC_R_SUCCESS;
+ unsigned int level_count;
+ unsigned int common_labels;
+ unsigned int nlabels, hlabels;
+ int order;
+
+ REQUIRE(VALID_RBT(rbt));
+ REQUIRE(dns_name_isabsolute(name));
+ REQUIRE(nodep != NULL && *nodep == NULL);
+
+ /*
+ * Dear future BIND developer,
+ *
+ * After you have tried attempting to optimize this routine by
+ * using the hashtable and have realized your folly, please
+ * append another cross ("X") below as a warning to the next
+ * future BIND developer:
+ *
+ * Number of victim developers: X
+ *
+ * I wish the past developer had included such a notice.
+ *
+ * Long form: Unlike dns_rbt_findnode(), this function does not
+ * lend itself to be optimized using the hashtable:
+ *
+ * 1. In the subtree where the insertion occurs, this function
+ * needs to have the insertion point and the order where the
+ * lookup terminated (i.e., at the insertion point where left or
+ * right child is NULL). This cannot be determined from the
+ * hashtable, so at least in that subtree, a BST O(log N) lookup
+ * is necessary.
+ *
+ * 2. Our RBT nodes contain not only single labels but label
+ * sequences to optimize space usage. So at every level, we have
+ * to look for a match in the hashtable for all superdomains in
+ * the rest of the name we're searching. This is an O(N)
+ * operation at least, here N being the label size of name, each
+ * of which is a hashtable lookup involving dns_name_equal()
+ * comparisons.
+ */
+
+ /*
+ * Create a copy of the name so the original name structure is
+ * not modified.
+ */
+ add_name = dns_fixedname_initname(&fixedcopy);
+ INSIST(add_name != NULL);
+ dns_name_clone(name, add_name);
+
+ if (rbt->root == NULL) {
+ result = create_node(rbt->mctx, add_name, &new_current);
+ if (result == ISC_R_SUCCESS) {
+ rbt->nodecount++;
+ new_current->is_root = 1;
+
+ UPPERNODE(new_current) = NULL;
+
+ rbt->root = new_current;
+ *nodep = new_current;
+ hash_node(rbt, new_current, name);
+ }
+ return (result);
+ }
+
+ level_count = 0;
+
+ prefix = dns_fixedname_initname(&fixedprefix);
+ suffix = dns_fixedname_initname(&fixedsuffix);
+
+ INSIST(prefix != NULL);
+ INSIST(suffix != NULL);
+
+ root = &rbt->root;
+ INSIST(IS_ROOT(*root));
+ parent = NULL;
+ current = NULL;
+ child = *root;
+ dns_name_init(&current_name, current_offsets);
+ new_name = dns_fixedname_initname(&fnewname);
+ nlabels = dns_name_countlabels(name);
+ hlabels = 0;
+
+ do {
+ current = child;
+
+ NODENAME(current, &current_name);
+ compared = dns_name_fullcompare(add_name, &current_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(&current_name, common_labels,
+ prefix, suffix);
+ result = create_node(rbt->mctx, suffix,
+ &new_current);
+
+ if (result != ISC_R_SUCCESS) {
+ break;
+ }
+
+ /*
+ * Reproduce the tree attributes of the
+ * current node.
+ */
+ new_current->is_root = current->is_root;
+ if (current->nsec == DNS_RBT_NSEC_HAS_NSEC) {
+ new_current->nsec = DNS_RBT_NSEC_NORMAL;
+ } else {
+ new_current->nsec = current->nsec;
+ }
+ PARENT(new_current) = PARENT(current);
+ LEFT(new_current) = LEFT(current);
+ RIGHT(new_current) = RIGHT(current);
+ COLOR(new_current) = COLOR(current);
+
+ /*
+ * Fix pointers that were to the current node.
+ */
+ if (parent != NULL) {
+ if (LEFT(parent) == current) {
+ LEFT(parent) = new_current;
+ } else {
+ RIGHT(parent) = new_current;
+ }
+ }
+ if (LEFT(new_current) != NULL) {
+ PARENT(LEFT(new_current)) = new_current;
+ }
+ if (RIGHT(new_current) != NULL) {
+ PARENT(RIGHT(new_current)) =
+ new_current;
+ }
+ if (*root == current) {
+ *root = new_current;
+ }
+
+ NAMELEN(current) = prefix->length;
+ OFFSETLEN(current) = prefix->labels;
+
+ /*
+ * Set up the new root of the next level.
+ * By definition it will not be the top
+ * level tree, so clear DNS_NAMEATTR_ABSOLUTE.
+ */
+ current->is_root = 1;
+ PARENT(current) = new_current;
+ DOWN(new_current) = current;
+ root = &DOWN(new_current);
+
+ UPPERNODE(new_current) = UPPERNODE(current);
+ UPPERNODE(current) = new_current;
+
+ INSIST(level_count < DNS_RBT_LEVELBLOCK);
+ level_count++;
+
+ LEFT(current) = NULL;
+ RIGHT(current) = NULL;
+
+ MAKE_BLACK(current);
+ ATTRS(current) &= ~DNS_NAMEATTR_ABSOLUTE;
+
+ rbt->nodecount++;
+ dns_name_getlabelsequence(name,
+ nlabels - hlabels,
+ hlabels, new_name);
+ hash_node(rbt, new_current, new_name);
+
+ if (common_labels ==
+ dns_name_countlabels(add_name))
+ {
+ /*
+ * The name has been added by pushing
+ * the not-in-common parts down to
+ * a new level.
+ */
+ *nodep = new_current;
+ return (ISC_R_SUCCESS);
+ } else {
+ /*
+ * The current node has no data,
+ * because it is just a placeholder.
+ * Its data pointer is already NULL
+ * from create_node()), so there's
+ * nothing more to do to it.
+ */
+
+ /*
+ * The not-in-common parts of the new
+ * name will be inserted into the new
+ * level following this loop (unless
+ * result != ISC_R_SUCCESS, which
+ * is tested after the loop ends).
+ */
+ dns_name_split(add_name, common_labels,
+ add_name, NULL);
+
+ break;
+ }
+ }
+ }
+ } while (child != NULL);
+
+ if (result == ISC_R_SUCCESS) {
+ result = create_node(rbt->mctx, add_name, &new_current);
+ }
+
+ if (result == ISC_R_SUCCESS) {
+ if (*root == NULL) {
+ UPPERNODE(new_current) = current;
+ } else {
+ UPPERNODE(new_current) = PARENT(*root);
+ }
+
+ addonlevel(new_current, current, order, root);
+ rbt->nodecount++;
+ *nodep = new_current;
+ hash_node(rbt, new_current, name);
+ }
+
+ return (result);
+}
+
+/*
+ * Add a name to the tree of trees, associating it with some data.
+ */
+isc_result_t
+dns_rbt_addname(dns_rbt_t *rbt, const dns_name_t *name, void *data) {
+ isc_result_t result;
+ dns_rbtnode_t *node;
+
+ REQUIRE(VALID_RBT(rbt));
+ REQUIRE(dns_name_isabsolute(name));
+
+ node = NULL;
+
+ result = dns_rbt_addnode(rbt, name, &node);
+
+ /*
+ * dns_rbt_addnode will report the node exists even when
+ * it does not have data associated with it, but the
+ * dns_rbt_*name functions all behave depending on whether
+ * there is data associated with a node.
+ */
+ if (result == ISC_R_SUCCESS ||
+ (result == ISC_R_EXISTS && DATA(node) == NULL))
+ {
+ DATA(node) = data;
+ result = ISC_R_SUCCESS;
+ }
+
+ return (result);
+}
+
+/*
+ * Find the node for "name" in the tree of trees.
+ */
+isc_result_t
+dns_rbt_findnode(dns_rbt_t *rbt, const dns_name_t *name, dns_name_t *foundname,
+ dns_rbtnode_t **node, dns_rbtnodechain_t *chain,
+ unsigned int options, dns_rbtfindcallback_t callback,
+ void *callback_arg) {
+ dns_rbtnode_t *current, *last_compared;
+ dns_rbtnodechain_t localchain;
+ dns_name_t *search_name, current_name, *callback_name;
+ dns_fixedname_t fixedcallbackname, fixedsearchname;
+ dns_namereln_t compared;
+ isc_result_t result, saved_result;
+ unsigned int common_labels;
+ unsigned int hlabels = 0;
+ int order;
+ uint8_t hindex;
+
+ REQUIRE(VALID_RBT(rbt));
+ REQUIRE(dns_name_isabsolute(name));
+ REQUIRE(node != NULL && *node == NULL);
+ REQUIRE((options & (DNS_RBTFIND_NOEXACT | DNS_RBTFIND_NOPREDECESSOR)) !=
+ (DNS_RBTFIND_NOEXACT | DNS_RBTFIND_NOPREDECESSOR));
+
+ /*
+ * If there is a chain it needs to appear to be in a sane state,
+ * otherwise a chain is still needed to generate foundname and
+ * callback_name.
+ */
+ if (chain == NULL) {
+ options |= DNS_RBTFIND_NOPREDECESSOR;
+ chain = &localchain;
+ dns_rbtnodechain_init(chain);
+ } else {
+ dns_rbtnodechain_reset(chain);
+ }
+
+ if (rbt->root == NULL) {
+ return (ISC_R_NOTFOUND);
+ }
+
+ /*
+ * Appease GCC about variables it incorrectly thinks are
+ * possibly used uninitialized.
+ */
+ compared = dns_namereln_none;
+ last_compared = NULL;
+ order = 0;
+
+ callback_name = dns_fixedname_initname(&fixedcallbackname);
+
+ /*
+ * search_name is the name segment being sought in each tree level.
+ * By using a fixedname, the search_name will definitely have offsets
+ * for use by any splitting.
+ * By using dns_name_clone, no name data should be copied thanks to
+ * the lack of bitstring labels.
+ */
+ search_name = dns_fixedname_initname(&fixedsearchname);
+ INSIST(search_name != NULL);
+ dns_name_clone(name, search_name);
+
+ dns_name_init(&current_name, NULL);
+
+ saved_result = ISC_R_SUCCESS;
+ current = rbt->root;
+
+ while (current != NULL) {
+ NODENAME(current, &current_name);
+ compared = dns_name_fullcompare(search_name, &current_name,
+ &order, &common_labels);
+ /*
+ * last_compared is used as a shortcut to start (or
+ * continue rather) finding the stop-node of the search
+ * when hashing was used (see much below in this
+ * function).
+ */
+ last_compared = current;
+
+ if (compared == dns_namereln_equal) {
+ break;
+ }
+
+ if (compared == dns_namereln_none) {
+ /*
+ * Here, current is pointing at a subtree root
+ * node. We try to find a matching node using
+ * the hashtable. We can get one of 3 results
+ * here: (a) we locate the matching node, (b) we
+ * find a node to which the current node has a
+ * subdomain relation, (c) we fail to find (a)
+ * or (b).
+ */
+
+ dns_name_t hash_name;
+ dns_rbtnode_t *hnode;
+ dns_rbtnode_t *up_current;
+ unsigned int nlabels;
+ unsigned int tlabels = 1;
+ uint32_t hashval;
+ uint32_t hash;
+
+ /*
+ * The case of current not being a subtree root,
+ * that means a left or right pointer was
+ * followed, only happens when the algorithm
+ * fell through to the traditional binary search
+ * because of a bitstring label. Since we
+ * dropped the bitstring support, this should
+ * not happen.
+ */
+ INSIST(IS_ROOT(current));
+
+ nlabels = dns_name_countlabels(search_name);
+
+ /*
+ * current is the root of the current level, so
+ * its parent is the same as its "up" pointer.
+ */
+ up_current = PARENT(current);
+ dns_name_init(&hash_name, NULL);
+
+ hashagain:
+ hindex = rbt->hindex;
+ /*
+ * Compute the hash over the full absolute
+ * name. Look for the smallest suffix match at
+ * this tree level (hlevel), and then at every
+ * iteration, look for the next smallest suffix
+ * match (add another subdomain label to the
+ * absolute name being hashed).
+ */
+ dns_name_getlabelsequence(name, nlabels - tlabels,
+ hlabels + tlabels,
+ &hash_name);
+ hashval = dns_name_fullhash(&hash_name, false);
+
+ dns_name_getlabelsequence(search_name,
+ nlabels - tlabels, tlabels,
+ &hash_name);
+
+ nexttable:
+ /*
+ * Walk all the nodes in the hash bucket pointed
+ * by the computed hash value.
+ */
+
+ hash = hash_32(hashval, rbt->hashbits[hindex]);
+
+ for (hnode = rbt->hashtable[hindex][hash];
+ hnode != NULL; hnode = HASHNEXT(hnode))
+ {
+ dns_name_t hnode_name;
+
+ if (hashval != HASHVAL(hnode)) {
+ continue;
+ }
+ /*
+ * This checks that the hashed label sequence
+ * being looked up is at the same tree level, so
+ * that we don't match a labelsequence from some
+ * other subdomain.
+ */
+ if (get_upper_node(hnode) != up_current) {
+ continue;
+ }
+
+ dns_name_init(&hnode_name, NULL);
+ NODENAME(hnode, &hnode_name);
+ if (dns_name_equal(&hnode_name, &hash_name)) {
+ break;
+ }
+ }
+
+ if (hnode != NULL) {
+ current = hnode;
+ /*
+ * This is an optimization. If hashing found
+ * the right node, the next call to
+ * dns_name_fullcompare() would obviously
+ * return _equal or _subdomain. Determine
+ * which of those would be the case by
+ * checking if the full name was hashed. Then
+ * make it look like dns_name_fullcompare
+ * was called and jump to the right place.
+ */
+ if (tlabels == nlabels) {
+ compared = dns_namereln_equal;
+ break;
+ } else {
+ common_labels = tlabels;
+ compared = dns_namereln_subdomain;
+ goto subdomain;
+ }
+ }
+
+ if (TRY_NEXTTABLE(hindex, rbt)) {
+ /*
+ * Rehashing in progress, check the other table
+ */
+ hindex = RBT_HASH_NEXTTABLE(rbt->hindex);
+ goto nexttable;
+ }
+
+ if (tlabels++ < nlabels) {
+ goto hashagain;
+ }
+
+ /*
+ * All of the labels have been tried against the hash
+ * table. Since we dropped the support of bitstring
+ * labels, the name isn't in the table.
+ */
+ current = NULL;
+ continue;
+ } else {
+ /*
+ * The names have some common suffix labels.
+ *
+ * If the number in common are equal in length to
+ * the current node's name length, then follow the
+ * down pointer and search in the new tree.
+ */
+ if (compared == dns_namereln_subdomain) {
+ subdomain:
+ /*
+ * Whack off the current node's common parts
+ * for the name to search in the next level.
+ */
+ dns_name_split(search_name, common_labels,
+ search_name, NULL);
+ hlabels += common_labels;
+ /*
+ * This might be the closest enclosing name.
+ */
+ if (WANTEMPTYDATA_OR_DATA(options, current)) {
+ *node = current;
+ }
+
+ /*
+ * Point the chain to the next level. This
+ * needs to be done before 'current' is pointed
+ * there because the callback in the next
+ * block of code needs the current 'current',
+ * but in the event the callback requests that
+ * the search be stopped then the
+ * DNS_R_PARTIALMATCH code at the end of this
+ * function needs the chain pointed to the
+ * next level.
+ */
+ ADD_LEVEL(chain, current);
+
+ /*
+ * The caller may want to interrupt the
+ * downward search when certain special nodes
+ * are traversed. If this is a special node,
+ * the callback is used to learn what the
+ * caller wants to do.
+ */
+ if (callback != NULL && FINDCALLBACK(current)) {
+ result = chain_name(
+ chain, callback_name, false);
+ if (result != ISC_R_SUCCESS) {
+ dns_rbtnodechain_reset(chain);
+ return (result);
+ }
+
+ result = (callback)(current,
+ callback_name,
+ callback_arg);
+ if (result != DNS_R_CONTINUE) {
+ saved_result = result;
+ /*
+ * Treat this node as if it
+ * had no down pointer.
+ */
+ current = NULL;
+ break;
+ }
+ }
+
+ /*
+ * Finally, head to the next tree level.
+ */
+ current = DOWN(current);
+ } else {
+ /*
+ * Though there are labels in common, the
+ * entire name at this node is not common
+ * with the search name so the search
+ * name does not exist in the tree.
+ */
+ INSIST(compared ==
+ dns_namereln_commonancestor ||
+ compared == dns_namereln_contains);
+
+ current = NULL;
+ }
+ }
+ }
+
+ /*
+ * If current is not NULL, NOEXACT is not disallowing exact matches,
+ * and either the node has data or an empty node is ok, return
+ * ISC_R_SUCCESS to indicate an exact match.
+ */
+ if (current != NULL && (options & DNS_RBTFIND_NOEXACT) == 0 &&
+ WANTEMPTYDATA_OR_DATA(options, current))
+ {
+ /*
+ * Found an exact match.
+ */
+ chain->end = current;
+ chain->level_matches = chain->level_count;
+
+ if (foundname != NULL) {
+ result = chain_name(chain, foundname, true);
+ } else {
+ result = ISC_R_SUCCESS;
+ }
+
+ if (result == ISC_R_SUCCESS) {
+ *node = current;
+ result = saved_result;
+ } else {
+ *node = NULL;
+ }
+ } else {
+ /*
+ * Did not find an exact match (or did not want one).
+ */
+ if (*node != NULL) {
+ /*
+ * ... but found a partially matching superdomain.
+ * Unwind the chain to the partial match node
+ * to set level_matches to the level above the node,
+ * and then to derive the name.
+ *
+ * chain->level_count is guaranteed to be at least 1
+ * here because by definition of finding a superdomain,
+ * the chain is pointed to at least the first subtree.
+ */
+ chain->level_matches = chain->level_count - 1;
+
+ while (chain->levels[chain->level_matches] != *node) {
+ INSIST(chain->level_matches > 0);
+ chain->level_matches--;
+ }
+
+ if (foundname != NULL) {
+ unsigned int saved_count = chain->level_count;
+
+ chain->level_count = chain->level_matches + 1;
+
+ result = chain_name(chain, foundname, false);
+
+ chain->level_count = saved_count;
+ } else {
+ result = ISC_R_SUCCESS;
+ }
+
+ if (result == ISC_R_SUCCESS) {
+ result = DNS_R_PARTIALMATCH;
+ }
+ } else {
+ result = ISC_R_NOTFOUND;
+ }
+
+ if (current != NULL) {
+ /*
+ * There was an exact match but either
+ * DNS_RBTFIND_NOEXACT was set, or
+ * DNS_RBTFIND_EMPTYDATA was set and the node had no
+ * data. A policy decision was made to set the
+ * chain to the exact match, but this is subject
+ * to change if it becomes apparent that something
+ * else would be more useful. It is important that
+ * this case is handled here, because the predecessor
+ * setting code below assumes the match was not exact.
+ */
+ INSIST(((options & DNS_RBTFIND_NOEXACT) != 0) ||
+ ((options & DNS_RBTFIND_EMPTYDATA) == 0 &&
+ DATA(current) == NULL));
+ chain->end = current;
+ } else if ((options & DNS_RBTFIND_NOPREDECESSOR) != 0) {
+ /*
+ * Ensure the chain points nowhere.
+ */
+ chain->end = NULL;
+ } else {
+ /*
+ * Since there was no exact match, the chain argument
+ * needs to be pointed at the DNSSEC predecessor of
+ * the search name.
+ */
+ if (compared == dns_namereln_subdomain) {
+ /*
+ * Attempted to follow a down pointer that was
+ * NULL, which means the searched for name was
+ * a subdomain of a terminal name in the tree.
+ * Since there are no existing subdomains to
+ * order against, the terminal name is the
+ * predecessor.
+ */
+ INSIST(chain->level_count > 0);
+ INSIST(chain->level_matches <
+ chain->level_count);
+ chain->end =
+ chain->levels[--chain->level_count];
+ } else {
+ isc_result_t result2;
+
+ /*
+ * Point current to the node that stopped
+ * the search.
+ *
+ * With the hashing modification that has been
+ * added to the algorithm, the stop node of a
+ * standard binary search is not known. So it
+ * has to be found. There is probably a more
+ * clever way of doing this.
+ *
+ * The assignment of current to NULL when
+ * the relationship is *not* dns_namereln_none,
+ * even though it later gets set to the same
+ * last_compared anyway, is simply to not push
+ * the while loop in one more level of
+ * indentation.
+ */
+ if (compared == dns_namereln_none) {
+ current = last_compared;
+ } else {
+ current = NULL;
+ }
+
+ while (current != NULL) {
+ NODENAME(current, &current_name);
+ compared = dns_name_fullcompare(
+ search_name, &current_name,
+ &order, &common_labels);
+ POST(compared);
+
+ last_compared = current;
+
+ /*
+ * Standard binary search movement.
+ */
+ if (order < 0) {
+ current = LEFT(current);
+ } else {
+ current = RIGHT(current);
+ }
+ }
+
+ current = last_compared;
+
+ /*
+ * Reached a point within a level tree that
+ * positively indicates the name is not
+ * present, but the stop node could be either
+ * less than the desired name (order > 0) or
+ * greater than the desired name (order < 0).
+ *
+ * If the stop node is less, it is not
+ * necessarily the predecessor. If the stop
+ * node has a down pointer, then the real
+ * predecessor is at the end of a level below
+ * (not necessarily the next level).
+ * Move down levels until the rightmost node
+ * does not have a down pointer.
+ *
+ * When the stop node is greater, it is
+ * the successor. All the logic for finding
+ * the predecessor is handily encapsulated
+ * in dns_rbtnodechain_prev. In the event
+ * that the search name is less than anything
+ * else in the tree, the chain is reset.
+ * XXX DCL What is the best way for the caller
+ * to know that the search name has
+ * no predecessor?
+ */
+
+ if (order > 0) {
+ if (DOWN(current) != NULL) {
+ ADD_LEVEL(chain, current);
+
+ result2 = move_chain_to_last(
+ chain, DOWN(current));
+
+ if (result2 != ISC_R_SUCCESS) {
+ result = result2;
+ }
+ } else {
+ /*
+ * Ah, the pure and simple
+ * case. The stop node is the
+ * predecessor.
+ */
+ chain->end = current;
+ }
+ } else {
+ INSIST(order < 0);
+
+ chain->end = current;
+
+ result2 = dns_rbtnodechain_prev(
+ chain, NULL, NULL);
+ if (result2 == ISC_R_SUCCESS ||
+ result2 == DNS_R_NEWORIGIN)
+ {
+ /* Nothing. */
+ } else if (result2 == ISC_R_NOMORE) {
+ /*
+ * There is no predecessor.
+ */
+ dns_rbtnodechain_reset(chain);
+ } else {
+ result = result2;
+ }
+ }
+ }
+ }
+ }
+
+ ENSURE(*node == NULL || DNS_RBTNODE_VALID(*node));
+
+ return (result);
+}
+
+/*
+ * Get the data pointer associated with 'name'.
+ */
+isc_result_t
+dns_rbt_findname(dns_rbt_t *rbt, const dns_name_t *name, unsigned int options,
+ dns_name_t *foundname, void **data) {
+ dns_rbtnode_t *node = NULL;
+ isc_result_t result;
+
+ REQUIRE(data != NULL && *data == NULL);
+
+ result = dns_rbt_findnode(rbt, name, foundname, &node, NULL, options,
+ NULL, NULL);
+
+ if (node != NULL && WANTEMPTYDATA_OR_DATA(options, node)) {
+ *data = DATA(node);
+ } else {
+ result = ISC_R_NOTFOUND;
+ }
+
+ return (result);
+}
+
+/*
+ * Delete a name from the tree of trees.
+ */
+isc_result_t
+dns_rbt_deletename(dns_rbt_t *rbt, const dns_name_t *name, bool recurse) {
+ dns_rbtnode_t *node = NULL;
+ isc_result_t result;
+
+ REQUIRE(VALID_RBT(rbt));
+ REQUIRE(dns_name_isabsolute(name));
+
+ /*
+ * First, find the node.
+ *
+ * When searching, the name might not have an exact match:
+ * consider a.b.a.com, b.b.a.com and c.b.a.com as the only
+ * elements of a tree, which would make layer 1 a single
+ * node tree of "b.a.com" and layer 2 a three node tree of
+ * a, b, and c. Deleting a.com would find only a partial depth
+ * match in the first layer. Should it be a requirement that
+ * that the name to be deleted have data? For now, it is.
+ *
+ * ->dirty, ->locknum and ->references are ignored; they are
+ * solely the province of rbtdb.c.
+ */
+ result = dns_rbt_findnode(rbt, name, NULL, &node, NULL,
+ DNS_RBTFIND_NOOPTIONS, NULL, NULL);
+
+ if (result == ISC_R_SUCCESS) {
+ if (DATA(node) != NULL) {
+ result = dns_rbt_deletenode(rbt, node, recurse);
+ } else {
+ result = ISC_R_NOTFOUND;
+ }
+ } else if (result == DNS_R_PARTIALMATCH) {
+ result = ISC_R_NOTFOUND;
+ }
+
+ return (result);
+}
+
+/*
+ * Remove a node from the tree of trees.
+ *
+ * NOTE WELL: deletion is *not* symmetric with addition; that is, reversing
+ * a sequence of additions to be deletions will not generally get the
+ * tree back to the state it started in. For example, if the addition
+ * of "b.c" caused the node "a.b.c" to be split, pushing "a" to its own level,
+ * then the subsequent deletion of "b.c" will not cause "a" to be pulled up,
+ * restoring "a.b.c". The RBT *used* to do this kind of rejoining, but it
+ * turned out to be a bad idea because it could corrupt an active nodechain
+ * that had "b.c" as one of its levels -- and the RBT has no idea what
+ * nodechains are in use by callers, so it can't even *try* to helpfully
+ * fix them up (which would probably be doomed to failure anyway).
+ *
+ * Similarly, it is possible to leave the tree in a state where a supposedly
+ * deleted node still exists. The first case of this is obvious; take
+ * the tree which has "b.c" on one level, pointing to "a". Now deleted "b.c".
+ * It was just established in the previous paragraph why we can't pull "a"
+ * back up to its parent level. But what happens when "a" then gets deleted?
+ * "b.c" is left hanging around without data or children. This condition
+ * is actually pretty easy to detect, but ... should it really be removed?
+ * Is a chain pointing to it? An iterator? Who knows! (Note that the
+ * references structure member cannot be looked at because it is private to
+ * rbtdb.) This is ugly and makes me unhappy, but after hours of trying to
+ * make it more aesthetically proper and getting nowhere, this is the way it
+ * is going to stay until such time as it proves to be a *real* problem.
+ *
+ * Finally, for reference, note that the original routine that did node
+ * joining was called join_nodes(). It has been excised, living now only
+ * in the CVS history, but comments have been left behind that point to it just
+ * in case someone wants to muck with this some more.
+ *
+ * The one positive aspect of all of this is that joining used to have a
+ * case where it might fail. Without trying to join, now this function always
+ * succeeds. It still returns isc_result_t, though, so the API wouldn't change.
+ */
+isc_result_t
+dns_rbt_deletenode(dns_rbt_t *rbt, dns_rbtnode_t *node, bool recurse) {
+ dns_rbtnode_t *parent;
+
+ REQUIRE(VALID_RBT(rbt));
+ REQUIRE(DNS_RBTNODE_VALID(node));
+ INSIST(rbt->nodecount != 0);
+
+ if (DOWN(node) != NULL) {
+ if (recurse) {
+ PARENT(DOWN(node)) = NULL;
+ deletetreeflat(rbt, 0, true, &DOWN(node));
+ } else {
+ if (DATA(node) != NULL && rbt->data_deleter != NULL) {
+ rbt->data_deleter(DATA(node), rbt->deleter_arg);
+ }
+ DATA(node) = NULL;
+
+ /*
+ * Since there is at least one node below this one and
+ * no recursion was requested, the deletion is
+ * complete. The down node from this node might be all
+ * by itself on a single level, so join_nodes() could
+ * be used to collapse the tree (with all the caveats
+ * of the comment at the start of this function).
+ * But join_nodes() function has now been removed.
+ */
+ return (ISC_R_SUCCESS);
+ }
+ }
+
+ /*
+ * Note the node that points to the level of the node
+ * that is being deleted. If the deleted node is the
+ * top level, parent will be set to NULL.
+ */
+ parent = get_upper_node(node);
+
+ /*
+ * This node now has no down pointer, so now it needs
+ * to be removed from this level.
+ */
+ deletefromlevel(node, parent == NULL ? &rbt->root : &DOWN(parent));
+
+ if (DATA(node) != NULL && rbt->data_deleter != NULL) {
+ rbt->data_deleter(DATA(node), rbt->deleter_arg);
+ }
+
+ unhash_node(rbt, node);
+#if DNS_RBT_USEMAGIC
+ node->magic = 0;
+#endif /* if DNS_RBT_USEMAGIC */
+ isc_refcount_destroy(&node->references);
+
+ freenode(rbt, &node);
+
+ /*
+ * This function never fails.
+ */
+ return (ISC_R_SUCCESS);
+}
+
+void
+dns_rbt_namefromnode(dns_rbtnode_t *node, dns_name_t *name) {
+ REQUIRE(DNS_RBTNODE_VALID(node));
+ REQUIRE(name != NULL);
+ REQUIRE(name->offsets == NULL);
+
+ NODENAME(node, name);
+}
+
+isc_result_t
+dns_rbt_fullnamefromnode(dns_rbtnode_t *node, dns_name_t *name) {
+ dns_name_t current;
+ isc_result_t result;
+
+ REQUIRE(DNS_RBTNODE_VALID(node));
+ REQUIRE(name != NULL);
+ REQUIRE(name->buffer != NULL);
+
+ dns_name_init(&current, NULL);
+ dns_name_reset(name);
+
+ do {
+ INSIST(node != NULL);
+
+ NODENAME(node, &current);
+
+ result = dns_name_concatenate(name, &current, 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, "<error building name: %s>",
+ isc_result_totext(result));
+ }
+
+ return (printname);
+}
+
+static isc_result_t
+create_node(isc_mem_t *mctx, const dns_name_t *name, dns_rbtnode_t **nodep) {
+ dns_rbtnode_t *node;
+ isc_region_t region;
+ unsigned int labels;
+ size_t nodelen;
+
+ REQUIRE(name->offsets != NULL);
+
+ dns_name_toregion(name, &region);
+ labels = dns_name_countlabels(name);
+ ENSURE(labels > 0);
+
+ /*
+ * Allocate space for the node structure, the name, and the offsets.
+ */
+ nodelen = sizeof(dns_rbtnode_t) + region.length + labels + 1;
+ node = isc_mem_get(mctx, nodelen);
+ memset(node, 0, nodelen);
+
+ node->is_root = 0;
+ PARENT(node) = NULL;
+ RIGHT(node) = NULL;
+ LEFT(node) = NULL;
+ DOWN(node) = NULL;
+ DATA(node) = NULL;
+ node->rpz = 0;
+
+ HASHNEXT(node) = NULL;
+ HASHVAL(node) = 0;
+
+ ISC_LINK_INIT(node, deadlink);
+
+ LOCKNUM(node) = 0;
+ WILD(node) = 0;
+ DIRTY(node) = 0;
+ isc_refcount_init(&node->references, 0);
+ node->find_callback = 0;
+ node->nsec = DNS_RBT_NSEC_NORMAL;
+
+ MAKE_BLACK(node);
+
+ /*
+ * The following is stored to make reconstructing a name from the
+ * stored value in the node easy: the length of the name, the number
+ * of labels, whether the name is absolute or not, the name itself,
+ * and the name's offsets table.
+ *
+ * XXX RTH
+ * The offsets table could be made smaller by eliminating the
+ * first offset, which is always 0. This requires changes to
+ * lib/dns/name.c.
+ *
+ * Note: OLDOFFSETLEN *must* be assigned *after* OLDNAMELEN is assigned
+ * as it uses OLDNAMELEN.
+ */
+ OLDNAMELEN(node) = NAMELEN(node) = region.length;
+ OLDOFFSETLEN(node) = OFFSETLEN(node) = labels;
+ ATTRS(node) = name->attributes;
+
+ memmove(NAME(node), region.base, region.length);
+ memmove(OFFSETS(node), name->offsets, labels);
+
+#if DNS_RBT_USEMAGIC
+ node->magic = DNS_RBTNODE_MAGIC;
+#endif /* if DNS_RBT_USEMAGIC */
+ *nodep = node;
+
+ return (ISC_R_SUCCESS);
+}
+
+/*
+ * Add a node to the hash table
+ */
+static void
+hash_add_node(dns_rbt_t *rbt, dns_rbtnode_t *node, const dns_name_t *name) {
+ uint32_t hash;
+
+ REQUIRE(name != NULL);
+
+ HASHVAL(node) = dns_name_fullhash(name, false);
+
+ hash = hash_32(HASHVAL(node), rbt->hashbits[rbt->hindex]);
+ HASHNEXT(node) = rbt->hashtable[rbt->hindex][hash];
+
+ rbt->hashtable[rbt->hindex][hash] = node;
+}
+
+/*
+ * Initialize hash table
+ */
+static void
+hashtable_new(dns_rbt_t *rbt, uint8_t index, uint8_t bits) {
+ size_t size;
+
+ REQUIRE(rbt->hashbits[index] == RBT_HASH_NO_BITS);
+ REQUIRE(rbt->hashtable[index] == NULL);
+ REQUIRE(bits >= RBT_HASH_MIN_BITS);
+ REQUIRE(bits < RBT_HASH_MAX_BITS);
+
+ rbt->hashbits[index] = bits;
+
+ size = HASHSIZE(rbt->hashbits[index]) * sizeof(dns_rbtnode_t *);
+ rbt->hashtable[index] = isc_mem_get(rbt->mctx, size);
+ memset(rbt->hashtable[index], 0, size);
+}
+
+static void
+hashtable_free(dns_rbt_t *rbt, uint8_t index) {
+ size_t size = HASHSIZE(rbt->hashbits[index]) * sizeof(dns_rbtnode_t *);
+ isc_mem_put(rbt->mctx, rbt->hashtable[index], size);
+
+ rbt->hashbits[index] = RBT_HASH_NO_BITS;
+ rbt->hashtable[index] = NULL;
+}
+
+static uint32_t
+rehash_bits(dns_rbt_t *rbt, size_t newcount) {
+ uint32_t newbits = rbt->hashbits[rbt->hindex];
+
+ while (newcount >= HASHSIZE(newbits) && newbits < RBT_HASH_MAX_BITS) {
+ newbits += 1;
+ }
+
+ return (newbits);
+}
+
+/*
+ * Rebuild the hashtable to reduce the load factor
+ */
+static void
+hashtable_rehash(dns_rbt_t *rbt, uint32_t newbits) {
+ uint8_t oldindex = rbt->hindex;
+ uint32_t oldbits = rbt->hashbits[oldindex];
+ uint8_t newindex = RBT_HASH_NEXTTABLE(oldindex);
+
+ REQUIRE(rbt->hashbits[oldindex] >= RBT_HASH_MIN_BITS);
+ REQUIRE(rbt->hashbits[oldindex] <= RBT_HASH_MAX_BITS);
+ REQUIRE(rbt->hashtable[oldindex] != NULL);
+
+ REQUIRE(newbits <= RBT_HASH_MAX_BITS);
+ REQUIRE(rbt->hashbits[newindex] == RBT_HASH_NO_BITS);
+ REQUIRE(rbt->hashtable[newindex] == NULL);
+
+ REQUIRE(newbits > oldbits);
+
+ hashtable_new(rbt, newindex, newbits);
+
+ rbt->hindex = newindex;
+
+ hashtable_rehash_one(rbt);
+}
+
+static void
+hashtable_rehash_one(dns_rbt_t *rbt) {
+ dns_rbtnode_t **newtable = rbt->hashtable[rbt->hindex];
+ uint32_t oldsize =
+ HASHSIZE(rbt->hashbits[RBT_HASH_NEXTTABLE(rbt->hindex)]);
+ dns_rbtnode_t **oldtable =
+ rbt->hashtable[RBT_HASH_NEXTTABLE(rbt->hindex)];
+ dns_rbtnode_t *node = NULL;
+ dns_rbtnode_t *nextnode;
+
+ /* Find first non-empty node */
+ while (rbt->hiter < oldsize && oldtable[rbt->hiter] == NULL) {
+ rbt->hiter++;
+ }
+
+ /* Rehashing complete */
+ if (rbt->hiter == oldsize) {
+ hashtable_free(rbt, RBT_HASH_NEXTTABLE(rbt->hindex));
+ rbt->hiter = 0;
+ return;
+ }
+
+ /* Move the first non-empty node from old hashtable to new hashtable */
+ for (node = oldtable[rbt->hiter]; node != NULL; node = nextnode) {
+ uint32_t hash = hash_32(HASHVAL(node),
+ rbt->hashbits[rbt->hindex]);
+ nextnode = HASHNEXT(node);
+ HASHNEXT(node) = newtable[hash];
+ newtable[hash] = node;
+ }
+
+ oldtable[rbt->hiter] = NULL;
+
+ rbt->hiter++;
+}
+
+static void
+maybe_rehash(dns_rbt_t *rbt, size_t newcount) {
+ uint32_t newbits = rehash_bits(rbt, newcount);
+
+ if (rbt->hashbits[rbt->hindex] < newbits &&
+ newbits <= RBT_HASH_MAX_BITS)
+ {
+ hashtable_rehash(rbt, newbits);
+ }
+}
+
+static bool
+rehashing_in_progress(dns_rbt_t *rbt) {
+ return (rbt->hashtable[RBT_HASH_NEXTTABLE(rbt->hindex)] != NULL);
+}
+
+static bool
+hashtable_is_overcommited(dns_rbt_t *rbt) {
+ return (rbt->nodecount >=
+ (HASHSIZE(rbt->hashbits[rbt->hindex]) * RBT_HASH_OVERCOMMIT));
+}
+
+/*
+ * Add a node to the hash table. Rehash the hashtable if the node count
+ * rises above a critical level.
+ */
+static void
+hash_node(dns_rbt_t *rbt, dns_rbtnode_t *node, const dns_name_t *name) {
+ REQUIRE(DNS_RBTNODE_VALID(node));
+
+ if (rehashing_in_progress(rbt)) {
+ /* Rehash in progress */
+ hashtable_rehash_one(rbt);
+ } else if (hashtable_is_overcommited(rbt)) {
+ /* Rehash requested */
+ maybe_rehash(rbt, rbt->nodecount);
+ }
+
+ hash_add_node(rbt, node, name);
+}
+
+/*
+ * Remove a node from the hash table
+ */
+static void
+unhash_node(dns_rbt_t *rbt, dns_rbtnode_t *dnode) {
+ uint32_t hash;
+ uint8_t hindex = rbt->hindex;
+ dns_rbtnode_t *hnode;
+
+ REQUIRE(DNS_RBTNODE_VALID(dnode));
+
+ /*
+ * The node could be either in:
+ * a) current table: no rehashing in progress, or
+ * b) current table: the node has been already moved, or
+ * c) other table: the node hasn't been moved yet.
+ */
+nexttable:
+ hash = hash_32(HASHVAL(dnode), rbt->hashbits[hindex]);
+
+ hnode = rbt->hashtable[hindex][hash];
+
+ if (hnode == dnode) {
+ rbt->hashtable[hindex][hash] = HASHNEXT(hnode);
+ return;
+ } else {
+ for (; hnode != NULL; hnode = HASHNEXT(hnode)) {
+ if (HASHNEXT(hnode) == dnode) {
+ HASHNEXT(hnode) = HASHNEXT(dnode);
+ return;
+ }
+ }
+ }
+
+ if (TRY_NEXTTABLE(hindex, rbt)) {
+ /* Rehashing in progress, delete from the other table */
+ hindex = RBT_HASH_NEXTTABLE(hindex);
+ goto nexttable;
+ }
+
+ /* We haven't found any matching node, this should not be possible. */
+ UNREACHABLE();
+}
+
+static void
+rotate_left(dns_rbtnode_t *node, dns_rbtnode_t **rootp) {
+ dns_rbtnode_t *child;
+
+ REQUIRE(DNS_RBTNODE_VALID(node));
+ REQUIRE(rootp != NULL);
+
+ child = RIGHT(node);
+ INSIST(child != NULL);
+
+ RIGHT(node) = LEFT(child);
+ if (LEFT(child) != NULL) {
+ PARENT(LEFT(child)) = node;
+ }
+ LEFT(child) = node;
+
+ PARENT(child) = PARENT(node);
+
+ if (IS_ROOT(node)) {
+ *rootp = child;
+ child->is_root = 1;
+ node->is_root = 0;
+ } else {
+ if (LEFT(PARENT(node)) == node) {
+ LEFT(PARENT(node)) = child;
+ } else {
+ RIGHT(PARENT(node)) = child;
+ }
+ }
+
+ PARENT(node) = child;
+}
+
+static void
+rotate_right(dns_rbtnode_t *node, dns_rbtnode_t **rootp) {
+ dns_rbtnode_t *child;
+
+ REQUIRE(DNS_RBTNODE_VALID(node));
+ REQUIRE(rootp != NULL);
+
+ child = LEFT(node);
+ INSIST(child != NULL);
+
+ LEFT(node) = RIGHT(child);
+ if (RIGHT(child) != NULL) {
+ PARENT(RIGHT(child)) = node;
+ }
+ RIGHT(child) = node;
+
+ PARENT(child) = PARENT(node);
+
+ if (IS_ROOT(node)) {
+ *rootp = child;
+ child->is_root = 1;
+ node->is_root = 0;
+ } else {
+ if (LEFT(PARENT(node)) == node) {
+ LEFT(PARENT(node)) = child;
+ } else {
+ RIGHT(PARENT(node)) = child;
+ }
+ }
+
+ PARENT(node) = child;
+}
+
+/*
+ * This is the real workhorse of the insertion code, because it does the
+ * true red/black tree on a single level.
+ */
+static void
+addonlevel(dns_rbtnode_t *node, dns_rbtnode_t *current, int order,
+ dns_rbtnode_t **rootp) {
+ dns_rbtnode_t *child, *root, *parent, *grandparent;
+ dns_name_t add_name, current_name;
+ dns_offsets_t add_offsets, current_offsets;
+
+ REQUIRE(rootp != NULL);
+ REQUIRE(DNS_RBTNODE_VALID(node) && LEFT(node) == NULL &&
+ RIGHT(node) == NULL);
+ REQUIRE(current != NULL);
+
+ root = *rootp;
+ if (root == NULL) {
+ /*
+ * First node of a level.
+ */
+ MAKE_BLACK(node);
+ node->is_root = 1;
+ PARENT(node) = current;
+ *rootp = node;
+ return;
+ }
+
+ child = root;
+ POST(child);
+
+ dns_name_init(&add_name, add_offsets);
+ NODENAME(node, &add_name);
+
+ dns_name_init(&current_name, current_offsets);
+ NODENAME(current, &current_name);
+
+ if (order < 0) {
+ INSIST(LEFT(current) == NULL);
+ LEFT(current) = node;
+ } else {
+ INSIST(RIGHT(current) == NULL);
+ RIGHT(current) = node;
+ }
+
+ INSIST(PARENT(node) == NULL);
+ PARENT(node) = current;
+
+ MAKE_RED(node);
+
+ while (node != root && IS_RED(PARENT(node))) {
+ /*
+ * XXXDCL could do away with separate parent and grandparent
+ * variables. They are vestiges of the days before parent
+ * pointers. However, they make the code a little clearer.
+ */
+
+ parent = PARENT(node);
+ grandparent = PARENT(parent);
+
+ if (parent == LEFT(grandparent)) {
+ child = RIGHT(grandparent);
+ if (child != NULL && IS_RED(child)) {
+ MAKE_BLACK(parent);
+ MAKE_BLACK(child);
+ MAKE_RED(grandparent);
+ node = grandparent;
+ } else {
+ if (node == RIGHT(parent)) {
+ rotate_left(parent, &root);
+ node = parent;
+ parent = PARENT(node);
+ grandparent = PARENT(parent);
+ }
+ MAKE_BLACK(parent);
+ MAKE_RED(grandparent);
+ rotate_right(grandparent, &root);
+ }
+ } else {
+ child = LEFT(grandparent);
+ if (child != NULL && IS_RED(child)) {
+ MAKE_BLACK(parent);
+ MAKE_BLACK(child);
+ MAKE_RED(grandparent);
+ node = grandparent;
+ } else {
+ if (node == LEFT(parent)) {
+ rotate_right(parent, &root);
+ node = parent;
+ parent = PARENT(node);
+ grandparent = PARENT(parent);
+ }
+ MAKE_BLACK(parent);
+ MAKE_RED(grandparent);
+ rotate_left(grandparent, &root);
+ }
+ }
+ }
+
+ MAKE_BLACK(root);
+ ENSURE(IS_ROOT(root));
+ *rootp = root;
+
+ return;
+}
+
+/*
+ * This is the real workhorse of the deletion code, because it does the
+ * true red/black tree on a single level.
+ */
+static void
+deletefromlevel(dns_rbtnode_t *item, dns_rbtnode_t **rootp) {
+ dns_rbtnode_t *child, *sibling, *parent;
+ dns_rbtnode_t *successor;
+
+ REQUIRE(item != NULL);
+
+ /*
+ * Verify that the parent history is (apparently) correct.
+ */
+ INSIST((IS_ROOT(item) && *rootp == item) ||
+ (!IS_ROOT(item) &&
+ (LEFT(PARENT(item)) == item || RIGHT(PARENT(item)) == item)));
+
+ child = NULL;
+
+ if (LEFT(item) == NULL) {
+ if (RIGHT(item) == NULL) {
+ if (IS_ROOT(item)) {
+ /*
+ * This is the only item in the tree.
+ */
+ *rootp = NULL;
+ return;
+ }
+ } else {
+ /*
+ * This node has one child, on the right.
+ */
+ child = RIGHT(item);
+ }
+ } else if (RIGHT(item) == NULL) {
+ /*
+ * This node has one child, on the left.
+ */
+ child = LEFT(item);
+ } else {
+ dns_rbtnode_t *saved_parent, *saved_right;
+ int saved_color;
+
+ /*
+ * This node has two children, so it cannot be directly
+ * deleted. Find its immediate in-order successor and
+ * move it to this location, then do the deletion at the
+ * old site of the successor.
+ */
+ successor = RIGHT(item);
+ while (LEFT(successor) != NULL) {
+ successor = LEFT(successor);
+ }
+
+ /*
+ * The successor cannot possibly have a left child;
+ * if there is any child, it is on the right.
+ */
+ if (RIGHT(successor) != NULL) {
+ child = RIGHT(successor);
+ }
+
+ /*
+ * Swap the two nodes; it would be simpler to just replace
+ * the value being deleted with that of the successor,
+ * but this rigamarole is done so the caller has complete
+ * control over the pointers (and memory allocation) of
+ * all of nodes. If just the key value were removed from
+ * the tree, the pointer to the node would be unchanged.
+ */
+
+ /*
+ * First, put the successor in the tree location of the
+ * node to be deleted. Save its existing tree pointer
+ * information, which will be needed when linking up
+ * delete to the successor's old location.
+ */
+ saved_parent = PARENT(successor);
+ saved_right = RIGHT(successor);
+ saved_color = COLOR(successor);
+
+ if (IS_ROOT(item)) {
+ *rootp = successor;
+ successor->is_root = true;
+ item->is_root = false;
+ } else if (LEFT(PARENT(item)) == item) {
+ LEFT(PARENT(item)) = successor;
+ } else {
+ RIGHT(PARENT(item)) = successor;
+ }
+
+ PARENT(successor) = PARENT(item);
+ LEFT(successor) = LEFT(item);
+ RIGHT(successor) = RIGHT(item);
+ COLOR(successor) = COLOR(item);
+
+ if (LEFT(successor) != NULL) {
+ PARENT(LEFT(successor)) = successor;
+ }
+ if (RIGHT(successor) != successor) {
+ PARENT(RIGHT(successor)) = successor;
+ }
+
+ /*
+ * Now relink the node to be deleted into the
+ * successor's previous tree location.
+ */
+ INSIST(!IS_ROOT(item));
+
+ if (saved_parent == item) {
+ /*
+ * Node being deleted was successor's parent.
+ */
+ RIGHT(successor) = item;
+ PARENT(item) = successor;
+ } else {
+ LEFT(saved_parent) = item;
+ PARENT(item) = saved_parent;
+ }
+
+ /*
+ * Original location of successor node has no left.
+ */
+ LEFT(item) = NULL;
+ RIGHT(item) = saved_right;
+ COLOR(item) = saved_color;
+ }
+
+ /*
+ * Remove the node by removing the links from its parent.
+ */
+ if (!IS_ROOT(item)) {
+ if (LEFT(PARENT(item)) == item) {
+ LEFT(PARENT(item)) = child;
+ } else {
+ RIGHT(PARENT(item)) = child;
+ }
+
+ if (child != NULL) {
+ PARENT(child) = PARENT(item);
+ }
+ } else {
+ /*
+ * This is the root being deleted, and at this point
+ * it is known to have just one child.
+ */
+ *rootp = child;
+ child->is_root = 1;
+ PARENT(child) = PARENT(item);
+ }
+
+ /*
+ * Fix color violations.
+ */
+ if (IS_BLACK(item)) {
+ parent = PARENT(item);
+
+ while (child != *rootp && IS_BLACK(child)) {
+ INSIST(child == NULL || !IS_ROOT(child));
+
+ if (LEFT(parent) == child) {
+ sibling = RIGHT(parent);
+
+ if (IS_RED(sibling)) {
+ MAKE_BLACK(sibling);
+ MAKE_RED(parent);
+ rotate_left(parent, rootp);
+ sibling = RIGHT(parent);
+ }
+
+ INSIST(sibling != NULL);
+
+ if (IS_BLACK(LEFT(sibling)) &&
+ IS_BLACK(RIGHT(sibling)))
+ {
+ MAKE_RED(sibling);
+ child = parent;
+ } else {
+ if (IS_BLACK(RIGHT(sibling))) {
+ MAKE_BLACK(LEFT(sibling));
+ MAKE_RED(sibling);
+ rotate_right(sibling, rootp);
+ sibling = RIGHT(parent);
+ }
+
+ COLOR(sibling) = COLOR(parent);
+ MAKE_BLACK(parent);
+ INSIST(RIGHT(sibling) != NULL);
+ MAKE_BLACK(RIGHT(sibling));
+ rotate_left(parent, rootp);
+ child = *rootp;
+ }
+ } else {
+ /*
+ * Child is parent's right child.
+ * Everything is done the same as above,
+ * except mirrored.
+ */
+ sibling = LEFT(parent);
+
+ if (IS_RED(sibling)) {
+ MAKE_BLACK(sibling);
+ MAKE_RED(parent);
+ rotate_right(parent, rootp);
+ sibling = LEFT(parent);
+ }
+
+ INSIST(sibling != NULL);
+
+ if (IS_BLACK(LEFT(sibling)) &&
+ IS_BLACK(RIGHT(sibling)))
+ {
+ MAKE_RED(sibling);
+ child = parent;
+ } else {
+ if (IS_BLACK(LEFT(sibling))) {
+ MAKE_BLACK(RIGHT(sibling));
+ MAKE_RED(sibling);
+ rotate_left(sibling, rootp);
+ sibling = LEFT(parent);
+ }
+
+ COLOR(sibling) = COLOR(parent);
+ MAKE_BLACK(parent);
+ INSIST(LEFT(sibling) != NULL);
+ MAKE_BLACK(LEFT(sibling));
+ rotate_right(parent, rootp);
+ child = *rootp;
+ }
+ }
+
+ parent = PARENT(child);
+ }
+
+ if (IS_RED(child)) {
+ MAKE_BLACK(child);
+ }
+ }
+}
+
+static void
+freenode(dns_rbt_t *rbt, dns_rbtnode_t **nodep) {
+ dns_rbtnode_t *node = *nodep;
+ *nodep = NULL;
+
+ isc_mem_put(rbt->mctx, node, NODE_SIZE(node));
+
+ rbt->nodecount--;
+}
+
+static void
+deletetreeflat(dns_rbt_t *rbt, unsigned int quantum, bool unhash,
+ dns_rbtnode_t **nodep) {
+ dns_rbtnode_t *root = *nodep;
+
+ while (root != NULL) {
+ /*
+ * If there is a left, right or down node, walk into it
+ * and iterate.
+ */
+ if (LEFT(root) != NULL) {
+ dns_rbtnode_t *node = root;
+ root = LEFT(root);
+ LEFT(node) = NULL;
+ } else if (RIGHT(root) != NULL) {
+ dns_rbtnode_t *node = root;
+ root = RIGHT(root);
+ RIGHT(node) = NULL;
+ } else if (DOWN(root) != NULL) {
+ dns_rbtnode_t *node = root;
+ root = DOWN(root);
+ DOWN(node) = NULL;
+ } else {
+ /*
+ * There are no left, right or down nodes, so we
+ * can free this one and go back to its parent.
+ */
+ dns_rbtnode_t *node = root;
+ root = PARENT(root);
+
+ if (rbt->data_deleter != NULL && DATA(node) != NULL) {
+ rbt->data_deleter(DATA(node), rbt->deleter_arg);
+ }
+ if (unhash) {
+ unhash_node(rbt, node);
+ }
+ /*
+ * Note: we don't call unhash_node() here as we
+ * are destroying the complete RBT tree.
+ */
+#if DNS_RBT_USEMAGIC
+ node->magic = 0;
+#endif /* if DNS_RBT_USEMAGIC */
+ freenode(rbt, &node);
+ if (quantum != 0 && --quantum == 0) {
+ break;
+ }
+ }
+ }
+
+ *nodep = root;
+}
+
+static size_t
+getheight_helper(dns_rbtnode_t *node) {
+ size_t dl, dr;
+ size_t this_height, down_height;
+
+ if (node == NULL) {
+ return (0);
+ }
+
+ dl = getheight_helper(LEFT(node));
+ dr = getheight_helper(RIGHT(node));
+
+ this_height = ISC_MAX(dl + 1, dr + 1);
+ down_height = getheight_helper(DOWN(node));
+
+ return (ISC_MAX(this_height, down_height));
+}
+
+size_t
+dns__rbt_getheight(dns_rbt_t *rbt) {
+ return (getheight_helper(rbt->root));
+}
+
+static bool
+check_properties_helper(dns_rbtnode_t *node) {
+ if (node == NULL) {
+ return (true);
+ }
+
+ if (IS_RED(node)) {
+ /* Root nodes must be BLACK. */
+ if (IS_ROOT(node)) {
+ return (false);
+ }
+
+ /* Both children of RED nodes must be BLACK. */
+ if (IS_RED(LEFT(node)) || IS_RED(RIGHT(node))) {
+ return (false);
+ }
+ }
+
+ if ((DOWN(node) != NULL) && (!IS_ROOT(DOWN(node)))) {
+ return (false);
+ }
+
+ if (IS_ROOT(node)) {
+ if ((PARENT(node) != NULL) && (DOWN(PARENT(node)) != node)) {
+ return (false);
+ }
+
+ if (get_upper_node(node) != PARENT(node)) {
+ return (false);
+ }
+ }
+
+ /* If node is assigned to the down_ pointer of its parent, it is
+ * a subtree root and must have the flag set.
+ */
+ if (((!PARENT(node)) || (DOWN(PARENT(node)) == node)) &&
+ (!IS_ROOT(node)))
+ {
+ return (false);
+ }
+
+ /* Repeat tests with this node's children. */
+ return (check_properties_helper(LEFT(node)) &&
+ check_properties_helper(RIGHT(node)) &&
+ check_properties_helper(DOWN(node)));
+}
+
+static bool
+check_black_distance_helper(dns_rbtnode_t *node, size_t *distance) {
+ size_t dl, dr, dd;
+
+ if (node == NULL) {
+ *distance = 1;
+ return (true);
+ }
+
+ if (!check_black_distance_helper(LEFT(node), &dl)) {
+ return (false);
+ }
+
+ if (!check_black_distance_helper(RIGHT(node), &dr)) {
+ return (false);
+ }
+
+ if (!check_black_distance_helper(DOWN(node), &dd)) {
+ return (false);
+ }
+
+ /* Left and right side black node counts must match. */
+ if (dl != dr) {
+ return (false);
+ }
+
+ if (IS_BLACK(node)) {
+ dl++;
+ }
+
+ *distance = dl;
+
+ return (true);
+}
+
+bool
+dns__rbt_checkproperties(dns_rbt_t *rbt) {
+ size_t dd;
+
+ if (!check_properties_helper(rbt->root)) {
+ return (false);
+ }
+
+ /* Path from a given node to all its leaves must contain the
+ * same number of BLACK child nodes. This is done separately
+ * here instead of inside check_properties_helper() as
+ * it would take (n log n) complexity otherwise.
+ */
+ return (check_black_distance_helper(rbt->root, &dd));
+}
+
+static void
+dns_rbt_indent(FILE *f, int depth) {
+ int i;
+
+ fprintf(f, "%4d ", depth);
+
+ for (i = 0; i < depth; i++) {
+ fprintf(f, "- ");
+ }
+}
+
+void
+dns_rbt_printnodeinfo(dns_rbtnode_t *n, FILE *f) {
+ if (n == NULL) {
+ fprintf(f, "Null node\n");
+ return;
+ }
+
+ fprintf(f, "Node info for nodename: ");
+ printnodename(n, true, f);
+ fprintf(f, "\n");
+
+ fprintf(f, "n = %p\n", n);
+
+ fprintf(f, "node lock address = %u\n", n->locknum);
+
+ fprintf(f, "Parent: %p\n", n->parent);
+ fprintf(f, "Right: %p\n", n->right);
+ fprintf(f, "Left: %p\n", n->left);
+ fprintf(f, "Down: %p\n", n->down);
+ fprintf(f, "Data: %p\n", n->data);
+}
+
+static void
+printnodename(dns_rbtnode_t *node, bool quoted, FILE *f) {
+ isc_region_t r;
+ dns_name_t name;
+ char buffer[DNS_NAME_FORMATSIZE];
+ dns_offsets_t offsets;
+
+ r.length = NAMELEN(node);
+ r.base = NAME(node);
+
+ dns_name_init(&name, offsets);
+ dns_name_fromregion(&name, &r);
+
+ dns_name_format(&name, buffer, sizeof(buffer));
+
+ if (quoted) {
+ fprintf(f, "\"%s\"", buffer);
+ } else {
+ fprintf(f, "%s", buffer);
+ }
+}
+
+static void
+print_text_helper(dns_rbtnode_t *root, dns_rbtnode_t *parent, int depth,
+ const char *direction, void (*data_printer)(FILE *, void *),
+ FILE *f) {
+ dns_rbt_indent(f, depth);
+
+ if (root != NULL) {
+ printnodename(root, true, f);
+ fprintf(f, " (%s, %s", direction,
+ COLOR(root) == RED ? "RED" : "BLACK");
+
+ if ((!IS_ROOT(root) && PARENT(root) != parent) ||
+ (IS_ROOT(root) && depth > 0 && DOWN(PARENT(root)) != root))
+ {
+ fprintf(f, " (BAD parent pointer! -> ");
+ if (PARENT(root) != NULL) {
+ printnodename(PARENT(root), true, f);
+ } else {
+ fprintf(f, "NULL");
+ }
+ fprintf(f, ")");
+ }
+
+ fprintf(f, ")");
+
+ if (root->data != NULL && data_printer != NULL) {
+ fprintf(f, " data@%p: ", root->data);
+ data_printer(f, root->data);
+ }
+ fprintf(f, "\n");
+
+ depth++;
+
+ if (COLOR(root) == RED && IS_RED(LEFT(root))) {
+ fprintf(f, "** Red/Red color violation on left\n");
+ }
+ print_text_helper(LEFT(root), root, depth, "left", data_printer,
+ f);
+
+ if (COLOR(root) == RED && IS_RED(RIGHT(root))) {
+ fprintf(f, "** Red/Red color violation on right\n");
+ }
+ print_text_helper(RIGHT(root), root, depth, "right",
+ data_printer, f);
+
+ print_text_helper(DOWN(root), NULL, depth, "down", data_printer,
+ f);
+ } else {
+ fprintf(f, "NULL (%s)\n", direction);
+ }
+}
+
+void
+dns_rbt_printtext(dns_rbt_t *rbt, void (*data_printer)(FILE *, void *),
+ FILE *f) {
+ REQUIRE(VALID_RBT(rbt));
+
+ print_text_helper(rbt->root, NULL, 0, "root", data_printer, f);
+}
+
+static int
+print_dot_helper(dns_rbtnode_t *node, unsigned int *nodecount,
+ bool show_pointers, FILE *f) {
+ unsigned int l, r, d;
+
+ if (node == NULL) {
+ return (0);
+ }
+
+ l = print_dot_helper(LEFT(node), nodecount, show_pointers, f);
+ r = print_dot_helper(RIGHT(node), nodecount, show_pointers, f);
+ d = print_dot_helper(DOWN(node), nodecount, show_pointers, f);
+
+ *nodecount += 1;
+
+ fprintf(f, "node%u[label = \"<f0> |<f1> ", *nodecount);
+ printnodename(node, false, f);
+ fprintf(f, "|<f2>");
+
+ if (show_pointers) {
+ fprintf(f, "|<f3> n=%p|<f4> p=%p", node, PARENT(node));
+ }
+
+ fprintf(f, "\"] [");
+
+ if (IS_RED(node)) {
+ fprintf(f, "color=red");
+ } else {
+ fprintf(f, "color=black");
+ }
+
+ /* XXXMUKS: verify that IS_ROOT() indicates subtree root and not
+ * forest root.
+ */
+ if (IS_ROOT(node)) {
+ fprintf(f, ",penwidth=3");
+ }
+
+ if (IS_EMPTY(node)) {
+ fprintf(f, ",style=filled,fillcolor=lightgrey");
+ }
+
+ fprintf(f, "];\n");
+
+ if (LEFT(node) != NULL) {
+ fprintf(f, "\"node%u\":f0 -> \"node%u\":f1;\n", *nodecount, l);
+ }
+
+ if (DOWN(node) != NULL) {
+ fprintf(f, "\"node%u\":f1 -> \"node%u\":f1 [penwidth=5];\n",
+ *nodecount, d);
+ }
+ if (RIGHT(node) != NULL) {
+ fprintf(f, "\"node%u\":f2 -> \"node%u\":f1;\n", *nodecount, r);
+ }
+
+ return (*nodecount);
+}
+
+void
+dns_rbt_printdot(dns_rbt_t *rbt, bool show_pointers, FILE *f) {
+ unsigned int nodecount = 0;
+
+ REQUIRE(VALID_RBT(rbt));
+
+ fprintf(f, "digraph g {\n");
+ fprintf(f, "node [shape = record,height=.1];\n");
+ print_dot_helper(rbt->root, &nodecount, show_pointers, f);
+ fprintf(f, "}\n");
+}
+
+/*
+ * Chain Functions
+ */
+
+void
+dns_rbtnodechain_init(dns_rbtnodechain_t *chain) {
+ REQUIRE(chain != NULL);
+
+ /*
+ * Initialize 'chain'.
+ */
+ chain->end = NULL;
+ chain->level_count = 0;
+ chain->level_matches = 0;
+ memset(chain->levels, 0, sizeof(chain->levels));
+
+ chain->magic = CHAIN_MAGIC;
+}
+
+isc_result_t
+dns_rbtnodechain_current(dns_rbtnodechain_t *chain, dns_name_t *name,
+ dns_name_t *origin, dns_rbtnode_t **node) {
+ isc_result_t result = ISC_R_SUCCESS;
+
+ REQUIRE(VALID_CHAIN(chain));
+
+ if (node != NULL) {
+ *node = chain->end;
+ }
+
+ if (chain->end == NULL) {
+ return (ISC_R_NOTFOUND);
+ }
+
+ if (name != NULL) {
+ NODENAME(chain->end, name);
+
+ if (chain->level_count == 0) {
+ /*
+ * Names in the top level tree are all absolute.
+ * Always make 'name' relative.
+ */
+ INSIST(dns_name_isabsolute(name));
+
+ /*
+ * This is cheaper than
+ * dns_name_getlabelsequence().
+ */
+ name->labels--;
+ name->length--;
+ name->attributes &= ~DNS_NAMEATTR_ABSOLUTE;
+ }
+ }
+
+ if (origin != NULL) {
+ if (chain->level_count > 0) {
+ result = chain_name(chain, origin, false);
+ } else {
+ dns_name_copy(dns_rootname, origin);
+ }
+ }
+
+ return (result);
+}
+
+isc_result_t
+dns_rbtnodechain_prev(dns_rbtnodechain_t *chain, dns_name_t *name,
+ dns_name_t *origin) {
+ dns_rbtnode_t *current, *previous, *predecessor;
+ isc_result_t result = ISC_R_SUCCESS;
+ bool new_origin = false;
+
+ REQUIRE(VALID_CHAIN(chain) && chain->end != NULL);
+
+ predecessor = NULL;
+
+ current = chain->end;
+
+ if (LEFT(current) != NULL) {
+ /*
+ * Moving left one then right as far as possible is the
+ * previous node, at least for this level.
+ */
+ current = LEFT(current);
+
+ while (RIGHT(current) != NULL) {
+ current = RIGHT(current);
+ }
+
+ predecessor = current;
+ } else {
+ /*
+ * No left links, so move toward the root. If at any
+ * point on the way there the link from parent to child
+ * is a right link, then the parent is the previous
+ * node, at least for this level.
+ */
+ while (!IS_ROOT(current)) {
+ previous = current;
+ current = PARENT(current);
+
+ if (RIGHT(current) == previous) {
+ predecessor = current;
+ break;
+ }
+ }
+ }
+
+ if (predecessor != NULL) {
+ /*
+ * Found a predecessor node in this level. It might not
+ * really be the predecessor, however.
+ */
+ if (DOWN(predecessor) != NULL) {
+ /*
+ * The predecessor is really down at least one
+ * level. Go down and as far right as possible,
+ * and repeat as long as the rightmost node has
+ * a down pointer.
+ */
+ do {
+ /*
+ * XXX DCL Need to do something about
+ * origins here. See whether to go down,
+ * and if so whether it is truly what
+ * Bob calls a new origin.
+ */
+ ADD_LEVEL(chain, predecessor);
+ predecessor = DOWN(predecessor);
+
+ /* XXX DCL duplicated from above; clever
+ * way to unduplicate? */
+
+ while (RIGHT(predecessor) != NULL) {
+ predecessor = RIGHT(predecessor);
+ }
+ } while (DOWN(predecessor) != NULL);
+
+ /* XXX DCL probably needs work on the concept */
+ if (origin != NULL) {
+ new_origin = true;
+ }
+ }
+ } else if (chain->level_count > 0) {
+ /*
+ * Dang, didn't find a predecessor in this level.
+ * Got to the root of this level without having
+ * traversed any right links. Ascend the tree one
+ * level; the node that points to this tree is the
+ * predecessor.
+ */
+ INSIST(chain->level_count > 0 && IS_ROOT(current));
+ predecessor = chain->levels[--chain->level_count];
+
+ /* XXX DCL probably needs work on the concept */
+ /*
+ * Don't declare an origin change when the new origin is
+ * "." at the top level tree, because "." is declared as
+ * the origin for the second level tree.
+ */
+ if (origin != NULL &&
+ (chain->level_count > 0 || OFFSETLEN(predecessor) > 1))
+ {
+ new_origin = true;
+ }
+ }
+
+ if (predecessor != NULL) {
+ chain->end = predecessor;
+
+ if (new_origin) {
+ result = dns_rbtnodechain_current(chain, name, origin,
+ NULL);
+ if (result == ISC_R_SUCCESS) {
+ result = DNS_R_NEWORIGIN;
+ }
+ } else {
+ result = dns_rbtnodechain_current(chain, name, NULL,
+ NULL);
+ }
+ } else {
+ result = ISC_R_NOMORE;
+ }
+
+ return (result);
+}
+
+isc_result_t
+dns_rbtnodechain_down(dns_rbtnodechain_t *chain, dns_name_t *name,
+ dns_name_t *origin) {
+ dns_rbtnode_t *current, *successor;
+ isc_result_t result = ISC_R_SUCCESS;
+ bool new_origin = false;
+
+ REQUIRE(VALID_CHAIN(chain) && chain->end != NULL);
+
+ successor = NULL;
+
+ current = chain->end;
+
+ if (DOWN(current) != NULL) {
+ /*
+ * Don't declare an origin change when the new origin is
+ * "." at the second level tree, because "." is already
+ * declared as the origin for the top level tree.
+ */
+ if (chain->level_count > 0 || OFFSETLEN(current) > 1) {
+ new_origin = true;
+ }
+
+ ADD_LEVEL(chain, current);
+ current = DOWN(current);
+
+ while (LEFT(current) != NULL) {
+ current = LEFT(current);
+ }
+
+ successor = current;
+ }
+
+ if (successor != NULL) {
+ chain->end = successor;
+
+ /*
+ * It is not necessary to use dns_rbtnodechain_current
+ * like the other functions because this function will
+ * never find a node in the topmost level. This is
+ * because the root level will never be more than one
+ * name, and everything in the megatree is a successor
+ * to that node, down at the second level or below.
+ */
+
+ if (name != NULL) {
+ NODENAME(chain->end, name);
+ }
+
+ if (new_origin) {
+ if (origin != NULL) {
+ result = chain_name(chain, origin, false);
+ }
+
+ if (result == ISC_R_SUCCESS) {
+ result = DNS_R_NEWORIGIN;
+ }
+ } else {
+ result = ISC_R_SUCCESS;
+ }
+ } else {
+ result = ISC_R_NOMORE;
+ }
+
+ return (result);
+}
+
+isc_result_t
+dns_rbtnodechain_nextflat(dns_rbtnodechain_t *chain, dns_name_t *name) {
+ dns_rbtnode_t *current, *previous, *successor;
+ isc_result_t result = ISC_R_SUCCESS;
+
+ REQUIRE(VALID_CHAIN(chain) && chain->end != NULL);
+
+ successor = NULL;
+
+ current = chain->end;
+
+ if (RIGHT(current) == NULL) {
+ while (!IS_ROOT(current)) {
+ previous = current;
+ current = PARENT(current);
+
+ if (LEFT(current) == previous) {
+ successor = current;
+ break;
+ }
+ }
+ } else {
+ current = RIGHT(current);
+
+ while (LEFT(current) != NULL) {
+ current = LEFT(current);
+ }
+
+ successor = current;
+ }
+
+ if (successor != NULL) {
+ chain->end = successor;
+
+ if (name != NULL) {
+ NODENAME(chain->end, name);
+ }
+
+ result = ISC_R_SUCCESS;
+ } else {
+ result = ISC_R_NOMORE;
+ }
+
+ return (result);
+}
+
+isc_result_t
+dns_rbtnodechain_next(dns_rbtnodechain_t *chain, dns_name_t *name,
+ dns_name_t *origin) {
+ dns_rbtnode_t *current, *previous, *successor;
+ isc_result_t result = ISC_R_SUCCESS;
+ bool new_origin = false;
+
+ REQUIRE(VALID_CHAIN(chain) && chain->end != NULL);
+
+ successor = NULL;
+
+ current = chain->end;
+
+ /*
+ * If there is a level below this node, the next node is the
+ * leftmost node of the next level.
+ */
+ if (DOWN(current) != NULL) {
+ /*
+ * Don't declare an origin change when the new origin is
+ * "." at the second level tree, because "." is already
+ * declared as the origin for the top level tree.
+ */
+ if (chain->level_count > 0 || OFFSETLEN(current) > 1) {
+ new_origin = true;
+ }
+
+ ADD_LEVEL(chain, current);
+ current = DOWN(current);
+
+ while (LEFT(current) != NULL) {
+ current = LEFT(current);
+ }
+
+ successor = current;
+ } else if (RIGHT(current) == NULL) {
+ /*
+ * The successor is up, either in this level or a
+ * previous one. Head back toward the root of the tree,
+ * looking for any path that was via a left link; the
+ * successor is the node that has that left link. In
+ * the event the root of the level is reached without
+ * having traversed any left links, ascend one level and
+ * look for either a right link off the point of ascent,
+ * or search for a left link upward again, repeating
+ * ascends until either case is true.
+ */
+ do {
+ while (!IS_ROOT(current)) {
+ previous = current;
+ current = PARENT(current);
+
+ if (LEFT(current) == previous) {
+ successor = current;
+ break;
+ }
+ }
+
+ if (successor == NULL) {
+ /*
+ * Reached the root without having
+ * traversed any left pointers, so this
+ * level is done.
+ */
+ if (chain->level_count == 0) {
+ /*
+ * If the tree we are iterating
+ * over was modified since this
+ * chain was initialized in a
+ * way that caused node splits
+ * to occur, "current" may now
+ * be pointing to a root node
+ * which appears to be at level
+ * 0, but still has a parent. If
+ * that happens, abort.
+ * Otherwise, we are done
+ * looking for a successor as we
+ * really reached the root node
+ * on level 0.
+ */
+ INSIST(PARENT(current) == NULL);
+ break;
+ }
+
+ current = chain->levels[--chain->level_count];
+ new_origin = true;
+
+ if (RIGHT(current) != NULL) {
+ break;
+ }
+ }
+ } while (successor == NULL);
+ }
+
+ if (successor == NULL && RIGHT(current) != NULL) {
+ current = RIGHT(current);
+
+ while (LEFT(current) != NULL) {
+ current = LEFT(current);
+ }
+
+ successor = current;
+ }
+
+ if (successor != NULL) {
+ /*
+ * If we determine that the current node is the
+ * successor to itself, we will run into an infinite
+ * loop, so abort instead.
+ */
+ INSIST(chain->end != successor);
+
+ chain->end = successor;
+
+ /*
+ * It is not necessary to use dns_rbtnodechain_current
+ * like the other functions because this function will
+ * never find a node in the topmost level. This is
+ * because the root level will never be more than one
+ * name, and everything in the megatree is a successor
+ * to that node, down at the second level or below.
+ */
+
+ if (name != NULL) {
+ NODENAME(chain->end, name);
+ }
+
+ if (new_origin) {
+ if (origin != NULL) {
+ result = chain_name(chain, origin, false);
+ }
+
+ if (result == ISC_R_SUCCESS) {
+ result = DNS_R_NEWORIGIN;
+ }
+ } else {
+ result = ISC_R_SUCCESS;
+ }
+ } else {
+ result = ISC_R_NOMORE;
+ }
+
+ return (result);
+}
+
+isc_result_t
+dns_rbtnodechain_first(dns_rbtnodechain_t *chain, dns_rbt_t *rbt,
+ dns_name_t *name, dns_name_t *origin)
+
+{
+ isc_result_t result;
+
+ REQUIRE(VALID_RBT(rbt));
+ REQUIRE(VALID_CHAIN(chain));
+
+ dns_rbtnodechain_reset(chain);
+
+ chain->end = rbt->root;
+
+ result = dns_rbtnodechain_current(chain, name, origin, NULL);
+
+ if (result == ISC_R_SUCCESS) {
+ result = DNS_R_NEWORIGIN;
+ }
+
+ return (result);
+}
+
+isc_result_t
+dns_rbtnodechain_last(dns_rbtnodechain_t *chain, dns_rbt_t *rbt,
+ dns_name_t *name, dns_name_t *origin)
+
+{
+ isc_result_t result;
+
+ REQUIRE(VALID_RBT(rbt));
+ REQUIRE(VALID_CHAIN(chain));
+
+ dns_rbtnodechain_reset(chain);
+
+ result = move_chain_to_last(chain, rbt->root);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+
+ result = dns_rbtnodechain_current(chain, name, origin, NULL);
+
+ if (result == ISC_R_SUCCESS) {
+ result = DNS_R_NEWORIGIN;
+ }
+
+ return (result);
+}
+
+void
+dns_rbtnodechain_reset(dns_rbtnodechain_t *chain) {
+ REQUIRE(VALID_CHAIN(chain));
+
+ /*
+ * Free any dynamic storage associated with 'chain', and then
+ * reinitialize 'chain'.
+ */
+ chain->end = NULL;
+ chain->level_count = 0;
+ chain->level_matches = 0;
+}
+
+void
+dns_rbtnodechain_invalidate(dns_rbtnodechain_t *chain) {
+ /*
+ * Free any dynamic storage associated with 'chain', and then
+ * invalidate 'chain'.
+ */
+
+ dns_rbtnodechain_reset(chain);
+
+ chain->magic = 0;
+}
+
+/* XXXMUKS:
+ *
+ * - worth removing inline as static functions are inlined automatically
+ * where suitable by modern compilers.
+ * - bump the size of dns_rbt.nodecount to size_t.
+ * - the dumpfile header also contains a nodecount that is unsigned
+ * int. If large files (> 2^32 nodes) are to be supported, the
+ * allocation for this field should be increased.
+ */
diff --git a/lib/dns/rbtdb.c b/lib/dns/rbtdb.c
new file mode 100644
index 0000000..5d36466
--- /dev/null
+++ b/lib/dns/rbtdb.c
@@ -0,0 +1,10241 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/*! \file */
+
+#include <ctype.h>
+#include <inttypes.h>
+#include <stdbool.h>
+#include <sys/mman.h>
+
+#include <isc/atomic.h>
+#include <isc/crc64.h>
+#include <isc/event.h>
+#include <isc/file.h>
+#include <isc/hash.h>
+#include <isc/heap.h>
+#include <isc/hex.h>
+#include <isc/mem.h>
+#include <isc/mutex.h>
+#include <isc/once.h>
+#include <isc/print.h>
+#include <isc/random.h>
+#include <isc/refcount.h>
+#include <isc/result.h>
+#include <isc/rwlock.h>
+#include <isc/serial.h>
+#include <isc/stdio.h>
+#include <isc/string.h>
+#include <isc/task.h>
+#include <isc/time.h>
+#include <isc/util.h>
+
+#include <dns/callbacks.h>
+#include <dns/db.h>
+#include <dns/dbiterator.h>
+#include <dns/events.h>
+#include <dns/fixedname.h>
+#include <dns/log.h>
+#include <dns/masterdump.h>
+#include <dns/nsec.h>
+#include <dns/nsec3.h>
+#include <dns/rbt.h>
+#include <dns/rdata.h>
+#include <dns/rdataset.h>
+#include <dns/rdatasetiter.h>
+#include <dns/rdataslab.h>
+#include <dns/rdatastruct.h>
+#include <dns/stats.h>
+#include <dns/time.h>
+#include <dns/view.h>
+#include <dns/zone.h>
+#include <dns/zonekey.h>
+
+#include "rbtdb.h"
+
+#define RBTDB_MAGIC ISC_MAGIC('R', 'B', 'D', '4')
+
+#define CHECK(op) \
+ do { \
+ result = (op); \
+ if (result != ISC_R_SUCCESS) \
+ goto failure; \
+ } while (0)
+
+/*%
+ * Note that "impmagic" is not the first four bytes of the struct, so
+ * ISC_MAGIC_VALID cannot be used.
+ */
+#define VALID_RBTDB(rbtdb) \
+ ((rbtdb) != NULL && (rbtdb)->common.impmagic == RBTDB_MAGIC)
+
+typedef uint32_t rbtdb_serial_t;
+typedef uint32_t rbtdb_rdatatype_t;
+
+#define RBTDB_RDATATYPE_BASE(type) ((dns_rdatatype_t)((type)&0xFFFF))
+#define RBTDB_RDATATYPE_EXT(type) ((dns_rdatatype_t)((type) >> 16))
+#define RBTDB_RDATATYPE_VALUE(base, ext) \
+ ((rbtdb_rdatatype_t)(((uint32_t)ext) << 16) | \
+ (((uint32_t)base) & 0xffff))
+
+#define RBTDB_RDATATYPE_SIGNSEC \
+ RBTDB_RDATATYPE_VALUE(dns_rdatatype_rrsig, dns_rdatatype_nsec)
+#define RBTDB_RDATATYPE_SIGNSEC3 \
+ RBTDB_RDATATYPE_VALUE(dns_rdatatype_rrsig, dns_rdatatype_nsec3)
+#define RBTDB_RDATATYPE_SIGNS \
+ RBTDB_RDATATYPE_VALUE(dns_rdatatype_rrsig, dns_rdatatype_ns)
+#define RBTDB_RDATATYPE_SIGCNAME \
+ RBTDB_RDATATYPE_VALUE(dns_rdatatype_rrsig, dns_rdatatype_cname)
+#define RBTDB_RDATATYPE_SIGDNAME \
+ RBTDB_RDATATYPE_VALUE(dns_rdatatype_rrsig, dns_rdatatype_dname)
+#define RBTDB_RDATATYPE_SIGDS \
+ RBTDB_RDATATYPE_VALUE(dns_rdatatype_rrsig, dns_rdatatype_ds)
+#define RBTDB_RDATATYPE_SIGSOA \
+ RBTDB_RDATATYPE_VALUE(dns_rdatatype_rrsig, dns_rdatatype_soa)
+#define RBTDB_RDATATYPE_NCACHEANY RBTDB_RDATATYPE_VALUE(0, dns_rdatatype_any)
+
+#define RBTDB_INITLOCK(l) isc_rwlock_init((l), 0, 0)
+#define RBTDB_DESTROYLOCK(l) isc_rwlock_destroy(l)
+#define RBTDB_LOCK(l, t) RWLOCK((l), (t))
+#define RBTDB_UNLOCK(l, t) RWUNLOCK((l), (t))
+
+/*
+ * Since node locking is sensitive to both performance and memory footprint,
+ * we need some trick here. If we have both high-performance rwlock and
+ * high performance and small-memory reference counters, we use rwlock for
+ * node lock and isc_refcount for node references. In this case, we don't have
+ * to protect the access to the counters by locks.
+ * Otherwise, we simply use ordinary mutex lock for node locking, and use
+ * simple integers as reference counters which is protected by the lock.
+ * In most cases, we can simply use wrapper macros such as NODE_LOCK and
+ * NODE_UNLOCK. In some other cases, however, we need to protect reference
+ * counters first and then protect other parts of a node as read-only data.
+ * Special additional macros, NODE_STRONGLOCK(), NODE_WEAKLOCK(), etc, are also
+ * provided for these special cases. When we can use the efficient backend
+ * routines, we should only protect the "other members" by NODE_WEAKLOCK(read).
+ * Otherwise, we should use NODE_STRONGLOCK() to protect the entire critical
+ * section including the access to the reference counter.
+ * Note that we cannot use NODE_LOCK()/NODE_UNLOCK() wherever the protected
+ * section is also protected by NODE_STRONGLOCK().
+ */
+typedef isc_rwlock_t nodelock_t;
+
+#define NODE_INITLOCK(l) isc_rwlock_init((l), 0, 0)
+#define NODE_DESTROYLOCK(l) isc_rwlock_destroy(l)
+#define NODE_LOCK(l, t) RWLOCK((l), (t))
+#define NODE_UNLOCK(l, t) RWUNLOCK((l), (t))
+#define NODE_TRYUPGRADE(l) isc_rwlock_tryupgrade(l)
+#define NODE_DOWNGRADE(l) isc_rwlock_downgrade(l)
+
+/*%
+ * Whether to rate-limit updating the LRU to avoid possible thread contention.
+ * Updating LRU requires write locking, so we don't do it every time the
+ * record is touched - only after some time passes.
+ */
+#ifndef DNS_RBTDB_LIMITLRUUPDATE
+#define DNS_RBTDB_LIMITLRUUPDATE 1
+#endif
+
+/*% Time after which we update LRU for glue records, 5 minutes */
+#define DNS_RBTDB_LRUUPDATE_GLUE 300
+/*% Time after which we update LRU for all other records, 10 minutes */
+#define DNS_RBTDB_LRUUPDATE_REGULAR 600
+
+/*
+ * Allow clients with a virtual time of up to 5 minutes in the past to see
+ * records that would have otherwise have expired.
+ */
+#define RBTDB_VIRTUAL 300
+
+struct noqname {
+ dns_name_t name;
+ void *neg;
+ void *negsig;
+ dns_rdatatype_t type;
+};
+
+typedef struct rdatasetheader {
+ /*%
+ * Locked by the owning node's lock.
+ */
+ rbtdb_serial_t serial;
+ dns_ttl_t rdh_ttl;
+ rbtdb_rdatatype_t type;
+ atomic_uint_least16_t attributes;
+ dns_trust_t trust;
+ atomic_uint_fast32_t last_refresh_fail_ts;
+ struct noqname *noqname;
+ struct noqname *closest;
+ unsigned int resign_lsb : 1;
+ /*%<
+ * We don't use the LIST macros, because the LIST structure has
+ * both head and tail pointers, and is doubly linked.
+ */
+
+ struct rdatasetheader *next;
+ /*%<
+ * If this is the top header for an rdataset, 'next' points
+ * to the top header for the next rdataset (i.e., the next type).
+ * Otherwise, it points up to the header whose down pointer points
+ * at this header.
+ */
+
+ struct rdatasetheader *down;
+ /*%<
+ * Points to the header for the next older version of
+ * this rdataset.
+ */
+
+ atomic_uint_fast32_t count;
+ /*%<
+ * Monotonously increased every time this rdataset is bound so that
+ * it is used as the base of the starting point in DNS responses
+ * when the "cyclic" rrset-order is required.
+ */
+
+ dns_rbtnode_t *node;
+ isc_stdtime_t last_used;
+ ISC_LINK(struct rdatasetheader) link;
+
+ unsigned int heap_index;
+ /*%<
+ * Used for TTL-based cache cleaning.
+ */
+ isc_stdtime_t resign;
+ /*%<
+ * Case vector. If the bit is set then the corresponding
+ * character in the owner name needs to be AND'd with 0x20,
+ * rendering that character upper case.
+ */
+ unsigned char upper[32];
+} rdatasetheader_t;
+
+typedef ISC_LIST(rdatasetheader_t) rdatasetheaderlist_t;
+typedef ISC_LIST(dns_rbtnode_t) rbtnodelist_t;
+
+#define RDATASET_ATTR_NONEXISTENT 0x0001
+/*%< May be potentially served as stale data. */
+#define RDATASET_ATTR_STALE 0x0002
+#define RDATASET_ATTR_IGNORE 0x0004
+#define RDATASET_ATTR_RETAIN 0x0008
+#define RDATASET_ATTR_NXDOMAIN 0x0010
+#define RDATASET_ATTR_RESIGN 0x0020
+#define RDATASET_ATTR_STATCOUNT 0x0040
+#define RDATASET_ATTR_OPTOUT 0x0080
+#define RDATASET_ATTR_NEGATIVE 0x0100
+#define RDATASET_ATTR_PREFETCH 0x0200
+#define RDATASET_ATTR_CASESET 0x0400
+#define RDATASET_ATTR_ZEROTTL 0x0800
+#define RDATASET_ATTR_CASEFULLYLOWER 0x1000
+/*%< Ancient - awaiting cleanup. */
+#define RDATASET_ATTR_ANCIENT 0x2000
+#define RDATASET_ATTR_STALE_WINDOW 0x4000
+
+/*
+ * XXX
+ * When the cache will pre-expire data (due to memory low or other
+ * situations) before the rdataset's TTL has expired, it MUST
+ * respect the RETAIN bit and not expire the data until its TTL is
+ * expired.
+ */
+
+#define EXISTS(header) \
+ ((atomic_load_acquire(&(header)->attributes) & \
+ RDATASET_ATTR_NONEXISTENT) == 0)
+#define NONEXISTENT(header) \
+ ((atomic_load_acquire(&(header)->attributes) & \
+ RDATASET_ATTR_NONEXISTENT) != 0)
+#define IGNORE(header) \
+ ((atomic_load_acquire(&(header)->attributes) & \
+ RDATASET_ATTR_IGNORE) != 0)
+#define RETAIN(header) \
+ ((atomic_load_acquire(&(header)->attributes) & \
+ RDATASET_ATTR_RETAIN) != 0)
+#define NXDOMAIN(header) \
+ ((atomic_load_acquire(&(header)->attributes) & \
+ RDATASET_ATTR_NXDOMAIN) != 0)
+#define STALE(header) \
+ ((atomic_load_acquire(&(header)->attributes) & RDATASET_ATTR_STALE) != \
+ 0)
+#define STALE_WINDOW(header) \
+ ((atomic_load_acquire(&(header)->attributes) & \
+ RDATASET_ATTR_STALE_WINDOW) != 0)
+#define RESIGN(header) \
+ ((atomic_load_acquire(&(header)->attributes) & \
+ RDATASET_ATTR_RESIGN) != 0)
+#define OPTOUT(header) \
+ ((atomic_load_acquire(&(header)->attributes) & \
+ RDATASET_ATTR_OPTOUT) != 0)
+#define NEGATIVE(header) \
+ ((atomic_load_acquire(&(header)->attributes) & \
+ RDATASET_ATTR_NEGATIVE) != 0)
+#define PREFETCH(header) \
+ ((atomic_load_acquire(&(header)->attributes) & \
+ RDATASET_ATTR_PREFETCH) != 0)
+#define CASESET(header) \
+ ((atomic_load_acquire(&(header)->attributes) & \
+ RDATASET_ATTR_CASESET) != 0)
+#define ZEROTTL(header) \
+ ((atomic_load_acquire(&(header)->attributes) & \
+ RDATASET_ATTR_ZEROTTL) != 0)
+#define CASEFULLYLOWER(header) \
+ ((atomic_load_acquire(&(header)->attributes) & \
+ RDATASET_ATTR_CASEFULLYLOWER) != 0)
+#define ANCIENT(header) \
+ ((atomic_load_acquire(&(header)->attributes) & \
+ RDATASET_ATTR_ANCIENT) != 0)
+#define STATCOUNT(header) \
+ ((atomic_load_acquire(&(header)->attributes) & \
+ RDATASET_ATTR_STATCOUNT) != 0)
+#define STALE_TTL(header, rbtdb) (NXDOMAIN(header) ? 0 : rbtdb->serve_stale_ttl)
+
+#define RDATASET_ATTR_GET(header, attribute) \
+ (atomic_load_acquire(&(header)->attributes) & attribute)
+#define RDATASET_ATTR_SET(header, attribute) \
+ atomic_fetch_or_release(&(header)->attributes, attribute)
+#define RDATASET_ATTR_CLR(header, attribute) \
+ atomic_fetch_and_release(&(header)->attributes, ~(attribute))
+
+#define ACTIVE(header, now) \
+ (((header)->rdh_ttl > (now)) || \
+ ((header)->rdh_ttl == (now) && ZEROTTL(header)))
+
+#define DEFAULT_NODE_LOCK_COUNT 7 /*%< Should be prime. */
+#define RBTDB_GLUE_TABLE_INIT_BITS 2U
+#define RBTDB_GLUE_TABLE_MAX_BITS 32U
+#define RBTDB_GLUE_TABLE_OVERCOMMIT 3
+
+#define GOLDEN_RATIO_32 0x61C88647
+#define HASHSIZE(bits) (UINT64_C(1) << (bits))
+
+static uint32_t
+hash_32(uint32_t val, unsigned int bits) {
+ REQUIRE(bits <= RBTDB_GLUE_TABLE_MAX_BITS);
+ /* High bits are more random. */
+ return (val * GOLDEN_RATIO_32 >> (32 - bits));
+}
+
+#define EXPIREDOK(rbtiterator) \
+ (((rbtiterator)->common.options & DNS_DB_EXPIREDOK) != 0)
+
+#define STALEOK(rbtiterator) \
+ (((rbtiterator)->common.options & DNS_DB_STALEOK) != 0)
+
+/*%
+ * Number of buckets for cache DB entries (locks, LRU lists, TTL heaps).
+ * There is a tradeoff issue about configuring this value: if this is too
+ * small, it may cause heavier contention between threads; if this is too large,
+ * LRU purge algorithm won't work well (entries tend to be purged prematurely).
+ * The default value should work well for most environments, but this can
+ * also be configurable at compilation time via the
+ * DNS_RBTDB_CACHE_NODE_LOCK_COUNT variable. This value must be larger than
+ * 1 due to the assumption of overmem_purge().
+ */
+#ifdef DNS_RBTDB_CACHE_NODE_LOCK_COUNT
+#if DNS_RBTDB_CACHE_NODE_LOCK_COUNT <= 1
+#error "DNS_RBTDB_CACHE_NODE_LOCK_COUNT must be larger than 1"
+#else /* if DNS_RBTDB_CACHE_NODE_LOCK_COUNT <= 1 */
+#define DEFAULT_CACHE_NODE_LOCK_COUNT DNS_RBTDB_CACHE_NODE_LOCK_COUNT
+#endif /* if DNS_RBTDB_CACHE_NODE_LOCK_COUNT <= 1 */
+#else /* ifdef DNS_RBTDB_CACHE_NODE_LOCK_COUNT */
+#define DEFAULT_CACHE_NODE_LOCK_COUNT 17
+#endif /* DNS_RBTDB_CACHE_NODE_LOCK_COUNT */
+
+typedef struct {
+ nodelock_t lock;
+ /* Protected in the refcount routines. */
+ isc_refcount_t references;
+ /* Locked by lock. */
+ bool exiting;
+} rbtdb_nodelock_t;
+
+typedef struct rbtdb_changed {
+ dns_rbtnode_t *node;
+ bool dirty;
+ ISC_LINK(struct rbtdb_changed) link;
+} rbtdb_changed_t;
+
+typedef ISC_LIST(rbtdb_changed_t) rbtdb_changedlist_t;
+
+typedef enum { dns_db_insecure, dns_db_partial, dns_db_secure } dns_db_secure_t;
+
+typedef struct dns_rbtdb dns_rbtdb_t;
+
+/* Reason for expiring a record from cache */
+typedef enum { expire_lru, expire_ttl, expire_flush } expire_t;
+
+typedef struct rbtdb_glue rbtdb_glue_t;
+
+typedef struct rbtdb_glue_table_node {
+ struct rbtdb_glue_table_node *next;
+ dns_rbtnode_t *node;
+ rbtdb_glue_t *glue_list;
+} rbtdb_glue_table_node_t;
+
+typedef enum {
+ rdataset_ttl_fresh,
+ rdataset_ttl_stale,
+ rdataset_ttl_ancient
+} rdataset_ttl_t;
+
+typedef struct rbtdb_version {
+ /* Not locked */
+ rbtdb_serial_t serial;
+ dns_rbtdb_t *rbtdb;
+ /*
+ * Protected in the refcount routines.
+ * XXXJT: should we change the lock policy based on the refcount
+ * performance?
+ */
+ isc_refcount_t references;
+ /* Locked by database lock. */
+ bool writer;
+ bool commit_ok;
+ rbtdb_changedlist_t changed_list;
+ rdatasetheaderlist_t resigned_list;
+ ISC_LINK(struct rbtdb_version) link;
+ dns_db_secure_t secure;
+ bool havensec3;
+ /* NSEC3 parameters */
+ dns_hash_t hash;
+ uint8_t flags;
+ uint16_t iterations;
+ uint8_t salt_length;
+ unsigned char salt[DNS_NSEC3_SALTSIZE];
+
+ /*
+ * records and xfrsize are covered by rwlock.
+ */
+ isc_rwlock_t rwlock;
+ uint64_t records;
+ uint64_t xfrsize;
+
+ isc_rwlock_t glue_rwlock;
+ size_t glue_table_bits;
+ size_t glue_table_nodecount;
+ rbtdb_glue_table_node_t **glue_table;
+} rbtdb_version_t;
+
+typedef ISC_LIST(rbtdb_version_t) rbtdb_versionlist_t;
+
+struct dns_rbtdb {
+ /* Unlocked. */
+ dns_db_t common;
+ /* Locks the data in this struct */
+ isc_rwlock_t lock;
+ /* Locks the tree structure (prevents nodes appearing/disappearing) */
+ isc_rwlock_t tree_lock;
+ /* Locks for individual tree nodes */
+ unsigned int node_lock_count;
+ rbtdb_nodelock_t *node_locks;
+ dns_rbtnode_t *origin_node;
+ dns_rbtnode_t *nsec3_origin_node;
+ dns_stats_t *rrsetstats; /* cache DB only */
+ isc_stats_t *cachestats; /* cache DB only */
+ isc_stats_t *gluecachestats; /* zone DB only */
+ /* Locked by lock. */
+ unsigned int active;
+ isc_refcount_t references;
+ unsigned int attributes;
+ rbtdb_serial_t current_serial;
+ rbtdb_serial_t least_serial;
+ rbtdb_serial_t next_serial;
+ rbtdb_version_t *current_version;
+ rbtdb_version_t *future_version;
+ rbtdb_versionlist_t open_versions;
+ isc_task_t *task;
+ dns_dbnode_t *soanode;
+ dns_dbnode_t *nsnode;
+
+ /*
+ * Maximum length of time to keep using a stale answer past its
+ * normal TTL expiry.
+ */
+ dns_ttl_t serve_stale_ttl;
+
+ /*
+ * The time after a failed lookup, where stale answers from cache
+ * may be used directly in a DNS response without attempting a
+ * new iterative lookup.
+ */
+ uint32_t serve_stale_refresh;
+
+ /*
+ * This is a linked list used to implement the LRU cache. There will
+ * be node_lock_count linked lists here. Nodes in bucket 1 will be
+ * placed on the linked list rdatasets[1].
+ */
+ rdatasetheaderlist_t *rdatasets;
+
+ /*%
+ * Temporary storage for stale cache nodes and dynamically deleted
+ * nodes that await being cleaned up.
+ */
+ rbtnodelist_t *deadnodes;
+
+ /*
+ * Heaps. These are used for TTL based expiry in a cache,
+ * or for zone resigning in a zone DB. hmctx is the memory
+ * context to use for the heap (which differs from the main
+ * database memory context in the case of a cache).
+ */
+ isc_mem_t *hmctx;
+ isc_heap_t **heaps;
+
+ /* Locked by tree_lock. */
+ dns_rbt_t *tree;
+ dns_rbt_t *nsec;
+ dns_rbt_t *nsec3;
+
+ /* Unlocked */
+ unsigned int quantum;
+};
+
+#define RBTDB_ATTR_LOADED 0x01
+#define RBTDB_ATTR_LOADING 0x02
+
+#define KEEPSTALE(rbtdb) ((rbtdb)->serve_stale_ttl > 0)
+
+/*%
+ * Search Context
+ */
+typedef struct {
+ dns_rbtdb_t *rbtdb;
+ rbtdb_version_t *rbtversion;
+ rbtdb_serial_t serial;
+ unsigned int options;
+ dns_rbtnodechain_t chain;
+ bool copy_name;
+ bool need_cleanup;
+ bool wild;
+ dns_rbtnode_t *zonecut;
+ rdatasetheader_t *zonecut_rdataset;
+ rdatasetheader_t *zonecut_sigrdataset;
+ dns_fixedname_t zonecut_name;
+ isc_stdtime_t now;
+} rbtdb_search_t;
+
+/*%
+ * Load Context
+ */
+typedef struct {
+ dns_rbtdb_t *rbtdb;
+ isc_stdtime_t now;
+} rbtdb_load_t;
+
+static void
+delete_callback(void *data, void *arg);
+static void
+rdataset_disassociate(dns_rdataset_t *rdataset);
+static isc_result_t
+rdataset_first(dns_rdataset_t *rdataset);
+static isc_result_t
+rdataset_next(dns_rdataset_t *rdataset);
+static void
+rdataset_current(dns_rdataset_t *rdataset, dns_rdata_t *rdata);
+static void
+rdataset_clone(dns_rdataset_t *source, dns_rdataset_t *target);
+static unsigned int
+rdataset_count(dns_rdataset_t *rdataset);
+static isc_result_t
+rdataset_getnoqname(dns_rdataset_t *rdataset, dns_name_t *name,
+ dns_rdataset_t *neg, dns_rdataset_t *negsig);
+static isc_result_t
+rdataset_getclosest(dns_rdataset_t *rdataset, dns_name_t *name,
+ dns_rdataset_t *neg, dns_rdataset_t *negsig);
+static bool
+need_headerupdate(rdatasetheader_t *header, isc_stdtime_t now);
+static void
+update_header(dns_rbtdb_t *rbtdb, rdatasetheader_t *header, isc_stdtime_t now);
+static void
+expire_header(dns_rbtdb_t *rbtdb, rdatasetheader_t *header, bool tree_locked,
+ expire_t reason);
+static void
+overmem_purge(dns_rbtdb_t *rbtdb, unsigned int locknum_start, size_t purgesize,
+ bool tree_locked);
+static void
+resign_insert(dns_rbtdb_t *rbtdb, int idx, rdatasetheader_t *newheader);
+static void
+resign_delete(dns_rbtdb_t *rbtdb, rbtdb_version_t *version,
+ rdatasetheader_t *header);
+static void
+prune_tree(isc_task_t *task, isc_event_t *event);
+static void
+rdataset_settrust(dns_rdataset_t *rdataset, dns_trust_t trust);
+static void
+rdataset_expire(dns_rdataset_t *rdataset);
+static void
+rdataset_clearprefetch(dns_rdataset_t *rdataset);
+static void
+rdataset_setownercase(dns_rdataset_t *rdataset, const dns_name_t *name);
+static void
+rdataset_getownercase(const dns_rdataset_t *rdataset, dns_name_t *name);
+static isc_result_t
+rdataset_addglue(dns_rdataset_t *rdataset, dns_dbversion_t *version,
+ dns_message_t *msg);
+static void
+free_gluetable(rbtdb_version_t *version);
+static isc_result_t
+nodefullname(dns_db_t *db, dns_dbnode_t *node, dns_name_t *name);
+
+static dns_rdatasetmethods_t rdataset_methods = { rdataset_disassociate,
+ rdataset_first,
+ rdataset_next,
+ rdataset_current,
+ rdataset_clone,
+ rdataset_count,
+ NULL, /* addnoqname */
+ rdataset_getnoqname,
+ NULL, /* addclosest */
+ rdataset_getclosest,
+ rdataset_settrust,
+ rdataset_expire,
+ rdataset_clearprefetch,
+ rdataset_setownercase,
+ rdataset_getownercase,
+ rdataset_addglue };
+
+static dns_rdatasetmethods_t slab_methods = {
+ rdataset_disassociate,
+ rdataset_first,
+ rdataset_next,
+ rdataset_current,
+ rdataset_clone,
+ rdataset_count,
+ NULL, /* addnoqname */
+ NULL, /* getnoqname */
+ NULL, /* addclosest */
+ NULL, /* getclosest */
+ NULL, /* settrust */
+ NULL, /* expire */
+ NULL, /* clearprefetch */
+ NULL, /* setownercase */
+ NULL, /* getownercase */
+ NULL /* addglue */
+};
+
+static void
+rdatasetiter_destroy(dns_rdatasetiter_t **iteratorp);
+static isc_result_t
+rdatasetiter_first(dns_rdatasetiter_t *iterator);
+static isc_result_t
+rdatasetiter_next(dns_rdatasetiter_t *iterator);
+static void
+rdatasetiter_current(dns_rdatasetiter_t *iterator, dns_rdataset_t *rdataset);
+
+static dns_rdatasetitermethods_t rdatasetiter_methods = {
+ rdatasetiter_destroy, rdatasetiter_first, rdatasetiter_next,
+ rdatasetiter_current
+};
+
+typedef struct rbtdb_rdatasetiter {
+ dns_rdatasetiter_t common;
+ rdatasetheader_t *current;
+} rbtdb_rdatasetiter_t;
+
+/*
+ * Note that these iterators, unless created with either DNS_DB_NSEC3ONLY or
+ * DNS_DB_NONSEC3, will transparently move between the last node of the
+ * "regular" RBT ("chain" field) and the root node of the NSEC3 RBT
+ * ("nsec3chain" field) of the database in question, as if the latter was a
+ * successor to the former in lexical order. The "current" field always holds
+ * the address of either "chain" or "nsec3chain", depending on which RBT is
+ * being traversed at given time.
+ */
+static void
+dbiterator_destroy(dns_dbiterator_t **iteratorp);
+static isc_result_t
+dbiterator_first(dns_dbiterator_t *iterator);
+static isc_result_t
+dbiterator_last(dns_dbiterator_t *iterator);
+static isc_result_t
+dbiterator_seek(dns_dbiterator_t *iterator, const dns_name_t *name);
+static isc_result_t
+dbiterator_prev(dns_dbiterator_t *iterator);
+static isc_result_t
+dbiterator_next(dns_dbiterator_t *iterator);
+static isc_result_t
+dbiterator_current(dns_dbiterator_t *iterator, dns_dbnode_t **nodep,
+ dns_name_t *name);
+static isc_result_t
+dbiterator_pause(dns_dbiterator_t *iterator);
+static isc_result_t
+dbiterator_origin(dns_dbiterator_t *iterator, dns_name_t *name);
+
+static dns_dbiteratormethods_t dbiterator_methods = {
+ dbiterator_destroy, dbiterator_first, dbiterator_last,
+ dbiterator_seek, dbiterator_prev, dbiterator_next,
+ dbiterator_current, dbiterator_pause, dbiterator_origin
+};
+
+#define DELETION_BATCH_MAX 64
+
+/*
+ * If 'paused' is true, then the tree lock is not being held.
+ */
+typedef struct rbtdb_dbiterator {
+ dns_dbiterator_t common;
+ bool paused;
+ bool new_origin;
+ isc_rwlocktype_t tree_locked;
+ isc_result_t result;
+ dns_fixedname_t name;
+ dns_fixedname_t origin;
+ dns_rbtnodechain_t chain;
+ dns_rbtnodechain_t nsec3chain;
+ dns_rbtnodechain_t *current;
+ dns_rbtnode_t *node;
+ dns_rbtnode_t *deletions[DELETION_BATCH_MAX];
+ int delcnt;
+ bool nsec3only;
+ bool nonsec3;
+} rbtdb_dbiterator_t;
+
+#define IS_STUB(rbtdb) (((rbtdb)->common.attributes & DNS_DBATTR_STUB) != 0)
+#define IS_CACHE(rbtdb) (((rbtdb)->common.attributes & DNS_DBATTR_CACHE) != 0)
+
+static void
+free_rbtdb(dns_rbtdb_t *rbtdb, bool log, isc_event_t *event);
+static void
+overmem(dns_db_t *db, bool over);
+static void
+setnsec3parameters(dns_db_t *db, rbtdb_version_t *version);
+static void
+setownercase(rdatasetheader_t *header, const dns_name_t *name);
+
+/*%
+ * 'init_count' is used to initialize 'newheader->count' which inturn
+ * is used to determine where in the cycle rrset-order cyclic starts.
+ * We don't lock this as we don't care about simultaneous updates.
+ *
+ * Note:
+ * Both init_count and header->count can be UINT32_MAX.
+ * The count on the returned rdataset however can't be as
+ * that indicates that the database does not implement cyclic
+ * processing.
+ */
+static atomic_uint_fast32_t init_count = 0;
+
+/*
+ * Locking
+ *
+ * If a routine is going to lock more than one lock in this module, then
+ * the locking must be done in the following order:
+ *
+ * Tree Lock
+ *
+ * Node Lock (Only one from the set may be locked at one time by
+ * any caller)
+ *
+ * Database Lock
+ *
+ * Failure to follow this hierarchy can result in deadlock.
+ */
+
+/*
+ * Deleting Nodes
+ *
+ * For zone databases the node for the origin of the zone MUST NOT be deleted.
+ */
+
+/* Fixed RRSet helper macros */
+
+#define DNS_RDATASET_LENGTH 2;
+
+#if DNS_RDATASET_FIXED
+#define DNS_RDATASET_ORDER 2
+#define DNS_RDATASET_COUNT (count * 4)
+#else /* !DNS_RDATASET_FIXED */
+#define DNS_RDATASET_ORDER 0
+#define DNS_RDATASET_COUNT 0
+#endif /* DNS_RDATASET_FIXED */
+
+/*
+ * DB Routines
+ */
+
+static void
+attach(dns_db_t *source, dns_db_t **targetp) {
+ dns_rbtdb_t *rbtdb = (dns_rbtdb_t *)source;
+
+ REQUIRE(VALID_RBTDB(rbtdb));
+
+ isc_refcount_increment(&rbtdb->references);
+
+ *targetp = source;
+}
+
+static void
+free_rbtdb_callback(isc_task_t *task, isc_event_t *event) {
+ dns_rbtdb_t *rbtdb = event->ev_arg;
+
+ UNUSED(task);
+
+ free_rbtdb(rbtdb, true, event);
+}
+
+static void
+update_cachestats(dns_rbtdb_t *rbtdb, isc_result_t result) {
+ INSIST(IS_CACHE(rbtdb));
+
+ if (rbtdb->cachestats == NULL) {
+ return;
+ }
+
+ switch (result) {
+ case DNS_R_COVERINGNSEC:
+ isc_stats_increment(rbtdb->cachestats,
+ dns_cachestatscounter_coveringnsec);
+ FALLTHROUGH;
+ case ISC_R_SUCCESS:
+ case DNS_R_CNAME:
+ case DNS_R_DNAME:
+ case DNS_R_DELEGATION:
+ case DNS_R_NCACHENXDOMAIN:
+ case DNS_R_NCACHENXRRSET:
+ isc_stats_increment(rbtdb->cachestats,
+ dns_cachestatscounter_hits);
+ break;
+ default:
+ isc_stats_increment(rbtdb->cachestats,
+ dns_cachestatscounter_misses);
+ }
+}
+
+static bool
+do_stats(rdatasetheader_t *header) {
+ return (EXISTS(header) && STATCOUNT(header));
+}
+
+static void
+update_rrsetstats(dns_rbtdb_t *rbtdb, const rbtdb_rdatatype_t htype,
+ const uint_least16_t hattributes, const bool increment) {
+ dns_rdatastatstype_t statattributes = 0;
+ dns_rdatastatstype_t base = 0;
+ dns_rdatastatstype_t type;
+ rdatasetheader_t *header = &(rdatasetheader_t){
+ .type = htype,
+ .attributes = hattributes,
+ };
+
+ if (!do_stats(header)) {
+ return;
+ }
+
+ /* At the moment we count statistics only for cache DB */
+ INSIST(IS_CACHE(rbtdb));
+
+ if (NEGATIVE(header)) {
+ if (NXDOMAIN(header)) {
+ statattributes = DNS_RDATASTATSTYPE_ATTR_NXDOMAIN;
+ } else {
+ statattributes = DNS_RDATASTATSTYPE_ATTR_NXRRSET;
+ base = RBTDB_RDATATYPE_EXT(header->type);
+ }
+ } else {
+ base = RBTDB_RDATATYPE_BASE(header->type);
+ }
+
+ if (STALE(header)) {
+ statattributes |= DNS_RDATASTATSTYPE_ATTR_STALE;
+ }
+ if (ANCIENT(header)) {
+ statattributes |= DNS_RDATASTATSTYPE_ATTR_ANCIENT;
+ }
+
+ type = DNS_RDATASTATSTYPE_VALUE(base, statattributes);
+ if (increment) {
+ dns_rdatasetstats_increment(rbtdb->rrsetstats, type);
+ } else {
+ dns_rdatasetstats_decrement(rbtdb->rrsetstats, type);
+ }
+}
+
+static void
+set_ttl(dns_rbtdb_t *rbtdb, rdatasetheader_t *header, dns_ttl_t newttl) {
+ int idx;
+ isc_heap_t *heap;
+ dns_ttl_t oldttl;
+
+ if (!IS_CACHE(rbtdb)) {
+ header->rdh_ttl = newttl;
+ return;
+ }
+
+ oldttl = header->rdh_ttl;
+ header->rdh_ttl = newttl;
+
+ /*
+ * It's possible the rbtdb is not a cache. If this is the case,
+ * we will not have a heap, and we move on. If we do, though,
+ * we might need to adjust things.
+ */
+ if (header->heap_index == 0 || newttl == oldttl) {
+ return;
+ }
+ idx = header->node->locknum;
+ if (rbtdb->heaps == NULL || rbtdb->heaps[idx] == NULL) {
+ return;
+ }
+ heap = rbtdb->heaps[idx];
+
+ if (newttl < oldttl) {
+ isc_heap_increased(heap, header->heap_index);
+ } else {
+ isc_heap_decreased(heap, header->heap_index);
+ }
+}
+
+/*%
+ * These functions allow the heap code to rank the priority of each
+ * element. It returns true if v1 happens "sooner" than v2.
+ */
+static bool
+ttl_sooner(void *v1, void *v2) {
+ rdatasetheader_t *h1 = v1;
+ rdatasetheader_t *h2 = v2;
+
+ return (h1->rdh_ttl < h2->rdh_ttl);
+}
+
+/*%
+ * Return which RRset should be resigned sooner. If the RRsets have the
+ * same signing time, prefer the other RRset over the SOA RRset.
+ */
+static bool
+resign_sooner(void *v1, void *v2) {
+ rdatasetheader_t *h1 = v1;
+ rdatasetheader_t *h2 = v2;
+
+ return (h1->resign < h2->resign ||
+ (h1->resign == h2->resign && h1->resign_lsb < h2->resign_lsb) ||
+ (h1->resign == h2->resign && h1->resign_lsb == h2->resign_lsb &&
+ h2->type == RBTDB_RDATATYPE_SIGSOA));
+}
+
+/*%
+ * This function sets the heap index into the header.
+ */
+static void
+set_index(void *what, unsigned int idx) {
+ rdatasetheader_t *h = what;
+
+ h->heap_index = idx;
+}
+
+/*%
+ * Work out how many nodes can be deleted in the time between two
+ * requests to the nameserver. Smooth the resulting number and use it
+ * as a estimate for the number of nodes to be deleted in the next
+ * iteration.
+ */
+static unsigned int
+adjust_quantum(unsigned int old, isc_time_t *start) {
+ unsigned int pps = dns_pps; /* packets per second */
+ unsigned int interval;
+ uint64_t usecs;
+ isc_time_t end;
+ unsigned int nodes;
+
+ if (pps < 100) {
+ pps = 100;
+ }
+ isc_time_now(&end);
+
+ interval = 1000000 / pps; /* interval in usec */
+ if (interval == 0) {
+ interval = 1;
+ }
+ usecs = isc_time_microdiff(&end, start);
+ if (usecs == 0) {
+ /*
+ * We were unable to measure the amount of time taken.
+ * Double the nodes deleted next time.
+ */
+ old *= 2;
+ if (old > 1000) {
+ old = 1000;
+ }
+ return (old);
+ }
+ nodes = old * interval;
+ nodes /= (unsigned int)usecs;
+ if (nodes == 0) {
+ nodes = 1;
+ } else if (nodes > 1000) {
+ nodes = 1000;
+ }
+
+ /* Smooth */
+ nodes = (nodes + old * 3) / 4;
+
+ if (nodes != old) {
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE,
+ DNS_LOGMODULE_CACHE, ISC_LOG_DEBUG(1),
+ "adjust_quantum: old=%d, new=%d", old, nodes);
+ }
+
+ return (nodes);
+}
+
+static void
+free_rbtdb(dns_rbtdb_t *rbtdb, bool log, isc_event_t *event) {
+ unsigned int i;
+ isc_result_t result;
+ char buf[DNS_NAME_FORMATSIZE];
+ dns_rbt_t **treep;
+ isc_time_t start;
+
+ if (IS_CACHE(rbtdb) && rbtdb->common.rdclass == dns_rdataclass_in) {
+ overmem((dns_db_t *)rbtdb, (bool)-1);
+ }
+
+ REQUIRE(rbtdb->current_version != NULL || EMPTY(rbtdb->open_versions));
+ REQUIRE(rbtdb->future_version == NULL);
+
+ if (rbtdb->current_version != NULL) {
+ isc_refcount_decrementz(&rbtdb->current_version->references);
+ UNLINK(rbtdb->open_versions, rbtdb->current_version, link);
+ isc_rwlock_destroy(&rbtdb->current_version->glue_rwlock);
+ isc_refcount_destroy(&rbtdb->current_version->references);
+ isc_rwlock_destroy(&rbtdb->current_version->rwlock);
+ isc_mem_put(rbtdb->common.mctx, rbtdb->current_version,
+ sizeof(rbtdb_version_t));
+ }
+
+ /*
+ * We assume the number of remaining dead nodes is reasonably small;
+ * the overhead of unlinking all nodes here should be negligible.
+ */
+ for (i = 0; i < rbtdb->node_lock_count; i++) {
+ dns_rbtnode_t *node;
+
+ node = ISC_LIST_HEAD(rbtdb->deadnodes[i]);
+ while (node != NULL) {
+ ISC_LIST_UNLINK(rbtdb->deadnodes[i], node, deadlink);
+ node = ISC_LIST_HEAD(rbtdb->deadnodes[i]);
+ }
+ }
+
+ if (event == NULL) {
+ rbtdb->quantum = (rbtdb->task != NULL) ? 100 : 0;
+ }
+
+ for (;;) {
+ /*
+ * pick the next tree to (start to) destroy
+ */
+ treep = &rbtdb->tree;
+ if (*treep == NULL) {
+ treep = &rbtdb->nsec;
+ if (*treep == NULL) {
+ treep = &rbtdb->nsec3;
+ /*
+ * we're finished after clear cutting
+ */
+ if (*treep == NULL) {
+ break;
+ }
+ }
+ }
+
+ isc_time_now(&start);
+ result = dns_rbt_destroy2(treep, rbtdb->quantum);
+ if (result == ISC_R_QUOTA) {
+ INSIST(rbtdb->task != NULL);
+ if (rbtdb->quantum != 0) {
+ rbtdb->quantum = adjust_quantum(rbtdb->quantum,
+ &start);
+ }
+ if (event == NULL) {
+ event = isc_event_allocate(
+ rbtdb->common.mctx, NULL,
+ DNS_EVENT_FREESTORAGE,
+ free_rbtdb_callback, rbtdb,
+ sizeof(isc_event_t));
+ }
+ isc_task_send(rbtdb->task, &event);
+ return;
+ }
+ INSIST(result == ISC_R_SUCCESS && *treep == NULL);
+ }
+
+ if (event != NULL) {
+ isc_event_free(&event);
+ }
+ if (log) {
+ if (dns_name_dynamic(&rbtdb->common.origin)) {
+ dns_name_format(&rbtdb->common.origin, buf,
+ sizeof(buf));
+ } else {
+ strlcpy(buf, "<UNKNOWN>", sizeof(buf));
+ }
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE,
+ DNS_LOGMODULE_CACHE, ISC_LOG_DEBUG(1),
+ "done free_rbtdb(%s)", buf);
+ }
+ if (dns_name_dynamic(&rbtdb->common.origin)) {
+ dns_name_free(&rbtdb->common.origin, rbtdb->common.mctx);
+ }
+ for (i = 0; i < rbtdb->node_lock_count; i++) {
+ isc_refcount_destroy(&rbtdb->node_locks[i].references);
+ NODE_DESTROYLOCK(&rbtdb->node_locks[i].lock);
+ }
+
+ /*
+ * Clean up LRU / re-signing order lists.
+ */
+ if (rbtdb->rdatasets != NULL) {
+ for (i = 0; i < rbtdb->node_lock_count; i++) {
+ INSIST(ISC_LIST_EMPTY(rbtdb->rdatasets[i]));
+ }
+ isc_mem_put(rbtdb->common.mctx, rbtdb->rdatasets,
+ rbtdb->node_lock_count *
+ sizeof(rdatasetheaderlist_t));
+ }
+ /*
+ * Clean up dead node buckets.
+ */
+ if (rbtdb->deadnodes != NULL) {
+ for (i = 0; i < rbtdb->node_lock_count; i++) {
+ INSIST(ISC_LIST_EMPTY(rbtdb->deadnodes[i]));
+ }
+ isc_mem_put(rbtdb->common.mctx, rbtdb->deadnodes,
+ rbtdb->node_lock_count * sizeof(rbtnodelist_t));
+ }
+ /*
+ * Clean up heap objects.
+ */
+ if (rbtdb->heaps != NULL) {
+ for (i = 0; i < rbtdb->node_lock_count; i++) {
+ isc_heap_destroy(&rbtdb->heaps[i]);
+ }
+ isc_mem_put(rbtdb->hmctx, rbtdb->heaps,
+ rbtdb->node_lock_count * sizeof(isc_heap_t *));
+ }
+
+ if (rbtdb->rrsetstats != NULL) {
+ dns_stats_detach(&rbtdb->rrsetstats);
+ }
+ if (rbtdb->cachestats != NULL) {
+ isc_stats_detach(&rbtdb->cachestats);
+ }
+ if (rbtdb->gluecachestats != NULL) {
+ isc_stats_detach(&rbtdb->gluecachestats);
+ }
+
+ isc_mem_put(rbtdb->common.mctx, rbtdb->node_locks,
+ rbtdb->node_lock_count * sizeof(rbtdb_nodelock_t));
+ isc_rwlock_destroy(&rbtdb->tree_lock);
+ isc_refcount_destroy(&rbtdb->references);
+ if (rbtdb->task != NULL) {
+ isc_task_detach(&rbtdb->task);
+ }
+
+ RBTDB_DESTROYLOCK(&rbtdb->lock);
+ rbtdb->common.magic = 0;
+ rbtdb->common.impmagic = 0;
+ isc_mem_detach(&rbtdb->hmctx);
+
+ INSIST(ISC_LIST_EMPTY(rbtdb->common.update_listeners));
+
+ isc_mem_putanddetach(&rbtdb->common.mctx, rbtdb, sizeof(*rbtdb));
+}
+
+static void
+maybe_free_rbtdb(dns_rbtdb_t *rbtdb) {
+ bool want_free = false;
+ unsigned int i;
+ unsigned int inactive = 0;
+
+ /* XXX check for open versions here */
+
+ if (rbtdb->soanode != NULL) {
+ dns_db_detachnode((dns_db_t *)rbtdb, &rbtdb->soanode);
+ }
+ if (rbtdb->nsnode != NULL) {
+ dns_db_detachnode((dns_db_t *)rbtdb, &rbtdb->nsnode);
+ }
+
+ /*
+ * The current version's glue table needs to be freed early
+ * so the nodes are dereferenced before we check the active
+ * node count below.
+ */
+ if (rbtdb->current_version != NULL) {
+ free_gluetable(rbtdb->current_version);
+ }
+
+ /*
+ * Even though there are no external direct references, there still
+ * may be nodes in use.
+ */
+ for (i = 0; i < rbtdb->node_lock_count; i++) {
+ NODE_LOCK(&rbtdb->node_locks[i].lock, isc_rwlocktype_write);
+ rbtdb->node_locks[i].exiting = true;
+ if (isc_refcount_current(&rbtdb->node_locks[i].references) == 0)
+ {
+ inactive++;
+ }
+ NODE_UNLOCK(&rbtdb->node_locks[i].lock, isc_rwlocktype_write);
+ }
+
+ if (inactive != 0) {
+ RBTDB_LOCK(&rbtdb->lock, isc_rwlocktype_write);
+ rbtdb->active -= inactive;
+ if (rbtdb->active == 0) {
+ want_free = true;
+ }
+ RBTDB_UNLOCK(&rbtdb->lock, isc_rwlocktype_write);
+ if (want_free) {
+ char buf[DNS_NAME_FORMATSIZE];
+ if (dns_name_dynamic(&rbtdb->common.origin)) {
+ dns_name_format(&rbtdb->common.origin, buf,
+ sizeof(buf));
+ } else {
+ strlcpy(buf, "<UNKNOWN>", sizeof(buf));
+ }
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE,
+ DNS_LOGMODULE_CACHE, ISC_LOG_DEBUG(1),
+ "calling free_rbtdb(%s)", buf);
+ free_rbtdb(rbtdb, true, NULL);
+ }
+ }
+}
+
+static void
+detach(dns_db_t **dbp) {
+ REQUIRE(dbp != NULL && VALID_RBTDB((dns_rbtdb_t *)(*dbp)));
+ dns_rbtdb_t *rbtdb = (dns_rbtdb_t *)(*dbp);
+ *dbp = NULL;
+
+ if (isc_refcount_decrement(&rbtdb->references) == 1) {
+ maybe_free_rbtdb(rbtdb);
+ }
+}
+
+static void
+currentversion(dns_db_t *db, dns_dbversion_t **versionp) {
+ dns_rbtdb_t *rbtdb = (dns_rbtdb_t *)db;
+ rbtdb_version_t *version;
+
+ REQUIRE(VALID_RBTDB(rbtdb));
+
+ RBTDB_LOCK(&rbtdb->lock, isc_rwlocktype_read);
+ version = rbtdb->current_version;
+ isc_refcount_increment(&version->references);
+ RBTDB_UNLOCK(&rbtdb->lock, isc_rwlocktype_read);
+
+ *versionp = (dns_dbversion_t *)version;
+}
+
+static rbtdb_version_t *
+allocate_version(isc_mem_t *mctx, rbtdb_serial_t serial,
+ unsigned int references, bool writer) {
+ rbtdb_version_t *version;
+ size_t size;
+
+ version = isc_mem_get(mctx, sizeof(*version));
+ version->serial = serial;
+
+ isc_refcount_init(&version->references, references);
+ isc_rwlock_init(&version->glue_rwlock, 0, 0);
+
+ version->glue_table_bits = RBTDB_GLUE_TABLE_INIT_BITS;
+ version->glue_table_nodecount = 0U;
+
+ size = HASHSIZE(version->glue_table_bits) *
+ sizeof(version->glue_table[0]);
+ version->glue_table = isc_mem_get(mctx, size);
+ memset(version->glue_table, 0, size);
+
+ version->writer = writer;
+ version->commit_ok = false;
+ ISC_LIST_INIT(version->changed_list);
+ ISC_LIST_INIT(version->resigned_list);
+ ISC_LINK_INIT(version, link);
+
+ return (version);
+}
+
+static isc_result_t
+newversion(dns_db_t *db, dns_dbversion_t **versionp) {
+ dns_rbtdb_t *rbtdb = (dns_rbtdb_t *)db;
+ rbtdb_version_t *version;
+
+ REQUIRE(VALID_RBTDB(rbtdb));
+ REQUIRE(versionp != NULL && *versionp == NULL);
+ REQUIRE(rbtdb->future_version == NULL);
+
+ RBTDB_LOCK(&rbtdb->lock, isc_rwlocktype_write);
+ RUNTIME_CHECK(rbtdb->next_serial != 0); /* XXX Error? */
+ version = allocate_version(rbtdb->common.mctx, rbtdb->next_serial, 1,
+ true);
+ version->rbtdb = rbtdb;
+ version->commit_ok = true;
+ version->secure = rbtdb->current_version->secure;
+ version->havensec3 = rbtdb->current_version->havensec3;
+ if (version->havensec3) {
+ version->flags = rbtdb->current_version->flags;
+ version->iterations = rbtdb->current_version->iterations;
+ version->hash = rbtdb->current_version->hash;
+ version->salt_length = rbtdb->current_version->salt_length;
+ memmove(version->salt, rbtdb->current_version->salt,
+ version->salt_length);
+ } else {
+ version->flags = 0;
+ version->iterations = 0;
+ version->hash = 0;
+ version->salt_length = 0;
+ memset(version->salt, 0, sizeof(version->salt));
+ }
+ isc_rwlock_init(&version->rwlock, 0, 0);
+ RWLOCK(&rbtdb->current_version->rwlock, isc_rwlocktype_read);
+ version->records = rbtdb->current_version->records;
+ version->xfrsize = rbtdb->current_version->xfrsize;
+ RWUNLOCK(&rbtdb->current_version->rwlock, isc_rwlocktype_read);
+ rbtdb->next_serial++;
+ rbtdb->future_version = version;
+ RBTDB_UNLOCK(&rbtdb->lock, isc_rwlocktype_write);
+
+ *versionp = version;
+
+ return (ISC_R_SUCCESS);
+}
+
+static void
+attachversion(dns_db_t *db, dns_dbversion_t *source,
+ dns_dbversion_t **targetp) {
+ dns_rbtdb_t *rbtdb = (dns_rbtdb_t *)db;
+ rbtdb_version_t *rbtversion = source;
+
+ REQUIRE(VALID_RBTDB(rbtdb));
+ INSIST(rbtversion != NULL && rbtversion->rbtdb == rbtdb);
+
+ isc_refcount_increment(&rbtversion->references);
+
+ *targetp = rbtversion;
+}
+
+static rbtdb_changed_t *
+add_changed(dns_rbtdb_t *rbtdb, rbtdb_version_t *version, dns_rbtnode_t *node) {
+ rbtdb_changed_t *changed;
+
+ /*
+ * Caller must be holding the node lock if its reference must be
+ * protected by the lock.
+ */
+
+ changed = isc_mem_get(rbtdb->common.mctx, sizeof(*changed));
+
+ RBTDB_LOCK(&rbtdb->lock, isc_rwlocktype_write);
+
+ REQUIRE(version->writer);
+
+ if (changed != NULL) {
+ isc_refcount_increment(&node->references);
+ changed->node = node;
+ changed->dirty = false;
+ ISC_LIST_INITANDAPPEND(version->changed_list, changed, link);
+ } else {
+ version->commit_ok = false;
+ }
+
+ RBTDB_UNLOCK(&rbtdb->lock, isc_rwlocktype_write);
+
+ return (changed);
+}
+
+static void
+free_noqname(isc_mem_t *mctx, struct noqname **noqname) {
+ if (dns_name_dynamic(&(*noqname)->name)) {
+ dns_name_free(&(*noqname)->name, mctx);
+ }
+ if ((*noqname)->neg != NULL) {
+ isc_mem_put(mctx, (*noqname)->neg,
+ dns_rdataslab_size((*noqname)->neg, 0));
+ }
+ if ((*noqname)->negsig != NULL) {
+ isc_mem_put(mctx, (*noqname)->negsig,
+ dns_rdataslab_size((*noqname)->negsig, 0));
+ }
+ isc_mem_put(mctx, *noqname, sizeof(**noqname));
+ *noqname = NULL;
+}
+
+static void
+init_rdataset(dns_rbtdb_t *rbtdb, rdatasetheader_t *h) {
+ ISC_LINK_INIT(h, link);
+ h->heap_index = 0;
+ atomic_init(&h->attributes, 0);
+ atomic_init(&h->last_refresh_fail_ts, 0);
+
+ STATIC_ASSERT((sizeof(h->attributes) == 2),
+ "The .attributes field of rdatasetheader_t needs to be "
+ "16-bit int type exactly.");
+
+#if TRACE_HEADER
+ if (IS_CACHE(rbtdb) && rbtdb->common.rdclass == dns_rdataclass_in) {
+ fprintf(stderr, "initialized header: %p\n", h);
+ }
+#else /* if TRACE_HEADER */
+ UNUSED(rbtdb);
+#endif /* if TRACE_HEADER */
+}
+
+static void
+update_newheader(rdatasetheader_t *newh, rdatasetheader_t *old) {
+ if (CASESET(old)) {
+ uint_least16_t attr = RDATASET_ATTR_GET(
+ old,
+ (RDATASET_ATTR_CASESET | RDATASET_ATTR_CASEFULLYLOWER));
+ RDATASET_ATTR_SET(newh, attr);
+ memmove(newh->upper, old->upper, sizeof(old->upper));
+ }
+}
+
+static rdatasetheader_t *
+new_rdataset(dns_rbtdb_t *rbtdb, isc_mem_t *mctx) {
+ rdatasetheader_t *h;
+
+ h = isc_mem_get(mctx, sizeof(*h));
+
+#if TRACE_HEADER
+ if (IS_CACHE(rbtdb) && rbtdb->common.rdclass == dns_rdataclass_in) {
+ fprintf(stderr, "allocated header: %p\n", h);
+ }
+#endif /* if TRACE_HEADER */
+ memset(h->upper, 0xeb, sizeof(h->upper));
+ init_rdataset(rbtdb, h);
+ h->rdh_ttl = 0;
+ return (h);
+}
+
+static void
+free_rdataset(dns_rbtdb_t *rbtdb, isc_mem_t *mctx, rdatasetheader_t *rdataset) {
+ unsigned int size;
+ int idx;
+
+ update_rrsetstats(rbtdb, rdataset->type,
+ atomic_load_acquire(&rdataset->attributes), false);
+
+ idx = rdataset->node->locknum;
+ if (ISC_LINK_LINKED(rdataset, link)) {
+ INSIST(IS_CACHE(rbtdb));
+ ISC_LIST_UNLINK(rbtdb->rdatasets[idx], rdataset, link);
+ }
+
+ if (rdataset->heap_index != 0) {
+ isc_heap_delete(rbtdb->heaps[idx], rdataset->heap_index);
+ }
+ rdataset->heap_index = 0;
+
+ if (rdataset->noqname != NULL) {
+ free_noqname(mctx, &rdataset->noqname);
+ }
+ if (rdataset->closest != NULL) {
+ free_noqname(mctx, &rdataset->closest);
+ }
+
+ if (NONEXISTENT(rdataset)) {
+ size = sizeof(*rdataset);
+ } else {
+ size = dns_rdataslab_size((unsigned char *)rdataset,
+ sizeof(*rdataset));
+ }
+
+ isc_mem_put(mctx, rdataset, size);
+}
+
+static void
+rollback_node(dns_rbtnode_t *node, rbtdb_serial_t serial) {
+ rdatasetheader_t *header, *dcurrent;
+ bool make_dirty = false;
+
+ /*
+ * Caller must hold the node lock.
+ */
+
+ /*
+ * We set the IGNORE attribute on rdatasets with serial number
+ * 'serial'. When the reference count goes to zero, these rdatasets
+ * will be cleaned up; until that time, they will be ignored.
+ */
+ for (header = node->data; header != NULL; header = header->next) {
+ if (header->serial == serial) {
+ RDATASET_ATTR_SET(header, RDATASET_ATTR_IGNORE);
+ make_dirty = true;
+ }
+ for (dcurrent = header->down; dcurrent != NULL;
+ dcurrent = dcurrent->down)
+ {
+ if (dcurrent->serial == serial) {
+ RDATASET_ATTR_SET(dcurrent,
+ RDATASET_ATTR_IGNORE);
+ make_dirty = true;
+ }
+ }
+ }
+ if (make_dirty) {
+ node->dirty = 1;
+ }
+}
+
+static void
+mark_header_ancient(dns_rbtdb_t *rbtdb, rdatasetheader_t *header) {
+ uint_least16_t attributes = atomic_load_acquire(&header->attributes);
+ uint_least16_t newattributes = 0;
+
+ /*
+ * If we are already ancient there is nothing to do.
+ */
+ do {
+ if ((attributes & RDATASET_ATTR_ANCIENT) != 0) {
+ return;
+ }
+ newattributes = attributes | RDATASET_ATTR_ANCIENT;
+ } while (!atomic_compare_exchange_weak_acq_rel(
+ &header->attributes, &attributes, newattributes));
+
+ /*
+ * Decrement the stats counter for the appropriate RRtype.
+ * If the STALE attribute is set, this will decrement the
+ * stale type counter, otherwise it decrements the active
+ * stats type counter.
+ */
+ update_rrsetstats(rbtdb, header->type, attributes, false);
+ header->node->dirty = 1;
+
+ /* Increment the stats counter for the ancient RRtype. */
+ update_rrsetstats(rbtdb, header->type, newattributes, true);
+}
+
+static void
+mark_header_stale(dns_rbtdb_t *rbtdb, rdatasetheader_t *header) {
+ uint_least16_t attributes = atomic_load_acquire(&header->attributes);
+ uint_least16_t newattributes = 0;
+
+ INSIST((attributes & RDATASET_ATTR_ZEROTTL) == 0);
+
+ /*
+ * If we are already stale there is nothing to do.
+ */
+ do {
+ if ((attributes & RDATASET_ATTR_STALE) != 0) {
+ return;
+ }
+ newattributes = attributes | RDATASET_ATTR_STALE;
+ } while (!atomic_compare_exchange_weak_acq_rel(
+ &header->attributes, &attributes, newattributes));
+
+ /* Decrement the stats counter for the appropriate RRtype.
+ * If the ANCIENT attribute is set (although it is very
+ * unlikely that an RRset goes from ANCIENT to STALE), this
+ * will decrement the ancient stale type counter, otherwise it
+ * decrements the active stats type counter.
+ */
+
+ update_rrsetstats(rbtdb, header->type, attributes, false);
+ update_rrsetstats(rbtdb, header->type, newattributes, true);
+}
+
+static void
+clean_stale_headers(dns_rbtdb_t *rbtdb, isc_mem_t *mctx,
+ rdatasetheader_t *top) {
+ rdatasetheader_t *d, *down_next;
+
+ for (d = top->down; d != NULL; d = down_next) {
+ down_next = d->down;
+ free_rdataset(rbtdb, mctx, d);
+ }
+ top->down = NULL;
+}
+
+static void
+clean_cache_node(dns_rbtdb_t *rbtdb, dns_rbtnode_t *node) {
+ rdatasetheader_t *current, *top_prev, *top_next;
+ isc_mem_t *mctx = rbtdb->common.mctx;
+
+ /*
+ * Caller must be holding the node lock.
+ */
+
+ top_prev = NULL;
+ for (current = node->data; current != NULL; current = top_next) {
+ top_next = current->next;
+ clean_stale_headers(rbtdb, mctx, current);
+ /*
+ * If current is nonexistent, ancient, or stale and
+ * we are not keeping stale, we can clean it up.
+ */
+ if (NONEXISTENT(current) || ANCIENT(current) ||
+ (STALE(current) && !KEEPSTALE(rbtdb)))
+ {
+ if (top_prev != NULL) {
+ top_prev->next = current->next;
+ } else {
+ node->data = current->next;
+ }
+ free_rdataset(rbtdb, mctx, current);
+ } else {
+ top_prev = current;
+ }
+ }
+ node->dirty = 0;
+}
+
+static void
+clean_zone_node(dns_rbtdb_t *rbtdb, dns_rbtnode_t *node,
+ rbtdb_serial_t least_serial) {
+ rdatasetheader_t *current, *dcurrent, *down_next, *dparent;
+ rdatasetheader_t *top_prev, *top_next;
+ isc_mem_t *mctx = rbtdb->common.mctx;
+ bool still_dirty = false;
+
+ /*
+ * Caller must be holding the node lock.
+ */
+ REQUIRE(least_serial != 0);
+
+ top_prev = NULL;
+ for (current = node->data; current != NULL; current = top_next) {
+ top_next = current->next;
+
+ /*
+ * First, we clean up any instances of multiple rdatasets
+ * with the same serial number, or that have the IGNORE
+ * attribute.
+ */
+ dparent = current;
+ for (dcurrent = current->down; dcurrent != NULL;
+ dcurrent = down_next)
+ {
+ down_next = dcurrent->down;
+ INSIST(dcurrent->serial <= dparent->serial);
+ if (dcurrent->serial == dparent->serial ||
+ IGNORE(dcurrent))
+ {
+ if (down_next != NULL) {
+ down_next->next = dparent;
+ }
+ dparent->down = down_next;
+ free_rdataset(rbtdb, mctx, dcurrent);
+ } else {
+ dparent = dcurrent;
+ }
+ }
+
+ /*
+ * We've now eliminated all IGNORE datasets with the possible
+ * exception of current, which we now check.
+ */
+ if (IGNORE(current)) {
+ down_next = current->down;
+ if (down_next == NULL) {
+ if (top_prev != NULL) {
+ top_prev->next = current->next;
+ } else {
+ node->data = current->next;
+ }
+ free_rdataset(rbtdb, mctx, current);
+ /*
+ * current no longer exists, so we can
+ * just continue with the loop.
+ */
+ continue;
+ } else {
+ /*
+ * Pull up current->down, making it the new
+ * current.
+ */
+ if (top_prev != NULL) {
+ top_prev->next = down_next;
+ } else {
+ node->data = down_next;
+ }
+ down_next->next = top_next;
+ free_rdataset(rbtdb, mctx, current);
+ current = down_next;
+ }
+ }
+
+ /*
+ * We now try to find the first down node less than the
+ * least serial.
+ */
+ dparent = current;
+ for (dcurrent = current->down; dcurrent != NULL;
+ dcurrent = down_next)
+ {
+ down_next = dcurrent->down;
+ if (dcurrent->serial < least_serial) {
+ break;
+ }
+ dparent = dcurrent;
+ }
+
+ /*
+ * If there is a such an rdataset, delete it and any older
+ * versions.
+ */
+ if (dcurrent != NULL) {
+ do {
+ down_next = dcurrent->down;
+ INSIST(dcurrent->serial <= least_serial);
+ free_rdataset(rbtdb, mctx, dcurrent);
+ dcurrent = down_next;
+ } while (dcurrent != NULL);
+ dparent->down = NULL;
+ }
+
+ /*
+ * Note. The serial number of 'current' might be less than
+ * least_serial too, but we cannot delete it because it is
+ * the most recent version, unless it is a NONEXISTENT
+ * rdataset.
+ */
+ if (current->down != NULL) {
+ still_dirty = true;
+ top_prev = current;
+ } else {
+ /*
+ * If this is a NONEXISTENT rdataset, we can delete it.
+ */
+ if (NONEXISTENT(current)) {
+ if (top_prev != NULL) {
+ top_prev->next = current->next;
+ } else {
+ node->data = current->next;
+ }
+ free_rdataset(rbtdb, mctx, current);
+ } else {
+ top_prev = current;
+ }
+ }
+ }
+ if (!still_dirty) {
+ node->dirty = 0;
+ }
+}
+
+/*
+ * tree_lock(write) must be held.
+ */
+static void
+delete_node(dns_rbtdb_t *rbtdb, dns_rbtnode_t *node) {
+ dns_rbtnode_t *nsecnode;
+ dns_fixedname_t fname;
+ dns_name_t *name;
+ isc_result_t result = ISC_R_UNEXPECTED;
+
+ INSIST(!ISC_LINK_LINKED(node, deadlink));
+
+ if (isc_log_wouldlog(dns_lctx, ISC_LOG_DEBUG(1))) {
+ char printname[DNS_NAME_FORMATSIZE];
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE,
+ DNS_LOGMODULE_CACHE, ISC_LOG_DEBUG(1),
+ "delete_node(): %p %s (bucket %d)", node,
+ dns_rbt_formatnodename(node, printname,
+ sizeof(printname)),
+ node->locknum);
+ }
+
+ switch (node->nsec) {
+ case DNS_RBT_NSEC_NORMAL:
+ result = dns_rbt_deletenode(rbtdb->tree, node, false);
+ break;
+ case DNS_RBT_NSEC_HAS_NSEC:
+ /*
+ * Though this may be wasteful, it has to be done before
+ * node is deleted.
+ */
+ name = dns_fixedname_initname(&fname);
+ dns_rbt_fullnamefromnode(node, name);
+ /*
+ * Delete the corresponding node from the auxiliary NSEC
+ * tree before deleting from the main tree.
+ */
+ nsecnode = NULL;
+ result = dns_rbt_findnode(rbtdb->nsec, name, NULL, &nsecnode,
+ NULL, DNS_RBTFIND_EMPTYDATA, NULL,
+ NULL);
+ if (result != ISC_R_SUCCESS) {
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE,
+ DNS_LOGMODULE_CACHE, ISC_LOG_WARNING,
+ "delete_node: "
+ "dns_rbt_findnode(nsec): %s",
+ isc_result_totext(result));
+ } else {
+ result = dns_rbt_deletenode(rbtdb->nsec, nsecnode,
+ false);
+ if (result != ISC_R_SUCCESS) {
+ isc_log_write(
+ dns_lctx, DNS_LOGCATEGORY_DATABASE,
+ DNS_LOGMODULE_CACHE, ISC_LOG_WARNING,
+ "delete_node(): "
+ "dns_rbt_deletenode(nsecnode): %s",
+ isc_result_totext(result));
+ }
+ }
+ result = dns_rbt_deletenode(rbtdb->tree, node, false);
+ break;
+ case DNS_RBT_NSEC_NSEC:
+ result = dns_rbt_deletenode(rbtdb->nsec, node, false);
+ break;
+ case DNS_RBT_NSEC_NSEC3:
+ result = dns_rbt_deletenode(rbtdb->nsec3, node, false);
+ break;
+ }
+ if (result != ISC_R_SUCCESS) {
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE,
+ DNS_LOGMODULE_CACHE, ISC_LOG_WARNING,
+ "delete_node(): "
+ "dns_rbt_deletenode: %s",
+ isc_result_totext(result));
+ }
+}
+
+/*
+ * Caller must be holding the node lock.
+ */
+static void
+new_reference(dns_rbtdb_t *rbtdb, dns_rbtnode_t *node,
+ isc_rwlocktype_t locktype) {
+ if (locktype == isc_rwlocktype_write && ISC_LINK_LINKED(node, deadlink))
+ {
+ ISC_LIST_UNLINK(rbtdb->deadnodes[node->locknum], node,
+ deadlink);
+ }
+ if (isc_refcount_increment0(&node->references) == 0) {
+ /* this is the first reference to the node */
+ isc_refcount_increment0(
+ &rbtdb->node_locks[node->locknum].references);
+ }
+}
+
+/*%
+ * The tree lock must be held for the result to be valid.
+ */
+static bool
+is_leaf(dns_rbtnode_t *node) {
+ return (node->parent != NULL && node->parent->down == node &&
+ node->left == NULL && node->right == NULL);
+}
+
+static void
+send_to_prune_tree(dns_rbtdb_t *rbtdb, dns_rbtnode_t *node,
+ isc_rwlocktype_t locktype) {
+ isc_event_t *ev;
+ dns_db_t *db;
+
+ ev = isc_event_allocate(rbtdb->common.mctx, NULL, DNS_EVENT_RBTPRUNE,
+ prune_tree, node, sizeof(isc_event_t));
+ new_reference(rbtdb, node, locktype);
+ db = NULL;
+ attach((dns_db_t *)rbtdb, &db);
+ ev->ev_sender = db;
+ isc_task_send(rbtdb->task, &ev);
+}
+
+/*%
+ * Clean up dead nodes. These are nodes which have no references, and
+ * have no data. They are dead but we could not or chose not to delete
+ * them when we deleted all the data at that node because we did not want
+ * to wait for the tree write lock.
+ *
+ * The caller must hold a tree write lock and bucketnum'th node (write) lock.
+ */
+static void
+cleanup_dead_nodes(dns_rbtdb_t *rbtdb, int bucketnum) {
+ dns_rbtnode_t *node;
+ int count = 10; /* XXXJT: should be adjustable */
+
+ node = ISC_LIST_HEAD(rbtdb->deadnodes[bucketnum]);
+ while (node != NULL && count > 0) {
+ ISC_LIST_UNLINK(rbtdb->deadnodes[bucketnum], node, deadlink);
+
+ /*
+ * We might have reactivated this node without a tree write
+ * lock, so we couldn't remove this node from deadnodes then
+ * and we have to do it now.
+ */
+ if (isc_refcount_current(&node->references) != 0 ||
+ node->data != NULL)
+ {
+ node = ISC_LIST_HEAD(rbtdb->deadnodes[bucketnum]);
+ count--;
+ continue;
+ }
+
+ if (is_leaf(node) && rbtdb->task != NULL) {
+ send_to_prune_tree(rbtdb, node, isc_rwlocktype_write);
+ } else if (node->down == NULL && node->data == NULL) {
+ /*
+ * Not a interior node and not needing to be
+ * reactivated.
+ */
+ delete_node(rbtdb, node);
+ } else if (node->data == NULL) {
+ /*
+ * A interior node without data. Leave linked to
+ * to be cleaned up when node->down becomes NULL.
+ */
+ ISC_LIST_APPEND(rbtdb->deadnodes[bucketnum], node,
+ deadlink);
+ }
+ node = ISC_LIST_HEAD(rbtdb->deadnodes[bucketnum]);
+ count--;
+ }
+}
+
+/*
+ * This function is assumed to be called when a node is newly referenced
+ * and can be in the deadnode list. In that case the node must be retrieved
+ * from the list because it is going to be used. In addition, if the caller
+ * happens to hold a write lock on the tree, it's a good chance to purge dead
+ * nodes.
+ * Note: while a new reference is gained in multiple places, there are only very
+ * few cases where the node can be in the deadnode list (only empty nodes can
+ * have been added to the list).
+ */
+static void
+reactivate_node(dns_rbtdb_t *rbtdb, dns_rbtnode_t *node,
+ isc_rwlocktype_t treelocktype) {
+ isc_rwlocktype_t locktype = isc_rwlocktype_read;
+ nodelock_t *nodelock = &rbtdb->node_locks[node->locknum].lock;
+ bool maybe_cleanup = false;
+
+ POST(locktype);
+
+ NODE_LOCK(nodelock, locktype);
+
+ /*
+ * Check if we can possibly cleanup the dead node. If so, upgrade
+ * the node lock below to perform the cleanup.
+ */
+ if (!ISC_LIST_EMPTY(rbtdb->deadnodes[node->locknum]) &&
+ treelocktype == isc_rwlocktype_write)
+ {
+ maybe_cleanup = true;
+ }
+
+ if (ISC_LINK_LINKED(node, deadlink) || maybe_cleanup) {
+ /*
+ * Upgrade the lock and test if we still need to unlink.
+ */
+ NODE_UNLOCK(nodelock, locktype);
+ locktype = isc_rwlocktype_write;
+ POST(locktype);
+ NODE_LOCK(nodelock, locktype);
+ if (ISC_LINK_LINKED(node, deadlink)) {
+ ISC_LIST_UNLINK(rbtdb->deadnodes[node->locknum], node,
+ deadlink);
+ }
+ if (maybe_cleanup) {
+ cleanup_dead_nodes(rbtdb, node->locknum);
+ }
+ }
+
+ new_reference(rbtdb, node, locktype);
+
+ NODE_UNLOCK(nodelock, locktype);
+}
+
+/*
+ * Caller must be holding the node lock; either the "strong", read or write
+ * lock. Note that the lock must be held even when node references are
+ * atomically modified; in that case the decrement operation itself does not
+ * have to be protected, but we must avoid a race condition where multiple
+ * threads are decreasing the reference to zero simultaneously and at least
+ * one of them is going to free the node.
+ *
+ * This function returns true if and only if the node reference decreases
+ * to zero.
+ *
+ * NOTE: Decrementing the reference count of a node to zero does not mean it
+ * will be immediately freed.
+ */
+static bool
+decrement_reference(dns_rbtdb_t *rbtdb, dns_rbtnode_t *node,
+ rbtdb_serial_t least_serial, isc_rwlocktype_t nlock,
+ isc_rwlocktype_t tlock, bool pruning) {
+ isc_result_t result;
+ bool write_locked;
+ bool locked = tlock != isc_rwlocktype_none;
+ rbtdb_nodelock_t *nodelock;
+ int bucket = node->locknum;
+ bool no_reference = true;
+ uint_fast32_t refs;
+
+ nodelock = &rbtdb->node_locks[bucket];
+
+#define KEEP_NODE(n, r, l) \
+ ((n)->data != NULL || ((l) && (n)->down != NULL) || \
+ (n) == (r)->origin_node || (n) == (r)->nsec3_origin_node)
+
+ /* Handle easy and typical case first. */
+ if (!node->dirty && KEEP_NODE(node, rbtdb, locked)) {
+ if (isc_refcount_decrement(&node->references) == 1) {
+ refs = isc_refcount_decrement(&nodelock->references);
+ INSIST(refs > 0);
+ return (true);
+ } else {
+ return (false);
+ }
+ }
+
+ /* Upgrade the lock? */
+ if (nlock == isc_rwlocktype_read) {
+ NODE_UNLOCK(&nodelock->lock, isc_rwlocktype_read);
+ NODE_LOCK(&nodelock->lock, isc_rwlocktype_write);
+ }
+
+ if (isc_refcount_decrement(&node->references) > 1) {
+ /* Restore the lock? */
+ if (nlock == isc_rwlocktype_read) {
+ NODE_DOWNGRADE(&nodelock->lock);
+ }
+ return (false);
+ }
+
+ if (node->dirty) {
+ if (IS_CACHE(rbtdb)) {
+ clean_cache_node(rbtdb, node);
+ } else {
+ if (least_serial == 0) {
+ /*
+ * Caller doesn't know the least serial.
+ * Get it.
+ */
+ RBTDB_LOCK(&rbtdb->lock, isc_rwlocktype_read);
+ least_serial = rbtdb->least_serial;
+ RBTDB_UNLOCK(&rbtdb->lock, isc_rwlocktype_read);
+ }
+ clean_zone_node(rbtdb, node, least_serial);
+ }
+ }
+
+ /*
+ * Attempt to switch to a write lock on the tree. If this fails,
+ * we will add this node to a linked list of nodes in this locking
+ * bucket which we will free later.
+ */
+ if (tlock != isc_rwlocktype_write) {
+ /*
+ * Locking hierarchy notwithstanding, we don't need to free
+ * the node lock before acquiring the tree write lock because
+ * we only do a trylock.
+ */
+ if (tlock == isc_rwlocktype_read) {
+ result = isc_rwlock_tryupgrade(&rbtdb->tree_lock);
+ } else {
+ result = isc_rwlock_trylock(&rbtdb->tree_lock,
+ isc_rwlocktype_write);
+ }
+ RUNTIME_CHECK(result == ISC_R_SUCCESS ||
+ result == ISC_R_LOCKBUSY);
+
+ write_locked = (result == ISC_R_SUCCESS);
+ } else {
+ write_locked = true;
+ }
+
+ refs = isc_refcount_decrement(&nodelock->references);
+ INSIST(refs > 0);
+
+ if (KEEP_NODE(node, rbtdb, locked || write_locked)) {
+ goto restore_locks;
+ }
+
+#undef KEEP_NODE
+
+ if (write_locked) {
+ /*
+ * We can now delete the node.
+ */
+
+ /*
+ * If this node is the only one in the level it's in, deleting
+ * this node may recursively make its parent the only node in
+ * the parent level; if so, and if no one is currently using
+ * the parent node, this is almost the only opportunity to
+ * clean it up. But the recursive cleanup is not that trivial
+ * since the child and parent may be in different lock buckets,
+ * which would cause a lock order reversal problem. To avoid
+ * the trouble, we'll dispatch a separate event for batch
+ * cleaning. We need to check whether we're deleting the node
+ * as a result of pruning to avoid infinite dispatching.
+ * Note: pruning happens only when a task has been set for the
+ * rbtdb. If the user of the rbtdb chooses not to set a task,
+ * it's their responsibility to purge stale leaves (e.g. by
+ * periodic walk-through).
+ */
+ if (!pruning && is_leaf(node) && rbtdb->task != NULL) {
+ send_to_prune_tree(rbtdb, node, isc_rwlocktype_write);
+ no_reference = false;
+ } else {
+ delete_node(rbtdb, node);
+ }
+ } else {
+ INSIST(node->data == NULL);
+ if (!ISC_LINK_LINKED(node, deadlink)) {
+ ISC_LIST_APPEND(rbtdb->deadnodes[bucket], node,
+ deadlink);
+ }
+ }
+
+restore_locks:
+ /* Restore the lock? */
+ if (nlock == isc_rwlocktype_read) {
+ NODE_DOWNGRADE(&nodelock->lock);
+ }
+
+ /*
+ * Relock a read lock, or unlock the write lock if no lock was held.
+ */
+ if (tlock == isc_rwlocktype_none) {
+ if (write_locked) {
+ RWUNLOCK(&rbtdb->tree_lock, isc_rwlocktype_write);
+ }
+ }
+
+ if (tlock == isc_rwlocktype_read) {
+ if (write_locked) {
+ isc_rwlock_downgrade(&rbtdb->tree_lock);
+ }
+ }
+
+ return (no_reference);
+}
+
+/*
+ * Prune the tree by recursively cleaning-up single leaves. In the worst
+ * case, the number of iteration is the number of tree levels, which is at
+ * most the maximum number of domain name labels, i.e, 127. In practice, this
+ * should be much smaller (only a few times), and even the worst case would be
+ * acceptable for a single event.
+ */
+static void
+prune_tree(isc_task_t *task, isc_event_t *event) {
+ dns_rbtdb_t *rbtdb = event->ev_sender;
+ dns_rbtnode_t *node = event->ev_arg;
+ dns_rbtnode_t *parent;
+ unsigned int locknum;
+
+ UNUSED(task);
+
+ isc_event_free(&event);
+
+ RWLOCK(&rbtdb->tree_lock, isc_rwlocktype_write);
+ locknum = node->locknum;
+ NODE_LOCK(&rbtdb->node_locks[locknum].lock, isc_rwlocktype_write);
+ do {
+ parent = node->parent;
+ decrement_reference(rbtdb, node, 0, isc_rwlocktype_write,
+ isc_rwlocktype_write, true);
+
+ if (parent != NULL && parent->down == NULL) {
+ /*
+ * node was the only down child of the parent and has
+ * just been removed. We'll then need to examine the
+ * parent. Keep the lock if possible; otherwise,
+ * release the old lock and acquire one for the parent.
+ */
+ if (parent->locknum != locknum) {
+ NODE_UNLOCK(&rbtdb->node_locks[locknum].lock,
+ isc_rwlocktype_write);
+ locknum = parent->locknum;
+ NODE_LOCK(&rbtdb->node_locks[locknum].lock,
+ isc_rwlocktype_write);
+ }
+
+ /*
+ * We need to gain a reference to the node before
+ * decrementing it in the next iteration.
+ */
+ if (ISC_LINK_LINKED(parent, deadlink)) {
+ ISC_LIST_UNLINK(rbtdb->deadnodes[locknum],
+ parent, deadlink);
+ }
+ new_reference(rbtdb, parent, isc_rwlocktype_write);
+ } else {
+ parent = NULL;
+ }
+
+ node = parent;
+ } while (node != NULL);
+ NODE_UNLOCK(&rbtdb->node_locks[locknum].lock, isc_rwlocktype_write);
+ RWUNLOCK(&rbtdb->tree_lock, isc_rwlocktype_write);
+
+ detach((dns_db_t **)&rbtdb);
+}
+
+static void
+make_least_version(dns_rbtdb_t *rbtdb, rbtdb_version_t *version,
+ rbtdb_changedlist_t *cleanup_list) {
+ /*
+ * Caller must be holding the database lock.
+ */
+
+ rbtdb->least_serial = version->serial;
+ *cleanup_list = version->changed_list;
+ ISC_LIST_INIT(version->changed_list);
+}
+
+static void
+cleanup_nondirty(rbtdb_version_t *version, rbtdb_changedlist_t *cleanup_list) {
+ rbtdb_changed_t *changed, *next_changed;
+
+ /*
+ * If the changed record is dirty, then
+ * an update created multiple versions of
+ * a given rdataset. We keep this list
+ * until we're the least open version, at
+ * which point it's safe to get rid of any
+ * older versions.
+ *
+ * If the changed record isn't dirty, then
+ * we don't need it anymore since we're
+ * committing and not rolling back.
+ *
+ * The caller must be holding the database lock.
+ */
+ for (changed = HEAD(version->changed_list); changed != NULL;
+ changed = next_changed)
+ {
+ next_changed = NEXT(changed, link);
+ if (!changed->dirty) {
+ UNLINK(version->changed_list, changed, link);
+ APPEND(*cleanup_list, changed, link);
+ }
+ }
+}
+
+static void
+iszonesecure(dns_db_t *db, rbtdb_version_t *version, dns_dbnode_t *origin) {
+ dns_rdataset_t keyset;
+ dns_rdataset_t nsecset, signsecset;
+ bool haszonekey = false;
+ bool hasnsec = false;
+ isc_result_t result;
+
+ dns_rdataset_init(&keyset);
+ result = dns_db_findrdataset(db, origin, version, dns_rdatatype_dnskey,
+ 0, 0, &keyset, NULL);
+ if (result == ISC_R_SUCCESS) {
+ result = dns_rdataset_first(&keyset);
+ while (result == ISC_R_SUCCESS) {
+ dns_rdata_t keyrdata = DNS_RDATA_INIT;
+ dns_rdataset_current(&keyset, &keyrdata);
+ if (dns_zonekey_iszonekey(&keyrdata)) {
+ haszonekey = true;
+ break;
+ }
+ result = dns_rdataset_next(&keyset);
+ }
+ dns_rdataset_disassociate(&keyset);
+ }
+ if (!haszonekey) {
+ version->secure = dns_db_insecure;
+ version->havensec3 = false;
+ return;
+ }
+
+ dns_rdataset_init(&nsecset);
+ dns_rdataset_init(&signsecset);
+ result = dns_db_findrdataset(db, origin, version, dns_rdatatype_nsec, 0,
+ 0, &nsecset, &signsecset);
+ if (result == ISC_R_SUCCESS) {
+ if (dns_rdataset_isassociated(&signsecset)) {
+ hasnsec = true;
+ dns_rdataset_disassociate(&signsecset);
+ }
+ dns_rdataset_disassociate(&nsecset);
+ }
+
+ setnsec3parameters(db, version);
+
+ /*
+ * Do we have a valid NSEC/NSEC3 chain?
+ */
+ if (version->havensec3 || hasnsec) {
+ version->secure = dns_db_secure;
+ } else {
+ version->secure = dns_db_insecure;
+ }
+}
+
+/*%<
+ * Walk the origin node looking for NSEC3PARAM records.
+ * Cache the nsec3 parameters.
+ */
+static void
+setnsec3parameters(dns_db_t *db, rbtdb_version_t *version) {
+ dns_rbtnode_t *node;
+ dns_rdata_nsec3param_t nsec3param;
+ dns_rdata_t rdata = DNS_RDATA_INIT;
+ isc_region_t region;
+ isc_result_t result;
+ rdatasetheader_t *header, *header_next;
+ unsigned char *raw; /* RDATASLAB */
+ unsigned int count, length;
+ dns_rbtdb_t *rbtdb = (dns_rbtdb_t *)db;
+
+ RWLOCK(&rbtdb->tree_lock, isc_rwlocktype_read);
+ version->havensec3 = false;
+ node = rbtdb->origin_node;
+ NODE_LOCK(&(rbtdb->node_locks[node->locknum].lock),
+ isc_rwlocktype_read);
+ for (header = node->data; header != NULL; header = header_next) {
+ header_next = header->next;
+ do {
+ if (header->serial <= version->serial &&
+ !IGNORE(header))
+ {
+ if (NONEXISTENT(header)) {
+ header = NULL;
+ }
+ break;
+ } else {
+ header = header->down;
+ }
+ } while (header != NULL);
+
+ if (header != NULL &&
+ (header->type == dns_rdatatype_nsec3param))
+ {
+ /*
+ * Find A NSEC3PARAM with a supported algorithm.
+ */
+ raw = (unsigned char *)header + sizeof(*header);
+ count = raw[0] * 256 + raw[1]; /* count */
+ raw += DNS_RDATASET_COUNT + DNS_RDATASET_LENGTH;
+ while (count-- > 0U) {
+ length = raw[0] * 256 + raw[1];
+ raw += DNS_RDATASET_ORDER + DNS_RDATASET_LENGTH;
+ region.base = raw;
+ region.length = length;
+ raw += length;
+ dns_rdata_fromregion(
+ &rdata, rbtdb->common.rdclass,
+ dns_rdatatype_nsec3param, &region);
+ result = dns_rdata_tostruct(&rdata, &nsec3param,
+ NULL);
+ INSIST(result == ISC_R_SUCCESS);
+ dns_rdata_reset(&rdata);
+
+ if (nsec3param.hash != DNS_NSEC3_UNKNOWNALG &&
+ !dns_nsec3_supportedhash(nsec3param.hash))
+ {
+ continue;
+ }
+
+ if (nsec3param.flags != 0) {
+ continue;
+ }
+
+ memmove(version->salt, nsec3param.salt,
+ nsec3param.salt_length);
+ version->hash = nsec3param.hash;
+ version->salt_length = nsec3param.salt_length;
+ version->iterations = nsec3param.iterations;
+ version->flags = nsec3param.flags;
+ version->havensec3 = true;
+ /*
+ * Look for a better algorithm than the
+ * unknown test algorithm.
+ */
+ if (nsec3param.hash != DNS_NSEC3_UNKNOWNALG) {
+ goto unlock;
+ }
+ }
+ }
+ }
+unlock:
+ NODE_UNLOCK(&(rbtdb->node_locks[node->locknum].lock),
+ isc_rwlocktype_read);
+ RWUNLOCK(&rbtdb->tree_lock, isc_rwlocktype_read);
+}
+
+static void
+cleanup_dead_nodes_callback(isc_task_t *task, isc_event_t *event) {
+ dns_rbtdb_t *rbtdb = event->ev_arg;
+ bool again = false;
+ unsigned int locknum;
+
+ RWLOCK(&rbtdb->tree_lock, isc_rwlocktype_write);
+ for (locknum = 0; locknum < rbtdb->node_lock_count; locknum++) {
+ NODE_LOCK(&rbtdb->node_locks[locknum].lock,
+ isc_rwlocktype_write);
+ cleanup_dead_nodes(rbtdb, locknum);
+ if (ISC_LIST_HEAD(rbtdb->deadnodes[locknum]) != NULL) {
+ again = true;
+ }
+ NODE_UNLOCK(&rbtdb->node_locks[locknum].lock,
+ isc_rwlocktype_write);
+ }
+ RWUNLOCK(&rbtdb->tree_lock, isc_rwlocktype_write);
+ if (again) {
+ isc_task_send(task, &event);
+ } else {
+ isc_event_free(&event);
+ if (isc_refcount_decrement(&rbtdb->references) == 1) {
+ (void)isc_refcount_current(&rbtdb->references);
+ maybe_free_rbtdb(rbtdb);
+ }
+ }
+}
+
+static void
+closeversion(dns_db_t *db, dns_dbversion_t **versionp, bool commit) {
+ dns_rbtdb_t *rbtdb = (dns_rbtdb_t *)db;
+ rbtdb_version_t *version, *cleanup_version, *least_greater;
+ bool rollback = false;
+ rbtdb_changedlist_t cleanup_list;
+ rdatasetheaderlist_t resigned_list;
+ rbtdb_changed_t *changed, *next_changed;
+ rbtdb_serial_t serial, least_serial;
+ dns_rbtnode_t *rbtnode;
+ rdatasetheader_t *header;
+
+ REQUIRE(VALID_RBTDB(rbtdb));
+ version = (rbtdb_version_t *)*versionp;
+ INSIST(version->rbtdb == rbtdb);
+
+ cleanup_version = NULL;
+ ISC_LIST_INIT(cleanup_list);
+ ISC_LIST_INIT(resigned_list);
+
+ if (isc_refcount_decrement(&version->references) > 1) {
+ /* typical and easy case first */
+ if (commit) {
+ RBTDB_LOCK(&rbtdb->lock, isc_rwlocktype_read);
+ INSIST(!version->writer);
+ RBTDB_UNLOCK(&rbtdb->lock, isc_rwlocktype_read);
+ }
+ goto end;
+ }
+
+ /*
+ * Update the zone's secure status in version before making
+ * it the current version.
+ */
+ if (version->writer && commit && !IS_CACHE(rbtdb)) {
+ iszonesecure(db, version, rbtdb->origin_node);
+ }
+
+ RBTDB_LOCK(&rbtdb->lock, isc_rwlocktype_write);
+ serial = version->serial;
+ if (version->writer) {
+ if (commit) {
+ unsigned cur_ref;
+ rbtdb_version_t *cur_version;
+
+ INSIST(version->commit_ok);
+ INSIST(version == rbtdb->future_version);
+ /*
+ * The current version is going to be replaced.
+ * Release the (likely last) reference to it from the
+ * DB itself and unlink it from the open list.
+ */
+ cur_version = rbtdb->current_version;
+ cur_ref = isc_refcount_decrement(
+ &cur_version->references);
+ if (cur_ref == 1) {
+ (void)isc_refcount_current(
+ &cur_version->references);
+ if (cur_version->serial == rbtdb->least_serial)
+ {
+ INSIST(EMPTY(
+ cur_version->changed_list));
+ }
+ UNLINK(rbtdb->open_versions, cur_version, link);
+ }
+ if (EMPTY(rbtdb->open_versions)) {
+ /*
+ * We're going to become the least open
+ * version.
+ */
+ make_least_version(rbtdb, version,
+ &cleanup_list);
+ } else {
+ /*
+ * Some other open version is the
+ * least version. We can't cleanup
+ * records that were changed in this
+ * version because the older versions
+ * may still be in use by an open
+ * version.
+ *
+ * We can, however, discard the
+ * changed records for things that
+ * we've added that didn't exist in
+ * prior versions.
+ */
+ cleanup_nondirty(version, &cleanup_list);
+ }
+ /*
+ * If the (soon to be former) current version
+ * isn't being used by anyone, we can clean
+ * it up.
+ */
+ if (cur_ref == 1) {
+ cleanup_version = cur_version;
+ APPENDLIST(version->changed_list,
+ cleanup_version->changed_list, link);
+ }
+ /*
+ * Become the current version.
+ */
+ version->writer = false;
+ rbtdb->current_version = version;
+ rbtdb->current_serial = version->serial;
+ rbtdb->future_version = NULL;
+
+ /*
+ * Keep the current version in the open list, and
+ * gain a reference for the DB itself (see the DB
+ * creation function below). This must be the only
+ * case where we need to increment the counter from
+ * zero and need to use isc_refcount_increment0().
+ */
+ INSIST(isc_refcount_increment0(&version->references) ==
+ 0);
+ PREPEND(rbtdb->open_versions, rbtdb->current_version,
+ link);
+ resigned_list = version->resigned_list;
+ ISC_LIST_INIT(version->resigned_list);
+ } else {
+ /*
+ * We're rolling back this transaction.
+ */
+ cleanup_list = version->changed_list;
+ ISC_LIST_INIT(version->changed_list);
+ resigned_list = version->resigned_list;
+ ISC_LIST_INIT(version->resigned_list);
+ rollback = true;
+ cleanup_version = version;
+ rbtdb->future_version = NULL;
+ }
+ } else {
+ if (version != rbtdb->current_version) {
+ /*
+ * There are no external or internal references
+ * to this version and it can be cleaned up.
+ */
+ cleanup_version = version;
+
+ /*
+ * Find the version with the least serial
+ * number greater than ours.
+ */
+ least_greater = PREV(version, link);
+ if (least_greater == NULL) {
+ least_greater = rbtdb->current_version;
+ }
+
+ INSIST(version->serial < least_greater->serial);
+ /*
+ * Is this the least open version?
+ */
+ if (version->serial == rbtdb->least_serial) {
+ /*
+ * Yes. Install the new least open
+ * version.
+ */
+ make_least_version(rbtdb, least_greater,
+ &cleanup_list);
+ } else {
+ /*
+ * Add any unexecuted cleanups to
+ * those of the least greater version.
+ */
+ APPENDLIST(least_greater->changed_list,
+ version->changed_list, link);
+ }
+ } else if (version->serial == rbtdb->least_serial) {
+ INSIST(EMPTY(version->changed_list));
+ }
+ UNLINK(rbtdb->open_versions, version, link);
+ }
+ least_serial = rbtdb->least_serial;
+ RBTDB_UNLOCK(&rbtdb->lock, isc_rwlocktype_write);
+
+ if (cleanup_version != NULL) {
+ INSIST(EMPTY(cleanup_version->changed_list));
+ free_gluetable(cleanup_version);
+ isc_rwlock_destroy(&cleanup_version->glue_rwlock);
+ isc_rwlock_destroy(&cleanup_version->rwlock);
+ isc_mem_put(rbtdb->common.mctx, cleanup_version,
+ sizeof(*cleanup_version));
+ }
+
+ /*
+ * Commit/rollback re-signed headers.
+ */
+ for (header = HEAD(resigned_list); header != NULL;
+ header = HEAD(resigned_list))
+ {
+ nodelock_t *lock;
+
+ ISC_LIST_UNLINK(resigned_list, header, link);
+
+ lock = &rbtdb->node_locks[header->node->locknum].lock;
+ NODE_LOCK(lock, isc_rwlocktype_write);
+ if (rollback && !IGNORE(header)) {
+ resign_insert(rbtdb, header->node->locknum, header);
+ }
+ decrement_reference(rbtdb, header->node, least_serial,
+ isc_rwlocktype_write, isc_rwlocktype_none,
+ false);
+ NODE_UNLOCK(lock, isc_rwlocktype_write);
+ }
+
+ if (!EMPTY(cleanup_list)) {
+ isc_event_t *event = NULL;
+ isc_rwlocktype_t tlock = isc_rwlocktype_none;
+
+ if (rbtdb->task != NULL) {
+ event = isc_event_allocate(rbtdb->common.mctx, NULL,
+ DNS_EVENT_RBTDEADNODES,
+ cleanup_dead_nodes_callback,
+ rbtdb, sizeof(isc_event_t));
+ }
+ if (event == NULL) {
+ /*
+ * We acquire a tree write lock here in order to make
+ * sure that stale nodes will be removed in
+ * decrement_reference(). If we didn't have the lock,
+ * those nodes could miss the chance to be removed
+ * until the server stops. The write lock is
+ * expensive, but this event should be rare enough
+ * to justify the cost.
+ */
+ RWLOCK(&rbtdb->tree_lock, isc_rwlocktype_write);
+ tlock = isc_rwlocktype_write;
+ }
+
+ for (changed = HEAD(cleanup_list); changed != NULL;
+ changed = next_changed)
+ {
+ nodelock_t *lock;
+
+ next_changed = NEXT(changed, link);
+ rbtnode = changed->node;
+ lock = &rbtdb->node_locks[rbtnode->locknum].lock;
+
+ NODE_LOCK(lock, isc_rwlocktype_write);
+ /*
+ * This is a good opportunity to purge any dead nodes,
+ * so use it.
+ */
+ if (event == NULL) {
+ cleanup_dead_nodes(rbtdb, rbtnode->locknum);
+ }
+
+ if (rollback) {
+ rollback_node(rbtnode, serial);
+ }
+ decrement_reference(rbtdb, rbtnode, least_serial,
+ isc_rwlocktype_write, tlock, false);
+
+ NODE_UNLOCK(lock, isc_rwlocktype_write);
+
+ isc_mem_put(rbtdb->common.mctx, changed,
+ sizeof(*changed));
+ }
+ if (event != NULL) {
+ isc_refcount_increment(&rbtdb->references);
+ isc_task_send(rbtdb->task, &event);
+ } else {
+ RWUNLOCK(&rbtdb->tree_lock, isc_rwlocktype_write);
+ }
+ }
+
+end:
+ *versionp = NULL;
+}
+
+/*
+ * Add the necessary magic for the wildcard name 'name'
+ * to be found in 'rbtdb'.
+ *
+ * In order for wildcard matching to work correctly in
+ * zone_find(), we must ensure that a node for the wildcarding
+ * level exists in the database, and has its 'find_callback'
+ * and 'wild' bits set.
+ *
+ * E.g. if the wildcard name is "*.sub.example." then we
+ * must ensure that "sub.example." exists and is marked as
+ * a wildcard level.
+ *
+ * tree_lock(write) must be held.
+ */
+static isc_result_t
+add_wildcard_magic(dns_rbtdb_t *rbtdb, const dns_name_t *name, bool lock) {
+ isc_result_t result;
+ dns_name_t foundname;
+ dns_offsets_t offsets;
+ unsigned int n;
+ dns_rbtnode_t *node = NULL;
+
+ dns_name_init(&foundname, offsets);
+ n = dns_name_countlabels(name);
+ INSIST(n >= 2);
+ n--;
+ dns_name_getlabelsequence(name, 1, n, &foundname);
+ result = dns_rbt_addnode(rbtdb->tree, &foundname, &node);
+ if (result != ISC_R_SUCCESS && result != ISC_R_EXISTS) {
+ return (result);
+ }
+ if (result == ISC_R_SUCCESS) {
+ node->nsec = DNS_RBT_NSEC_NORMAL;
+ }
+ node->find_callback = 1;
+ if (lock) {
+ NODE_LOCK(&rbtdb->node_locks[node->locknum].lock,
+ isc_rwlocktype_write);
+ }
+ node->wild = 1;
+ if (lock) {
+ NODE_UNLOCK(&rbtdb->node_locks[node->locknum].lock,
+ isc_rwlocktype_write);
+ }
+ return (ISC_R_SUCCESS);
+}
+
+/*
+ * tree_lock(write) must be held.
+ */
+static isc_result_t
+add_empty_wildcards(dns_rbtdb_t *rbtdb, const dns_name_t *name, bool lock) {
+ isc_result_t result;
+ dns_name_t foundname;
+ dns_offsets_t offsets;
+ unsigned int n, l, i;
+
+ dns_name_init(&foundname, offsets);
+ n = dns_name_countlabels(name);
+ l = dns_name_countlabels(&rbtdb->common.origin);
+ i = l + 1;
+ while (i < n) {
+ dns_rbtnode_t *node = NULL; /* dummy */
+ dns_name_getlabelsequence(name, n - i, i, &foundname);
+ if (dns_name_iswildcard(&foundname)) {
+ result = add_wildcard_magic(rbtdb, &foundname, lock);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+ result = dns_rbt_addnode(rbtdb->tree, &foundname,
+ &node);
+ if (result != ISC_R_SUCCESS && result != ISC_R_EXISTS) {
+ return (result);
+ }
+ if (result == ISC_R_SUCCESS) {
+ node->nsec = DNS_RBT_NSEC_NORMAL;
+ }
+ }
+ i++;
+ }
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+findnodeintree(dns_rbtdb_t *rbtdb, dns_rbt_t *tree, const dns_name_t *name,
+ bool create, dns_dbnode_t **nodep) {
+ dns_rbtnode_t *node = NULL;
+ dns_name_t nodename;
+ isc_result_t result;
+ isc_rwlocktype_t locktype = isc_rwlocktype_read;
+
+ INSIST(tree == rbtdb->tree || tree == rbtdb->nsec3);
+
+ dns_name_init(&nodename, NULL);
+ RWLOCK(&rbtdb->tree_lock, locktype);
+ result = dns_rbt_findnode(tree, name, NULL, &node, NULL,
+ DNS_RBTFIND_EMPTYDATA, NULL, NULL);
+ if (result != ISC_R_SUCCESS) {
+ RWUNLOCK(&rbtdb->tree_lock, locktype);
+ if (!create) {
+ if (result == DNS_R_PARTIALMATCH) {
+ result = ISC_R_NOTFOUND;
+ }
+ return (result);
+ }
+ /*
+ * It would be nice to try to upgrade the lock instead of
+ * unlocking then relocking.
+ */
+ locktype = isc_rwlocktype_write;
+ RWLOCK(&rbtdb->tree_lock, locktype);
+ node = NULL;
+ result = dns_rbt_addnode(tree, name, &node);
+ if (result == ISC_R_SUCCESS) {
+ dns_rbt_namefromnode(node, &nodename);
+ node->locknum = node->hashval % rbtdb->node_lock_count;
+ if (tree == rbtdb->tree) {
+ add_empty_wildcards(rbtdb, name, true);
+
+ if (dns_name_iswildcard(name)) {
+ result = add_wildcard_magic(rbtdb, name,
+ true);
+ if (result != ISC_R_SUCCESS) {
+ RWUNLOCK(&rbtdb->tree_lock,
+ locktype);
+ return (result);
+ }
+ }
+ }
+ if (tree == rbtdb->nsec3) {
+ node->nsec = DNS_RBT_NSEC_NSEC3;
+ }
+ } else if (result != ISC_R_EXISTS) {
+ RWUNLOCK(&rbtdb->tree_lock, locktype);
+ return (result);
+ }
+ }
+
+ if (tree == rbtdb->nsec3) {
+ INSIST(node->nsec == DNS_RBT_NSEC_NSEC3);
+ }
+
+ reactivate_node(rbtdb, node, locktype);
+
+ RWUNLOCK(&rbtdb->tree_lock, locktype);
+
+ *nodep = (dns_dbnode_t *)node;
+
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+findnode(dns_db_t *db, const dns_name_t *name, bool create,
+ dns_dbnode_t **nodep) {
+ dns_rbtdb_t *rbtdb = (dns_rbtdb_t *)db;
+
+ REQUIRE(VALID_RBTDB(rbtdb));
+
+ return (findnodeintree(rbtdb, rbtdb->tree, name, create, nodep));
+}
+
+static isc_result_t
+findnsec3node(dns_db_t *db, const dns_name_t *name, bool create,
+ dns_dbnode_t **nodep) {
+ dns_rbtdb_t *rbtdb = (dns_rbtdb_t *)db;
+
+ REQUIRE(VALID_RBTDB(rbtdb));
+
+ return (findnodeintree(rbtdb, rbtdb->nsec3, name, create, nodep));
+}
+
+static isc_result_t
+zone_zonecut_callback(dns_rbtnode_t *node, dns_name_t *name, void *arg) {
+ rbtdb_search_t *search = arg;
+ rdatasetheader_t *header, *header_next;
+ rdatasetheader_t *dname_header, *sigdname_header, *ns_header;
+ rdatasetheader_t *found;
+ isc_result_t result;
+ dns_rbtnode_t *onode;
+
+ /*
+ * We only want to remember the topmost zone cut, since it's the one
+ * that counts, so we'll just continue if we've already found a
+ * zonecut.
+ */
+ if (search->zonecut != NULL) {
+ return (DNS_R_CONTINUE);
+ }
+
+ found = NULL;
+ result = DNS_R_CONTINUE;
+ onode = search->rbtdb->origin_node;
+
+ NODE_LOCK(&(search->rbtdb->node_locks[node->locknum].lock),
+ isc_rwlocktype_read);
+
+ /*
+ * Look for an NS or DNAME rdataset active in our version.
+ */
+ ns_header = NULL;
+ dname_header = NULL;
+ sigdname_header = NULL;
+ for (header = node->data; header != NULL; header = header_next) {
+ header_next = header->next;
+ if (header->type == dns_rdatatype_ns ||
+ header->type == dns_rdatatype_dname ||
+ header->type == RBTDB_RDATATYPE_SIGDNAME)
+ {
+ do {
+ if (header->serial <= search->serial &&
+ !IGNORE(header))
+ {
+ /*
+ * Is this a "this rdataset doesn't
+ * exist" record?
+ */
+ if (NONEXISTENT(header)) {
+ header = NULL;
+ }
+ break;
+ } else {
+ header = header->down;
+ }
+ } while (header != NULL);
+ if (header != NULL) {
+ if (header->type == dns_rdatatype_dname) {
+ dname_header = header;
+ } else if (header->type ==
+ RBTDB_RDATATYPE_SIGDNAME)
+ {
+ sigdname_header = header;
+ } else if (node != onode ||
+ IS_STUB(search->rbtdb))
+ {
+ /*
+ * We've found an NS rdataset that
+ * isn't at the origin node. We check
+ * that they're not at the origin node,
+ * because otherwise we'd erroneously
+ * treat the zone top as if it were
+ * a delegation.
+ */
+ ns_header = header;
+ }
+ }
+ }
+ }
+
+ /*
+ * Did we find anything?
+ */
+ if (!IS_CACHE(search->rbtdb) && !IS_STUB(search->rbtdb) &&
+ ns_header != NULL)
+ {
+ /*
+ * Note that NS has precedence over DNAME if both exist
+ * in a zone. Otherwise DNAME take precedence over NS.
+ */
+ found = ns_header;
+ search->zonecut_sigrdataset = NULL;
+ } else if (dname_header != NULL) {
+ found = dname_header;
+ search->zonecut_sigrdataset = sigdname_header;
+ } else if (ns_header != NULL) {
+ found = ns_header;
+ search->zonecut_sigrdataset = NULL;
+ }
+
+ if (found != NULL) {
+ /*
+ * We increment the reference count on node to ensure that
+ * search->zonecut_rdataset will still be valid later.
+ */
+ new_reference(search->rbtdb, node, isc_rwlocktype_read);
+ search->zonecut = node;
+ search->zonecut_rdataset = found;
+ search->need_cleanup = true;
+ /*
+ * Since we've found a zonecut, anything beneath it is
+ * glue and is not subject to wildcard matching, so we
+ * may clear search->wild.
+ */
+ search->wild = false;
+ if ((search->options & DNS_DBFIND_GLUEOK) == 0) {
+ /*
+ * If the caller does not want to find glue, then
+ * this is the best answer and the search should
+ * stop now.
+ */
+ result = DNS_R_PARTIALMATCH;
+ } else {
+ dns_name_t *zcname;
+
+ /*
+ * The search will continue beneath the zone cut.
+ * This may or may not be the best match. In case it
+ * is, we need to remember the node name.
+ */
+ zcname = dns_fixedname_name(&search->zonecut_name);
+ dns_name_copy(name, zcname);
+ search->copy_name = true;
+ }
+ } else {
+ /*
+ * There is no zonecut at this node which is active in this
+ * version.
+ *
+ * If this is a "wild" node and the caller hasn't disabled
+ * wildcard matching, remember that we've seen a wild node
+ * in case we need to go searching for wildcard matches
+ * later on.
+ */
+ if (node->wild && (search->options & DNS_DBFIND_NOWILD) == 0) {
+ search->wild = true;
+ }
+ }
+
+ NODE_UNLOCK(&(search->rbtdb->node_locks[node->locknum].lock),
+ isc_rwlocktype_read);
+
+ return (result);
+}
+
+static void
+bind_rdataset(dns_rbtdb_t *rbtdb, dns_rbtnode_t *node, rdatasetheader_t *header,
+ isc_stdtime_t now, isc_rwlocktype_t locktype,
+ dns_rdataset_t *rdataset) {
+ unsigned char *raw; /* RDATASLAB */
+ bool stale = STALE(header);
+ bool ancient = ANCIENT(header);
+
+ /*
+ * Caller must be holding the node reader lock.
+ * XXXJT: technically, we need a writer lock, since we'll increment
+ * the header count below. However, since the actual counter value
+ * doesn't matter, we prioritize performance here. (We may want to
+ * use atomic increment when available).
+ */
+
+ if (rdataset == NULL) {
+ return;
+ }
+
+ new_reference(rbtdb, node, locktype);
+
+ INSIST(rdataset->methods == NULL); /* We must be disassociated. */
+
+ /*
+ * Mark header stale or ancient if the RRset is no longer active.
+ */
+ if (!ACTIVE(header, now)) {
+ dns_ttl_t stale_ttl = header->rdh_ttl +
+ STALE_TTL(header, rbtdb);
+ /*
+ * If this data is in the stale window keep it and if
+ * DNS_DBFIND_STALEOK is not set we tell the caller to
+ * skip this record. We skip the records with ZEROTTL
+ * (these records should not be cached anyway).
+ */
+
+ if (KEEPSTALE(rbtdb) && stale_ttl > now) {
+ stale = true;
+ } else {
+ /*
+ * We are not keeping stale, or it is outside the
+ * stale window. Mark ancient, i.e. ready for cleanup.
+ */
+ ancient = true;
+ }
+ }
+
+ rdataset->methods = &rdataset_methods;
+ rdataset->rdclass = rbtdb->common.rdclass;
+ rdataset->type = RBTDB_RDATATYPE_BASE(header->type);
+ rdataset->covers = RBTDB_RDATATYPE_EXT(header->type);
+ rdataset->ttl = header->rdh_ttl - now;
+ rdataset->trust = header->trust;
+
+ if (NEGATIVE(header)) {
+ rdataset->attributes |= DNS_RDATASETATTR_NEGATIVE;
+ }
+ if (NXDOMAIN(header)) {
+ rdataset->attributes |= DNS_RDATASETATTR_NXDOMAIN;
+ }
+ if (OPTOUT(header)) {
+ rdataset->attributes |= DNS_RDATASETATTR_OPTOUT;
+ }
+ if (PREFETCH(header)) {
+ rdataset->attributes |= DNS_RDATASETATTR_PREFETCH;
+ }
+
+ if (stale && !ancient) {
+ dns_ttl_t stale_ttl = header->rdh_ttl +
+ STALE_TTL(header, rbtdb);
+ if (stale_ttl > now) {
+ rdataset->ttl = stale_ttl - now;
+ } else {
+ rdataset->ttl = 0;
+ }
+ if (STALE_WINDOW(header)) {
+ rdataset->attributes |= DNS_RDATASETATTR_STALE_WINDOW;
+ }
+ rdataset->attributes |= DNS_RDATASETATTR_STALE;
+ } else if (IS_CACHE(rbtdb) && !ACTIVE(header, now)) {
+ rdataset->attributes |= DNS_RDATASETATTR_ANCIENT;
+ rdataset->ttl = header->rdh_ttl;
+ }
+
+ rdataset->private1 = rbtdb;
+ rdataset->private2 = node;
+ raw = (unsigned char *)header + sizeof(*header);
+ rdataset->private3 = raw;
+ rdataset->count = atomic_fetch_add_relaxed(&header->count, 1);
+ if (rdataset->count == UINT32_MAX) {
+ rdataset->count = 0;
+ }
+
+ /*
+ * Reset iterator state.
+ */
+ rdataset->privateuint4 = 0;
+ rdataset->private5 = NULL;
+
+ /*
+ * Add noqname proof.
+ */
+ rdataset->private6 = header->noqname;
+ if (rdataset->private6 != NULL) {
+ rdataset->attributes |= DNS_RDATASETATTR_NOQNAME;
+ }
+ rdataset->private7 = header->closest;
+ if (rdataset->private7 != NULL) {
+ rdataset->attributes |= DNS_RDATASETATTR_CLOSEST;
+ }
+
+ /*
+ * Copy out re-signing information.
+ */
+ if (RESIGN(header)) {
+ rdataset->attributes |= DNS_RDATASETATTR_RESIGN;
+ rdataset->resign = (header->resign << 1) | header->resign_lsb;
+ } else {
+ rdataset->resign = 0;
+ }
+}
+
+static isc_result_t
+setup_delegation(rbtdb_search_t *search, dns_dbnode_t **nodep,
+ dns_name_t *foundname, dns_rdataset_t *rdataset,
+ dns_rdataset_t *sigrdataset) {
+ dns_name_t *zcname;
+ rbtdb_rdatatype_t type;
+ dns_rbtnode_t *node;
+
+ REQUIRE(search != NULL);
+ REQUIRE(search->zonecut != NULL);
+ REQUIRE(search->zonecut_rdataset != NULL);
+
+ /*
+ * The caller MUST NOT be holding any node locks.
+ */
+
+ node = search->zonecut;
+ type = search->zonecut_rdataset->type;
+
+ /*
+ * If we have to set foundname, we do it before anything else.
+ * If we were to set foundname after we had set nodep or bound the
+ * rdataset, then we'd have to undo that work if dns_name_copy()
+ * failed. By setting foundname first, there's nothing to undo if
+ * we have trouble.
+ */
+ if (foundname != NULL && search->copy_name) {
+ zcname = dns_fixedname_name(&search->zonecut_name);
+ dns_name_copy(zcname, foundname);
+ }
+ if (nodep != NULL) {
+ /*
+ * Note that we don't have to increment the node's reference
+ * count here because we're going to use the reference we
+ * already have in the search block.
+ */
+ *nodep = node;
+ search->need_cleanup = false;
+ }
+ if (rdataset != NULL) {
+ NODE_LOCK(&(search->rbtdb->node_locks[node->locknum].lock),
+ isc_rwlocktype_read);
+ bind_rdataset(search->rbtdb, node, search->zonecut_rdataset,
+ search->now, isc_rwlocktype_read, rdataset);
+ if (sigrdataset != NULL && search->zonecut_sigrdataset != NULL)
+ {
+ bind_rdataset(search->rbtdb, node,
+ search->zonecut_sigrdataset, search->now,
+ isc_rwlocktype_read, sigrdataset);
+ }
+ NODE_UNLOCK(&(search->rbtdb->node_locks[node->locknum].lock),
+ isc_rwlocktype_read);
+ }
+
+ if (type == dns_rdatatype_dname) {
+ return (DNS_R_DNAME);
+ }
+ return (DNS_R_DELEGATION);
+}
+
+static bool
+valid_glue(rbtdb_search_t *search, dns_name_t *name, rbtdb_rdatatype_t type,
+ dns_rbtnode_t *node) {
+ unsigned char *raw; /* RDATASLAB */
+ unsigned int count, size;
+ dns_name_t ns_name;
+ bool valid = false;
+ dns_offsets_t offsets;
+ isc_region_t region;
+ rdatasetheader_t *header;
+
+ /*
+ * No additional locking is required.
+ */
+
+ /*
+ * Valid glue types are A, AAAA, A6. NS is also a valid glue type
+ * if it occurs at a zone cut, but is not valid below it.
+ */
+ if (type == dns_rdatatype_ns) {
+ if (node != search->zonecut) {
+ return (false);
+ }
+ } else if (type != dns_rdatatype_a && type != dns_rdatatype_aaaa &&
+ type != dns_rdatatype_a6)
+ {
+ return (false);
+ }
+
+ header = search->zonecut_rdataset;
+ raw = (unsigned char *)header + sizeof(*header);
+ count = raw[0] * 256 + raw[1];
+ raw += DNS_RDATASET_COUNT + DNS_RDATASET_LENGTH;
+
+ while (count > 0) {
+ count--;
+ size = raw[0] * 256 + raw[1];
+ raw += DNS_RDATASET_ORDER + DNS_RDATASET_LENGTH;
+ region.base = raw;
+ region.length = size;
+ raw += size;
+ /*
+ * XXX Until we have rdata structures, we have no choice but
+ * to directly access the rdata format.
+ */
+ dns_name_init(&ns_name, offsets);
+ dns_name_fromregion(&ns_name, &region);
+ if (dns_name_compare(&ns_name, name) == 0) {
+ valid = true;
+ break;
+ }
+ }
+
+ return (valid);
+}
+
+static bool
+activeempty(rbtdb_search_t *search, dns_rbtnodechain_t *chain,
+ const dns_name_t *name) {
+ dns_fixedname_t fnext;
+ dns_fixedname_t forigin;
+ dns_name_t *next;
+ dns_name_t *origin;
+ dns_name_t prefix;
+ dns_rbtdb_t *rbtdb;
+ dns_rbtnode_t *node;
+ isc_result_t result;
+ bool answer = false;
+ rdatasetheader_t *header;
+
+ rbtdb = search->rbtdb;
+
+ dns_name_init(&prefix, NULL);
+ next = dns_fixedname_initname(&fnext);
+ origin = dns_fixedname_initname(&forigin);
+
+ result = dns_rbtnodechain_next(chain, NULL, NULL);
+ while (result == ISC_R_SUCCESS || result == DNS_R_NEWORIGIN) {
+ node = NULL;
+ result = dns_rbtnodechain_current(chain, &prefix, origin,
+ &node);
+ if (result != ISC_R_SUCCESS) {
+ break;
+ }
+ NODE_LOCK(&(rbtdb->node_locks[node->locknum].lock),
+ isc_rwlocktype_read);
+ for (header = node->data; header != NULL; header = header->next)
+ {
+ if (header->serial <= search->serial &&
+ !IGNORE(header) && EXISTS(header))
+ {
+ break;
+ }
+ }
+ NODE_UNLOCK(&(rbtdb->node_locks[node->locknum].lock),
+ isc_rwlocktype_read);
+ if (header != NULL) {
+ break;
+ }
+ result = dns_rbtnodechain_next(chain, NULL, NULL);
+ }
+ if (result == ISC_R_SUCCESS) {
+ result = dns_name_concatenate(&prefix, origin, next, NULL);
+ }
+ if (result == ISC_R_SUCCESS && dns_name_issubdomain(next, name)) {
+ answer = true;
+ }
+ return (answer);
+}
+
+static bool
+activeemptynode(rbtdb_search_t *search, const dns_name_t *qname,
+ dns_name_t *wname) {
+ dns_fixedname_t fnext;
+ dns_fixedname_t forigin;
+ dns_fixedname_t fprev;
+ dns_name_t *next;
+ dns_name_t *origin;
+ dns_name_t *prev;
+ dns_name_t name;
+ dns_name_t rname;
+ dns_name_t tname;
+ dns_rbtdb_t *rbtdb;
+ dns_rbtnode_t *node;
+ dns_rbtnodechain_t chain;
+ bool check_next = true;
+ bool check_prev = true;
+ bool answer = false;
+ isc_result_t result;
+ rdatasetheader_t *header;
+ unsigned int n;
+
+ rbtdb = search->rbtdb;
+
+ dns_name_init(&name, NULL);
+ dns_name_init(&tname, NULL);
+ dns_name_init(&rname, NULL);
+ next = dns_fixedname_initname(&fnext);
+ prev = dns_fixedname_initname(&fprev);
+ origin = dns_fixedname_initname(&forigin);
+
+ /*
+ * Find if qname is at or below a empty node.
+ * Use our own copy of the chain.
+ */
+
+ chain = search->chain;
+ do {
+ node = NULL;
+ result = dns_rbtnodechain_current(&chain, &name, origin, &node);
+ if (result != ISC_R_SUCCESS) {
+ break;
+ }
+ NODE_LOCK(&(rbtdb->node_locks[node->locknum].lock),
+ isc_rwlocktype_read);
+ for (header = node->data; header != NULL; header = header->next)
+ {
+ if (header->serial <= search->serial &&
+ !IGNORE(header) && EXISTS(header))
+ {
+ break;
+ }
+ }
+ NODE_UNLOCK(&(rbtdb->node_locks[node->locknum].lock),
+ isc_rwlocktype_read);
+ if (header != NULL) {
+ break;
+ }
+ result = dns_rbtnodechain_prev(&chain, NULL, NULL);
+ } while (result == ISC_R_SUCCESS || result == DNS_R_NEWORIGIN);
+ if (result == ISC_R_SUCCESS) {
+ result = dns_name_concatenate(&name, origin, prev, NULL);
+ }
+ if (result != ISC_R_SUCCESS) {
+ check_prev = false;
+ }
+
+ result = dns_rbtnodechain_next(&chain, NULL, NULL);
+ while (result == ISC_R_SUCCESS || result == DNS_R_NEWORIGIN) {
+ node = NULL;
+ result = dns_rbtnodechain_current(&chain, &name, origin, &node);
+ if (result != ISC_R_SUCCESS) {
+ break;
+ }
+ NODE_LOCK(&(rbtdb->node_locks[node->locknum].lock),
+ isc_rwlocktype_read);
+ for (header = node->data; header != NULL; header = header->next)
+ {
+ if (header->serial <= search->serial &&
+ !IGNORE(header) && EXISTS(header))
+ {
+ break;
+ }
+ }
+ NODE_UNLOCK(&(rbtdb->node_locks[node->locknum].lock),
+ isc_rwlocktype_read);
+ if (header != NULL) {
+ break;
+ }
+ result = dns_rbtnodechain_next(&chain, NULL, NULL);
+ }
+ if (result == ISC_R_SUCCESS) {
+ result = dns_name_concatenate(&name, origin, next, NULL);
+ }
+ if (result != ISC_R_SUCCESS) {
+ check_next = false;
+ }
+
+ dns_name_clone(qname, &rname);
+
+ /*
+ * Remove the wildcard label to find the terminal name.
+ */
+ n = dns_name_countlabels(wname);
+ dns_name_getlabelsequence(wname, 1, n - 1, &tname);
+
+ do {
+ if ((check_prev && dns_name_issubdomain(prev, &rname)) ||
+ (check_next && dns_name_issubdomain(next, &rname)))
+ {
+ answer = true;
+ break;
+ }
+ /*
+ * Remove the left hand label.
+ */
+ n = dns_name_countlabels(&rname);
+ dns_name_getlabelsequence(&rname, 1, n - 1, &rname);
+ } while (!dns_name_equal(&rname, &tname));
+ return (answer);
+}
+
+static isc_result_t
+find_wildcard(rbtdb_search_t *search, dns_rbtnode_t **nodep,
+ const dns_name_t *qname) {
+ unsigned int i, j;
+ dns_rbtnode_t *node, *level_node, *wnode;
+ rdatasetheader_t *header;
+ isc_result_t result = ISC_R_NOTFOUND;
+ dns_name_t name;
+ dns_name_t *wname;
+ dns_fixedname_t fwname;
+ dns_rbtdb_t *rbtdb;
+ bool done, wild, active;
+ dns_rbtnodechain_t wchain;
+
+ /*
+ * Caller must be holding the tree lock and MUST NOT be holding
+ * any node locks.
+ */
+
+ /*
+ * Examine each ancestor level. If the level's wild bit
+ * is set, then construct the corresponding wildcard name and
+ * search for it. If the wildcard node exists, and is active in
+ * this version, we're done. If not, then we next check to see
+ * if the ancestor is active in this version. If so, then there
+ * can be no possible wildcard match and again we're done. If not,
+ * continue the search.
+ */
+
+ rbtdb = search->rbtdb;
+ i = search->chain.level_matches;
+ done = false;
+ node = *nodep;
+ do {
+ NODE_LOCK(&(rbtdb->node_locks[node->locknum].lock),
+ isc_rwlocktype_read);
+
+ /*
+ * First we try to figure out if this node is active in
+ * the search's version. We do this now, even though we
+ * may not need the information, because it simplifies the
+ * locking and code flow.
+ */
+ for (header = node->data; header != NULL; header = header->next)
+ {
+ if (header->serial <= search->serial &&
+ !IGNORE(header) && EXISTS(header) &&
+ !ANCIENT(header))
+ {
+ break;
+ }
+ }
+ if (header != NULL) {
+ active = true;
+ } else {
+ active = false;
+ }
+
+ if (node->wild) {
+ wild = true;
+ } else {
+ wild = false;
+ }
+
+ NODE_UNLOCK(&(rbtdb->node_locks[node->locknum].lock),
+ isc_rwlocktype_read);
+
+ if (wild) {
+ /*
+ * Construct the wildcard name for this level.
+ */
+ dns_name_init(&name, NULL);
+ dns_rbt_namefromnode(node, &name);
+ wname = dns_fixedname_initname(&fwname);
+ result = dns_name_concatenate(dns_wildcardname, &name,
+ wname, NULL);
+ j = i;
+ while (result == ISC_R_SUCCESS && j != 0) {
+ j--;
+ level_node = search->chain.levels[j];
+ dns_name_init(&name, NULL);
+ dns_rbt_namefromnode(level_node, &name);
+ result = dns_name_concatenate(wname, &name,
+ wname, NULL);
+ }
+ if (result != ISC_R_SUCCESS) {
+ break;
+ }
+
+ wnode = NULL;
+ dns_rbtnodechain_init(&wchain);
+ result = dns_rbt_findnode(
+ rbtdb->tree, wname, NULL, &wnode, &wchain,
+ DNS_RBTFIND_EMPTYDATA, NULL, NULL);
+ if (result == ISC_R_SUCCESS) {
+ nodelock_t *lock;
+
+ /*
+ * We have found the wildcard node. If it
+ * is active in the search's version, we're
+ * done.
+ */
+ lock = &rbtdb->node_locks[wnode->locknum].lock;
+ NODE_LOCK(lock, isc_rwlocktype_read);
+ for (header = wnode->data; header != NULL;
+ header = header->next)
+ {
+ if (header->serial <= search->serial &&
+ !IGNORE(header) && EXISTS(header) &&
+ !ANCIENT(header))
+ {
+ break;
+ }
+ }
+ NODE_UNLOCK(lock, isc_rwlocktype_read);
+ if (header != NULL ||
+ activeempty(search, &wchain, wname))
+ {
+ if (activeemptynode(search, qname,
+ wname))
+ {
+ return (ISC_R_NOTFOUND);
+ }
+ /*
+ * The wildcard node is active!
+ *
+ * Note: result is still ISC_R_SUCCESS
+ * so we don't have to set it.
+ */
+ *nodep = wnode;
+ break;
+ }
+ } else if (result != ISC_R_NOTFOUND &&
+ result != DNS_R_PARTIALMATCH)
+ {
+ /*
+ * An error has occurred. Bail out.
+ */
+ break;
+ }
+ }
+
+ if (active) {
+ /*
+ * The level node is active. Any wildcarding
+ * present at higher levels has no
+ * effect and we're done.
+ */
+ result = ISC_R_NOTFOUND;
+ break;
+ }
+
+ if (i > 0) {
+ i--;
+ node = search->chain.levels[i];
+ } else {
+ done = true;
+ }
+ } while (!done);
+
+ return (result);
+}
+
+static bool
+matchparams(rdatasetheader_t *header, rbtdb_search_t *search) {
+ dns_rdata_t rdata = DNS_RDATA_INIT;
+ dns_rdata_nsec3_t nsec3;
+ unsigned char *raw; /* RDATASLAB */
+ unsigned int rdlen, count;
+ isc_region_t region;
+ isc_result_t result;
+
+ REQUIRE(header->type == dns_rdatatype_nsec3);
+
+ raw = (unsigned char *)header + sizeof(*header);
+ count = raw[0] * 256 + raw[1]; /* count */
+ raw += DNS_RDATASET_COUNT + DNS_RDATASET_LENGTH;
+
+ while (count-- > 0) {
+ rdlen = raw[0] * 256 + raw[1];
+ raw += DNS_RDATASET_ORDER + DNS_RDATASET_LENGTH;
+ region.base = raw;
+ region.length = rdlen;
+ dns_rdata_fromregion(&rdata, search->rbtdb->common.rdclass,
+ dns_rdatatype_nsec3, &region);
+ raw += rdlen;
+ result = dns_rdata_tostruct(&rdata, &nsec3, NULL);
+ INSIST(result == ISC_R_SUCCESS);
+ if (nsec3.hash == search->rbtversion->hash &&
+ nsec3.iterations == search->rbtversion->iterations &&
+ nsec3.salt_length == search->rbtversion->salt_length &&
+ memcmp(nsec3.salt, search->rbtversion->salt,
+ nsec3.salt_length) == 0)
+ {
+ return (true);
+ }
+ dns_rdata_reset(&rdata);
+ }
+ return (false);
+}
+
+/*
+ * Find node of the NSEC/NSEC3 record that is 'name'.
+ */
+static isc_result_t
+previous_closest_nsec(dns_rdatatype_t type, rbtdb_search_t *search,
+ dns_name_t *name, dns_name_t *origin,
+ dns_rbtnode_t **nodep, dns_rbtnodechain_t *nsecchain,
+ bool *firstp) {
+ dns_fixedname_t ftarget;
+ dns_name_t *target;
+ dns_rbtnode_t *nsecnode;
+ isc_result_t result;
+
+ REQUIRE(nodep != NULL && *nodep == NULL);
+ REQUIRE(type == dns_rdatatype_nsec3 || firstp != NULL);
+
+ if (type == dns_rdatatype_nsec3) {
+ result = dns_rbtnodechain_prev(&search->chain, NULL, NULL);
+ if (result != ISC_R_SUCCESS && result != DNS_R_NEWORIGIN) {
+ return (result);
+ }
+ result = dns_rbtnodechain_current(&search->chain, name, origin,
+ nodep);
+ return (result);
+ }
+
+ target = dns_fixedname_initname(&ftarget);
+
+ for (;;) {
+ if (*firstp) {
+ /*
+ * Construct the name of the second node to check.
+ * It is the first node sought in the NSEC tree.
+ */
+ *firstp = false;
+ dns_rbtnodechain_init(nsecchain);
+ result = dns_name_concatenate(name, origin, target,
+ NULL);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+ nsecnode = NULL;
+ result = dns_rbt_findnode(
+ search->rbtdb->nsec, target, NULL, &nsecnode,
+ nsecchain, DNS_RBTFIND_EMPTYDATA, NULL, NULL);
+ if (result == ISC_R_SUCCESS) {
+ /*
+ * Since this was the first loop, finding the
+ * name in the NSEC tree implies that the first
+ * node checked in the main tree had an
+ * unacceptable NSEC record.
+ * Try the previous node in the NSEC tree.
+ */
+ result = dns_rbtnodechain_prev(nsecchain, name,
+ origin);
+ if (result == DNS_R_NEWORIGIN) {
+ result = ISC_R_SUCCESS;
+ }
+ } else if (result == ISC_R_NOTFOUND ||
+ result == DNS_R_PARTIALMATCH)
+ {
+ result = dns_rbtnodechain_current(
+ nsecchain, name, origin, NULL);
+ if (result == ISC_R_NOTFOUND) {
+ result = ISC_R_NOMORE;
+ }
+ }
+ } else {
+ /*
+ * This is a second or later trip through the auxiliary
+ * tree for the name of a third or earlier NSEC node in
+ * the main tree. Previous trips through the NSEC tree
+ * must have found nodes in the main tree with NSEC
+ * records. Perhaps they lacked signature records.
+ */
+ result = dns_rbtnodechain_prev(nsecchain, name, origin);
+ if (result == DNS_R_NEWORIGIN) {
+ result = ISC_R_SUCCESS;
+ }
+ }
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+
+ /*
+ * Construct the name to seek in the main tree.
+ */
+ result = dns_name_concatenate(name, origin, target, NULL);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+
+ *nodep = NULL;
+ result = dns_rbt_findnode(search->rbtdb->tree, target, NULL,
+ nodep, &search->chain,
+ DNS_RBTFIND_EMPTYDATA, NULL, NULL);
+ if (result == ISC_R_SUCCESS) {
+ return (result);
+ }
+
+ /*
+ * There should always be a node in the main tree with the
+ * same name as the node in the auxiliary NSEC tree, except for
+ * nodes in the auxiliary tree that are awaiting deletion.
+ */
+ if (result != DNS_R_PARTIALMATCH && result != ISC_R_NOTFOUND) {
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE,
+ DNS_LOGMODULE_CACHE, ISC_LOG_ERROR,
+ "previous_closest_nsec(): %s",
+ isc_result_totext(result));
+ return (DNS_R_BADDB);
+ }
+ }
+}
+
+/*
+ * Find the NSEC/NSEC3 which is or before the current point on the
+ * search chain. For NSEC3 records only NSEC3 records that match the
+ * current NSEC3PARAM record are considered.
+ */
+static isc_result_t
+find_closest_nsec(rbtdb_search_t *search, dns_dbnode_t **nodep,
+ dns_name_t *foundname, dns_rdataset_t *rdataset,
+ dns_rdataset_t *sigrdataset, dns_rbt_t *tree,
+ dns_db_secure_t secure) {
+ dns_rbtnode_t *node, *prevnode;
+ rdatasetheader_t *header, *header_next, *found, *foundsig;
+ dns_rbtnodechain_t nsecchain;
+ bool empty_node;
+ isc_result_t result;
+ dns_fixedname_t fname, forigin;
+ dns_name_t *name, *origin;
+ dns_rdatatype_t type;
+ rbtdb_rdatatype_t sigtype;
+ bool wraps;
+ bool first = true;
+ bool need_sig = (secure == dns_db_secure);
+
+ if (tree == search->rbtdb->nsec3) {
+ type = dns_rdatatype_nsec3;
+ sigtype = RBTDB_RDATATYPE_SIGNSEC3;
+ wraps = true;
+ } else {
+ type = dns_rdatatype_nsec;
+ sigtype = RBTDB_RDATATYPE_SIGNSEC;
+ wraps = false;
+ }
+
+ /*
+ * Use the auxiliary tree only starting with the second node in the
+ * hope that the original node will be right much of the time.
+ */
+ name = dns_fixedname_initname(&fname);
+ origin = dns_fixedname_initname(&forigin);
+again:
+ node = NULL;
+ prevnode = NULL;
+ result = dns_rbtnodechain_current(&search->chain, name, origin, &node);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+ do {
+ NODE_LOCK(&(search->rbtdb->node_locks[node->locknum].lock),
+ isc_rwlocktype_read);
+ found = NULL;
+ foundsig = NULL;
+ empty_node = true;
+ for (header = node->data; header != NULL; header = header_next)
+ {
+ header_next = header->next;
+ /*
+ * Look for an active, extant NSEC or RRSIG NSEC.
+ */
+ do {
+ if (header->serial <= search->serial &&
+ !IGNORE(header))
+ {
+ /*
+ * Is this a "this rdataset doesn't
+ * exist" record?
+ */
+ if (NONEXISTENT(header)) {
+ header = NULL;
+ }
+ break;
+ } else {
+ header = header->down;
+ }
+ } while (header != NULL);
+ if (header != NULL) {
+ /*
+ * We now know that there is at least one
+ * active rdataset at this node.
+ */
+ empty_node = false;
+ if (header->type == type) {
+ found = header;
+ if (foundsig != NULL) {
+ break;
+ }
+ } else if (header->type == sigtype) {
+ foundsig = header;
+ if (found != NULL) {
+ break;
+ }
+ }
+ }
+ }
+ if (!empty_node) {
+ if (found != NULL && search->rbtversion->havensec3 &&
+ found->type == dns_rdatatype_nsec3 &&
+ !matchparams(found, search))
+ {
+ empty_node = true;
+ found = NULL;
+ foundsig = NULL;
+ result = previous_closest_nsec(
+ type, search, name, origin, &prevnode,
+ NULL, NULL);
+ } else if (found != NULL &&
+ (foundsig != NULL || !need_sig))
+ {
+ /*
+ * We've found the right NSEC/NSEC3 record.
+ *
+ * Note: for this to really be the right
+ * NSEC record, it's essential that the NSEC
+ * records of any nodes obscured by a zone
+ * cut have been removed; we assume this is
+ * the case.
+ */
+ result = dns_name_concatenate(name, origin,
+ foundname, NULL);
+ if (result == ISC_R_SUCCESS) {
+ if (nodep != NULL) {
+ new_reference(
+ search->rbtdb, node,
+ isc_rwlocktype_read);
+ *nodep = node;
+ }
+ bind_rdataset(search->rbtdb, node,
+ found, search->now,
+ isc_rwlocktype_read,
+ rdataset);
+ if (foundsig != NULL) {
+ bind_rdataset(
+ search->rbtdb, node,
+ foundsig, search->now,
+ isc_rwlocktype_read,
+ sigrdataset);
+ }
+ }
+ } else if (found == NULL && foundsig == NULL) {
+ /*
+ * This node is active, but has no NSEC or
+ * RRSIG NSEC. That means it's glue or
+ * other obscured zone data that isn't
+ * relevant for our search. Treat the
+ * node as if it were empty and keep looking.
+ */
+ empty_node = true;
+ result = previous_closest_nsec(
+ type, search, name, origin, &prevnode,
+ &nsecchain, &first);
+ } else {
+ /*
+ * We found an active node, but either the
+ * NSEC or the RRSIG NSEC is missing. This
+ * shouldn't happen.
+ */
+ result = DNS_R_BADDB;
+ }
+ } else {
+ /*
+ * This node isn't active. We've got to keep
+ * looking.
+ */
+ result = previous_closest_nsec(type, search, name,
+ origin, &prevnode,
+ &nsecchain, &first);
+ }
+ NODE_UNLOCK(&(search->rbtdb->node_locks[node->locknum].lock),
+ isc_rwlocktype_read);
+ node = prevnode;
+ prevnode = NULL;
+ } while (empty_node && result == ISC_R_SUCCESS);
+
+ if (!first) {
+ dns_rbtnodechain_invalidate(&nsecchain);
+ }
+
+ if (result == ISC_R_NOMORE && wraps) {
+ result = dns_rbtnodechain_last(&search->chain, tree, NULL,
+ NULL);
+ if (result == ISC_R_SUCCESS || result == DNS_R_NEWORIGIN) {
+ wraps = false;
+ goto again;
+ }
+ }
+
+ /*
+ * If the result is ISC_R_NOMORE, then we got to the beginning of
+ * the database and didn't find a NSEC record. This shouldn't
+ * happen.
+ */
+ if (result == ISC_R_NOMORE) {
+ result = DNS_R_BADDB;
+ }
+
+ return (result);
+}
+
+static isc_result_t
+zone_find(dns_db_t *db, const dns_name_t *name, dns_dbversion_t *version,
+ dns_rdatatype_t type, unsigned int options, isc_stdtime_t now,
+ dns_dbnode_t **nodep, dns_name_t *foundname, dns_rdataset_t *rdataset,
+ dns_rdataset_t *sigrdataset) {
+ dns_rbtnode_t *node = NULL;
+ isc_result_t result;
+ rbtdb_search_t search;
+ bool cname_ok = true;
+ bool close_version = false;
+ bool maybe_zonecut = false;
+ bool at_zonecut = false;
+ bool wild;
+ bool empty_node;
+ rdatasetheader_t *header, *header_next, *found, *nsecheader;
+ rdatasetheader_t *foundsig, *cnamesig, *nsecsig;
+ rbtdb_rdatatype_t sigtype;
+ bool active;
+ nodelock_t *lock;
+ dns_rbt_t *tree;
+
+ search.rbtdb = (dns_rbtdb_t *)db;
+
+ REQUIRE(VALID_RBTDB(search.rbtdb));
+ INSIST(version == NULL ||
+ ((rbtdb_version_t *)version)->rbtdb == (dns_rbtdb_t *)db);
+
+ /*
+ * We don't care about 'now'.
+ */
+ UNUSED(now);
+
+ /*
+ * If the caller didn't supply a version, attach to the current
+ * version.
+ */
+ if (version == NULL) {
+ currentversion(db, &version);
+ close_version = true;
+ }
+
+ search.rbtversion = version;
+ search.serial = search.rbtversion->serial;
+ search.options = options;
+ search.copy_name = false;
+ search.need_cleanup = false;
+ search.wild = false;
+ search.zonecut = NULL;
+ dns_fixedname_init(&search.zonecut_name);
+ dns_rbtnodechain_init(&search.chain);
+ search.now = 0;
+
+ /*
+ * 'wild' will be true iff. we've matched a wildcard.
+ */
+ wild = false;
+
+ RWLOCK(&search.rbtdb->tree_lock, isc_rwlocktype_read);
+
+ /*
+ * Search down from the root of the tree. If, while going down, we
+ * encounter a callback node, zone_zonecut_callback() will search the
+ * rdatasets at the zone cut for active DNAME or NS rdatasets.
+ */
+ tree = (options & DNS_DBFIND_FORCENSEC3) != 0 ? search.rbtdb->nsec3
+ : search.rbtdb->tree;
+ result = dns_rbt_findnode(tree, name, foundname, &node, &search.chain,
+ DNS_RBTFIND_EMPTYDATA, zone_zonecut_callback,
+ &search);
+
+ if (result == DNS_R_PARTIALMATCH) {
+ partial_match:
+ if (search.zonecut != NULL) {
+ result = setup_delegation(&search, nodep, foundname,
+ rdataset, sigrdataset);
+ goto tree_exit;
+ }
+
+ if (search.wild) {
+ /*
+ * At least one of the levels in the search chain
+ * potentially has a wildcard. For each such level,
+ * we must see if there's a matching wildcard active
+ * in the current version.
+ */
+ result = find_wildcard(&search, &node, name);
+ if (result == ISC_R_SUCCESS) {
+ dns_name_copy(name, foundname);
+ wild = true;
+ goto found;
+ } else if (result != ISC_R_NOTFOUND) {
+ goto tree_exit;
+ }
+ }
+
+ active = false;
+ if ((options & DNS_DBFIND_FORCENSEC3) == 0) {
+ /*
+ * The NSEC3 tree won't have empty nodes,
+ * so it isn't necessary to check for them.
+ */
+ dns_rbtnodechain_t chain = search.chain;
+ active = activeempty(&search, &chain, name);
+ }
+
+ /*
+ * If we're here, then the name does not exist, is not
+ * beneath a zonecut, and there's no matching wildcard.
+ */
+ if ((search.rbtversion->secure == dns_db_secure &&
+ !search.rbtversion->havensec3) ||
+ (search.options & DNS_DBFIND_FORCENSEC) != 0 ||
+ (search.options & DNS_DBFIND_FORCENSEC3) != 0)
+ {
+ result = find_closest_nsec(&search, nodep, foundname,
+ rdataset, sigrdataset, tree,
+ search.rbtversion->secure);
+ if (result == ISC_R_SUCCESS) {
+ result = active ? DNS_R_EMPTYNAME
+ : DNS_R_NXDOMAIN;
+ }
+ } else {
+ result = active ? DNS_R_EMPTYNAME : DNS_R_NXDOMAIN;
+ }
+ goto tree_exit;
+ } else if (result != ISC_R_SUCCESS) {
+ goto tree_exit;
+ }
+
+found:
+ /*
+ * We have found a node whose name is the desired name, or we
+ * have matched a wildcard.
+ */
+
+ if (search.zonecut != NULL) {
+ /*
+ * If we're beneath a zone cut, we don't want to look for
+ * CNAMEs because they're not legitimate zone glue.
+ */
+ cname_ok = false;
+ } else {
+ /*
+ * The node may be a zone cut itself. If it might be one,
+ * make sure we check for it later.
+ *
+ * DS records live above the zone cut in ordinary zone so
+ * we want to ignore any referral.
+ *
+ * Stub zones don't have anything "above" the delegation so
+ * we always return a referral.
+ */
+ if (node->find_callback &&
+ ((node != search.rbtdb->origin_node &&
+ !dns_rdatatype_atparent(type)) ||
+ IS_STUB(search.rbtdb)))
+ {
+ maybe_zonecut = true;
+ }
+ }
+
+ /*
+ * Certain DNSSEC types are not subject to CNAME matching
+ * (RFC4035, section 2.5 and RFC3007).
+ *
+ * We don't check for RRSIG, because we don't store RRSIG records
+ * directly.
+ */
+ if (type == dns_rdatatype_key || type == dns_rdatatype_nsec) {
+ cname_ok = false;
+ }
+
+ /*
+ * We now go looking for rdata...
+ */
+
+ lock = &search.rbtdb->node_locks[node->locknum].lock;
+ NODE_LOCK(lock, isc_rwlocktype_read);
+
+ found = NULL;
+ foundsig = NULL;
+ sigtype = RBTDB_RDATATYPE_VALUE(dns_rdatatype_rrsig, type);
+ nsecheader = NULL;
+ nsecsig = NULL;
+ cnamesig = NULL;
+ empty_node = true;
+ for (header = node->data; header != NULL; header = header_next) {
+ header_next = header->next;
+ /*
+ * Look for an active, extant rdataset.
+ */
+ do {
+ if (header->serial <= search.serial && !IGNORE(header))
+ {
+ /*
+ * Is this a "this rdataset doesn't
+ * exist" record?
+ */
+ if (NONEXISTENT(header)) {
+ header = NULL;
+ }
+ break;
+ } else {
+ header = header->down;
+ }
+ } while (header != NULL);
+ if (header != NULL) {
+ /*
+ * We now know that there is at least one active
+ * rdataset at this node.
+ */
+ empty_node = false;
+
+ /*
+ * Do special zone cut handling, if requested.
+ */
+ if (maybe_zonecut && header->type == dns_rdatatype_ns) {
+ /*
+ * We increment the reference count on node to
+ * ensure that search->zonecut_rdataset will
+ * still be valid later.
+ */
+ new_reference(search.rbtdb, node,
+ isc_rwlocktype_read);
+ search.zonecut = node;
+ search.zonecut_rdataset = header;
+ search.zonecut_sigrdataset = NULL;
+ search.need_cleanup = true;
+ maybe_zonecut = false;
+ at_zonecut = true;
+ /*
+ * It is not clear if KEY should still be
+ * allowed at the parent side of the zone
+ * cut or not. It is needed for RFC3007
+ * validated updates.
+ */
+ if ((search.options & DNS_DBFIND_GLUEOK) == 0 &&
+ type != dns_rdatatype_nsec &&
+ type != dns_rdatatype_key)
+ {
+ /*
+ * Glue is not OK, but any answer we
+ * could return would be glue. Return
+ * the delegation.
+ */
+ found = NULL;
+ break;
+ }
+ if (found != NULL && foundsig != NULL) {
+ break;
+ }
+ }
+
+ /*
+ * If the NSEC3 record doesn't match the chain
+ * we are using behave as if it isn't here.
+ */
+ if (header->type == dns_rdatatype_nsec3 &&
+ !matchparams(header, &search))
+ {
+ NODE_UNLOCK(lock, isc_rwlocktype_read);
+ goto partial_match;
+ }
+ /*
+ * If we found a type we were looking for,
+ * remember it.
+ */
+ if (header->type == type || type == dns_rdatatype_any ||
+ (header->type == dns_rdatatype_cname && cname_ok))
+ {
+ /*
+ * We've found the answer!
+ */
+ found = header;
+ if (header->type == dns_rdatatype_cname &&
+ cname_ok)
+ {
+ /*
+ * We may be finding a CNAME instead
+ * of the desired type.
+ *
+ * If we've already got the CNAME RRSIG,
+ * use it, otherwise change sigtype
+ * so that we find it.
+ */
+ if (cnamesig != NULL) {
+ foundsig = cnamesig;
+ } else {
+ sigtype =
+ RBTDB_RDATATYPE_SIGCNAME;
+ }
+ }
+ /*
+ * If we've got all we need, end the search.
+ */
+ if (!maybe_zonecut && foundsig != NULL) {
+ break;
+ }
+ } else if (header->type == sigtype) {
+ /*
+ * We've found the RRSIG rdataset for our
+ * target type. Remember it.
+ */
+ foundsig = header;
+ /*
+ * If we've got all we need, end the search.
+ */
+ if (!maybe_zonecut && found != NULL) {
+ break;
+ }
+ } else if (header->type == dns_rdatatype_nsec &&
+ !search.rbtversion->havensec3)
+ {
+ /*
+ * Remember a NSEC rdataset even if we're
+ * not specifically looking for it, because
+ * we might need it later.
+ */
+ nsecheader = header;
+ } else if (header->type == RBTDB_RDATATYPE_SIGNSEC &&
+ !search.rbtversion->havensec3)
+ {
+ /*
+ * If we need the NSEC rdataset, we'll also
+ * need its signature.
+ */
+ nsecsig = header;
+ } else if (cname_ok &&
+ header->type == RBTDB_RDATATYPE_SIGCNAME)
+ {
+ /*
+ * If we get a CNAME match, we'll also need
+ * its signature.
+ */
+ cnamesig = header;
+ }
+ }
+ }
+
+ if (empty_node) {
+ /*
+ * We have an exact match for the name, but there are no
+ * active rdatasets in the desired version. That means that
+ * this node doesn't exist in the desired version, and that
+ * we really have a partial match.
+ */
+ if (!wild) {
+ NODE_UNLOCK(lock, isc_rwlocktype_read);
+ goto partial_match;
+ }
+ }
+
+ /*
+ * If we didn't find what we were looking for...
+ */
+ if (found == NULL) {
+ if (search.zonecut != NULL) {
+ /*
+ * We were trying to find glue at a node beneath a
+ * zone cut, but didn't.
+ *
+ * Return the delegation.
+ */
+ NODE_UNLOCK(lock, isc_rwlocktype_read);
+ result = setup_delegation(&search, nodep, foundname,
+ rdataset, sigrdataset);
+ goto tree_exit;
+ }
+ /*
+ * The desired type doesn't exist.
+ */
+ result = DNS_R_NXRRSET;
+ if (search.rbtversion->secure == dns_db_secure &&
+ !search.rbtversion->havensec3 &&
+ (nsecheader == NULL || nsecsig == NULL))
+ {
+ /*
+ * The zone is secure but there's no NSEC,
+ * or the NSEC has no signature!
+ */
+ if (!wild) {
+ result = DNS_R_BADDB;
+ goto node_exit;
+ }
+
+ NODE_UNLOCK(lock, isc_rwlocktype_read);
+ result = find_closest_nsec(&search, nodep, foundname,
+ rdataset, sigrdataset,
+ search.rbtdb->tree,
+ search.rbtversion->secure);
+ if (result == ISC_R_SUCCESS) {
+ result = DNS_R_EMPTYWILD;
+ }
+ goto tree_exit;
+ }
+ if ((search.options & DNS_DBFIND_FORCENSEC) != 0 &&
+ nsecheader == NULL)
+ {
+ /*
+ * There's no NSEC record, and we were told
+ * to find one.
+ */
+ result = DNS_R_BADDB;
+ goto node_exit;
+ }
+ if (nodep != NULL) {
+ new_reference(search.rbtdb, node, isc_rwlocktype_read);
+ *nodep = node;
+ }
+ if ((search.rbtversion->secure == dns_db_secure &&
+ !search.rbtversion->havensec3) ||
+ (search.options & DNS_DBFIND_FORCENSEC) != 0)
+ {
+ bind_rdataset(search.rbtdb, node, nsecheader, 0,
+ isc_rwlocktype_read, rdataset);
+ if (nsecsig != NULL) {
+ bind_rdataset(search.rbtdb, node, nsecsig, 0,
+ isc_rwlocktype_read, sigrdataset);
+ }
+ }
+ if (wild) {
+ foundname->attributes |= DNS_NAMEATTR_WILDCARD;
+ }
+ goto node_exit;
+ }
+
+ /*
+ * We found what we were looking for, or we found a CNAME.
+ */
+
+ if (type != found->type && type != dns_rdatatype_any &&
+ found->type == dns_rdatatype_cname)
+ {
+ /*
+ * We weren't doing an ANY query and we found a CNAME instead
+ * of the type we were looking for, so we need to indicate
+ * that result to the caller.
+ */
+ result = DNS_R_CNAME;
+ } else if (search.zonecut != NULL) {
+ /*
+ * If we're beneath a zone cut, we must indicate that the
+ * result is glue, unless we're actually at the zone cut
+ * and the type is NSEC or KEY.
+ */
+ if (search.zonecut == node) {
+ /*
+ * It is not clear if KEY should still be
+ * allowed at the parent side of the zone
+ * cut or not. It is needed for RFC3007
+ * validated updates.
+ */
+ if (type == dns_rdatatype_nsec ||
+ type == dns_rdatatype_nsec3 ||
+ type == dns_rdatatype_key)
+ {
+ result = ISC_R_SUCCESS;
+ } else if (type == dns_rdatatype_any) {
+ result = DNS_R_ZONECUT;
+ } else {
+ result = DNS_R_GLUE;
+ }
+ } else {
+ result = DNS_R_GLUE;
+ }
+ /*
+ * We might have found data that isn't glue, but was occluded
+ * by a dynamic update. If the caller cares about this, they
+ * will have told us to validate glue.
+ *
+ * XXX We should cache the glue validity state!
+ */
+ if (result == DNS_R_GLUE &&
+ (search.options & DNS_DBFIND_VALIDATEGLUE) != 0 &&
+ !valid_glue(&search, foundname, type, node))
+ {
+ NODE_UNLOCK(lock, isc_rwlocktype_read);
+ result = setup_delegation(&search, nodep, foundname,
+ rdataset, sigrdataset);
+ goto tree_exit;
+ }
+ } else {
+ /*
+ * An ordinary successful query!
+ */
+ result = ISC_R_SUCCESS;
+ }
+
+ if (nodep != NULL) {
+ if (!at_zonecut) {
+ new_reference(search.rbtdb, node, isc_rwlocktype_read);
+ } else {
+ search.need_cleanup = false;
+ }
+ *nodep = node;
+ }
+
+ if (type != dns_rdatatype_any) {
+ bind_rdataset(search.rbtdb, node, found, 0, isc_rwlocktype_read,
+ rdataset);
+ if (foundsig != NULL) {
+ bind_rdataset(search.rbtdb, node, foundsig, 0,
+ isc_rwlocktype_read, sigrdataset);
+ }
+ }
+
+ if (wild) {
+ foundname->attributes |= DNS_NAMEATTR_WILDCARD;
+ }
+
+node_exit:
+ NODE_UNLOCK(lock, isc_rwlocktype_read);
+
+tree_exit:
+ RWUNLOCK(&search.rbtdb->tree_lock, isc_rwlocktype_read);
+
+ /*
+ * If we found a zonecut but aren't going to use it, we have to
+ * let go of it.
+ */
+ if (search.need_cleanup) {
+ node = search.zonecut;
+ INSIST(node != NULL);
+ lock = &(search.rbtdb->node_locks[node->locknum].lock);
+
+ NODE_LOCK(lock, isc_rwlocktype_read);
+ decrement_reference(search.rbtdb, node, 0, isc_rwlocktype_read,
+ isc_rwlocktype_none, false);
+ NODE_UNLOCK(lock, isc_rwlocktype_read);
+ }
+
+ if (close_version) {
+ closeversion(db, &version, false);
+ }
+
+ dns_rbtnodechain_reset(&search.chain);
+
+ return (result);
+}
+
+static isc_result_t
+zone_findzonecut(dns_db_t *db, const dns_name_t *name, unsigned int options,
+ isc_stdtime_t now, dns_dbnode_t **nodep, dns_name_t *foundname,
+ dns_name_t *dcname, dns_rdataset_t *rdataset,
+ dns_rdataset_t *sigrdataset) {
+ UNUSED(db);
+ UNUSED(name);
+ UNUSED(options);
+ UNUSED(now);
+ UNUSED(nodep);
+ UNUSED(foundname);
+ UNUSED(dcname);
+ UNUSED(rdataset);
+ UNUSED(sigrdataset);
+
+ FATAL_ERROR("zone_findzonecut() called!");
+
+ UNREACHABLE();
+ return (ISC_R_NOTIMPLEMENTED);
+}
+
+static bool
+check_stale_header(dns_rbtnode_t *node, rdatasetheader_t *header,
+ isc_rwlocktype_t *locktype, nodelock_t *lock,
+ rbtdb_search_t *search, rdatasetheader_t **header_prev) {
+ if (!ACTIVE(header, search->now)) {
+ dns_ttl_t stale = header->rdh_ttl +
+ STALE_TTL(header, search->rbtdb);
+ /*
+ * If this data is in the stale window keep it and if
+ * DNS_DBFIND_STALEOK is not set we tell the caller to
+ * skip this record. We skip the records with ZEROTTL
+ * (these records should not be cached anyway).
+ */
+
+ RDATASET_ATTR_CLR(header, RDATASET_ATTR_STALE_WINDOW);
+ if (!ZEROTTL(header) && KEEPSTALE(search->rbtdb) &&
+ stale > search->now)
+ {
+ mark_header_stale(search->rbtdb, header);
+ *header_prev = header;
+ /*
+ * If DNS_DBFIND_STALESTART is set then it means we
+ * failed to resolve the name during recursion, in
+ * this case we mark the time in which the refresh
+ * failed.
+ */
+ if ((search->options & DNS_DBFIND_STALESTART) != 0) {
+ atomic_store_release(
+ &header->last_refresh_fail_ts,
+ search->now);
+ } else if ((search->options &
+ DNS_DBFIND_STALEENABLED) != 0 &&
+ search->now <
+ (atomic_load_acquire(
+ &header->last_refresh_fail_ts) +
+ search->rbtdb->serve_stale_refresh))
+ {
+ /*
+ * If we are within interval between last
+ * refresh failure time + 'stale-refresh-time',
+ * then don't skip this stale entry but use it
+ * instead.
+ */
+ RDATASET_ATTR_SET(header,
+ RDATASET_ATTR_STALE_WINDOW);
+ return (false);
+ } else if ((search->options &
+ DNS_DBFIND_STALETIMEOUT) != 0)
+ {
+ /*
+ * We want stale RRset due to timeout, so we
+ * don't skip it.
+ */
+ return (false);
+ }
+ return ((search->options & DNS_DBFIND_STALEOK) == 0);
+ }
+
+ /*
+ * This rdataset is stale. If no one else is using the
+ * node, we can clean it up right now, otherwise we mark
+ * it as ancient, and the node as dirty, so it will get
+ * cleaned up later.
+ */
+ if ((header->rdh_ttl < search->now - RBTDB_VIRTUAL) &&
+ (*locktype == isc_rwlocktype_write ||
+ NODE_TRYUPGRADE(lock) == ISC_R_SUCCESS))
+ {
+ /*
+ * We update the node's status only when we can
+ * get write access; otherwise, we leave others
+ * to this work. Periodical cleaning will
+ * eventually take the job as the last resort.
+ * We won't downgrade the lock, since other
+ * rdatasets are probably stale, too.
+ */
+ *locktype = isc_rwlocktype_write;
+
+ if (isc_refcount_current(&node->references) == 0) {
+ isc_mem_t *mctx;
+
+ /*
+ * header->down can be non-NULL if the
+ * refcount has just decremented to 0
+ * but decrement_reference() has not
+ * performed clean_cache_node(), in
+ * which case we need to purge the stale
+ * headers first.
+ */
+ mctx = search->rbtdb->common.mctx;
+ clean_stale_headers(search->rbtdb, mctx,
+ header);
+ if (*header_prev != NULL) {
+ (*header_prev)->next = header->next;
+ } else {
+ node->data = header->next;
+ }
+ free_rdataset(search->rbtdb, mctx, header);
+ } else {
+ mark_header_ancient(search->rbtdb, header);
+ *header_prev = header;
+ }
+ } else {
+ *header_prev = header;
+ }
+ return (true);
+ }
+ return (false);
+}
+
+static isc_result_t
+cache_zonecut_callback(dns_rbtnode_t *node, dns_name_t *name, void *arg) {
+ rbtdb_search_t *search = arg;
+ rdatasetheader_t *header, *header_prev, *header_next;
+ rdatasetheader_t *dname_header, *sigdname_header;
+ isc_result_t result;
+ nodelock_t *lock;
+ isc_rwlocktype_t locktype;
+
+ /* XXX comment */
+
+ REQUIRE(search->zonecut == NULL);
+
+ /*
+ * Keep compiler silent.
+ */
+ UNUSED(name);
+
+ lock = &(search->rbtdb->node_locks[node->locknum].lock);
+ locktype = isc_rwlocktype_read;
+ NODE_LOCK(lock, locktype);
+
+ /*
+ * Look for a DNAME or RRSIG DNAME rdataset.
+ */
+ dname_header = NULL;
+ sigdname_header = NULL;
+ header_prev = NULL;
+ for (header = node->data; header != NULL; header = header_next) {
+ header_next = header->next;
+ if (check_stale_header(node, header, &locktype, lock, search,
+ &header_prev))
+ {
+ /* Do nothing. */
+ } else if (header->type == dns_rdatatype_dname &&
+ EXISTS(header) && !ANCIENT(header))
+ {
+ dname_header = header;
+ header_prev = header;
+ } else if (header->type == RBTDB_RDATATYPE_SIGDNAME &&
+ EXISTS(header) && !ANCIENT(header))
+ {
+ sigdname_header = header;
+ header_prev = header;
+ } else {
+ header_prev = header;
+ }
+ }
+
+ if (dname_header != NULL &&
+ (!DNS_TRUST_PENDING(dname_header->trust) ||
+ (search->options & DNS_DBFIND_PENDINGOK) != 0))
+ {
+ /*
+ * We increment the reference count on node to ensure that
+ * search->zonecut_rdataset will still be valid later.
+ */
+ new_reference(search->rbtdb, node, locktype);
+ search->zonecut = node;
+ search->zonecut_rdataset = dname_header;
+ search->zonecut_sigrdataset = sigdname_header;
+ search->need_cleanup = true;
+ result = DNS_R_PARTIALMATCH;
+ } else {
+ result = DNS_R_CONTINUE;
+ }
+
+ NODE_UNLOCK(lock, locktype);
+
+ return (result);
+}
+
+static isc_result_t
+find_deepest_zonecut(rbtdb_search_t *search, dns_rbtnode_t *node,
+ dns_dbnode_t **nodep, dns_name_t *foundname,
+ dns_rdataset_t *rdataset, dns_rdataset_t *sigrdataset) {
+ unsigned int i;
+ dns_rbtnode_t *level_node;
+ rdatasetheader_t *header, *header_prev, *header_next;
+ rdatasetheader_t *found, *foundsig;
+ isc_result_t result = ISC_R_NOTFOUND;
+ dns_name_t name;
+ dns_rbtdb_t *rbtdb;
+ bool done;
+ nodelock_t *lock;
+ isc_rwlocktype_t locktype;
+
+ /*
+ * Caller must be holding the tree lock.
+ */
+
+ rbtdb = search->rbtdb;
+ i = search->chain.level_matches;
+ done = false;
+ do {
+ locktype = isc_rwlocktype_read;
+ lock = &rbtdb->node_locks[node->locknum].lock;
+ NODE_LOCK(lock, locktype);
+
+ /*
+ * Look for NS and RRSIG NS rdatasets.
+ */
+ found = NULL;
+ foundsig = NULL;
+ header_prev = NULL;
+ for (header = node->data; header != NULL; header = header_next)
+ {
+ header_next = header->next;
+ if (check_stale_header(node, header, &locktype, lock,
+ search, &header_prev))
+ {
+ /* Do nothing. */
+ } else if (EXISTS(header) && !ANCIENT(header)) {
+ /*
+ * We've found an extant rdataset. See if
+ * we're interested in it.
+ */
+ if (header->type == dns_rdatatype_ns) {
+ found = header;
+ if (foundsig != NULL) {
+ break;
+ }
+ } else if (header->type ==
+ RBTDB_RDATATYPE_SIGNS)
+ {
+ foundsig = header;
+ if (found != NULL) {
+ break;
+ }
+ }
+ header_prev = header;
+ } else {
+ header_prev = header;
+ }
+ }
+
+ if (found != NULL) {
+ /*
+ * If we have to set foundname, we do it before
+ * anything else. If we were to set foundname after
+ * we had set nodep or bound the rdataset, then we'd
+ * have to undo that work if dns_name_concatenate()
+ * failed. By setting foundname first, there's
+ * nothing to undo if we have trouble.
+ */
+ if (foundname != NULL) {
+ dns_name_init(&name, NULL);
+ dns_rbt_namefromnode(node, &name);
+ dns_name_copy(&name, foundname);
+ while (i > 0) {
+ i--;
+ level_node = search->chain.levels[i];
+ dns_name_init(&name, NULL);
+ dns_rbt_namefromnode(level_node, &name);
+ result = dns_name_concatenate(
+ foundname, &name, foundname,
+ NULL);
+ if (result != ISC_R_SUCCESS) {
+ if (nodep != NULL) {
+ *nodep = NULL;
+ }
+ goto node_exit;
+ }
+ }
+ }
+ result = DNS_R_DELEGATION;
+ if (nodep != NULL) {
+ new_reference(search->rbtdb, node, locktype);
+ *nodep = node;
+ }
+ bind_rdataset(search->rbtdb, node, found, search->now,
+ locktype, rdataset);
+ if (foundsig != NULL) {
+ bind_rdataset(search->rbtdb, node, foundsig,
+ search->now, locktype,
+ sigrdataset);
+ }
+ if (need_headerupdate(found, search->now) ||
+ (foundsig != NULL &&
+ need_headerupdate(foundsig, search->now)))
+ {
+ if (locktype != isc_rwlocktype_write) {
+ NODE_UNLOCK(lock, locktype);
+ NODE_LOCK(lock, isc_rwlocktype_write);
+ locktype = isc_rwlocktype_write;
+ POST(locktype);
+ }
+ if (need_headerupdate(found, search->now)) {
+ update_header(search->rbtdb, found,
+ search->now);
+ }
+ if (foundsig != NULL &&
+ need_headerupdate(foundsig, search->now))
+ {
+ update_header(search->rbtdb, foundsig,
+ search->now);
+ }
+ }
+ }
+
+ node_exit:
+ NODE_UNLOCK(lock, locktype);
+
+ if (found == NULL && i > 0) {
+ i--;
+ node = search->chain.levels[i];
+ } else {
+ done = true;
+ }
+ } while (!done);
+
+ return (result);
+}
+
+/*
+ * Look for a potentially covering NSEC in the cache where `name`
+ * is known not to exist. This uses the auxiliary NSEC tree to find
+ * the potential NSEC owner. If found, we update 'foundname', 'nodep',
+ * 'rdataset' and 'sigrdataset', and return DNS_R_COVERINGNSEC.
+ * Otherwise, return ISC_R_NOTFOUND.
+ */
+static isc_result_t
+find_coveringnsec(rbtdb_search_t *search, const dns_name_t *name,
+ dns_dbnode_t **nodep, isc_stdtime_t now,
+ dns_name_t *foundname, dns_rdataset_t *rdataset,
+ dns_rdataset_t *sigrdataset) {
+ dns_fixedname_t fprefix, forigin, ftarget, fixed;
+ dns_name_t *prefix = NULL, *origin = NULL;
+ dns_name_t *target = NULL, *fname = NULL;
+ dns_rbtnode_t *node = NULL;
+ dns_rbtnodechain_t chain;
+ isc_result_t result;
+ isc_rwlocktype_t locktype;
+ nodelock_t *lock = NULL;
+ rbtdb_rdatatype_t matchtype, sigmatchtype;
+ rdatasetheader_t *found = NULL, *foundsig = NULL;
+ rdatasetheader_t *header = NULL;
+ rdatasetheader_t *header_next = NULL, *header_prev = NULL;
+
+ /*
+ * Look for the node in the auxilary tree.
+ */
+ dns_rbtnodechain_init(&chain);
+ target = dns_fixedname_initname(&ftarget);
+ result = dns_rbt_findnode(search->rbtdb->nsec, name, target, &node,
+ &chain, DNS_RBTFIND_EMPTYDATA, NULL, NULL);
+ if (result != DNS_R_PARTIALMATCH) {
+ dns_rbtnodechain_reset(&chain);
+ return (ISC_R_NOTFOUND);
+ }
+
+ prefix = dns_fixedname_initname(&fprefix);
+ origin = dns_fixedname_initname(&forigin);
+ target = dns_fixedname_initname(&ftarget);
+ fname = dns_fixedname_initname(&fixed);
+
+ locktype = isc_rwlocktype_read;
+ matchtype = RBTDB_RDATATYPE_VALUE(dns_rdatatype_nsec, 0);
+ sigmatchtype = RBTDB_RDATATYPE_VALUE(dns_rdatatype_rrsig,
+ dns_rdatatype_nsec);
+
+ /*
+ * Extract predecessor from chain.
+ */
+ result = dns_rbtnodechain_current(&chain, prefix, origin, NULL);
+ dns_rbtnodechain_reset(&chain);
+ if (result != ISC_R_SUCCESS && result != DNS_R_NEWORIGIN) {
+ return (ISC_R_NOTFOUND);
+ }
+
+ result = dns_name_concatenate(prefix, origin, target, NULL);
+ if (result != ISC_R_SUCCESS) {
+ return (ISC_R_NOTFOUND);
+ }
+
+ /*
+ * Lookup the predecessor in the main tree.
+ */
+ node = NULL;
+ result = dns_rbt_findnode(search->rbtdb->tree, target, fname, &node,
+ NULL, DNS_RBTFIND_EMPTYDATA, NULL, NULL);
+ if (result != ISC_R_SUCCESS) {
+ return (ISC_R_NOTFOUND);
+ }
+
+ lock = &(search->rbtdb->node_locks[node->locknum].lock);
+ NODE_LOCK(lock, locktype);
+ for (header = node->data; header != NULL; header = header_next) {
+ header_next = header->next;
+ if (check_stale_header(node, header, &locktype, lock, search,
+ &header_prev))
+ {
+ continue;
+ }
+ if (NONEXISTENT(header) ||
+ RBTDB_RDATATYPE_BASE(header->type) == 0)
+ {
+ header_prev = header;
+ continue;
+ }
+ if (header->type == matchtype) {
+ found = header;
+ if (foundsig != NULL) {
+ break;
+ }
+ } else if (header->type == sigmatchtype) {
+ foundsig = header;
+ if (found != NULL) {
+ break;
+ }
+ }
+ header_prev = header;
+ }
+ if (found != NULL) {
+ bind_rdataset(search->rbtdb, node, found, now, locktype,
+ rdataset);
+ if (foundsig != NULL) {
+ bind_rdataset(search->rbtdb, node, foundsig, now,
+ locktype, sigrdataset);
+ }
+ new_reference(search->rbtdb, node, locktype);
+
+ dns_name_copy(fname, foundname);
+
+ *nodep = node;
+ result = DNS_R_COVERINGNSEC;
+ } else {
+ result = ISC_R_NOTFOUND;
+ }
+ NODE_UNLOCK(lock, locktype);
+ return (result);
+}
+
+static isc_result_t
+cache_find(dns_db_t *db, const dns_name_t *name, dns_dbversion_t *version,
+ dns_rdatatype_t type, unsigned int options, isc_stdtime_t now,
+ dns_dbnode_t **nodep, dns_name_t *foundname,
+ dns_rdataset_t *rdataset, dns_rdataset_t *sigrdataset) {
+ dns_rbtnode_t *node = NULL;
+ isc_result_t result;
+ rbtdb_search_t search;
+ bool cname_ok = true;
+ bool found_noqname = false;
+ bool all_negative = true;
+ bool empty_node;
+ nodelock_t *lock;
+ isc_rwlocktype_t locktype;
+ rdatasetheader_t *header, *header_prev, *header_next;
+ rdatasetheader_t *found, *nsheader;
+ rdatasetheader_t *foundsig, *nssig, *cnamesig;
+ rdatasetheader_t *update, *updatesig;
+ rdatasetheader_t *nsecheader, *nsecsig;
+ rbtdb_rdatatype_t sigtype, negtype;
+
+ UNUSED(version);
+
+ search.rbtdb = (dns_rbtdb_t *)db;
+
+ REQUIRE(VALID_RBTDB(search.rbtdb));
+ REQUIRE(version == NULL);
+
+ if (now == 0) {
+ isc_stdtime_get(&now);
+ }
+
+ search.rbtversion = NULL;
+ search.serial = 1;
+ search.options = options;
+ search.copy_name = false;
+ search.need_cleanup = false;
+ search.wild = false;
+ search.zonecut = NULL;
+ search.zonecut_rdataset = NULL;
+ search.zonecut_sigrdataset = NULL;
+ dns_fixedname_init(&search.zonecut_name);
+ dns_rbtnodechain_init(&search.chain);
+ search.now = now;
+ update = NULL;
+ updatesig = NULL;
+
+ RWLOCK(&search.rbtdb->tree_lock, isc_rwlocktype_read);
+
+ /*
+ * Search down from the root of the tree. If, while going down, we
+ * encounter a callback node, cache_zonecut_callback() will search the
+ * rdatasets at the zone cut for a DNAME rdataset.
+ */
+ result = dns_rbt_findnode(search.rbtdb->tree, name, foundname, &node,
+ &search.chain, DNS_RBTFIND_EMPTYDATA,
+ cache_zonecut_callback, &search);
+
+ if (result == DNS_R_PARTIALMATCH) {
+ /*
+ * If dns_rbt_findnode discovered a covering DNAME skip
+ * looking for a covering NSEC.
+ */
+ if ((search.options & DNS_DBFIND_COVERINGNSEC) != 0 &&
+ (search.zonecut_rdataset == NULL ||
+ search.zonecut_rdataset->type != dns_rdatatype_dname))
+ {
+ result = find_coveringnsec(&search, name, nodep, now,
+ foundname, rdataset,
+ sigrdataset);
+ if (result == DNS_R_COVERINGNSEC) {
+ goto tree_exit;
+ }
+ }
+ if (search.zonecut != NULL) {
+ result = setup_delegation(&search, nodep, foundname,
+ rdataset, sigrdataset);
+ goto tree_exit;
+ } else {
+ find_ns:
+ result = find_deepest_zonecut(&search, node, nodep,
+ foundname, rdataset,
+ sigrdataset);
+ goto tree_exit;
+ }
+ } else if (result != ISC_R_SUCCESS) {
+ goto tree_exit;
+ }
+
+ /*
+ * Certain DNSSEC types are not subject to CNAME matching
+ * (RFC4035, section 2.5 and RFC3007).
+ *
+ * We don't check for RRSIG, because we don't store RRSIG records
+ * directly.
+ */
+ if (type == dns_rdatatype_key || type == dns_rdatatype_nsec) {
+ cname_ok = false;
+ }
+
+ /*
+ * We now go looking for rdata...
+ */
+
+ lock = &(search.rbtdb->node_locks[node->locknum].lock);
+ locktype = isc_rwlocktype_read;
+ NODE_LOCK(lock, locktype);
+
+ found = NULL;
+ foundsig = NULL;
+ sigtype = RBTDB_RDATATYPE_VALUE(dns_rdatatype_rrsig, type);
+ negtype = RBTDB_RDATATYPE_VALUE(0, type);
+ nsheader = NULL;
+ nsecheader = NULL;
+ nssig = NULL;
+ nsecsig = NULL;
+ cnamesig = NULL;
+ empty_node = true;
+ header_prev = NULL;
+ for (header = node->data; header != NULL; header = header_next) {
+ header_next = header->next;
+ if (check_stale_header(node, header, &locktype, lock, &search,
+ &header_prev))
+ {
+ /* Do nothing. */
+ } else if (EXISTS(header) && !ANCIENT(header)) {
+ /*
+ * We now know that there is at least one active
+ * non-stale rdataset at this node.
+ */
+ empty_node = false;
+ if (header->noqname != NULL &&
+ header->trust == dns_trust_secure)
+ {
+ found_noqname = true;
+ }
+ if (!NEGATIVE(header)) {
+ all_negative = false;
+ }
+
+ /*
+ * If we found a type we were looking for, remember
+ * it.
+ */
+ if (header->type == type ||
+ (type == dns_rdatatype_any &&
+ RBTDB_RDATATYPE_BASE(header->type) != 0) ||
+ (cname_ok && header->type == dns_rdatatype_cname))
+ {
+ /*
+ * We've found the answer.
+ */
+ found = header;
+ if (header->type == dns_rdatatype_cname &&
+ cname_ok && cnamesig != NULL)
+ {
+ /*
+ * If we've already got the
+ * CNAME RRSIG, use it.
+ */
+ foundsig = cnamesig;
+ }
+ } else if (header->type == sigtype) {
+ /*
+ * We've found the RRSIG rdataset for our
+ * target type. Remember it.
+ */
+ foundsig = header;
+ } else if (header->type == RBTDB_RDATATYPE_NCACHEANY ||
+ header->type == negtype)
+ {
+ /*
+ * We've found a negative cache entry.
+ */
+ found = header;
+ } else if (header->type == dns_rdatatype_ns) {
+ /*
+ * Remember a NS rdataset even if we're
+ * not specifically looking for it, because
+ * we might need it later.
+ */
+ nsheader = header;
+ } else if (header->type == RBTDB_RDATATYPE_SIGNS) {
+ /*
+ * If we need the NS rdataset, we'll also
+ * need its signature.
+ */
+ nssig = header;
+ } else if (header->type == dns_rdatatype_nsec) {
+ nsecheader = header;
+ } else if (header->type == RBTDB_RDATATYPE_SIGNSEC) {
+ nsecsig = header;
+ } else if (cname_ok &&
+ header->type == RBTDB_RDATATYPE_SIGCNAME)
+ {
+ /*
+ * If we get a CNAME match, we'll also need
+ * its signature.
+ */
+ cnamesig = header;
+ }
+ header_prev = header;
+ } else {
+ header_prev = header;
+ }
+ }
+
+ if (empty_node) {
+ /*
+ * We have an exact match for the name, but there are no
+ * extant rdatasets. That means that this node doesn't
+ * meaningfully exist, and that we really have a partial match.
+ */
+ NODE_UNLOCK(lock, locktype);
+ if ((search.options & DNS_DBFIND_COVERINGNSEC) != 0) {
+ result = find_coveringnsec(&search, name, nodep, now,
+ foundname, rdataset,
+ sigrdataset);
+ if (result == DNS_R_COVERINGNSEC) {
+ goto tree_exit;
+ }
+ }
+ goto find_ns;
+ }
+
+ /*
+ * If we didn't find what we were looking for...
+ */
+ if (found == NULL ||
+ (DNS_TRUST_ADDITIONAL(found->trust) &&
+ ((options & DNS_DBFIND_ADDITIONALOK) == 0)) ||
+ (found->trust == dns_trust_glue &&
+ ((options & DNS_DBFIND_GLUEOK) == 0)) ||
+ (DNS_TRUST_PENDING(found->trust) &&
+ ((options & DNS_DBFIND_PENDINGOK) == 0)))
+ {
+ /*
+ * Return covering NODATA NSEC record.
+ */
+ if ((search.options & DNS_DBFIND_COVERINGNSEC) != 0 &&
+ nsecheader != NULL)
+ {
+ if (nodep != NULL) {
+ new_reference(search.rbtdb, node, locktype);
+ *nodep = node;
+ }
+ bind_rdataset(search.rbtdb, node, nsecheader,
+ search.now, locktype, rdataset);
+ if (need_headerupdate(nsecheader, search.now)) {
+ update = nsecheader;
+ }
+ if (nsecsig != NULL) {
+ bind_rdataset(search.rbtdb, node, nsecsig,
+ search.now, locktype,
+ sigrdataset);
+ if (need_headerupdate(nsecsig, search.now)) {
+ updatesig = nsecsig;
+ }
+ }
+ result = DNS_R_COVERINGNSEC;
+ goto node_exit;
+ }
+
+ /*
+ * This name was from a wild card. Look for a covering NSEC.
+ */
+ if (found == NULL && (found_noqname || all_negative) &&
+ (search.options & DNS_DBFIND_COVERINGNSEC) != 0)
+ {
+ NODE_UNLOCK(lock, locktype);
+ result = find_coveringnsec(&search, name, nodep, now,
+ foundname, rdataset,
+ sigrdataset);
+ if (result == DNS_R_COVERINGNSEC) {
+ goto tree_exit;
+ }
+ goto find_ns;
+ }
+
+ /*
+ * If there is an NS rdataset at this node, then this is the
+ * deepest zone cut.
+ */
+ if (nsheader != NULL) {
+ if (nodep != NULL) {
+ new_reference(search.rbtdb, node, locktype);
+ *nodep = node;
+ }
+ bind_rdataset(search.rbtdb, node, nsheader, search.now,
+ locktype, rdataset);
+ if (need_headerupdate(nsheader, search.now)) {
+ update = nsheader;
+ }
+ if (nssig != NULL) {
+ bind_rdataset(search.rbtdb, node, nssig,
+ search.now, locktype,
+ sigrdataset);
+ if (need_headerupdate(nssig, search.now)) {
+ updatesig = nssig;
+ }
+ }
+ result = DNS_R_DELEGATION;
+ goto node_exit;
+ }
+
+ /*
+ * Go find the deepest zone cut.
+ */
+ NODE_UNLOCK(lock, locktype);
+ goto find_ns;
+ }
+
+ /*
+ * We found what we were looking for, or we found a CNAME.
+ */
+
+ if (nodep != NULL) {
+ new_reference(search.rbtdb, node, locktype);
+ *nodep = node;
+ }
+
+ if (NEGATIVE(found)) {
+ /*
+ * We found a negative cache entry.
+ */
+ if (NXDOMAIN(found)) {
+ result = DNS_R_NCACHENXDOMAIN;
+ } else {
+ result = DNS_R_NCACHENXRRSET;
+ }
+ } else if (type != found->type && type != dns_rdatatype_any &&
+ found->type == dns_rdatatype_cname)
+ {
+ /*
+ * We weren't doing an ANY query and we found a CNAME instead
+ * of the type we were looking for, so we need to indicate
+ * that result to the caller.
+ */
+ result = DNS_R_CNAME;
+ } else {
+ /*
+ * An ordinary successful query!
+ */
+ result = ISC_R_SUCCESS;
+ }
+
+ if (type != dns_rdatatype_any || result == DNS_R_NCACHENXDOMAIN ||
+ result == DNS_R_NCACHENXRRSET)
+ {
+ bind_rdataset(search.rbtdb, node, found, search.now, locktype,
+ rdataset);
+ if (need_headerupdate(found, search.now)) {
+ update = found;
+ }
+ if (!NEGATIVE(found) && foundsig != NULL) {
+ bind_rdataset(search.rbtdb, node, foundsig, search.now,
+ locktype, sigrdataset);
+ if (need_headerupdate(foundsig, search.now)) {
+ updatesig = foundsig;
+ }
+ }
+ }
+
+node_exit:
+ if ((update != NULL || updatesig != NULL) &&
+ locktype != isc_rwlocktype_write)
+ {
+ NODE_UNLOCK(lock, locktype);
+ NODE_LOCK(lock, isc_rwlocktype_write);
+ locktype = isc_rwlocktype_write;
+ POST(locktype);
+ }
+ if (update != NULL && need_headerupdate(update, search.now)) {
+ update_header(search.rbtdb, update, search.now);
+ }
+ if (updatesig != NULL && need_headerupdate(updatesig, search.now)) {
+ update_header(search.rbtdb, updatesig, search.now);
+ }
+
+ NODE_UNLOCK(lock, locktype);
+
+tree_exit:
+ RWUNLOCK(&search.rbtdb->tree_lock, isc_rwlocktype_read);
+
+ /*
+ * If we found a zonecut but aren't going to use it, we have to
+ * let go of it.
+ */
+ if (search.need_cleanup) {
+ node = search.zonecut;
+ INSIST(node != NULL);
+ lock = &(search.rbtdb->node_locks[node->locknum].lock);
+
+ NODE_LOCK(lock, isc_rwlocktype_read);
+ decrement_reference(search.rbtdb, node, 0, isc_rwlocktype_read,
+ isc_rwlocktype_none, false);
+ NODE_UNLOCK(lock, isc_rwlocktype_read);
+ }
+
+ dns_rbtnodechain_reset(&search.chain);
+
+ update_cachestats(search.rbtdb, result);
+ return (result);
+}
+
+static isc_result_t
+cache_findzonecut(dns_db_t *db, const dns_name_t *name, unsigned int options,
+ isc_stdtime_t now, dns_dbnode_t **nodep,
+ dns_name_t *foundname, dns_name_t *dcname,
+ dns_rdataset_t *rdataset, dns_rdataset_t *sigrdataset) {
+ dns_rbtnode_t *node = NULL;
+ nodelock_t *lock;
+ isc_result_t result;
+ rbtdb_search_t search;
+ rdatasetheader_t *header, *header_prev, *header_next;
+ rdatasetheader_t *found, *foundsig;
+ unsigned int rbtoptions = DNS_RBTFIND_EMPTYDATA;
+ isc_rwlocktype_t locktype;
+ bool dcnull = (dcname == NULL);
+
+ search.rbtdb = (dns_rbtdb_t *)db;
+
+ REQUIRE(VALID_RBTDB(search.rbtdb));
+
+ if (now == 0) {
+ isc_stdtime_get(&now);
+ }
+
+ search.rbtversion = NULL;
+ search.serial = 1;
+ search.options = options;
+ search.copy_name = false;
+ search.need_cleanup = false;
+ search.wild = false;
+ search.zonecut = NULL;
+ dns_fixedname_init(&search.zonecut_name);
+ dns_rbtnodechain_init(&search.chain);
+ search.now = now;
+
+ if (dcnull) {
+ dcname = foundname;
+ }
+
+ if ((options & DNS_DBFIND_NOEXACT) != 0) {
+ rbtoptions |= DNS_RBTFIND_NOEXACT;
+ }
+
+ RWLOCK(&search.rbtdb->tree_lock, isc_rwlocktype_read);
+
+ /*
+ * Search down from the root of the tree.
+ */
+ result = dns_rbt_findnode(search.rbtdb->tree, name, dcname, &node,
+ &search.chain, rbtoptions, NULL, &search);
+
+ if (result == DNS_R_PARTIALMATCH) {
+ result = find_deepest_zonecut(&search, node, nodep, foundname,
+ rdataset, sigrdataset);
+ goto tree_exit;
+ } else if (result != ISC_R_SUCCESS) {
+ goto tree_exit;
+ } else if (!dcnull) {
+ dns_name_copy(dcname, foundname);
+ }
+
+ /*
+ * We now go looking for an NS rdataset at the node.
+ */
+
+ lock = &(search.rbtdb->node_locks[node->locknum].lock);
+ locktype = isc_rwlocktype_read;
+ NODE_LOCK(lock, locktype);
+
+ found = NULL;
+ foundsig = NULL;
+ header_prev = NULL;
+ for (header = node->data; header != NULL; header = header_next) {
+ header_next = header->next;
+ if (check_stale_header(node, header, &locktype, lock, &search,
+ &header_prev))
+ {
+ /*
+ * The function dns_rbt_findnode found us the a matching
+ * node for 'name' and stored the result in 'dcname'.
+ * This is the deepest known zonecut in our database.
+ * However, this node may be stale and if serve-stale
+ * is not enabled (in other words 'stale-answer-enable'
+ * is set to no), this node may not be used as a
+ * zonecut we know about. If so, find the deepest
+ * zonecut from this node up and return that instead.
+ */
+ NODE_UNLOCK(lock, locktype);
+ result = find_deepest_zonecut(&search, node, nodep,
+ foundname, rdataset,
+ sigrdataset);
+ dns_name_copy(foundname, dcname);
+ goto tree_exit;
+ } else if (EXISTS(header) && !ANCIENT(header)) {
+ /*
+ * If we found a type we were looking for, remember
+ * it.
+ */
+ if (header->type == dns_rdatatype_ns) {
+ /*
+ * Remember a NS rdataset even if we're
+ * not specifically looking for it, because
+ * we might need it later.
+ */
+ found = header;
+ } else if (header->type == RBTDB_RDATATYPE_SIGNS) {
+ /*
+ * If we need the NS rdataset, we'll also
+ * need its signature.
+ */
+ foundsig = header;
+ }
+ header_prev = header;
+ } else {
+ header_prev = header;
+ }
+ }
+
+ if (found == NULL) {
+ /*
+ * No NS records here.
+ */
+ NODE_UNLOCK(lock, locktype);
+ result = find_deepest_zonecut(&search, node, nodep, foundname,
+ rdataset, sigrdataset);
+ goto tree_exit;
+ }
+
+ if (nodep != NULL) {
+ new_reference(search.rbtdb, node, locktype);
+ *nodep = node;
+ }
+
+ bind_rdataset(search.rbtdb, node, found, search.now, locktype,
+ rdataset);
+ if (foundsig != NULL) {
+ bind_rdataset(search.rbtdb, node, foundsig, search.now,
+ locktype, sigrdataset);
+ }
+
+ if (need_headerupdate(found, search.now) ||
+ (foundsig != NULL && need_headerupdate(foundsig, search.now)))
+ {
+ if (locktype != isc_rwlocktype_write) {
+ NODE_UNLOCK(lock, locktype);
+ NODE_LOCK(lock, isc_rwlocktype_write);
+ locktype = isc_rwlocktype_write;
+ POST(locktype);
+ }
+ if (need_headerupdate(found, search.now)) {
+ update_header(search.rbtdb, found, search.now);
+ }
+ if (foundsig != NULL && need_headerupdate(foundsig, search.now))
+ {
+ update_header(search.rbtdb, foundsig, search.now);
+ }
+ }
+
+ NODE_UNLOCK(lock, locktype);
+
+tree_exit:
+ RWUNLOCK(&search.rbtdb->tree_lock, isc_rwlocktype_read);
+
+ INSIST(!search.need_cleanup);
+
+ dns_rbtnodechain_reset(&search.chain);
+
+ if (result == DNS_R_DELEGATION) {
+ result = ISC_R_SUCCESS;
+ }
+
+ return (result);
+}
+
+static void
+attachnode(dns_db_t *db, dns_dbnode_t *source, dns_dbnode_t **targetp) {
+ dns_rbtdb_t *rbtdb = (dns_rbtdb_t *)db;
+ dns_rbtnode_t *node = (dns_rbtnode_t *)source;
+
+ REQUIRE(VALID_RBTDB(rbtdb));
+ REQUIRE(targetp != NULL && *targetp == NULL);
+
+ isc_refcount_increment(&node->references);
+
+ *targetp = source;
+}
+
+static void
+detachnode(dns_db_t *db, dns_dbnode_t **targetp) {
+ dns_rbtdb_t *rbtdb = (dns_rbtdb_t *)db;
+ dns_rbtnode_t *node;
+ bool want_free = false;
+ bool inactive = false;
+ rbtdb_nodelock_t *nodelock;
+
+ REQUIRE(VALID_RBTDB(rbtdb));
+ REQUIRE(targetp != NULL && *targetp != NULL);
+
+ node = (dns_rbtnode_t *)(*targetp);
+ nodelock = &rbtdb->node_locks[node->locknum];
+
+ NODE_LOCK(&nodelock->lock, isc_rwlocktype_read);
+
+ if (decrement_reference(rbtdb, node, 0, isc_rwlocktype_read,
+ isc_rwlocktype_none, false))
+ {
+ if (isc_refcount_current(&nodelock->references) == 0 &&
+ nodelock->exiting)
+ {
+ inactive = true;
+ }
+ }
+
+ NODE_UNLOCK(&nodelock->lock, isc_rwlocktype_read);
+
+ *targetp = NULL;
+
+ if (inactive) {
+ RBTDB_LOCK(&rbtdb->lock, isc_rwlocktype_write);
+ rbtdb->active--;
+ if (rbtdb->active == 0) {
+ want_free = true;
+ }
+ RBTDB_UNLOCK(&rbtdb->lock, isc_rwlocktype_write);
+ if (want_free) {
+ char buf[DNS_NAME_FORMATSIZE];
+ if (dns_name_dynamic(&rbtdb->common.origin)) {
+ dns_name_format(&rbtdb->common.origin, buf,
+ sizeof(buf));
+ } else {
+ strlcpy(buf, "<UNKNOWN>", sizeof(buf));
+ }
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE,
+ DNS_LOGMODULE_CACHE, ISC_LOG_DEBUG(1),
+ "calling free_rbtdb(%s)", buf);
+ free_rbtdb(rbtdb, true, NULL);
+ }
+ }
+}
+
+static isc_result_t
+expirenode(dns_db_t *db, dns_dbnode_t *node, isc_stdtime_t now) {
+ dns_rbtdb_t *rbtdb = (dns_rbtdb_t *)db;
+ dns_rbtnode_t *rbtnode = node;
+ rdatasetheader_t *header;
+ bool force_expire = false;
+ /*
+ * These are the category and module used by the cache cleaner.
+ */
+ bool log = false;
+ isc_logcategory_t *category = DNS_LOGCATEGORY_DATABASE;
+ isc_logmodule_t *module = DNS_LOGMODULE_CACHE;
+ int level = ISC_LOG_DEBUG(2);
+ char printname[DNS_NAME_FORMATSIZE];
+
+ REQUIRE(VALID_RBTDB(rbtdb));
+
+ /*
+ * Caller must hold a tree lock.
+ */
+
+ if (now == 0) {
+ isc_stdtime_get(&now);
+ }
+
+ if (isc_mem_isovermem(rbtdb->common.mctx)) {
+ /*
+ * Force expire with 25% probability.
+ * XXXDCL Could stand to have a better policy, like LRU.
+ */
+ force_expire = (rbtnode->down == NULL &&
+ (isc_random32() % 4) == 0);
+
+ /*
+ * Note that 'log' can be true IFF overmem is also true.
+ * overmem can currently only be true for cache
+ * databases -- hence all of the "overmem cache" log strings.
+ */
+ log = isc_log_wouldlog(dns_lctx, level);
+ if (log) {
+ isc_log_write(
+ dns_lctx, category, module, level,
+ "overmem cache: %s %s",
+ force_expire ? "FORCE" : "check",
+ dns_rbt_formatnodename(rbtnode, printname,
+ sizeof(printname)));
+ }
+ }
+
+ /*
+ * We may not need write access, but this code path is not performance
+ * sensitive, so it should be okay to always lock as a writer.
+ */
+ NODE_LOCK(&rbtdb->node_locks[rbtnode->locknum].lock,
+ isc_rwlocktype_write);
+
+ for (header = rbtnode->data; header != NULL; header = header->next) {
+ if (header->rdh_ttl + STALE_TTL(header, rbtdb) <=
+ now - RBTDB_VIRTUAL)
+ {
+ /*
+ * We don't check if refcurrent(rbtnode) == 0 and try
+ * to free like we do in cache_find(), because
+ * refcurrent(rbtnode) must be non-zero. This is so
+ * because 'node' is an argument to the function.
+ */
+ mark_header_ancient(rbtdb, header);
+ if (log) {
+ isc_log_write(dns_lctx, category, module, level,
+ "overmem cache: ancient %s",
+ printname);
+ }
+ } else if (force_expire) {
+ if (!RETAIN(header)) {
+ set_ttl(rbtdb, header, 0);
+ mark_header_ancient(rbtdb, header);
+ } else if (log) {
+ isc_log_write(dns_lctx, category, module, level,
+ "overmem cache: "
+ "reprieve by RETAIN() %s",
+ printname);
+ }
+ } else if (isc_mem_isovermem(rbtdb->common.mctx) && log) {
+ isc_log_write(dns_lctx, category, module, level,
+ "overmem cache: saved %s", printname);
+ }
+ }
+
+ NODE_UNLOCK(&rbtdb->node_locks[rbtnode->locknum].lock,
+ isc_rwlocktype_write);
+
+ return (ISC_R_SUCCESS);
+}
+
+static void
+overmem(dns_db_t *db, bool over) {
+ /* This is an empty callback. See adb.c:water() */
+
+ UNUSED(db);
+ UNUSED(over);
+
+ return;
+}
+
+static void
+printnode(dns_db_t *db, dns_dbnode_t *node, FILE *out) {
+ dns_rbtdb_t *rbtdb = (dns_rbtdb_t *)db;
+ dns_rbtnode_t *rbtnode = node;
+ bool first;
+ uint32_t refs;
+
+ REQUIRE(VALID_RBTDB(rbtdb));
+
+ NODE_LOCK(&rbtdb->node_locks[rbtnode->locknum].lock,
+ isc_rwlocktype_read);
+
+ refs = isc_refcount_current(&rbtnode->references);
+ fprintf(out, "node %p, %" PRIu32 " references, locknum = %u\n", rbtnode,
+ refs, rbtnode->locknum);
+ if (rbtnode->data != NULL) {
+ rdatasetheader_t *current, *top_next;
+
+ for (current = rbtnode->data; current != NULL;
+ current = top_next)
+ {
+ top_next = current->next;
+ first = true;
+ fprintf(out, "\ttype %u", current->type);
+ do {
+ uint_least16_t attributes = atomic_load_acquire(
+ &current->attributes);
+ if (!first) {
+ fprintf(out, "\t");
+ }
+ first = false;
+ fprintf(out,
+ "\tserial = %lu, ttl = %u, "
+ "trust = %u, attributes = %" PRIuLEAST16
+ ", "
+ "resign = %u\n",
+ (unsigned long)current->serial,
+ current->rdh_ttl, current->trust,
+ attributes,
+ (current->resign << 1) |
+ current->resign_lsb);
+ current = current->down;
+ } while (current != NULL);
+ }
+ } else {
+ fprintf(out, "(empty)\n");
+ }
+
+ NODE_UNLOCK(&rbtdb->node_locks[rbtnode->locknum].lock,
+ isc_rwlocktype_read);
+}
+
+static isc_result_t
+createiterator(dns_db_t *db, unsigned int options,
+ dns_dbiterator_t **iteratorp) {
+ dns_rbtdb_t *rbtdb = (dns_rbtdb_t *)db;
+ rbtdb_dbiterator_t *rbtdbiter;
+
+ REQUIRE(VALID_RBTDB(rbtdb));
+
+ rbtdbiter = isc_mem_get(rbtdb->common.mctx, sizeof(*rbtdbiter));
+
+ rbtdbiter->common.methods = &dbiterator_methods;
+ rbtdbiter->common.db = NULL;
+ dns_db_attach(db, &rbtdbiter->common.db);
+ rbtdbiter->common.relative_names = ((options & DNS_DB_RELATIVENAMES) !=
+ 0);
+ rbtdbiter->common.magic = DNS_DBITERATOR_MAGIC;
+ rbtdbiter->common.cleaning = false;
+ rbtdbiter->paused = true;
+ rbtdbiter->tree_locked = isc_rwlocktype_none;
+ rbtdbiter->result = ISC_R_SUCCESS;
+ dns_fixedname_init(&rbtdbiter->name);
+ dns_fixedname_init(&rbtdbiter->origin);
+ rbtdbiter->node = NULL;
+ rbtdbiter->delcnt = 0;
+ rbtdbiter->nsec3only = ((options & DNS_DB_NSEC3ONLY) != 0);
+ rbtdbiter->nonsec3 = ((options & DNS_DB_NONSEC3) != 0);
+ memset(rbtdbiter->deletions, 0, sizeof(rbtdbiter->deletions));
+ dns_rbtnodechain_init(&rbtdbiter->chain);
+ dns_rbtnodechain_init(&rbtdbiter->nsec3chain);
+ if (rbtdbiter->nsec3only) {
+ rbtdbiter->current = &rbtdbiter->nsec3chain;
+ } else {
+ rbtdbiter->current = &rbtdbiter->chain;
+ }
+
+ *iteratorp = (dns_dbiterator_t *)rbtdbiter;
+
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+zone_findrdataset(dns_db_t *db, dns_dbnode_t *node, dns_dbversion_t *version,
+ dns_rdatatype_t type, dns_rdatatype_t covers,
+ isc_stdtime_t now, dns_rdataset_t *rdataset,
+ dns_rdataset_t *sigrdataset) {
+ dns_rbtdb_t *rbtdb = (dns_rbtdb_t *)db;
+ dns_rbtnode_t *rbtnode = (dns_rbtnode_t *)node;
+ rdatasetheader_t *header, *header_next, *found, *foundsig;
+ rbtdb_serial_t serial;
+ rbtdb_version_t *rbtversion = version;
+ bool close_version = false;
+ rbtdb_rdatatype_t matchtype, sigmatchtype;
+
+ REQUIRE(VALID_RBTDB(rbtdb));
+ REQUIRE(type != dns_rdatatype_any);
+ INSIST(rbtversion == NULL || rbtversion->rbtdb == rbtdb);
+
+ if (rbtversion == NULL) {
+ currentversion(db, (dns_dbversion_t **)(void *)(&rbtversion));
+ close_version = true;
+ }
+ serial = rbtversion->serial;
+ now = 0;
+
+ NODE_LOCK(&rbtdb->node_locks[rbtnode->locknum].lock,
+ isc_rwlocktype_read);
+
+ found = NULL;
+ foundsig = NULL;
+ matchtype = RBTDB_RDATATYPE_VALUE(type, covers);
+ if (covers == 0) {
+ sigmatchtype = RBTDB_RDATATYPE_VALUE(dns_rdatatype_rrsig, type);
+ } else {
+ sigmatchtype = 0;
+ }
+
+ for (header = rbtnode->data; header != NULL; header = header_next) {
+ header_next = header->next;
+ do {
+ if (header->serial <= serial && !IGNORE(header)) {
+ /*
+ * Is this a "this rdataset doesn't
+ * exist" record?
+ */
+ if (NONEXISTENT(header)) {
+ header = NULL;
+ }
+ break;
+ } else {
+ header = header->down;
+ }
+ } while (header != NULL);
+ if (header != NULL) {
+ /*
+ * We have an active, extant rdataset. If it's a
+ * type we're looking for, remember it.
+ */
+ if (header->type == matchtype) {
+ found = header;
+ if (foundsig != NULL) {
+ break;
+ }
+ } else if (header->type == sigmatchtype) {
+ foundsig = header;
+ if (found != NULL) {
+ break;
+ }
+ }
+ }
+ }
+ if (found != NULL) {
+ bind_rdataset(rbtdb, rbtnode, found, now, isc_rwlocktype_read,
+ rdataset);
+ if (foundsig != NULL) {
+ bind_rdataset(rbtdb, rbtnode, foundsig, now,
+ isc_rwlocktype_read, sigrdataset);
+ }
+ }
+
+ NODE_UNLOCK(&rbtdb->node_locks[rbtnode->locknum].lock,
+ isc_rwlocktype_read);
+
+ if (close_version) {
+ closeversion(db, (dns_dbversion_t **)(void *)(&rbtversion),
+ false);
+ }
+
+ if (found == NULL) {
+ return (ISC_R_NOTFOUND);
+ }
+
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+cache_findrdataset(dns_db_t *db, dns_dbnode_t *node, dns_dbversion_t *version,
+ dns_rdatatype_t type, dns_rdatatype_t covers,
+ isc_stdtime_t now, dns_rdataset_t *rdataset,
+ dns_rdataset_t *sigrdataset) {
+ dns_rbtdb_t *rbtdb = (dns_rbtdb_t *)db;
+ dns_rbtnode_t *rbtnode = (dns_rbtnode_t *)node;
+ rdatasetheader_t *header, *header_next, *found, *foundsig;
+ rbtdb_rdatatype_t matchtype, sigmatchtype, negtype;
+ isc_result_t result;
+ nodelock_t *lock;
+ isc_rwlocktype_t locktype;
+
+ REQUIRE(VALID_RBTDB(rbtdb));
+ REQUIRE(type != dns_rdatatype_any);
+
+ UNUSED(version);
+
+ result = ISC_R_SUCCESS;
+
+ if (now == 0) {
+ isc_stdtime_get(&now);
+ }
+
+ lock = &rbtdb->node_locks[rbtnode->locknum].lock;
+ locktype = isc_rwlocktype_read;
+ NODE_LOCK(lock, locktype);
+
+ found = NULL;
+ foundsig = NULL;
+ matchtype = RBTDB_RDATATYPE_VALUE(type, covers);
+ negtype = RBTDB_RDATATYPE_VALUE(0, type);
+ if (covers == 0) {
+ sigmatchtype = RBTDB_RDATATYPE_VALUE(dns_rdatatype_rrsig, type);
+ } else {
+ sigmatchtype = 0;
+ }
+
+ for (header = rbtnode->data; header != NULL; header = header_next) {
+ header_next = header->next;
+ if (!ACTIVE(header, now)) {
+ if ((header->rdh_ttl + STALE_TTL(header, rbtdb) <
+ now - RBTDB_VIRTUAL) &&
+ (locktype == isc_rwlocktype_write ||
+ NODE_TRYUPGRADE(lock) == ISC_R_SUCCESS))
+ {
+ /*
+ * We update the node's status only when we
+ * can get write access.
+ */
+ locktype = isc_rwlocktype_write;
+
+ /*
+ * We don't check if refcurrent(rbtnode) == 0
+ * and try to free like we do in cache_find(),
+ * because refcurrent(rbtnode) must be
+ * non-zero. This is so because 'node' is an
+ * argument to the function.
+ */
+ mark_header_ancient(rbtdb, header);
+ }
+ } else if (EXISTS(header) && !ANCIENT(header)) {
+ if (header->type == matchtype) {
+ found = header;
+ } else if (header->type == RBTDB_RDATATYPE_NCACHEANY ||
+ header->type == negtype)
+ {
+ found = header;
+ } else if (header->type == sigmatchtype) {
+ foundsig = header;
+ }
+ }
+ }
+ if (found != NULL) {
+ bind_rdataset(rbtdb, rbtnode, found, now, locktype, rdataset);
+ if (!NEGATIVE(found) && foundsig != NULL) {
+ bind_rdataset(rbtdb, rbtnode, foundsig, now, locktype,
+ sigrdataset);
+ }
+ }
+
+ NODE_UNLOCK(lock, locktype);
+
+ if (found == NULL) {
+ return (ISC_R_NOTFOUND);
+ }
+
+ if (NEGATIVE(found)) {
+ /*
+ * We found a negative cache entry.
+ */
+ if (NXDOMAIN(found)) {
+ result = DNS_R_NCACHENXDOMAIN;
+ } else {
+ result = DNS_R_NCACHENXRRSET;
+ }
+ }
+
+ update_cachestats(rbtdb, result);
+
+ return (result);
+}
+
+static isc_result_t
+allrdatasets(dns_db_t *db, dns_dbnode_t *node, dns_dbversion_t *version,
+ unsigned int options, isc_stdtime_t now,
+ dns_rdatasetiter_t **iteratorp) {
+ dns_rbtdb_t *rbtdb = (dns_rbtdb_t *)db;
+ dns_rbtnode_t *rbtnode = (dns_rbtnode_t *)node;
+ rbtdb_version_t *rbtversion = version;
+ rbtdb_rdatasetiter_t *iterator;
+
+ REQUIRE(VALID_RBTDB(rbtdb));
+
+ iterator = isc_mem_get(rbtdb->common.mctx, sizeof(*iterator));
+
+ if ((db->attributes & DNS_DBATTR_CACHE) == 0) {
+ now = 0;
+ if (rbtversion == NULL) {
+ currentversion(
+ db, (dns_dbversion_t **)(void *)(&rbtversion));
+ } else {
+ INSIST(rbtversion->rbtdb == rbtdb);
+
+ (void)isc_refcount_increment(&rbtversion->references);
+ }
+ } else {
+ if (now == 0) {
+ isc_stdtime_get(&now);
+ }
+ rbtversion = NULL;
+ }
+
+ iterator->common.magic = DNS_RDATASETITER_MAGIC;
+ iterator->common.methods = &rdatasetiter_methods;
+ iterator->common.db = db;
+ iterator->common.node = node;
+ iterator->common.version = (dns_dbversion_t *)rbtversion;
+ iterator->common.options = options;
+ iterator->common.now = now;
+
+ isc_refcount_increment(&rbtnode->references);
+
+ iterator->current = NULL;
+
+ *iteratorp = (dns_rdatasetiter_t *)iterator;
+
+ return (ISC_R_SUCCESS);
+}
+
+static bool
+cname_and_other_data(dns_rbtnode_t *node, rbtdb_serial_t serial) {
+ rdatasetheader_t *header, *header_next;
+ bool cname, other_data;
+ dns_rdatatype_t rdtype;
+
+ /*
+ * The caller must hold the node lock.
+ */
+
+ /*
+ * Look for CNAME and "other data" rdatasets active in our version.
+ */
+ cname = false;
+ other_data = false;
+ for (header = node->data; header != NULL; header = header_next) {
+ header_next = header->next;
+ if (header->type == dns_rdatatype_cname) {
+ /*
+ * Look for an active extant CNAME.
+ */
+ do {
+ if (header->serial <= serial && !IGNORE(header))
+ {
+ /*
+ * Is this a "this rdataset doesn't
+ * exist" record?
+ */
+ if (NONEXISTENT(header)) {
+ header = NULL;
+ }
+ break;
+ } else {
+ header = header->down;
+ }
+ } while (header != NULL);
+ if (header != NULL) {
+ cname = true;
+ }
+ } else {
+ /*
+ * Look for active extant "other data".
+ *
+ * "Other data" is any rdataset whose type is not
+ * KEY, NSEC, SIG or RRSIG.
+ */
+ rdtype = RBTDB_RDATATYPE_BASE(header->type);
+ if (rdtype != dns_rdatatype_key &&
+ rdtype != dns_rdatatype_sig &&
+ rdtype != dns_rdatatype_nsec &&
+ rdtype != dns_rdatatype_rrsig)
+ {
+ /*
+ * Is it active and extant?
+ */
+ do {
+ if (header->serial <= serial &&
+ !IGNORE(header))
+ {
+ /*
+ * Is this a "this rdataset
+ * doesn't exist" record?
+ */
+ if (NONEXISTENT(header)) {
+ header = NULL;
+ }
+ break;
+ } else {
+ header = header->down;
+ }
+ } while (header != NULL);
+ if (header != NULL) {
+ other_data = true;
+ }
+ }
+ }
+ }
+
+ if (cname && other_data) {
+ return (true);
+ }
+
+ return (false);
+}
+
+static void
+resign_insert(dns_rbtdb_t *rbtdb, int idx, rdatasetheader_t *newheader) {
+ INSIST(!IS_CACHE(rbtdb));
+ INSIST(newheader->heap_index == 0);
+ INSIST(!ISC_LINK_LINKED(newheader, link));
+
+ isc_heap_insert(rbtdb->heaps[idx], newheader);
+}
+
+/*
+ * node write lock must be held.
+ */
+static void
+resign_delete(dns_rbtdb_t *rbtdb, rbtdb_version_t *version,
+ rdatasetheader_t *header) {
+ /*
+ * Remove the old header from the heap
+ */
+ if (header != NULL && header->heap_index != 0) {
+ isc_heap_delete(rbtdb->heaps[header->node->locknum],
+ header->heap_index);
+ header->heap_index = 0;
+ if (version != NULL) {
+ new_reference(rbtdb, header->node,
+ isc_rwlocktype_write);
+ ISC_LIST_APPEND(version->resigned_list, header, link);
+ }
+ }
+}
+
+static uint64_t
+recordsize(rdatasetheader_t *header, unsigned int namelen) {
+ return (dns_rdataslab_rdatasize((unsigned char *)header,
+ sizeof(*header)) +
+ sizeof(dns_ttl_t) + sizeof(dns_rdatatype_t) +
+ sizeof(dns_rdataclass_t) + namelen);
+}
+
+static void
+update_recordsandxfrsize(bool add, rbtdb_version_t *rbtversion,
+ rdatasetheader_t *header, unsigned int namelen) {
+ unsigned char *hdr = (unsigned char *)header;
+ size_t hdrsize = sizeof(*header);
+
+ RWLOCK(&rbtversion->rwlock, isc_rwlocktype_write);
+ if (add) {
+ rbtversion->records += dns_rdataslab_count(hdr, hdrsize);
+ rbtversion->xfrsize += recordsize(header, namelen);
+ } else {
+ rbtversion->records -= dns_rdataslab_count(hdr, hdrsize);
+ rbtversion->xfrsize -= recordsize(header, namelen);
+ }
+ RWUNLOCK(&rbtversion->rwlock, isc_rwlocktype_write);
+}
+
+/*
+ * write lock on rbtnode must be held.
+ */
+static isc_result_t
+add32(dns_rbtdb_t *rbtdb, dns_rbtnode_t *rbtnode, const dns_name_t *nodename,
+ rbtdb_version_t *rbtversion, rdatasetheader_t *newheader,
+ unsigned int options, bool loading, dns_rdataset_t *addedrdataset,
+ isc_stdtime_t now) {
+ rbtdb_changed_t *changed = NULL;
+ rdatasetheader_t *topheader = NULL, *topheader_prev = NULL;
+ rdatasetheader_t *header = NULL, *sigheader = NULL;
+ unsigned char *merged = NULL;
+ isc_result_t result;
+ bool header_nx;
+ bool newheader_nx;
+ bool merge;
+ dns_rdatatype_t rdtype, covers;
+ rbtdb_rdatatype_t negtype, sigtype;
+ dns_trust_t trust;
+ int idx;
+
+ /*
+ * Add an rdatasetheader_t to a node.
+ */
+
+ /*
+ * Caller must be holding the node lock.
+ */
+
+ if ((options & DNS_DBADD_MERGE) != 0) {
+ REQUIRE(rbtversion != NULL);
+ merge = true;
+ } else {
+ merge = false;
+ }
+
+ if ((options & DNS_DBADD_FORCE) != 0) {
+ trust = dns_trust_ultimate;
+ } else {
+ trust = newheader->trust;
+ }
+
+ if (rbtversion != NULL && !loading) {
+ /*
+ * We always add a changed record, even if no changes end up
+ * being made to this node, because it's harmless and
+ * simplifies the code.
+ */
+ changed = add_changed(rbtdb, rbtversion, rbtnode);
+ if (changed == NULL) {
+ free_rdataset(rbtdb, rbtdb->common.mctx, newheader);
+ return (ISC_R_NOMEMORY);
+ }
+ }
+
+ newheader_nx = NONEXISTENT(newheader) ? true : false;
+ topheader_prev = NULL;
+ sigheader = NULL;
+ negtype = 0;
+ if (rbtversion == NULL && !newheader_nx) {
+ rdtype = RBTDB_RDATATYPE_BASE(newheader->type);
+ covers = RBTDB_RDATATYPE_EXT(newheader->type);
+ sigtype = RBTDB_RDATATYPE_VALUE(dns_rdatatype_rrsig, covers);
+ if (NEGATIVE(newheader)) {
+ /*
+ * We're adding a negative cache entry.
+ */
+ if (covers == dns_rdatatype_any) {
+ /*
+ * If we're adding an negative cache entry
+ * which covers all types (NXDOMAIN,
+ * NODATA(QTYPE=ANY)),
+ *
+ * We make all other data ancient so that the
+ * only rdataset that can be found at this
+ * node is the negative cache entry.
+ */
+ for (topheader = rbtnode->data;
+ topheader != NULL;
+ topheader = topheader->next)
+ {
+ set_ttl(rbtdb, topheader, 0);
+ mark_header_ancient(rbtdb, topheader);
+ }
+ goto find_header;
+ }
+ /*
+ * Otherwise look for any RRSIGs of the given
+ * type so they can be marked ancient later.
+ */
+ for (topheader = rbtnode->data; topheader != NULL;
+ topheader = topheader->next)
+ {
+ if (topheader->type == sigtype) {
+ sigheader = topheader;
+ }
+ }
+ negtype = RBTDB_RDATATYPE_VALUE(covers, 0);
+ } else {
+ /*
+ * We're adding something that isn't a
+ * negative cache entry. Look for an extant
+ * non-ancient NXDOMAIN/NODATA(QTYPE=ANY) negative
+ * cache entry. If we're adding an RRSIG, also
+ * check for an extant non-ancient NODATA ncache
+ * entry which covers the same type as the RRSIG.
+ */
+ for (topheader = rbtnode->data; topheader != NULL;
+ topheader = topheader->next)
+ {
+ if ((topheader->type ==
+ RBTDB_RDATATYPE_NCACHEANY) ||
+ (newheader->type == sigtype &&
+ topheader->type ==
+ RBTDB_RDATATYPE_VALUE(0, covers)))
+ {
+ break;
+ }
+ }
+ if (topheader != NULL && EXISTS(topheader) &&
+ ACTIVE(topheader, now))
+ {
+ /*
+ * Found one.
+ */
+ if (trust < topheader->trust) {
+ /*
+ * The NXDOMAIN/NODATA(QTYPE=ANY)
+ * is more trusted.
+ */
+ free_rdataset(rbtdb, rbtdb->common.mctx,
+ newheader);
+ if (addedrdataset != NULL) {
+ bind_rdataset(
+ rbtdb, rbtnode,
+ topheader, now,
+ isc_rwlocktype_write,
+ addedrdataset);
+ }
+ return (DNS_R_UNCHANGED);
+ }
+ /*
+ * The new rdataset is better. Expire the
+ * ncache entry.
+ */
+ set_ttl(rbtdb, topheader, 0);
+ mark_header_ancient(rbtdb, topheader);
+ topheader = NULL;
+ goto find_header;
+ }
+ negtype = RBTDB_RDATATYPE_VALUE(0, rdtype);
+ }
+ }
+
+ for (topheader = rbtnode->data; topheader != NULL;
+ topheader = topheader->next)
+ {
+ if (topheader->type == newheader->type ||
+ topheader->type == negtype)
+ {
+ break;
+ }
+ topheader_prev = topheader;
+ }
+
+find_header:
+ /*
+ * If header isn't NULL, we've found the right type. There may be
+ * IGNORE rdatasets between the top of the chain and the first real
+ * data. We skip over them.
+ */
+ header = topheader;
+ while (header != NULL && IGNORE(header)) {
+ header = header->down;
+ }
+ if (header != NULL) {
+ header_nx = NONEXISTENT(header) ? true : false;
+
+ /*
+ * Deleting an already non-existent rdataset has no effect.
+ */
+ if (header_nx && newheader_nx) {
+ free_rdataset(rbtdb, rbtdb->common.mctx, newheader);
+ return (DNS_R_UNCHANGED);
+ }
+
+ /*
+ * Trying to add an rdataset with lower trust to a cache
+ * DB has no effect, provided that the cache data isn't
+ * stale. If the cache data is stale, new lower trust
+ * data will supersede it below. Unclear what the best
+ * policy is here.
+ */
+ if (rbtversion == NULL && trust < header->trust &&
+ (ACTIVE(header, now) || header_nx))
+ {
+ free_rdataset(rbtdb, rbtdb->common.mctx, newheader);
+ if (addedrdataset != NULL) {
+ bind_rdataset(rbtdb, rbtnode, header, now,
+ isc_rwlocktype_write,
+ addedrdataset);
+ }
+ return (DNS_R_UNCHANGED);
+ }
+
+ /*
+ * Don't merge if a nonexistent rdataset is involved.
+ */
+ if (merge && (header_nx || newheader_nx)) {
+ merge = false;
+ }
+
+ /*
+ * If 'merge' is true, we'll try to create a new rdataset
+ * that is the union of 'newheader' and 'header'.
+ */
+ if (merge) {
+ unsigned int flags = 0;
+ INSIST(rbtversion->serial >= header->serial);
+ merged = NULL;
+ result = ISC_R_SUCCESS;
+
+ if ((options & DNS_DBADD_EXACT) != 0) {
+ flags |= DNS_RDATASLAB_EXACT;
+ }
+ /*
+ * TTL use here is irrelevant to the cache;
+ * merge is only done with zonedbs.
+ */
+ if ((options & DNS_DBADD_EXACTTTL) != 0 &&
+ newheader->rdh_ttl != header->rdh_ttl)
+ {
+ result = DNS_R_NOTEXACT;
+ } else if (newheader->rdh_ttl != header->rdh_ttl) {
+ flags |= DNS_RDATASLAB_FORCE;
+ }
+ if (result == ISC_R_SUCCESS) {
+ result = dns_rdataslab_merge(
+ (unsigned char *)header,
+ (unsigned char *)newheader,
+ (unsigned int)(sizeof(*newheader)),
+ rbtdb->common.mctx,
+ rbtdb->common.rdclass,
+ (dns_rdatatype_t)header->type, flags,
+ &merged);
+ }
+ if (result == ISC_R_SUCCESS) {
+ /*
+ * If 'header' has the same serial number as
+ * we do, we could clean it up now if we knew
+ * that our caller had no references to it.
+ * We don't know this, however, so we leave it
+ * alone. It will get cleaned up when
+ * clean_zone_node() runs.
+ */
+ free_rdataset(rbtdb, rbtdb->common.mctx,
+ newheader);
+ newheader = (rdatasetheader_t *)merged;
+ init_rdataset(rbtdb, newheader);
+ update_newheader(newheader, header);
+ if (loading && RESIGN(newheader) &&
+ RESIGN(header) &&
+ resign_sooner(header, newheader))
+ {
+ newheader->resign = header->resign;
+ newheader->resign_lsb =
+ header->resign_lsb;
+ }
+ } else {
+ free_rdataset(rbtdb, rbtdb->common.mctx,
+ newheader);
+ return (result);
+ }
+ }
+ /*
+ * Don't replace existing NS, A and AAAA RRsets in the
+ * cache if they are already exist. This prevents named
+ * being locked to old servers. Don't lower trust of
+ * existing record if the update is forced. Nothing
+ * special to be done w.r.t stale data; it gets replaced
+ * normally further down.
+ */
+ if (IS_CACHE(rbtdb) && ACTIVE(header, now) &&
+ header->type == dns_rdatatype_ns && !header_nx &&
+ !newheader_nx && header->trust >= newheader->trust &&
+ dns_rdataslab_equalx((unsigned char *)header,
+ (unsigned char *)newheader,
+ (unsigned int)(sizeof(*newheader)),
+ rbtdb->common.rdclass,
+ (dns_rdatatype_t)header->type))
+ {
+ /*
+ * Honour the new ttl if it is less than the
+ * older one.
+ */
+ if (header->rdh_ttl > newheader->rdh_ttl) {
+ set_ttl(rbtdb, header, newheader->rdh_ttl);
+ }
+ if (header->noqname == NULL &&
+ newheader->noqname != NULL)
+ {
+ header->noqname = newheader->noqname;
+ newheader->noqname = NULL;
+ }
+ if (header->closest == NULL &&
+ newheader->closest != NULL)
+ {
+ header->closest = newheader->closest;
+ newheader->closest = NULL;
+ }
+ free_rdataset(rbtdb, rbtdb->common.mctx, newheader);
+ if (addedrdataset != NULL) {
+ bind_rdataset(rbtdb, rbtnode, header, now,
+ isc_rwlocktype_write,
+ addedrdataset);
+ }
+ return (ISC_R_SUCCESS);
+ }
+ /*
+ * If we have will be replacing a NS RRset force its TTL
+ * to be no more than the current NS RRset's TTL. This
+ * ensures the delegations that are withdrawn are honoured.
+ */
+ if (IS_CACHE(rbtdb) && ACTIVE(header, now) &&
+ header->type == dns_rdatatype_ns && !header_nx &&
+ !newheader_nx && header->trust <= newheader->trust)
+ {
+ if (newheader->rdh_ttl > header->rdh_ttl) {
+ newheader->rdh_ttl = header->rdh_ttl;
+ }
+ }
+ if (IS_CACHE(rbtdb) && ACTIVE(header, now) &&
+ (options & DNS_DBADD_PREFETCH) == 0 &&
+ (header->type == dns_rdatatype_a ||
+ header->type == dns_rdatatype_aaaa ||
+ header->type == dns_rdatatype_ds ||
+ header->type == RBTDB_RDATATYPE_SIGDS) &&
+ !header_nx && !newheader_nx &&
+ header->trust >= newheader->trust &&
+ dns_rdataslab_equal((unsigned char *)header,
+ (unsigned char *)newheader,
+ (unsigned int)(sizeof(*newheader))))
+ {
+ /*
+ * Honour the new ttl if it is less than the
+ * older one.
+ */
+ if (header->rdh_ttl > newheader->rdh_ttl) {
+ set_ttl(rbtdb, header, newheader->rdh_ttl);
+ }
+ if (header->noqname == NULL &&
+ newheader->noqname != NULL)
+ {
+ header->noqname = newheader->noqname;
+ newheader->noqname = NULL;
+ }
+ if (header->closest == NULL &&
+ newheader->closest != NULL)
+ {
+ header->closest = newheader->closest;
+ newheader->closest = NULL;
+ }
+ free_rdataset(rbtdb, rbtdb->common.mctx, newheader);
+ if (addedrdataset != NULL) {
+ bind_rdataset(rbtdb, rbtnode, header, now,
+ isc_rwlocktype_write,
+ addedrdataset);
+ }
+ return (ISC_R_SUCCESS);
+ }
+ INSIST(rbtversion == NULL ||
+ rbtversion->serial >= topheader->serial);
+ if (loading) {
+ newheader->down = NULL;
+ idx = newheader->node->locknum;
+ if (IS_CACHE(rbtdb)) {
+ if (ZEROTTL(newheader)) {
+ ISC_LIST_APPEND(rbtdb->rdatasets[idx],
+ newheader, link);
+ } else {
+ ISC_LIST_PREPEND(rbtdb->rdatasets[idx],
+ newheader, link);
+ }
+ INSIST(rbtdb->heaps != NULL);
+ isc_heap_insert(rbtdb->heaps[idx], newheader);
+ } else if (RESIGN(newheader)) {
+ resign_insert(rbtdb, idx, newheader);
+ /*
+ * Don't call resign_delete as we don't need
+ * to reverse the delete. The free_rdataset
+ * call below will clean up the heap entry.
+ */
+ }
+
+ /*
+ * There are no other references to 'header' when
+ * loading, so we MAY clean up 'header' now.
+ * Since we don't generate changed records when
+ * loading, we MUST clean up 'header' now.
+ */
+ if (topheader_prev != NULL) {
+ topheader_prev->next = newheader;
+ } else {
+ rbtnode->data = newheader;
+ }
+ newheader->next = topheader->next;
+ if (rbtversion != NULL && !header_nx) {
+ update_recordsandxfrsize(false, rbtversion,
+ header,
+ nodename->length);
+ }
+ free_rdataset(rbtdb, rbtdb->common.mctx, header);
+ } else {
+ idx = newheader->node->locknum;
+ if (IS_CACHE(rbtdb)) {
+ INSIST(rbtdb->heaps != NULL);
+ isc_heap_insert(rbtdb->heaps[idx], newheader);
+ if (ZEROTTL(newheader)) {
+ ISC_LIST_APPEND(rbtdb->rdatasets[idx],
+ newheader, link);
+ } else {
+ ISC_LIST_PREPEND(rbtdb->rdatasets[idx],
+ newheader, link);
+ }
+ } else if (RESIGN(newheader)) {
+ resign_insert(rbtdb, idx, newheader);
+ resign_delete(rbtdb, rbtversion, header);
+ }
+ if (topheader_prev != NULL) {
+ topheader_prev->next = newheader;
+ } else {
+ rbtnode->data = newheader;
+ }
+ newheader->next = topheader->next;
+ newheader->down = topheader;
+ topheader->next = newheader;
+ rbtnode->dirty = 1;
+ if (changed != NULL) {
+ changed->dirty = true;
+ }
+ if (rbtversion == NULL) {
+ set_ttl(rbtdb, header, 0);
+ mark_header_ancient(rbtdb, header);
+ if (sigheader != NULL) {
+ set_ttl(rbtdb, sigheader, 0);
+ mark_header_ancient(rbtdb, sigheader);
+ }
+ }
+ if (rbtversion != NULL && !header_nx) {
+ update_recordsandxfrsize(false, rbtversion,
+ header,
+ nodename->length);
+ }
+ }
+ } else {
+ /*
+ * No non-IGNORED rdatasets of the given type exist at
+ * this node.
+ */
+
+ /*
+ * If we're trying to delete the type, don't bother.
+ */
+ if (newheader_nx) {
+ free_rdataset(rbtdb, rbtdb->common.mctx, newheader);
+ return (DNS_R_UNCHANGED);
+ }
+
+ idx = newheader->node->locknum;
+ if (IS_CACHE(rbtdb)) {
+ isc_heap_insert(rbtdb->heaps[idx], newheader);
+ if (ZEROTTL(newheader)) {
+ ISC_LIST_APPEND(rbtdb->rdatasets[idx],
+ newheader, link);
+ } else {
+ ISC_LIST_PREPEND(rbtdb->rdatasets[idx],
+ newheader, link);
+ }
+ } else if (RESIGN(newheader)) {
+ resign_insert(rbtdb, idx, newheader);
+ resign_delete(rbtdb, rbtversion, header);
+ }
+
+ if (topheader != NULL) {
+ /*
+ * We have an list of rdatasets of the given type,
+ * but they're all marked IGNORE. We simply insert
+ * the new rdataset at the head of the list.
+ *
+ * Ignored rdatasets cannot occur during loading, so
+ * we INSIST on it.
+ */
+ INSIST(!loading);
+ INSIST(rbtversion == NULL ||
+ rbtversion->serial >= topheader->serial);
+ if (topheader_prev != NULL) {
+ topheader_prev->next = newheader;
+ } else {
+ rbtnode->data = newheader;
+ }
+ newheader->next = topheader->next;
+ newheader->down = topheader;
+ topheader->next = newheader;
+ rbtnode->dirty = 1;
+ if (changed != NULL) {
+ changed->dirty = true;
+ }
+ } else {
+ /*
+ * No rdatasets of the given type exist at the node.
+ */
+ newheader->next = rbtnode->data;
+ newheader->down = NULL;
+ rbtnode->data = newheader;
+ }
+ }
+
+ if (rbtversion != NULL && !newheader_nx) {
+ update_recordsandxfrsize(true, rbtversion, newheader,
+ nodename->length);
+ }
+
+ /*
+ * Check if the node now contains CNAME and other data.
+ */
+ if (rbtversion != NULL &&
+ cname_and_other_data(rbtnode, rbtversion->serial))
+ {
+ return (DNS_R_CNAMEANDOTHER);
+ }
+
+ if (addedrdataset != NULL) {
+ bind_rdataset(rbtdb, rbtnode, newheader, now,
+ isc_rwlocktype_write, addedrdataset);
+ }
+
+ return (ISC_R_SUCCESS);
+}
+
+static bool
+delegating_type(dns_rbtdb_t *rbtdb, dns_rbtnode_t *node,
+ rbtdb_rdatatype_t type) {
+ if (IS_CACHE(rbtdb)) {
+ if (type == dns_rdatatype_dname) {
+ return (true);
+ } else {
+ return (false);
+ }
+ } else if (type == dns_rdatatype_dname ||
+ (type == dns_rdatatype_ns &&
+ (node != rbtdb->origin_node || IS_STUB(rbtdb))))
+ {
+ return (true);
+ }
+ return (false);
+}
+
+static isc_result_t
+addnoqname(dns_rbtdb_t *rbtdb, rdatasetheader_t *newheader,
+ dns_rdataset_t *rdataset) {
+ struct noqname *noqname;
+ isc_mem_t *mctx = rbtdb->common.mctx;
+ dns_name_t name;
+ dns_rdataset_t neg, negsig;
+ isc_result_t result;
+ isc_region_t r;
+
+ dns_name_init(&name, NULL);
+ dns_rdataset_init(&neg);
+ dns_rdataset_init(&negsig);
+
+ result = dns_rdataset_getnoqname(rdataset, &name, &neg, &negsig);
+ RUNTIME_CHECK(result == ISC_R_SUCCESS);
+
+ noqname = isc_mem_get(mctx, sizeof(*noqname));
+ dns_name_init(&noqname->name, NULL);
+ noqname->neg = NULL;
+ noqname->negsig = NULL;
+ noqname->type = neg.type;
+ dns_name_dup(&name, mctx, &noqname->name);
+ result = dns_rdataslab_fromrdataset(&neg, mctx, &r, 0);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+ noqname->neg = r.base;
+ result = dns_rdataslab_fromrdataset(&negsig, mctx, &r, 0);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+ noqname->negsig = r.base;
+ dns_rdataset_disassociate(&neg);
+ dns_rdataset_disassociate(&negsig);
+ newheader->noqname = noqname;
+ return (ISC_R_SUCCESS);
+
+cleanup:
+ dns_rdataset_disassociate(&neg);
+ dns_rdataset_disassociate(&negsig);
+ free_noqname(mctx, &noqname);
+ return (result);
+}
+
+static isc_result_t
+addclosest(dns_rbtdb_t *rbtdb, rdatasetheader_t *newheader,
+ dns_rdataset_t *rdataset) {
+ struct noqname *closest;
+ isc_mem_t *mctx = rbtdb->common.mctx;
+ dns_name_t name;
+ dns_rdataset_t neg, negsig;
+ isc_result_t result;
+ isc_region_t r;
+
+ dns_name_init(&name, NULL);
+ dns_rdataset_init(&neg);
+ dns_rdataset_init(&negsig);
+
+ result = dns_rdataset_getclosest(rdataset, &name, &neg, &negsig);
+ RUNTIME_CHECK(result == ISC_R_SUCCESS);
+
+ closest = isc_mem_get(mctx, sizeof(*closest));
+ dns_name_init(&closest->name, NULL);
+ closest->neg = NULL;
+ closest->negsig = NULL;
+ closest->type = neg.type;
+ dns_name_dup(&name, mctx, &closest->name);
+ result = dns_rdataslab_fromrdataset(&neg, mctx, &r, 0);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+ closest->neg = r.base;
+ result = dns_rdataslab_fromrdataset(&negsig, mctx, &r, 0);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+ closest->negsig = r.base;
+ dns_rdataset_disassociate(&neg);
+ dns_rdataset_disassociate(&negsig);
+ newheader->closest = closest;
+ return (ISC_R_SUCCESS);
+
+cleanup:
+ dns_rdataset_disassociate(&neg);
+ dns_rdataset_disassociate(&negsig);
+ free_noqname(mctx, &closest);
+ return (result);
+}
+
+static dns_dbmethods_t zone_methods;
+
+static size_t
+rdataset_size(rdatasetheader_t *header) {
+ if (!NONEXISTENT(header)) {
+ return (dns_rdataslab_size((unsigned char *)header,
+ sizeof(*header)));
+ }
+
+ return (sizeof(*header));
+}
+
+static isc_result_t
+addrdataset(dns_db_t *db, dns_dbnode_t *node, dns_dbversion_t *version,
+ isc_stdtime_t now, dns_rdataset_t *rdataset, unsigned int options,
+ dns_rdataset_t *addedrdataset) {
+ dns_rbtdb_t *rbtdb = (dns_rbtdb_t *)db;
+ dns_rbtnode_t *rbtnode = (dns_rbtnode_t *)node;
+ rbtdb_version_t *rbtversion = version;
+ isc_region_t region;
+ rdatasetheader_t *newheader;
+ rdatasetheader_t *header;
+ isc_result_t result;
+ bool delegating;
+ bool newnsec;
+ bool tree_locked = false;
+ bool cache_is_overmem = false;
+ dns_fixedname_t fixed;
+ dns_name_t *name;
+
+ REQUIRE(VALID_RBTDB(rbtdb));
+ INSIST(rbtversion == NULL || rbtversion->rbtdb == rbtdb);
+
+ if (rbtdb->common.methods == &zone_methods) {
+ /*
+ * SOA records are only allowed at top of zone.
+ */
+ if (rdataset->type == dns_rdatatype_soa &&
+ node != rbtdb->origin_node)
+ {
+ return (DNS_R_NOTZONETOP);
+ }
+ RWLOCK(&rbtdb->tree_lock, isc_rwlocktype_read);
+ REQUIRE(((rbtnode->nsec == DNS_RBT_NSEC_NSEC3 &&
+ (rdataset->type == dns_rdatatype_nsec3 ||
+ rdataset->covers == dns_rdatatype_nsec3)) ||
+ (rbtnode->nsec != DNS_RBT_NSEC_NSEC3 &&
+ rdataset->type != dns_rdatatype_nsec3 &&
+ rdataset->covers != dns_rdatatype_nsec3)));
+ RWUNLOCK(&rbtdb->tree_lock, isc_rwlocktype_read);
+ }
+
+ if (rbtversion == NULL) {
+ if (now == 0) {
+ isc_stdtime_get(&now);
+ }
+ } else {
+ now = 0;
+ }
+
+ result = dns_rdataslab_fromrdataset(rdataset, rbtdb->common.mctx,
+ &region, sizeof(rdatasetheader_t));
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+
+ name = dns_fixedname_initname(&fixed);
+ nodefullname(db, node, name);
+ dns_rdataset_getownercase(rdataset, name);
+
+ newheader = (rdatasetheader_t *)region.base;
+ init_rdataset(rbtdb, newheader);
+ setownercase(newheader, name);
+ set_ttl(rbtdb, newheader, rdataset->ttl + now);
+ newheader->type = RBTDB_RDATATYPE_VALUE(rdataset->type,
+ rdataset->covers);
+ atomic_init(&newheader->attributes, 0);
+ if (rdataset->ttl == 0U) {
+ RDATASET_ATTR_SET(newheader, RDATASET_ATTR_ZEROTTL);
+ }
+ newheader->noqname = NULL;
+ newheader->closest = NULL;
+ atomic_init(&newheader->count,
+ atomic_fetch_add_relaxed(&init_count, 1));
+ newheader->trust = rdataset->trust;
+ newheader->last_used = now;
+ newheader->node = rbtnode;
+ if (rbtversion != NULL) {
+ newheader->serial = rbtversion->serial;
+ now = 0;
+
+ if ((rdataset->attributes & DNS_RDATASETATTR_RESIGN) != 0) {
+ RDATASET_ATTR_SET(newheader, RDATASET_ATTR_RESIGN);
+ newheader->resign =
+ (isc_stdtime_t)(dns_time64_from32(
+ rdataset->resign) >>
+ 1);
+ newheader->resign_lsb = rdataset->resign & 0x1;
+ } else {
+ newheader->resign = 0;
+ newheader->resign_lsb = 0;
+ }
+ } else {
+ newheader->serial = 1;
+ newheader->resign = 0;
+ newheader->resign_lsb = 0;
+ if ((rdataset->attributes & DNS_RDATASETATTR_PREFETCH) != 0) {
+ RDATASET_ATTR_SET(newheader, RDATASET_ATTR_PREFETCH);
+ }
+ if ((rdataset->attributes & DNS_RDATASETATTR_NEGATIVE) != 0) {
+ RDATASET_ATTR_SET(newheader, RDATASET_ATTR_NEGATIVE);
+ }
+ if ((rdataset->attributes & DNS_RDATASETATTR_NXDOMAIN) != 0) {
+ RDATASET_ATTR_SET(newheader, RDATASET_ATTR_NXDOMAIN);
+ }
+ if ((rdataset->attributes & DNS_RDATASETATTR_OPTOUT) != 0) {
+ RDATASET_ATTR_SET(newheader, RDATASET_ATTR_OPTOUT);
+ }
+ if ((rdataset->attributes & DNS_RDATASETATTR_NOQNAME) != 0) {
+ result = addnoqname(rbtdb, newheader, rdataset);
+ if (result != ISC_R_SUCCESS) {
+ free_rdataset(rbtdb, rbtdb->common.mctx,
+ newheader);
+ return (result);
+ }
+ }
+ if ((rdataset->attributes & DNS_RDATASETATTR_CLOSEST) != 0) {
+ result = addclosest(rbtdb, newheader, rdataset);
+ if (result != ISC_R_SUCCESS) {
+ free_rdataset(rbtdb, rbtdb->common.mctx,
+ newheader);
+ return (result);
+ }
+ }
+ }
+
+ /*
+ * If we're adding a delegation type (e.g. NS or DNAME for a zone,
+ * just DNAME for the cache), then we need to set the callback bit
+ * on the node.
+ */
+ if (delegating_type(rbtdb, rbtnode, rdataset->type)) {
+ delegating = true;
+ } else {
+ delegating = false;
+ }
+
+ /*
+ * Add to the auxiliary NSEC tree if we're adding an NSEC record.
+ */
+ RWLOCK(&rbtdb->tree_lock, isc_rwlocktype_read);
+ if (rbtnode->nsec != DNS_RBT_NSEC_HAS_NSEC &&
+ rdataset->type == dns_rdatatype_nsec)
+ {
+ newnsec = true;
+ } else {
+ newnsec = false;
+ }
+ RWUNLOCK(&rbtdb->tree_lock, isc_rwlocktype_read);
+
+ /*
+ * If we're adding a delegation type, adding to the auxiliary NSEC
+ * tree, or the DB is a cache in an overmem state, hold an
+ * exclusive lock on the tree. In the latter case the lock does
+ * not necessarily have to be acquired but it will help purge
+ * ancient entries more effectively.
+ */
+ if (IS_CACHE(rbtdb) && isc_mem_isovermem(rbtdb->common.mctx)) {
+ cache_is_overmem = true;
+ }
+ if (delegating || newnsec || cache_is_overmem) {
+ tree_locked = true;
+ RWLOCK(&rbtdb->tree_lock, isc_rwlocktype_write);
+ }
+
+ if (cache_is_overmem) {
+ overmem_purge(rbtdb, rbtnode->locknum, rdataset_size(newheader),
+ tree_locked);
+ }
+
+ NODE_LOCK(&rbtdb->node_locks[rbtnode->locknum].lock,
+ isc_rwlocktype_write);
+
+ if (rbtdb->rrsetstats != NULL) {
+ RDATASET_ATTR_SET(newheader, RDATASET_ATTR_STATCOUNT);
+ update_rrsetstats(rbtdb, newheader->type,
+ atomic_load_acquire(&newheader->attributes),
+ true);
+ }
+
+ if (IS_CACHE(rbtdb)) {
+ if (tree_locked) {
+ cleanup_dead_nodes(rbtdb, rbtnode->locknum);
+ }
+
+ header = isc_heap_element(rbtdb->heaps[rbtnode->locknum], 1);
+ if (header != NULL) {
+ dns_ttl_t rdh_ttl = header->rdh_ttl;
+
+ /* Only account for stale TTL if cache is not overmem */
+ if (!cache_is_overmem) {
+ rdh_ttl += STALE_TTL(header, rbtdb);
+ }
+
+ if (rdh_ttl < now - RBTDB_VIRTUAL) {
+ expire_header(rbtdb, header, tree_locked,
+ expire_ttl);
+ }
+ }
+
+ /*
+ * If we've been holding a write lock on the tree just for
+ * cleaning, we can release it now. However, we still need the
+ * node lock.
+ */
+ if (tree_locked && !delegating && !newnsec) {
+ RWUNLOCK(&rbtdb->tree_lock, isc_rwlocktype_write);
+ tree_locked = false;
+ }
+ }
+
+ result = ISC_R_SUCCESS;
+ if (newnsec) {
+ dns_rbtnode_t *nsecnode;
+
+ nsecnode = NULL;
+ result = dns_rbt_addnode(rbtdb->nsec, name, &nsecnode);
+ if (result == ISC_R_SUCCESS) {
+ nsecnode->nsec = DNS_RBT_NSEC_NSEC;
+ rbtnode->nsec = DNS_RBT_NSEC_HAS_NSEC;
+ } else if (result == ISC_R_EXISTS) {
+ rbtnode->nsec = DNS_RBT_NSEC_HAS_NSEC;
+ result = ISC_R_SUCCESS;
+ }
+ }
+
+ if (result == ISC_R_SUCCESS) {
+ result = add32(rbtdb, rbtnode, name, rbtversion, newheader,
+ options, false, addedrdataset, now);
+ }
+ if (result == ISC_R_SUCCESS && delegating) {
+ rbtnode->find_callback = 1;
+ }
+
+ NODE_UNLOCK(&rbtdb->node_locks[rbtnode->locknum].lock,
+ isc_rwlocktype_write);
+
+ if (tree_locked) {
+ RWUNLOCK(&rbtdb->tree_lock, isc_rwlocktype_write);
+ }
+
+ /*
+ * Update the zone's secure status. If version is non-NULL
+ * this is deferred until closeversion() is called.
+ */
+ if (result == ISC_R_SUCCESS && version == NULL && !IS_CACHE(rbtdb)) {
+ iszonesecure(db, version, rbtdb->origin_node);
+ }
+
+ return (result);
+}
+
+static isc_result_t
+subtractrdataset(dns_db_t *db, dns_dbnode_t *node, dns_dbversion_t *version,
+ dns_rdataset_t *rdataset, unsigned int options,
+ dns_rdataset_t *newrdataset) {
+ dns_rbtdb_t *rbtdb = (dns_rbtdb_t *)db;
+ dns_rbtnode_t *rbtnode = (dns_rbtnode_t *)node;
+ rbtdb_version_t *rbtversion = version;
+ dns_fixedname_t fname;
+ dns_name_t *nodename = dns_fixedname_initname(&fname);
+ rdatasetheader_t *topheader, *topheader_prev, *header, *newheader;
+ unsigned char *subresult;
+ isc_region_t region;
+ isc_result_t result;
+ rbtdb_changed_t *changed;
+
+ REQUIRE(VALID_RBTDB(rbtdb));
+ REQUIRE(rbtversion != NULL && rbtversion->rbtdb == rbtdb);
+
+ if (rbtdb->common.methods == &zone_methods) {
+ RWLOCK(&rbtdb->tree_lock, isc_rwlocktype_read);
+ REQUIRE(((rbtnode->nsec == DNS_RBT_NSEC_NSEC3 &&
+ (rdataset->type == dns_rdatatype_nsec3 ||
+ rdataset->covers == dns_rdatatype_nsec3)) ||
+ (rbtnode->nsec != DNS_RBT_NSEC_NSEC3 &&
+ rdataset->type != dns_rdatatype_nsec3 &&
+ rdataset->covers != dns_rdatatype_nsec3)));
+ RWUNLOCK(&rbtdb->tree_lock, isc_rwlocktype_read);
+ }
+
+ nodefullname(db, node, nodename);
+
+ result = dns_rdataslab_fromrdataset(rdataset, rbtdb->common.mctx,
+ &region, sizeof(rdatasetheader_t));
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+ newheader = (rdatasetheader_t *)region.base;
+ init_rdataset(rbtdb, newheader);
+ set_ttl(rbtdb, newheader, rdataset->ttl);
+ newheader->type = RBTDB_RDATATYPE_VALUE(rdataset->type,
+ rdataset->covers);
+ atomic_init(&newheader->attributes, 0);
+ newheader->serial = rbtversion->serial;
+ newheader->trust = 0;
+ newheader->noqname = NULL;
+ newheader->closest = NULL;
+ atomic_init(&newheader->count,
+ atomic_fetch_add_relaxed(&init_count, 1));
+ newheader->last_used = 0;
+ newheader->node = rbtnode;
+ if ((rdataset->attributes & DNS_RDATASETATTR_RESIGN) != 0) {
+ RDATASET_ATTR_SET(newheader, RDATASET_ATTR_RESIGN);
+ newheader->resign =
+ (isc_stdtime_t)(dns_time64_from32(rdataset->resign) >>
+ 1);
+ newheader->resign_lsb = rdataset->resign & 0x1;
+ } else {
+ newheader->resign = 0;
+ newheader->resign_lsb = 0;
+ }
+
+ NODE_LOCK(&rbtdb->node_locks[rbtnode->locknum].lock,
+ isc_rwlocktype_write);
+
+ changed = add_changed(rbtdb, rbtversion, rbtnode);
+ if (changed == NULL) {
+ free_rdataset(rbtdb, rbtdb->common.mctx, newheader);
+ NODE_UNLOCK(&rbtdb->node_locks[rbtnode->locknum].lock,
+ isc_rwlocktype_write);
+ return (ISC_R_NOMEMORY);
+ }
+
+ topheader_prev = NULL;
+ for (topheader = rbtnode->data; topheader != NULL;
+ topheader = topheader->next)
+ {
+ if (topheader->type == newheader->type) {
+ break;
+ }
+ topheader_prev = topheader;
+ }
+ /*
+ * If header isn't NULL, we've found the right type. There may be
+ * IGNORE rdatasets between the top of the chain and the first real
+ * data. We skip over them.
+ */
+ header = topheader;
+ while (header != NULL && IGNORE(header)) {
+ header = header->down;
+ }
+ if (header != NULL && EXISTS(header)) {
+ unsigned int flags = 0;
+ subresult = NULL;
+ result = ISC_R_SUCCESS;
+ if ((options & DNS_DBSUB_EXACT) != 0) {
+ flags |= DNS_RDATASLAB_EXACT;
+ if (newheader->rdh_ttl != header->rdh_ttl) {
+ result = DNS_R_NOTEXACT;
+ }
+ }
+ if (result == ISC_R_SUCCESS) {
+ result = dns_rdataslab_subtract(
+ (unsigned char *)header,
+ (unsigned char *)newheader,
+ (unsigned int)(sizeof(*newheader)),
+ rbtdb->common.mctx, rbtdb->common.rdclass,
+ (dns_rdatatype_t)header->type, flags,
+ &subresult);
+ }
+ if (result == ISC_R_SUCCESS) {
+ free_rdataset(rbtdb, rbtdb->common.mctx, newheader);
+ newheader = (rdatasetheader_t *)subresult;
+ init_rdataset(rbtdb, newheader);
+ update_newheader(newheader, header);
+ if (RESIGN(header)) {
+ RDATASET_ATTR_SET(newheader,
+ RDATASET_ATTR_RESIGN);
+ newheader->resign = header->resign;
+ newheader->resign_lsb = header->resign_lsb;
+ resign_insert(rbtdb, rbtnode->locknum,
+ newheader);
+ }
+ /*
+ * We have to set the serial since the rdataslab
+ * subtraction routine copies the reserved portion of
+ * header, not newheader.
+ */
+ newheader->serial = rbtversion->serial;
+ /*
+ * XXXJT: dns_rdataslab_subtract() copied the pointers
+ * to additional info. We need to clear these fields
+ * to avoid having duplicated references.
+ */
+ update_recordsandxfrsize(true, rbtversion, newheader,
+ nodename->length);
+ } else if (result == DNS_R_NXRRSET) {
+ /*
+ * This subtraction would remove all of the rdata;
+ * add a nonexistent header instead.
+ */
+ free_rdataset(rbtdb, rbtdb->common.mctx, newheader);
+ newheader = new_rdataset(rbtdb, rbtdb->common.mctx);
+ if (newheader == NULL) {
+ result = ISC_R_NOMEMORY;
+ goto unlock;
+ }
+ init_rdataset(rbtdb, newheader);
+ set_ttl(rbtdb, newheader, 0);
+ newheader->type = topheader->type;
+ atomic_init(&newheader->attributes,
+ RDATASET_ATTR_NONEXISTENT);
+ newheader->trust = 0;
+ newheader->serial = rbtversion->serial;
+ newheader->noqname = NULL;
+ newheader->closest = NULL;
+ atomic_init(&newheader->count, 0);
+ newheader->node = rbtnode;
+ newheader->resign = 0;
+ newheader->resign_lsb = 0;
+ newheader->last_used = 0;
+ } else {
+ free_rdataset(rbtdb, rbtdb->common.mctx, newheader);
+ goto unlock;
+ }
+
+ /*
+ * If we're here, we want to link newheader in front of
+ * topheader.
+ */
+ INSIST(rbtversion->serial >= topheader->serial);
+ update_recordsandxfrsize(false, rbtversion, header,
+ nodename->length);
+ if (topheader_prev != NULL) {
+ topheader_prev->next = newheader;
+ } else {
+ rbtnode->data = newheader;
+ }
+ newheader->next = topheader->next;
+ newheader->down = topheader;
+ topheader->next = newheader;
+ rbtnode->dirty = 1;
+ changed->dirty = true;
+ resign_delete(rbtdb, rbtversion, header);
+ } else {
+ /*
+ * The rdataset doesn't exist, so we don't need to do anything
+ * to satisfy the deletion request.
+ */
+ free_rdataset(rbtdb, rbtdb->common.mctx, newheader);
+ if ((options & DNS_DBSUB_EXACT) != 0) {
+ result = DNS_R_NOTEXACT;
+ } else {
+ result = DNS_R_UNCHANGED;
+ }
+ }
+
+ if (result == ISC_R_SUCCESS && newrdataset != NULL) {
+ bind_rdataset(rbtdb, rbtnode, newheader, 0,
+ isc_rwlocktype_write, newrdataset);
+ }
+
+ if (result == DNS_R_NXRRSET && newrdataset != NULL &&
+ (options & DNS_DBSUB_WANTOLD) != 0)
+ {
+ bind_rdataset(rbtdb, rbtnode, header, 0, isc_rwlocktype_write,
+ newrdataset);
+ }
+
+unlock:
+ NODE_UNLOCK(&rbtdb->node_locks[rbtnode->locknum].lock,
+ isc_rwlocktype_write);
+
+ /*
+ * Update the zone's secure status. If version is non-NULL
+ * this is deferred until closeversion() is called.
+ */
+ if (result == ISC_R_SUCCESS && version == NULL && !IS_CACHE(rbtdb)) {
+ RBTDB_LOCK(&rbtdb->lock, isc_rwlocktype_read);
+ version = rbtdb->current_version;
+ RBTDB_UNLOCK(&rbtdb->lock, isc_rwlocktype_read);
+ iszonesecure(db, version, rbtdb->origin_node);
+ }
+
+ return (result);
+}
+
+static isc_result_t
+deleterdataset(dns_db_t *db, dns_dbnode_t *node, dns_dbversion_t *version,
+ dns_rdatatype_t type, dns_rdatatype_t covers) {
+ dns_rbtdb_t *rbtdb = (dns_rbtdb_t *)db;
+ dns_rbtnode_t *rbtnode = (dns_rbtnode_t *)node;
+ rbtdb_version_t *rbtversion = version;
+ dns_fixedname_t fname;
+ dns_name_t *nodename = dns_fixedname_initname(&fname);
+ isc_result_t result;
+ rdatasetheader_t *newheader;
+
+ REQUIRE(VALID_RBTDB(rbtdb));
+ INSIST(rbtversion == NULL || rbtversion->rbtdb == rbtdb);
+
+ if (type == dns_rdatatype_any) {
+ return (ISC_R_NOTIMPLEMENTED);
+ }
+ if (type == dns_rdatatype_rrsig && covers == 0) {
+ return (ISC_R_NOTIMPLEMENTED);
+ }
+
+ newheader = new_rdataset(rbtdb, rbtdb->common.mctx);
+ if (newheader == NULL) {
+ return (ISC_R_NOMEMORY);
+ }
+ init_rdataset(rbtdb, newheader);
+ set_ttl(rbtdb, newheader, 0);
+ newheader->type = RBTDB_RDATATYPE_VALUE(type, covers);
+ atomic_init(&newheader->attributes, RDATASET_ATTR_NONEXISTENT);
+ newheader->trust = 0;
+ newheader->noqname = NULL;
+ newheader->closest = NULL;
+ if (rbtversion != NULL) {
+ newheader->serial = rbtversion->serial;
+ } else {
+ newheader->serial = 0;
+ }
+ atomic_init(&newheader->count, 0);
+ newheader->last_used = 0;
+ newheader->node = rbtnode;
+
+ nodefullname(db, node, nodename);
+
+ NODE_LOCK(&rbtdb->node_locks[rbtnode->locknum].lock,
+ isc_rwlocktype_write);
+ result = add32(rbtdb, rbtnode, nodename, rbtversion, newheader,
+ DNS_DBADD_FORCE, false, NULL, 0);
+ NODE_UNLOCK(&rbtdb->node_locks[rbtnode->locknum].lock,
+ isc_rwlocktype_write);
+
+ /*
+ * Update the zone's secure status. If version is non-NULL
+ * this is deferred until closeversion() is called.
+ */
+ if (result == ISC_R_SUCCESS && version == NULL && !IS_CACHE(rbtdb)) {
+ RBTDB_LOCK(&rbtdb->lock, isc_rwlocktype_read);
+ version = rbtdb->current_version;
+ RBTDB_UNLOCK(&rbtdb->lock, isc_rwlocktype_read);
+ iszonesecure(db, version, rbtdb->origin_node);
+ }
+
+ return (result);
+}
+
+/*
+ * load a non-NSEC3 node in the main tree and optionally to the auxiliary NSEC
+ */
+static isc_result_t
+loadnode(dns_rbtdb_t *rbtdb, const dns_name_t *name, dns_rbtnode_t **nodep,
+ bool hasnsec) {
+ isc_result_t noderesult, nsecresult, tmpresult;
+ dns_rbtnode_t *nsecnode = NULL, *node = NULL;
+
+ noderesult = dns_rbt_addnode(rbtdb->tree, name, &node);
+ if (!hasnsec) {
+ goto done;
+ }
+ if (noderesult == ISC_R_EXISTS) {
+ /*
+ * Add a node to the auxiliary NSEC tree for an old node
+ * just now getting an NSEC record.
+ */
+ if (node->nsec == DNS_RBT_NSEC_HAS_NSEC) {
+ goto done;
+ }
+ } else if (noderesult != ISC_R_SUCCESS) {
+ goto done;
+ }
+
+ /*
+ * Build the auxiliary tree for NSECs as we go.
+ * This tree speeds searches for closest NSECs that would otherwise
+ * need to examine many irrelevant nodes in large TLDs.
+ *
+ * Add nodes to the auxiliary tree after corresponding nodes have
+ * been added to the main tree.
+ */
+ nsecresult = dns_rbt_addnode(rbtdb->nsec, name, &nsecnode);
+ if (nsecresult == ISC_R_SUCCESS) {
+ nsecnode->nsec = DNS_RBT_NSEC_NSEC;
+ node->nsec = DNS_RBT_NSEC_HAS_NSEC;
+ goto done;
+ }
+
+ if (nsecresult == ISC_R_EXISTS) {
+#if 1 /* 0 */
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE,
+ DNS_LOGMODULE_CACHE, ISC_LOG_WARNING,
+ "addnode: NSEC node already exists");
+#endif /* if 1 */
+ node->nsec = DNS_RBT_NSEC_HAS_NSEC;
+ goto done;
+ }
+
+ if (noderesult == ISC_R_SUCCESS) {
+ /*
+ * Remove the node we just added above.
+ */
+ tmpresult = dns_rbt_deletenode(rbtdb->tree, node, false);
+ if (tmpresult != ISC_R_SUCCESS) {
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE,
+ DNS_LOGMODULE_CACHE, ISC_LOG_WARNING,
+ "loading_addrdataset: "
+ "dns_rbt_deletenode: %s after "
+ "dns_rbt_addnode(NSEC): %s",
+ isc_result_totext(tmpresult),
+ isc_result_totext(noderesult));
+ }
+ }
+
+ /*
+ * Set the error condition to be returned.
+ */
+ noderesult = nsecresult;
+
+done:
+ if (noderesult == ISC_R_SUCCESS || noderesult == ISC_R_EXISTS) {
+ *nodep = node;
+ }
+
+ return (noderesult);
+}
+
+static isc_result_t
+loading_addrdataset(void *arg, const dns_name_t *name,
+ dns_rdataset_t *rdataset) {
+ rbtdb_load_t *loadctx = arg;
+ dns_rbtdb_t *rbtdb = loadctx->rbtdb;
+ dns_rbtnode_t *node;
+ isc_result_t result;
+ isc_region_t region;
+ rdatasetheader_t *newheader;
+
+ REQUIRE(rdataset->rdclass == rbtdb->common.rdclass);
+
+ /*
+ * SOA records are only allowed at top of zone.
+ */
+ if (rdataset->type == dns_rdatatype_soa && !IS_CACHE(rbtdb) &&
+ !dns_name_equal(name, &rbtdb->common.origin))
+ {
+ return (DNS_R_NOTZONETOP);
+ }
+
+ if (rdataset->type != dns_rdatatype_nsec3 &&
+ rdataset->covers != dns_rdatatype_nsec3)
+ {
+ add_empty_wildcards(rbtdb, name, false);
+ }
+
+ if (dns_name_iswildcard(name)) {
+ /*
+ * NS record owners cannot legally be wild cards.
+ */
+ if (rdataset->type == dns_rdatatype_ns) {
+ return (DNS_R_INVALIDNS);
+ }
+ /*
+ * NSEC3 record owners cannot legally be wild cards.
+ */
+ if (rdataset->type == dns_rdatatype_nsec3) {
+ return (DNS_R_INVALIDNSEC3);
+ }
+ result = add_wildcard_magic(rbtdb, name, false);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+ }
+
+ node = NULL;
+ if (rdataset->type == dns_rdatatype_nsec3 ||
+ rdataset->covers == dns_rdatatype_nsec3)
+ {
+ result = dns_rbt_addnode(rbtdb->nsec3, name, &node);
+ if (result == ISC_R_SUCCESS) {
+ node->nsec = DNS_RBT_NSEC_NSEC3;
+ }
+ } else if (rdataset->type == dns_rdatatype_nsec) {
+ result = loadnode(rbtdb, name, &node, true);
+ } else {
+ result = loadnode(rbtdb, name, &node, false);
+ }
+ if (result != ISC_R_SUCCESS && result != ISC_R_EXISTS) {
+ return (result);
+ }
+ if (result == ISC_R_SUCCESS) {
+ node->locknum = node->hashval % rbtdb->node_lock_count;
+ }
+
+ result = dns_rdataslab_fromrdataset(rdataset, rbtdb->common.mctx,
+ &region, sizeof(rdatasetheader_t));
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+ newheader = (rdatasetheader_t *)region.base;
+ init_rdataset(rbtdb, newheader);
+ set_ttl(rbtdb, newheader, rdataset->ttl + loadctx->now); /* XXX overflow
+ * check */
+ newheader->type = RBTDB_RDATATYPE_VALUE(rdataset->type,
+ rdataset->covers);
+ atomic_init(&newheader->attributes, 0);
+ newheader->trust = rdataset->trust;
+ newheader->serial = 1;
+ newheader->noqname = NULL;
+ newheader->closest = NULL;
+ atomic_init(&newheader->count,
+ atomic_fetch_add_relaxed(&init_count, 1));
+ newheader->last_used = 0;
+ newheader->node = node;
+ setownercase(newheader, name);
+
+ if ((rdataset->attributes & DNS_RDATASETATTR_RESIGN) != 0) {
+ RDATASET_ATTR_SET(newheader, RDATASET_ATTR_RESIGN);
+ newheader->resign =
+ (isc_stdtime_t)(dns_time64_from32(rdataset->resign) >>
+ 1);
+ newheader->resign_lsb = rdataset->resign & 0x1;
+ } else {
+ newheader->resign = 0;
+ newheader->resign_lsb = 0;
+ }
+
+ NODE_LOCK(&rbtdb->node_locks[node->locknum].lock, isc_rwlocktype_write);
+ result = add32(rbtdb, node, name, rbtdb->current_version, newheader,
+ DNS_DBADD_MERGE, true, NULL, 0);
+ NODE_UNLOCK(&rbtdb->node_locks[node->locknum].lock,
+ isc_rwlocktype_write);
+
+ if (result == ISC_R_SUCCESS &&
+ delegating_type(rbtdb, node, rdataset->type))
+ {
+ node->find_callback = 1;
+ } else if (result == DNS_R_UNCHANGED) {
+ result = ISC_R_SUCCESS;
+ }
+
+ return (result);
+}
+
+static isc_result_t
+beginload(dns_db_t *db, dns_rdatacallbacks_t *callbacks) {
+ rbtdb_load_t *loadctx;
+ dns_rbtdb_t *rbtdb;
+ rbtdb = (dns_rbtdb_t *)db;
+
+ REQUIRE(DNS_CALLBACK_VALID(callbacks));
+ REQUIRE(VALID_RBTDB(rbtdb));
+
+ loadctx = isc_mem_get(rbtdb->common.mctx, sizeof(*loadctx));
+
+ loadctx->rbtdb = rbtdb;
+ if (IS_CACHE(rbtdb)) {
+ isc_stdtime_get(&loadctx->now);
+ } else {
+ loadctx->now = 0;
+ }
+
+ RBTDB_LOCK(&rbtdb->lock, isc_rwlocktype_write);
+
+ REQUIRE((rbtdb->attributes &
+ (RBTDB_ATTR_LOADED | RBTDB_ATTR_LOADING)) == 0);
+ rbtdb->attributes |= RBTDB_ATTR_LOADING;
+
+ RBTDB_UNLOCK(&rbtdb->lock, isc_rwlocktype_write);
+
+ callbacks->add = loading_addrdataset;
+ callbacks->add_private = loadctx;
+
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+endload(dns_db_t *db, dns_rdatacallbacks_t *callbacks) {
+ rbtdb_load_t *loadctx;
+ dns_rbtdb_t *rbtdb = (dns_rbtdb_t *)db;
+
+ REQUIRE(VALID_RBTDB(rbtdb));
+ REQUIRE(DNS_CALLBACK_VALID(callbacks));
+ loadctx = callbacks->add_private;
+ REQUIRE(loadctx != NULL);
+ REQUIRE(loadctx->rbtdb == rbtdb);
+
+ RBTDB_LOCK(&rbtdb->lock, isc_rwlocktype_write);
+
+ REQUIRE((rbtdb->attributes & RBTDB_ATTR_LOADING) != 0);
+ REQUIRE((rbtdb->attributes & RBTDB_ATTR_LOADED) == 0);
+
+ rbtdb->attributes &= ~RBTDB_ATTR_LOADING;
+ rbtdb->attributes |= RBTDB_ATTR_LOADED;
+
+ /*
+ * If there's a KEY rdataset at the zone origin containing a
+ * zone key, we consider the zone secure.
+ */
+ if (!IS_CACHE(rbtdb) && rbtdb->origin_node != NULL) {
+ dns_dbversion_t *version = rbtdb->current_version;
+ RBTDB_UNLOCK(&rbtdb->lock, isc_rwlocktype_write);
+ iszonesecure(db, version, rbtdb->origin_node);
+ } else {
+ RBTDB_UNLOCK(&rbtdb->lock, isc_rwlocktype_write);
+ }
+
+ callbacks->add = NULL;
+ callbacks->add_private = NULL;
+
+ isc_mem_put(rbtdb->common.mctx, loadctx, sizeof(*loadctx));
+
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+dump(dns_db_t *db, dns_dbversion_t *version, const char *filename,
+ dns_masterformat_t masterformat) {
+ dns_rbtdb_t *rbtdb;
+ rbtdb_version_t *rbtversion = version;
+
+ rbtdb = (dns_rbtdb_t *)db;
+
+ REQUIRE(VALID_RBTDB(rbtdb));
+ INSIST(rbtversion == NULL || rbtversion->rbtdb == rbtdb);
+
+ return (dns_master_dump(rbtdb->common.mctx, db, version,
+ &dns_master_style_default, filename,
+ masterformat, NULL));
+}
+
+static void
+delete_callback(void *data, void *arg) {
+ dns_rbtdb_t *rbtdb = arg;
+ rdatasetheader_t *current, *next;
+ unsigned int locknum;
+
+ current = data;
+ locknum = current->node->locknum;
+ NODE_LOCK(&rbtdb->node_locks[locknum].lock, isc_rwlocktype_write);
+ while (current != NULL) {
+ next = current->next;
+ free_rdataset(rbtdb, rbtdb->common.mctx, current);
+ current = next;
+ }
+ NODE_UNLOCK(&rbtdb->node_locks[locknum].lock, isc_rwlocktype_write);
+}
+
+static bool
+issecure(dns_db_t *db) {
+ dns_rbtdb_t *rbtdb;
+ bool secure;
+
+ rbtdb = (dns_rbtdb_t *)db;
+
+ REQUIRE(VALID_RBTDB(rbtdb));
+
+ RBTDB_LOCK(&rbtdb->lock, isc_rwlocktype_read);
+ secure = (rbtdb->current_version->secure == dns_db_secure);
+ RBTDB_UNLOCK(&rbtdb->lock, isc_rwlocktype_read);
+
+ return (secure);
+}
+
+static bool
+isdnssec(dns_db_t *db) {
+ dns_rbtdb_t *rbtdb;
+ bool dnssec;
+
+ rbtdb = (dns_rbtdb_t *)db;
+
+ REQUIRE(VALID_RBTDB(rbtdb));
+
+ RBTDB_LOCK(&rbtdb->lock, isc_rwlocktype_read);
+ dnssec = (rbtdb->current_version->secure != dns_db_insecure);
+ RBTDB_UNLOCK(&rbtdb->lock, isc_rwlocktype_read);
+
+ return (dnssec);
+}
+
+static unsigned int
+nodecount(dns_db_t *db, dns_dbtree_t tree) {
+ dns_rbtdb_t *rbtdb;
+ unsigned int count;
+
+ rbtdb = (dns_rbtdb_t *)db;
+
+ REQUIRE(VALID_RBTDB(rbtdb));
+
+ RWLOCK(&rbtdb->tree_lock, isc_rwlocktype_read);
+ switch (tree) {
+ case dns_dbtree_main:
+ count = dns_rbt_nodecount(rbtdb->tree);
+ break;
+ case dns_dbtree_nsec:
+ count = dns_rbt_nodecount(rbtdb->nsec);
+ break;
+ case dns_dbtree_nsec3:
+ count = dns_rbt_nodecount(rbtdb->nsec3);
+ break;
+ default:
+ UNREACHABLE();
+ }
+ RWUNLOCK(&rbtdb->tree_lock, isc_rwlocktype_read);
+
+ return (count);
+}
+
+static size_t
+hashsize(dns_db_t *db) {
+ dns_rbtdb_t *rbtdb;
+ size_t size;
+
+ rbtdb = (dns_rbtdb_t *)db;
+
+ REQUIRE(VALID_RBTDB(rbtdb));
+
+ RWLOCK(&rbtdb->tree_lock, isc_rwlocktype_read);
+ size = dns_rbt_hashsize(rbtdb->tree);
+ RWUNLOCK(&rbtdb->tree_lock, isc_rwlocktype_read);
+
+ return (size);
+}
+
+static void
+settask(dns_db_t *db, isc_task_t *task) {
+ dns_rbtdb_t *rbtdb;
+
+ rbtdb = (dns_rbtdb_t *)db;
+
+ REQUIRE(VALID_RBTDB(rbtdb));
+
+ RBTDB_LOCK(&rbtdb->lock, isc_rwlocktype_write);
+ if (rbtdb->task != NULL) {
+ isc_task_detach(&rbtdb->task);
+ }
+ if (task != NULL) {
+ isc_task_attach(task, &rbtdb->task);
+ }
+ RBTDB_UNLOCK(&rbtdb->lock, isc_rwlocktype_write);
+}
+
+static bool
+ispersistent(dns_db_t *db) {
+ UNUSED(db);
+ return (false);
+}
+
+static isc_result_t
+getoriginnode(dns_db_t *db, dns_dbnode_t **nodep) {
+ dns_rbtdb_t *rbtdb = (dns_rbtdb_t *)db;
+ dns_rbtnode_t *onode;
+ isc_result_t result = ISC_R_SUCCESS;
+
+ REQUIRE(VALID_RBTDB(rbtdb));
+ REQUIRE(nodep != NULL && *nodep == NULL);
+
+ /* Note that the access to origin_node doesn't require a DB lock */
+ onode = (dns_rbtnode_t *)rbtdb->origin_node;
+ if (onode != NULL) {
+ new_reference(rbtdb, onode, isc_rwlocktype_none);
+ *nodep = rbtdb->origin_node;
+ } else {
+ INSIST(IS_CACHE(rbtdb));
+ result = ISC_R_NOTFOUND;
+ }
+
+ return (result);
+}
+
+static isc_result_t
+getnsec3parameters(dns_db_t *db, dns_dbversion_t *version, dns_hash_t *hash,
+ uint8_t *flags, uint16_t *iterations, unsigned char *salt,
+ size_t *salt_length) {
+ dns_rbtdb_t *rbtdb;
+ isc_result_t result = ISC_R_NOTFOUND;
+ rbtdb_version_t *rbtversion = version;
+
+ rbtdb = (dns_rbtdb_t *)db;
+
+ REQUIRE(VALID_RBTDB(rbtdb));
+ INSIST(rbtversion == NULL || rbtversion->rbtdb == rbtdb);
+
+ RBTDB_LOCK(&rbtdb->lock, isc_rwlocktype_read);
+ if (rbtversion == NULL) {
+ rbtversion = rbtdb->current_version;
+ }
+
+ if (rbtversion->havensec3) {
+ if (hash != NULL) {
+ *hash = rbtversion->hash;
+ }
+ if (salt != NULL && salt_length != NULL) {
+ REQUIRE(*salt_length >= rbtversion->salt_length);
+ memmove(salt, rbtversion->salt,
+ rbtversion->salt_length);
+ }
+ if (salt_length != NULL) {
+ *salt_length = rbtversion->salt_length;
+ }
+ if (iterations != NULL) {
+ *iterations = rbtversion->iterations;
+ }
+ if (flags != NULL) {
+ *flags = rbtversion->flags;
+ }
+ result = ISC_R_SUCCESS;
+ }
+ RBTDB_UNLOCK(&rbtdb->lock, isc_rwlocktype_read);
+
+ return (result);
+}
+
+static isc_result_t
+getsize(dns_db_t *db, dns_dbversion_t *version, uint64_t *records,
+ uint64_t *xfrsize) {
+ dns_rbtdb_t *rbtdb;
+ isc_result_t result = ISC_R_SUCCESS;
+ rbtdb_version_t *rbtversion = version;
+
+ rbtdb = (dns_rbtdb_t *)db;
+
+ REQUIRE(VALID_RBTDB(rbtdb));
+ INSIST(rbtversion == NULL || rbtversion->rbtdb == rbtdb);
+
+ RBTDB_LOCK(&rbtdb->lock, isc_rwlocktype_read);
+ if (rbtversion == NULL) {
+ rbtversion = rbtdb->current_version;
+ }
+
+ RWLOCK(&rbtversion->rwlock, isc_rwlocktype_read);
+ if (records != NULL) {
+ *records = rbtversion->records;
+ }
+
+ if (xfrsize != NULL) {
+ *xfrsize = rbtversion->xfrsize;
+ }
+ RWUNLOCK(&rbtversion->rwlock, isc_rwlocktype_read);
+ RBTDB_UNLOCK(&rbtdb->lock, isc_rwlocktype_read);
+
+ return (result);
+}
+
+static isc_result_t
+setsigningtime(dns_db_t *db, dns_rdataset_t *rdataset, isc_stdtime_t resign) {
+ dns_rbtdb_t *rbtdb = (dns_rbtdb_t *)db;
+ rdatasetheader_t *header, oldheader;
+
+ REQUIRE(VALID_RBTDB(rbtdb));
+ REQUIRE(!IS_CACHE(rbtdb));
+ REQUIRE(rdataset != NULL);
+
+ header = rdataset->private3;
+ header--;
+
+ NODE_LOCK(&rbtdb->node_locks[header->node->locknum].lock,
+ isc_rwlocktype_write);
+
+ oldheader = *header;
+ /*
+ * Only break the heap invariant (by adjusting resign and resign_lsb)
+ * if we are going to be restoring it by calling isc_heap_increased
+ * or isc_heap_decreased.
+ */
+ if (resign != 0) {
+ header->resign = (isc_stdtime_t)(dns_time64_from32(resign) >>
+ 1);
+ header->resign_lsb = resign & 0x1;
+ }
+ if (header->heap_index != 0) {
+ INSIST(RESIGN(header));
+ if (resign == 0) {
+ isc_heap_delete(rbtdb->heaps[header->node->locknum],
+ header->heap_index);
+ header->heap_index = 0;
+ } else if (resign_sooner(header, &oldheader)) {
+ isc_heap_increased(rbtdb->heaps[header->node->locknum],
+ header->heap_index);
+ } else if (resign_sooner(&oldheader, header)) {
+ isc_heap_decreased(rbtdb->heaps[header->node->locknum],
+ header->heap_index);
+ }
+ } else if (resign != 0) {
+ RDATASET_ATTR_SET(header, RDATASET_ATTR_RESIGN);
+ resign_insert(rbtdb, header->node->locknum, header);
+ }
+ NODE_UNLOCK(&rbtdb->node_locks[header->node->locknum].lock,
+ isc_rwlocktype_write);
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+getsigningtime(dns_db_t *db, dns_rdataset_t *rdataset, dns_name_t *foundname) {
+ dns_rbtdb_t *rbtdb = (dns_rbtdb_t *)db;
+ rdatasetheader_t *header = NULL, *this;
+ unsigned int i;
+ isc_result_t result = ISC_R_NOTFOUND;
+ unsigned int locknum = 0;
+
+ REQUIRE(VALID_RBTDB(rbtdb));
+
+ RWLOCK(&rbtdb->tree_lock, isc_rwlocktype_read);
+
+ for (i = 0; i < rbtdb->node_lock_count; i++) {
+ NODE_LOCK(&rbtdb->node_locks[i].lock, isc_rwlocktype_read);
+
+ /*
+ * Find for the earliest signing time among all of the
+ * heaps, each of which is covered by a different bucket
+ * lock.
+ */
+ this = isc_heap_element(rbtdb->heaps[i], 1);
+ if (this == NULL) {
+ /* Nothing found; unlock and try the next heap. */
+ NODE_UNLOCK(&rbtdb->node_locks[i].lock,
+ isc_rwlocktype_read);
+ continue;
+ }
+
+ if (header == NULL) {
+ /*
+ * Found a signing time: retain the bucket lock and
+ * preserve the lock number so we can unlock it
+ * later.
+ */
+ header = this;
+ locknum = i;
+ } else if (resign_sooner(this, header)) {
+ /*
+ * Found an earlier signing time; release the
+ * previous bucket lock and retain this one instead.
+ */
+ NODE_UNLOCK(&rbtdb->node_locks[locknum].lock,
+ isc_rwlocktype_read);
+ header = this;
+ locknum = i;
+ } else {
+ /*
+ * Earliest signing time in this heap isn't
+ * an improvement; unlock and try the next heap.
+ */
+ NODE_UNLOCK(&rbtdb->node_locks[i].lock,
+ isc_rwlocktype_read);
+ }
+ }
+
+ if (header != NULL) {
+ /*
+ * Found something; pass back the answer and unlock
+ * the bucket.
+ */
+ bind_rdataset(rbtdb, header->node, header, 0,
+ isc_rwlocktype_read, rdataset);
+
+ if (foundname != NULL) {
+ dns_rbt_fullnamefromnode(header->node, foundname);
+ }
+
+ NODE_UNLOCK(&rbtdb->node_locks[locknum].lock,
+ isc_rwlocktype_read);
+
+ result = ISC_R_SUCCESS;
+ }
+
+ RWUNLOCK(&rbtdb->tree_lock, isc_rwlocktype_read);
+
+ return (result);
+}
+
+static void
+resigned(dns_db_t *db, dns_rdataset_t *rdataset, dns_dbversion_t *version) {
+ rbtdb_version_t *rbtversion = (rbtdb_version_t *)version;
+ dns_rbtdb_t *rbtdb = (dns_rbtdb_t *)db;
+ dns_rbtnode_t *node;
+ rdatasetheader_t *header;
+
+ REQUIRE(VALID_RBTDB(rbtdb));
+ REQUIRE(rdataset != NULL);
+ REQUIRE(rdataset->methods == &rdataset_methods);
+ REQUIRE(rbtdb->future_version == rbtversion);
+ REQUIRE(rbtversion != NULL);
+ REQUIRE(rbtversion->writer);
+ REQUIRE(rbtversion->rbtdb == rbtdb);
+
+ node = rdataset->private2;
+ INSIST(node != NULL);
+ header = rdataset->private3;
+ INSIST(header != NULL);
+ header--;
+
+ if (header->heap_index == 0) {
+ return;
+ }
+
+ RWLOCK(&rbtdb->tree_lock, isc_rwlocktype_write);
+ NODE_LOCK(&rbtdb->node_locks[node->locknum].lock, isc_rwlocktype_write);
+ /*
+ * Delete from heap and save to re-signed list so that it can
+ * be restored if we backout of this change.
+ */
+ resign_delete(rbtdb, rbtversion, header);
+ NODE_UNLOCK(&rbtdb->node_locks[node->locknum].lock,
+ isc_rwlocktype_write);
+ RWUNLOCK(&rbtdb->tree_lock, isc_rwlocktype_write);
+}
+
+static isc_result_t
+setcachestats(dns_db_t *db, isc_stats_t *stats) {
+ dns_rbtdb_t *rbtdb = (dns_rbtdb_t *)db;
+
+ REQUIRE(VALID_RBTDB(rbtdb));
+ REQUIRE(IS_CACHE(rbtdb)); /* current restriction */
+ REQUIRE(stats != NULL);
+
+ isc_stats_attach(stats, &rbtdb->cachestats);
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+setgluecachestats(dns_db_t *db, isc_stats_t *stats) {
+ dns_rbtdb_t *rbtdb = (dns_rbtdb_t *)db;
+
+ REQUIRE(VALID_RBTDB(rbtdb));
+ REQUIRE(!IS_CACHE(rbtdb) && !IS_STUB(rbtdb));
+ REQUIRE(stats != NULL);
+
+ isc_stats_attach(stats, &rbtdb->gluecachestats);
+ return (ISC_R_SUCCESS);
+}
+
+static dns_stats_t *
+getrrsetstats(dns_db_t *db) {
+ dns_rbtdb_t *rbtdb = (dns_rbtdb_t *)db;
+
+ REQUIRE(VALID_RBTDB(rbtdb));
+ REQUIRE(IS_CACHE(rbtdb)); /* current restriction */
+
+ return (rbtdb->rrsetstats);
+}
+
+static isc_result_t
+nodefullname(dns_db_t *db, dns_dbnode_t *node, dns_name_t *name) {
+ dns_rbtdb_t *rbtdb = (dns_rbtdb_t *)db;
+ dns_rbtnode_t *rbtnode = (dns_rbtnode_t *)node;
+ isc_result_t result;
+
+ REQUIRE(VALID_RBTDB(rbtdb));
+ REQUIRE(node != NULL);
+ REQUIRE(name != NULL);
+
+ RWLOCK(&rbtdb->tree_lock, isc_rwlocktype_read);
+ result = dns_rbt_fullnamefromnode(rbtnode, name);
+ RWUNLOCK(&rbtdb->tree_lock, isc_rwlocktype_read);
+
+ return (result);
+}
+
+static isc_result_t
+setservestalettl(dns_db_t *db, dns_ttl_t ttl) {
+ dns_rbtdb_t *rbtdb = (dns_rbtdb_t *)db;
+
+ REQUIRE(VALID_RBTDB(rbtdb));
+ REQUIRE(IS_CACHE(rbtdb));
+
+ /* currently no bounds checking. 0 means disable. */
+ rbtdb->serve_stale_ttl = ttl;
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+getservestalettl(dns_db_t *db, dns_ttl_t *ttl) {
+ dns_rbtdb_t *rbtdb = (dns_rbtdb_t *)db;
+
+ REQUIRE(VALID_RBTDB(rbtdb));
+ REQUIRE(IS_CACHE(rbtdb));
+
+ *ttl = rbtdb->serve_stale_ttl;
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+setservestalerefresh(dns_db_t *db, uint32_t interval) {
+ dns_rbtdb_t *rbtdb = (dns_rbtdb_t *)db;
+
+ REQUIRE(VALID_RBTDB(rbtdb));
+ REQUIRE(IS_CACHE(rbtdb));
+
+ /* currently no bounds checking. 0 means disable. */
+ rbtdb->serve_stale_refresh = interval;
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+getservestalerefresh(dns_db_t *db, uint32_t *interval) {
+ dns_rbtdb_t *rbtdb = (dns_rbtdb_t *)db;
+
+ REQUIRE(VALID_RBTDB(rbtdb));
+ REQUIRE(IS_CACHE(rbtdb));
+
+ *interval = rbtdb->serve_stale_refresh;
+ return (ISC_R_SUCCESS);
+}
+
+static dns_dbmethods_t zone_methods = { attach,
+ detach,
+ beginload,
+ endload,
+ dump,
+ currentversion,
+ newversion,
+ attachversion,
+ closeversion,
+ findnode,
+ zone_find,
+ zone_findzonecut,
+ attachnode,
+ detachnode,
+ expirenode,
+ printnode,
+ createiterator,
+ zone_findrdataset,
+ allrdatasets,
+ addrdataset,
+ subtractrdataset,
+ deleterdataset,
+ issecure,
+ nodecount,
+ ispersistent,
+ overmem,
+ settask,
+ getoriginnode,
+ NULL, /* transfernode */
+ getnsec3parameters,
+ findnsec3node,
+ setsigningtime,
+ getsigningtime,
+ resigned,
+ isdnssec,
+ NULL, /* getrrsetstats */
+ NULL, /* rpz_attach */
+ NULL, /* rpz_ready */
+ NULL, /* findnodeext */
+ NULL, /* findext */
+ NULL, /* setcachestats */
+ hashsize,
+ nodefullname,
+ getsize,
+ NULL, /* setservestalettl */
+ NULL, /* getservestalettl */
+ NULL, /* setservestalerefresh */
+ NULL, /* getservestalerefresh */
+ setgluecachestats };
+
+static dns_dbmethods_t cache_methods = { attach,
+ detach,
+ beginload,
+ endload,
+ dump,
+ currentversion,
+ newversion,
+ attachversion,
+ closeversion,
+ findnode,
+ cache_find,
+ cache_findzonecut,
+ attachnode,
+ detachnode,
+ expirenode,
+ printnode,
+ createiterator,
+ cache_findrdataset,
+ allrdatasets,
+ addrdataset,
+ subtractrdataset,
+ deleterdataset,
+ issecure,
+ nodecount,
+ ispersistent,
+ overmem,
+ settask,
+ getoriginnode,
+ NULL, /* transfernode */
+ NULL, /* getnsec3parameters */
+ NULL, /* findnsec3node */
+ NULL, /* setsigningtime */
+ NULL, /* getsigningtime */
+ NULL, /* resigned */
+ isdnssec,
+ getrrsetstats,
+ NULL, /* rpz_attach */
+ NULL, /* rpz_ready */
+ NULL, /* findnodeext */
+ NULL, /* findext */
+ setcachestats,
+ hashsize,
+ nodefullname,
+ NULL, /* getsize */
+ setservestalettl,
+ getservestalettl,
+ setservestalerefresh,
+ getservestalerefresh,
+ NULL };
+
+isc_result_t
+dns_rbtdb_create(isc_mem_t *mctx, const dns_name_t *origin, dns_dbtype_t type,
+ dns_rdataclass_t rdclass, unsigned int argc, char *argv[],
+ void *driverarg, dns_db_t **dbp) {
+ dns_rbtdb_t *rbtdb;
+ isc_result_t result;
+ int i;
+ dns_name_t name;
+ bool (*sooner)(void *, void *);
+ isc_mem_t *hmctx = mctx;
+
+ /* Keep the compiler happy. */
+ UNUSED(driverarg);
+
+ rbtdb = isc_mem_get(mctx, sizeof(*rbtdb));
+
+ /*
+ * If argv[0] exists, it points to a memory context to use for heap
+ */
+ if (argc != 0) {
+ hmctx = (isc_mem_t *)argv[0];
+ }
+
+ memset(rbtdb, '\0', sizeof(*rbtdb));
+ dns_name_init(&rbtdb->common.origin, NULL);
+ rbtdb->common.attributes = 0;
+ if (type == dns_dbtype_cache) {
+ rbtdb->common.methods = &cache_methods;
+ rbtdb->common.attributes |= DNS_DBATTR_CACHE;
+ } else if (type == dns_dbtype_stub) {
+ rbtdb->common.methods = &zone_methods;
+ rbtdb->common.attributes |= DNS_DBATTR_STUB;
+ } else {
+ rbtdb->common.methods = &zone_methods;
+ }
+ rbtdb->common.rdclass = rdclass;
+ rbtdb->common.mctx = NULL;
+
+ ISC_LIST_INIT(rbtdb->common.update_listeners);
+
+ RBTDB_INITLOCK(&rbtdb->lock);
+
+ isc_rwlock_init(&rbtdb->tree_lock, 0, 0);
+
+ /*
+ * Initialize node_lock_count in a generic way to support future
+ * extension which allows the user to specify this value on creation.
+ * Note that when specified for a cache DB it must be larger than 1
+ * as commented with the definition of DEFAULT_CACHE_NODE_LOCK_COUNT.
+ */
+ if (rbtdb->node_lock_count == 0) {
+ if (IS_CACHE(rbtdb)) {
+ rbtdb->node_lock_count = DEFAULT_CACHE_NODE_LOCK_COUNT;
+ } else {
+ rbtdb->node_lock_count = DEFAULT_NODE_LOCK_COUNT;
+ }
+ } else if (rbtdb->node_lock_count < 2 && IS_CACHE(rbtdb)) {
+ result = ISC_R_RANGE;
+ goto cleanup_tree_lock;
+ }
+ INSIST(rbtdb->node_lock_count < (1 << DNS_RBT_LOCKLENGTH));
+ rbtdb->node_locks = isc_mem_get(mctx, rbtdb->node_lock_count *
+ sizeof(rbtdb_nodelock_t));
+
+ rbtdb->cachestats = NULL;
+ rbtdb->gluecachestats = NULL;
+
+ rbtdb->rrsetstats = NULL;
+ if (IS_CACHE(rbtdb)) {
+ result = dns_rdatasetstats_create(mctx, &rbtdb->rrsetstats);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup_node_locks;
+ }
+ rbtdb->rdatasets = isc_mem_get(
+ mctx,
+ rbtdb->node_lock_count * sizeof(rdatasetheaderlist_t));
+ for (i = 0; i < (int)rbtdb->node_lock_count; i++) {
+ ISC_LIST_INIT(rbtdb->rdatasets[i]);
+ }
+ } else {
+ rbtdb->rdatasets = NULL;
+ }
+
+ /*
+ * Create the heaps.
+ */
+ rbtdb->heaps = isc_mem_get(hmctx, rbtdb->node_lock_count *
+ sizeof(isc_heap_t *));
+ for (i = 0; i < (int)rbtdb->node_lock_count; i++) {
+ rbtdb->heaps[i] = NULL;
+ }
+ sooner = IS_CACHE(rbtdb) ? ttl_sooner : resign_sooner;
+ for (i = 0; i < (int)rbtdb->node_lock_count; i++) {
+ isc_heap_create(hmctx, sooner, set_index, 0, &rbtdb->heaps[i]);
+ }
+
+ /*
+ * Create deadnode lists.
+ */
+ rbtdb->deadnodes = isc_mem_get(mctx, rbtdb->node_lock_count *
+ sizeof(rbtnodelist_t));
+ for (i = 0; i < (int)rbtdb->node_lock_count; i++) {
+ ISC_LIST_INIT(rbtdb->deadnodes[i]);
+ }
+
+ rbtdb->active = rbtdb->node_lock_count;
+
+ for (i = 0; i < (int)(rbtdb->node_lock_count); i++) {
+ NODE_INITLOCK(&rbtdb->node_locks[i].lock);
+ isc_refcount_init(&rbtdb->node_locks[i].references, 0);
+ rbtdb->node_locks[i].exiting = false;
+ }
+
+ /*
+ * Attach to the mctx. The database will persist so long as there
+ * are references to it, and attaching to the mctx ensures that our
+ * mctx won't disappear out from under us.
+ */
+ isc_mem_attach(mctx, &rbtdb->common.mctx);
+ isc_mem_attach(hmctx, &rbtdb->hmctx);
+
+ /*
+ * Make a copy of the origin name.
+ */
+ result = dns_name_dupwithoffsets(origin, mctx, &rbtdb->common.origin);
+ if (result != ISC_R_SUCCESS) {
+ free_rbtdb(rbtdb, false, NULL);
+ return (result);
+ }
+
+ /*
+ * Make the Red-Black Trees.
+ */
+ result = dns_rbt_create(mctx, delete_callback, rbtdb, &rbtdb->tree);
+ if (result != ISC_R_SUCCESS) {
+ free_rbtdb(rbtdb, false, NULL);
+ return (result);
+ }
+
+ result = dns_rbt_create(mctx, delete_callback, rbtdb, &rbtdb->nsec);
+ if (result != ISC_R_SUCCESS) {
+ free_rbtdb(rbtdb, false, NULL);
+ return (result);
+ }
+
+ result = dns_rbt_create(mctx, delete_callback, rbtdb, &rbtdb->nsec3);
+ if (result != ISC_R_SUCCESS) {
+ free_rbtdb(rbtdb, false, NULL);
+ return (result);
+ }
+
+ /*
+ * In order to set the node callback bit correctly in zone databases,
+ * we need to know if the node has the origin name of the zone.
+ * In loading_addrdataset() we could simply compare the new name
+ * to the origin name, but this is expensive. Also, we don't know the
+ * node name in addrdataset(), so we need another way of knowing the
+ * zone's top.
+ *
+ * We now explicitly create a node for the zone's origin, and then
+ * we simply remember the node's address. This is safe, because
+ * the top-of-zone node can never be deleted, nor can its address
+ * change.
+ */
+ if (!IS_CACHE(rbtdb)) {
+ rbtdb->origin_node = NULL;
+ result = dns_rbt_addnode(rbtdb->tree, &rbtdb->common.origin,
+ &rbtdb->origin_node);
+ if (result != ISC_R_SUCCESS) {
+ INSIST(result != ISC_R_EXISTS);
+ free_rbtdb(rbtdb, false, NULL);
+ return (result);
+ }
+ INSIST(rbtdb->origin_node != NULL);
+ rbtdb->origin_node->nsec = DNS_RBT_NSEC_NORMAL;
+ /*
+ * We need to give the origin node the right locknum.
+ */
+ dns_name_init(&name, NULL);
+ dns_rbt_namefromnode(rbtdb->origin_node, &name);
+ rbtdb->origin_node->locknum = rbtdb->origin_node->hashval %
+ rbtdb->node_lock_count;
+ /*
+ * Add an apex node to the NSEC3 tree so that NSEC3 searches
+ * return partial matches when there is only a single NSEC3
+ * record in the tree.
+ */
+ rbtdb->nsec3_origin_node = NULL;
+ result = dns_rbt_addnode(rbtdb->nsec3, &rbtdb->common.origin,
+ &rbtdb->nsec3_origin_node);
+ if (result != ISC_R_SUCCESS) {
+ INSIST(result != ISC_R_EXISTS);
+ free_rbtdb(rbtdb, false, NULL);
+ return (result);
+ }
+ rbtdb->nsec3_origin_node->nsec = DNS_RBT_NSEC_NSEC3;
+ /*
+ * We need to give the nsec3 origin node the right locknum.
+ */
+ dns_name_init(&name, NULL);
+ dns_rbt_namefromnode(rbtdb->nsec3_origin_node, &name);
+ rbtdb->nsec3_origin_node->locknum =
+ rbtdb->nsec3_origin_node->hashval %
+ rbtdb->node_lock_count;
+ }
+
+ /*
+ * Misc. Initialization.
+ */
+ isc_refcount_init(&rbtdb->references, 1);
+ rbtdb->attributes = 0;
+ rbtdb->task = NULL;
+ rbtdb->serve_stale_ttl = 0;
+
+ /*
+ * Version Initialization.
+ */
+ rbtdb->current_serial = 1;
+ rbtdb->least_serial = 1;
+ rbtdb->next_serial = 2;
+ rbtdb->current_version = allocate_version(mctx, 1, 1, false);
+ rbtdb->current_version->rbtdb = rbtdb;
+ rbtdb->current_version->secure = dns_db_insecure;
+ rbtdb->current_version->havensec3 = false;
+ rbtdb->current_version->flags = 0;
+ rbtdb->current_version->iterations = 0;
+ rbtdb->current_version->hash = 0;
+ rbtdb->current_version->salt_length = 0;
+ memset(rbtdb->current_version->salt, 0,
+ sizeof(rbtdb->current_version->salt));
+ isc_rwlock_init(&rbtdb->current_version->rwlock, 0, 0);
+ rbtdb->current_version->records = 0;
+ rbtdb->current_version->xfrsize = 0;
+ rbtdb->future_version = NULL;
+ ISC_LIST_INIT(rbtdb->open_versions);
+ /*
+ * Keep the current version in the open list so that list operation
+ * won't happen in normal lookup operations.
+ */
+ PREPEND(rbtdb->open_versions, rbtdb->current_version, link);
+
+ rbtdb->common.magic = DNS_DB_MAGIC;
+ rbtdb->common.impmagic = RBTDB_MAGIC;
+
+ *dbp = (dns_db_t *)rbtdb;
+
+ return (ISC_R_SUCCESS);
+
+cleanup_node_locks:
+ isc_mem_put(mctx, rbtdb->node_locks,
+ rbtdb->node_lock_count * sizeof(rbtdb_nodelock_t));
+
+cleanup_tree_lock:
+ isc_rwlock_destroy(&rbtdb->tree_lock);
+ RBTDB_DESTROYLOCK(&rbtdb->lock);
+ isc_mem_put(mctx, rbtdb, sizeof(*rbtdb));
+ return (result);
+}
+
+/*
+ * Slabbed Rdataset Methods
+ */
+
+static void
+rdataset_disassociate(dns_rdataset_t *rdataset) {
+ dns_db_t *db = rdataset->private1;
+ dns_dbnode_t *node = rdataset->private2;
+
+ detachnode(db, &node);
+}
+
+static isc_result_t
+rdataset_first(dns_rdataset_t *rdataset) {
+ unsigned char *raw = rdataset->private3; /* RDATASLAB */
+ unsigned int count;
+
+ count = raw[0] * 256 + raw[1];
+ if (count == 0) {
+ rdataset->private5 = NULL;
+ return (ISC_R_NOMORE);
+ }
+
+ if ((rdataset->attributes & DNS_RDATASETATTR_LOADORDER) == 0) {
+ raw += DNS_RDATASET_COUNT;
+ }
+
+ raw += DNS_RDATASET_LENGTH;
+
+ /*
+ * The privateuint4 field is the number of rdata beyond the
+ * cursor position, so we decrement the total count by one
+ * before storing it.
+ *
+ * If DNS_RDATASETATTR_LOADORDER is not set 'raw' points to the
+ * first record. If DNS_RDATASETATTR_LOADORDER is set 'raw' points
+ * to the first entry in the offset table.
+ */
+ count--;
+ rdataset->privateuint4 = count;
+ rdataset->private5 = raw;
+
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+rdataset_next(dns_rdataset_t *rdataset) {
+ unsigned int count;
+ unsigned int length;
+ unsigned char *raw; /* RDATASLAB */
+
+ count = rdataset->privateuint4;
+ if (count == 0) {
+ return (ISC_R_NOMORE);
+ }
+ count--;
+ rdataset->privateuint4 = count;
+
+ /*
+ * Skip forward one record (length + 4) or one offset (4).
+ */
+ raw = rdataset->private5;
+#if DNS_RDATASET_FIXED
+ if ((rdataset->attributes & DNS_RDATASETATTR_LOADORDER) == 0)
+#endif /* DNS_RDATASET_FIXED */
+ {
+ length = raw[0] * 256 + raw[1];
+ raw += length;
+ }
+
+ rdataset->private5 = raw + DNS_RDATASET_ORDER + DNS_RDATASET_LENGTH;
+
+ return (ISC_R_SUCCESS);
+}
+
+static void
+rdataset_current(dns_rdataset_t *rdataset, dns_rdata_t *rdata) {
+ unsigned char *raw = rdataset->private5; /* RDATASLAB */
+ unsigned int length;
+ isc_region_t r;
+ unsigned int flags = 0;
+
+ REQUIRE(raw != NULL);
+
+ /*
+ * Find the start of the record if not already in private5
+ * then skip the length and order fields.
+ */
+#if DNS_RDATASET_FIXED
+ if ((rdataset->attributes & DNS_RDATASETATTR_LOADORDER) != 0) {
+ unsigned int offset;
+ offset = ((unsigned int)raw[0] << 24) +
+ ((unsigned int)raw[1] << 16) +
+ ((unsigned int)raw[2] << 8) + (unsigned int)raw[3];
+ raw = rdataset->private3;
+ raw += offset;
+ }
+#endif /* if DNS_RDATASET_FIXED */
+
+ length = raw[0] * 256 + raw[1];
+
+ raw += DNS_RDATASET_ORDER + DNS_RDATASET_LENGTH;
+
+ if (rdataset->type == dns_rdatatype_rrsig) {
+ if (*raw & DNS_RDATASLAB_OFFLINE) {
+ flags |= DNS_RDATA_OFFLINE;
+ }
+ length--;
+ raw++;
+ }
+ r.length = length;
+ r.base = raw;
+ dns_rdata_fromregion(rdata, rdataset->rdclass, rdataset->type, &r);
+ rdata->flags |= flags;
+}
+
+static void
+rdataset_clone(dns_rdataset_t *source, dns_rdataset_t *target) {
+ dns_db_t *db = source->private1;
+ dns_dbnode_t *node = source->private2;
+ dns_dbnode_t *cloned_node = NULL;
+
+ attachnode(db, node, &cloned_node);
+ INSIST(!ISC_LINK_LINKED(target, link));
+ *target = *source;
+ ISC_LINK_INIT(target, link);
+
+ /*
+ * Reset iterator state.
+ */
+ target->privateuint4 = 0;
+ target->private5 = NULL;
+}
+
+static unsigned int
+rdataset_count(dns_rdataset_t *rdataset) {
+ unsigned char *raw = rdataset->private3; /* RDATASLAB */
+ unsigned int count;
+
+ count = raw[0] * 256 + raw[1];
+
+ return (count);
+}
+
+static isc_result_t
+rdataset_getnoqname(dns_rdataset_t *rdataset, dns_name_t *name,
+ dns_rdataset_t *nsec, dns_rdataset_t *nsecsig) {
+ dns_db_t *db = rdataset->private1;
+ dns_dbnode_t *node = rdataset->private2;
+ dns_dbnode_t *cloned_node;
+ const struct noqname *noqname = rdataset->private6;
+
+ cloned_node = NULL;
+ attachnode(db, node, &cloned_node);
+ nsec->methods = &slab_methods;
+ nsec->rdclass = db->rdclass;
+ nsec->type = noqname->type;
+ nsec->covers = 0;
+ nsec->ttl = rdataset->ttl;
+ nsec->trust = rdataset->trust;
+ nsec->private1 = rdataset->private1;
+ nsec->private2 = rdataset->private2;
+ nsec->private3 = noqname->neg;
+ nsec->privateuint4 = 0;
+ nsec->private5 = NULL;
+ nsec->private6 = NULL;
+ nsec->private7 = NULL;
+
+ cloned_node = NULL;
+ attachnode(db, node, &cloned_node);
+ nsecsig->methods = &slab_methods;
+ nsecsig->rdclass = db->rdclass;
+ nsecsig->type = dns_rdatatype_rrsig;
+ nsecsig->covers = noqname->type;
+ nsecsig->ttl = rdataset->ttl;
+ nsecsig->trust = rdataset->trust;
+ nsecsig->private1 = rdataset->private1;
+ nsecsig->private2 = rdataset->private2;
+ nsecsig->private3 = noqname->negsig;
+ nsecsig->privateuint4 = 0;
+ nsecsig->private5 = NULL;
+ nsec->private6 = NULL;
+ nsec->private7 = NULL;
+
+ dns_name_clone(&noqname->name, name);
+
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+rdataset_getclosest(dns_rdataset_t *rdataset, dns_name_t *name,
+ dns_rdataset_t *nsec, dns_rdataset_t *nsecsig) {
+ dns_db_t *db = rdataset->private1;
+ dns_dbnode_t *node = rdataset->private2;
+ dns_dbnode_t *cloned_node;
+ const struct noqname *closest = rdataset->private7;
+
+ cloned_node = NULL;
+ attachnode(db, node, &cloned_node);
+ nsec->methods = &slab_methods;
+ nsec->rdclass = db->rdclass;
+ nsec->type = closest->type;
+ nsec->covers = 0;
+ nsec->ttl = rdataset->ttl;
+ nsec->trust = rdataset->trust;
+ nsec->private1 = rdataset->private1;
+ nsec->private2 = rdataset->private2;
+ nsec->private3 = closest->neg;
+ nsec->privateuint4 = 0;
+ nsec->private5 = NULL;
+ nsec->private6 = NULL;
+ nsec->private7 = NULL;
+
+ cloned_node = NULL;
+ attachnode(db, node, &cloned_node);
+ nsecsig->methods = &slab_methods;
+ nsecsig->rdclass = db->rdclass;
+ nsecsig->type = dns_rdatatype_rrsig;
+ nsecsig->covers = closest->type;
+ nsecsig->ttl = rdataset->ttl;
+ nsecsig->trust = rdataset->trust;
+ nsecsig->private1 = rdataset->private1;
+ nsecsig->private2 = rdataset->private2;
+ nsecsig->private3 = closest->negsig;
+ nsecsig->privateuint4 = 0;
+ nsecsig->private5 = NULL;
+ nsec->private6 = NULL;
+ nsec->private7 = NULL;
+
+ dns_name_clone(&closest->name, name);
+
+ return (ISC_R_SUCCESS);
+}
+
+static void
+rdataset_settrust(dns_rdataset_t *rdataset, dns_trust_t trust) {
+ dns_rbtdb_t *rbtdb = rdataset->private1;
+ dns_rbtnode_t *rbtnode = rdataset->private2;
+ rdatasetheader_t *header = rdataset->private3;
+
+ header--;
+ NODE_LOCK(&rbtdb->node_locks[rbtnode->locknum].lock,
+ isc_rwlocktype_write);
+ header->trust = rdataset->trust = trust;
+ NODE_UNLOCK(&rbtdb->node_locks[rbtnode->locknum].lock,
+ isc_rwlocktype_write);
+}
+
+static void
+rdataset_expire(dns_rdataset_t *rdataset) {
+ dns_rbtdb_t *rbtdb = rdataset->private1;
+ dns_rbtnode_t *rbtnode = rdataset->private2;
+ rdatasetheader_t *header = rdataset->private3;
+
+ header--;
+ NODE_LOCK(&rbtdb->node_locks[rbtnode->locknum].lock,
+ isc_rwlocktype_write);
+ expire_header(rbtdb, header, false, expire_flush);
+ NODE_UNLOCK(&rbtdb->node_locks[rbtnode->locknum].lock,
+ isc_rwlocktype_write);
+}
+
+static void
+rdataset_clearprefetch(dns_rdataset_t *rdataset) {
+ dns_rbtdb_t *rbtdb = rdataset->private1;
+ dns_rbtnode_t *rbtnode = rdataset->private2;
+ rdatasetheader_t *header = rdataset->private3;
+
+ header--;
+ NODE_LOCK(&rbtdb->node_locks[rbtnode->locknum].lock,
+ isc_rwlocktype_write);
+ RDATASET_ATTR_CLR(header, RDATASET_ATTR_PREFETCH);
+ NODE_UNLOCK(&rbtdb->node_locks[rbtnode->locknum].lock,
+ isc_rwlocktype_write);
+}
+
+/*
+ * Rdataset Iterator Methods
+ */
+
+static void
+rdatasetiter_destroy(dns_rdatasetiter_t **iteratorp) {
+ rbtdb_rdatasetiter_t *rbtiterator;
+
+ rbtiterator = (rbtdb_rdatasetiter_t *)(*iteratorp);
+
+ if (rbtiterator->common.version != NULL) {
+ closeversion(rbtiterator->common.db,
+ &rbtiterator->common.version, false);
+ }
+ detachnode(rbtiterator->common.db, &rbtiterator->common.node);
+ isc_mem_put(rbtiterator->common.db->mctx, rbtiterator,
+ sizeof(*rbtiterator));
+
+ *iteratorp = NULL;
+}
+
+static bool
+iterator_active(dns_rbtdb_t *rbtdb, rbtdb_rdatasetiter_t *rbtiterator,
+ rdatasetheader_t *header) {
+ dns_ttl_t stale_ttl = header->rdh_ttl + STALE_TTL(header, rbtdb);
+
+ /*
+ * Is this a "this rdataset doesn't exist" record?
+ */
+ if (NONEXISTENT(header)) {
+ return (false);
+ }
+
+ /*
+ * If this is a zone or this header still active then return it.
+ */
+ if (!IS_CACHE(rbtdb) || ACTIVE(header, rbtiterator->common.now)) {
+ return (true);
+ }
+
+ /*
+ * If we are not returning stale records or the rdataset is
+ * too old don't return it.
+ */
+ if (!STALEOK(rbtiterator) || (rbtiterator->common.now > stale_ttl)) {
+ return (false);
+ }
+ return (true);
+}
+
+static isc_result_t
+rdatasetiter_first(dns_rdatasetiter_t *iterator) {
+ rbtdb_rdatasetiter_t *rbtiterator = (rbtdb_rdatasetiter_t *)iterator;
+ dns_rbtdb_t *rbtdb = (dns_rbtdb_t *)(rbtiterator->common.db);
+ dns_rbtnode_t *rbtnode = rbtiterator->common.node;
+ rbtdb_version_t *rbtversion = rbtiterator->common.version;
+ rdatasetheader_t *header, *top_next;
+ rbtdb_serial_t serial = IS_CACHE(rbtdb) ? 1 : rbtversion->serial;
+
+ NODE_LOCK(&rbtdb->node_locks[rbtnode->locknum].lock,
+ isc_rwlocktype_read);
+
+ for (header = rbtnode->data; header != NULL; header = top_next) {
+ top_next = header->next;
+ do {
+ if (EXPIREDOK(rbtiterator)) {
+ if (!NONEXISTENT(header)) {
+ break;
+ }
+ header = header->down;
+ } else if (header->serial <= serial && !IGNORE(header))
+ {
+ if (!iterator_active(rbtdb, rbtiterator,
+ header))
+ {
+ header = NULL;
+ }
+ break;
+ } else {
+ header = header->down;
+ }
+ } while (header != NULL);
+ if (header != NULL) {
+ break;
+ }
+ }
+
+ NODE_UNLOCK(&rbtdb->node_locks[rbtnode->locknum].lock,
+ isc_rwlocktype_read);
+
+ rbtiterator->current = header;
+
+ if (header == NULL) {
+ return (ISC_R_NOMORE);
+ }
+
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+rdatasetiter_next(dns_rdatasetiter_t *iterator) {
+ rbtdb_rdatasetiter_t *rbtiterator = (rbtdb_rdatasetiter_t *)iterator;
+ dns_rbtdb_t *rbtdb = (dns_rbtdb_t *)(rbtiterator->common.db);
+ dns_rbtnode_t *rbtnode = rbtiterator->common.node;
+ rbtdb_version_t *rbtversion = rbtiterator->common.version;
+ rdatasetheader_t *header, *top_next;
+ rbtdb_serial_t serial = IS_CACHE(rbtdb) ? 1 : rbtversion->serial;
+ rbtdb_rdatatype_t type, negtype;
+ dns_rdatatype_t rdtype, covers;
+ bool expiredok = EXPIREDOK(rbtiterator);
+
+ header = rbtiterator->current;
+ if (header == NULL) {
+ return (ISC_R_NOMORE);
+ }
+
+ NODE_LOCK(&rbtdb->node_locks[rbtnode->locknum].lock,
+ isc_rwlocktype_read);
+
+ type = header->type;
+ rdtype = RBTDB_RDATATYPE_BASE(header->type);
+ if (NEGATIVE(header)) {
+ covers = RBTDB_RDATATYPE_EXT(header->type);
+ negtype = RBTDB_RDATATYPE_VALUE(covers, 0);
+ } else {
+ negtype = RBTDB_RDATATYPE_VALUE(0, rdtype);
+ }
+
+ /*
+ * Find the start of the header chain for the next type
+ * by walking back up the list.
+ */
+ top_next = header->next;
+ while (top_next != NULL &&
+ (top_next->type == type || top_next->type == negtype))
+ {
+ top_next = top_next->next;
+ }
+ if (expiredok) {
+ /*
+ * Keep walking down the list if possible or
+ * start the next type.
+ */
+ header = header->down != NULL ? header->down : top_next;
+ } else {
+ header = top_next;
+ }
+ for (; header != NULL; header = top_next) {
+ top_next = header->next;
+ do {
+ if (expiredok) {
+ if (!NONEXISTENT(header)) {
+ break;
+ }
+ header = header->down;
+ } else if (header->serial <= serial && !IGNORE(header))
+ {
+ if (!iterator_active(rbtdb, rbtiterator,
+ header))
+ {
+ header = NULL;
+ }
+ break;
+ } else {
+ header = header->down;
+ }
+ } while (header != NULL);
+ if (header != NULL) {
+ break;
+ }
+ /*
+ * Find the start of the header chain for the next type
+ * by walking back up the list.
+ */
+ while (top_next != NULL &&
+ (top_next->type == type || top_next->type == negtype))
+ {
+ top_next = top_next->next;
+ }
+ }
+
+ NODE_UNLOCK(&rbtdb->node_locks[rbtnode->locknum].lock,
+ isc_rwlocktype_read);
+
+ rbtiterator->current = header;
+
+ if (header == NULL) {
+ return (ISC_R_NOMORE);
+ }
+
+ return (ISC_R_SUCCESS);
+}
+
+static void
+rdatasetiter_current(dns_rdatasetiter_t *iterator, dns_rdataset_t *rdataset) {
+ rbtdb_rdatasetiter_t *rbtiterator = (rbtdb_rdatasetiter_t *)iterator;
+ dns_rbtdb_t *rbtdb = (dns_rbtdb_t *)(rbtiterator->common.db);
+ dns_rbtnode_t *rbtnode = rbtiterator->common.node;
+ rdatasetheader_t *header;
+
+ header = rbtiterator->current;
+ REQUIRE(header != NULL);
+
+ NODE_LOCK(&rbtdb->node_locks[rbtnode->locknum].lock,
+ isc_rwlocktype_read);
+
+ bind_rdataset(rbtdb, rbtnode, header, rbtiterator->common.now,
+ isc_rwlocktype_read, rdataset);
+
+ NODE_UNLOCK(&rbtdb->node_locks[rbtnode->locknum].lock,
+ isc_rwlocktype_read);
+}
+
+/*
+ * Database Iterator Methods
+ */
+
+static void
+reference_iter_node(rbtdb_dbiterator_t *rbtdbiter) {
+ dns_rbtdb_t *rbtdb = (dns_rbtdb_t *)rbtdbiter->common.db;
+ dns_rbtnode_t *node = rbtdbiter->node;
+
+ if (node == NULL) {
+ return;
+ }
+
+ INSIST(rbtdbiter->tree_locked != isc_rwlocktype_none);
+ reactivate_node(rbtdb, node, rbtdbiter->tree_locked);
+}
+
+static void
+dereference_iter_node(rbtdb_dbiterator_t *rbtdbiter) {
+ dns_rbtdb_t *rbtdb = (dns_rbtdb_t *)rbtdbiter->common.db;
+ dns_rbtnode_t *node = rbtdbiter->node;
+ nodelock_t *lock;
+
+ if (node == NULL) {
+ return;
+ }
+
+ lock = &rbtdb->node_locks[node->locknum].lock;
+ NODE_LOCK(lock, isc_rwlocktype_read);
+ decrement_reference(rbtdb, node, 0, isc_rwlocktype_read,
+ rbtdbiter->tree_locked, false);
+ NODE_UNLOCK(lock, isc_rwlocktype_read);
+
+ rbtdbiter->node = NULL;
+}
+
+static void
+flush_deletions(rbtdb_dbiterator_t *rbtdbiter) {
+ dns_rbtnode_t *node;
+ dns_rbtdb_t *rbtdb = (dns_rbtdb_t *)rbtdbiter->common.db;
+ bool was_read_locked = false;
+ nodelock_t *lock;
+ int i;
+
+ if (rbtdbiter->delcnt != 0) {
+ /*
+ * Note that "%d node of %d in tree" can report things like
+ * "flush_deletions: 59 nodes of 41 in tree". This means
+ * That some nodes appear on the deletions list more than
+ * once. Only the last occurrence will actually be deleted.
+ */
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE,
+ DNS_LOGMODULE_CACHE, ISC_LOG_DEBUG(1),
+ "flush_deletions: %d nodes of %d in tree",
+ rbtdbiter->delcnt,
+ dns_rbt_nodecount(rbtdb->tree));
+
+ if (rbtdbiter->tree_locked == isc_rwlocktype_read) {
+ RWUNLOCK(&rbtdb->tree_lock, isc_rwlocktype_read);
+ was_read_locked = true;
+ }
+ RWLOCK(&rbtdb->tree_lock, isc_rwlocktype_write);
+ rbtdbiter->tree_locked = isc_rwlocktype_write;
+
+ for (i = 0; i < rbtdbiter->delcnt; i++) {
+ node = rbtdbiter->deletions[i];
+ lock = &rbtdb->node_locks[node->locknum].lock;
+
+ NODE_LOCK(lock, isc_rwlocktype_read);
+ decrement_reference(rbtdb, node, 0, isc_rwlocktype_read,
+ rbtdbiter->tree_locked, false);
+ NODE_UNLOCK(lock, isc_rwlocktype_read);
+ }
+
+ rbtdbiter->delcnt = 0;
+
+ RWUNLOCK(&rbtdb->tree_lock, isc_rwlocktype_write);
+ if (was_read_locked) {
+ RWLOCK(&rbtdb->tree_lock, isc_rwlocktype_read);
+ rbtdbiter->tree_locked = isc_rwlocktype_read;
+ } else {
+ rbtdbiter->tree_locked = isc_rwlocktype_none;
+ }
+ }
+}
+
+static void
+resume_iteration(rbtdb_dbiterator_t *rbtdbiter) {
+ dns_rbtdb_t *rbtdb = (dns_rbtdb_t *)rbtdbiter->common.db;
+
+ REQUIRE(rbtdbiter->paused);
+ REQUIRE(rbtdbiter->tree_locked == isc_rwlocktype_none);
+
+ RWLOCK(&rbtdb->tree_lock, isc_rwlocktype_read);
+ rbtdbiter->tree_locked = isc_rwlocktype_read;
+
+ rbtdbiter->paused = false;
+}
+
+static void
+dbiterator_destroy(dns_dbiterator_t **iteratorp) {
+ rbtdb_dbiterator_t *rbtdbiter = (rbtdb_dbiterator_t *)(*iteratorp);
+ dns_rbtdb_t *rbtdb = (dns_rbtdb_t *)rbtdbiter->common.db;
+ dns_db_t *db = NULL;
+
+ if (rbtdbiter->tree_locked == isc_rwlocktype_read) {
+ RWUNLOCK(&rbtdb->tree_lock, isc_rwlocktype_read);
+ rbtdbiter->tree_locked = isc_rwlocktype_none;
+ } else {
+ INSIST(rbtdbiter->tree_locked == isc_rwlocktype_none);
+ }
+
+ dereference_iter_node(rbtdbiter);
+
+ flush_deletions(rbtdbiter);
+
+ dns_db_attach(rbtdbiter->common.db, &db);
+ dns_db_detach(&rbtdbiter->common.db);
+
+ dns_rbtnodechain_reset(&rbtdbiter->chain);
+ dns_rbtnodechain_reset(&rbtdbiter->nsec3chain);
+ isc_mem_put(db->mctx, rbtdbiter, sizeof(*rbtdbiter));
+ dns_db_detach(&db);
+
+ *iteratorp = NULL;
+}
+
+static isc_result_t
+dbiterator_first(dns_dbiterator_t *iterator) {
+ isc_result_t result;
+ rbtdb_dbiterator_t *rbtdbiter = (rbtdb_dbiterator_t *)iterator;
+ dns_rbtdb_t *rbtdb = (dns_rbtdb_t *)iterator->db;
+ dns_name_t *name, *origin;
+
+ if (rbtdbiter->result != ISC_R_SUCCESS &&
+ rbtdbiter->result != ISC_R_NOTFOUND &&
+ rbtdbiter->result != DNS_R_PARTIALMATCH &&
+ rbtdbiter->result != ISC_R_NOMORE)
+ {
+ return (rbtdbiter->result);
+ }
+
+ if (rbtdbiter->paused) {
+ resume_iteration(rbtdbiter);
+ }
+
+ dereference_iter_node(rbtdbiter);
+
+ name = dns_fixedname_name(&rbtdbiter->name);
+ origin = dns_fixedname_name(&rbtdbiter->origin);
+ dns_rbtnodechain_reset(&rbtdbiter->chain);
+ dns_rbtnodechain_reset(&rbtdbiter->nsec3chain);
+
+ if (rbtdbiter->nsec3only) {
+ rbtdbiter->current = &rbtdbiter->nsec3chain;
+ result = dns_rbtnodechain_first(rbtdbiter->current,
+ rbtdb->nsec3, name, origin);
+ } else {
+ rbtdbiter->current = &rbtdbiter->chain;
+ result = dns_rbtnodechain_first(rbtdbiter->current, rbtdb->tree,
+ name, origin);
+ if (!rbtdbiter->nonsec3 && result == ISC_R_NOTFOUND) {
+ rbtdbiter->current = &rbtdbiter->nsec3chain;
+ result = dns_rbtnodechain_first(
+ rbtdbiter->current, rbtdb->nsec3, name, origin);
+ }
+ }
+ if (result == ISC_R_SUCCESS || result == DNS_R_NEWORIGIN) {
+ result = dns_rbtnodechain_current(rbtdbiter->current, NULL,
+ NULL, &rbtdbiter->node);
+ if (result == ISC_R_SUCCESS) {
+ rbtdbiter->new_origin = true;
+ reference_iter_node(rbtdbiter);
+ }
+ } else {
+ INSIST(result == ISC_R_NOTFOUND);
+ result = ISC_R_NOMORE; /* The tree is empty. */
+ }
+
+ rbtdbiter->result = result;
+
+ if (result != ISC_R_SUCCESS) {
+ ENSURE(!rbtdbiter->paused);
+ }
+
+ return (result);
+}
+
+static isc_result_t
+dbiterator_last(dns_dbiterator_t *iterator) {
+ isc_result_t result;
+ rbtdb_dbiterator_t *rbtdbiter = (rbtdb_dbiterator_t *)iterator;
+ dns_rbtdb_t *rbtdb = (dns_rbtdb_t *)iterator->db;
+ dns_name_t *name, *origin;
+
+ if (rbtdbiter->result != ISC_R_SUCCESS &&
+ rbtdbiter->result != ISC_R_NOTFOUND &&
+ rbtdbiter->result != DNS_R_PARTIALMATCH &&
+ rbtdbiter->result != ISC_R_NOMORE)
+ {
+ return (rbtdbiter->result);
+ }
+
+ if (rbtdbiter->paused) {
+ resume_iteration(rbtdbiter);
+ }
+
+ dereference_iter_node(rbtdbiter);
+
+ name = dns_fixedname_name(&rbtdbiter->name);
+ origin = dns_fixedname_name(&rbtdbiter->origin);
+ dns_rbtnodechain_reset(&rbtdbiter->chain);
+ dns_rbtnodechain_reset(&rbtdbiter->nsec3chain);
+
+ result = ISC_R_NOTFOUND;
+ if (rbtdbiter->nsec3only && !rbtdbiter->nonsec3) {
+ rbtdbiter->current = &rbtdbiter->nsec3chain;
+ result = dns_rbtnodechain_last(rbtdbiter->current, rbtdb->nsec3,
+ name, origin);
+ }
+ if (!rbtdbiter->nsec3only && result == ISC_R_NOTFOUND) {
+ rbtdbiter->current = &rbtdbiter->chain;
+ result = dns_rbtnodechain_last(rbtdbiter->current, rbtdb->tree,
+ name, origin);
+ }
+ if (result == ISC_R_SUCCESS || result == DNS_R_NEWORIGIN) {
+ result = dns_rbtnodechain_current(rbtdbiter->current, NULL,
+ NULL, &rbtdbiter->node);
+ if (result == ISC_R_SUCCESS) {
+ rbtdbiter->new_origin = true;
+ reference_iter_node(rbtdbiter);
+ }
+ } else {
+ INSIST(result == ISC_R_NOTFOUND);
+ result = ISC_R_NOMORE; /* The tree is empty. */
+ }
+
+ rbtdbiter->result = result;
+
+ return (result);
+}
+
+static isc_result_t
+dbiterator_seek(dns_dbiterator_t *iterator, const dns_name_t *name) {
+ isc_result_t result, tresult;
+ rbtdb_dbiterator_t *rbtdbiter = (rbtdb_dbiterator_t *)iterator;
+ dns_rbtdb_t *rbtdb = (dns_rbtdb_t *)iterator->db;
+ dns_name_t *iname, *origin;
+
+ if (rbtdbiter->result != ISC_R_SUCCESS &&
+ rbtdbiter->result != ISC_R_NOTFOUND &&
+ rbtdbiter->result != DNS_R_PARTIALMATCH &&
+ rbtdbiter->result != ISC_R_NOMORE)
+ {
+ return (rbtdbiter->result);
+ }
+
+ if (rbtdbiter->paused) {
+ resume_iteration(rbtdbiter);
+ }
+
+ dereference_iter_node(rbtdbiter);
+
+ iname = dns_fixedname_name(&rbtdbiter->name);
+ origin = dns_fixedname_name(&rbtdbiter->origin);
+ dns_rbtnodechain_reset(&rbtdbiter->chain);
+ dns_rbtnodechain_reset(&rbtdbiter->nsec3chain);
+
+ if (rbtdbiter->nsec3only) {
+ rbtdbiter->current = &rbtdbiter->nsec3chain;
+ result = dns_rbt_findnode(rbtdb->nsec3, name, NULL,
+ &rbtdbiter->node, rbtdbiter->current,
+ DNS_RBTFIND_EMPTYDATA, NULL, NULL);
+ } else if (rbtdbiter->nonsec3) {
+ rbtdbiter->current = &rbtdbiter->chain;
+ result = dns_rbt_findnode(rbtdb->tree, name, NULL,
+ &rbtdbiter->node, rbtdbiter->current,
+ DNS_RBTFIND_EMPTYDATA, NULL, NULL);
+ } else {
+ /*
+ * Stay on main chain if not found on either chain.
+ */
+ rbtdbiter->current = &rbtdbiter->chain;
+ result = dns_rbt_findnode(rbtdb->tree, name, NULL,
+ &rbtdbiter->node, rbtdbiter->current,
+ DNS_RBTFIND_EMPTYDATA, NULL, NULL);
+ if (result == DNS_R_PARTIALMATCH) {
+ dns_rbtnode_t *node = NULL;
+ tresult = dns_rbt_findnode(
+ rbtdb->nsec3, name, NULL, &node,
+ &rbtdbiter->nsec3chain, DNS_RBTFIND_EMPTYDATA,
+ NULL, NULL);
+ if (tresult == ISC_R_SUCCESS) {
+ rbtdbiter->node = node;
+ rbtdbiter->current = &rbtdbiter->nsec3chain;
+ result = tresult;
+ }
+ }
+ }
+
+ if (result == ISC_R_SUCCESS || result == DNS_R_PARTIALMATCH) {
+ tresult = dns_rbtnodechain_current(rbtdbiter->current, iname,
+ origin, NULL);
+ if (tresult == ISC_R_SUCCESS) {
+ rbtdbiter->new_origin = true;
+ reference_iter_node(rbtdbiter);
+ } else {
+ result = tresult;
+ rbtdbiter->node = NULL;
+ }
+ } else {
+ rbtdbiter->node = NULL;
+ }
+
+ rbtdbiter->result = (result == DNS_R_PARTIALMATCH) ? ISC_R_SUCCESS
+ : result;
+
+ return (result);
+}
+
+static isc_result_t
+dbiterator_prev(dns_dbiterator_t *iterator) {
+ isc_result_t result;
+ rbtdb_dbiterator_t *rbtdbiter = (rbtdb_dbiterator_t *)iterator;
+ dns_name_t *name, *origin;
+ dns_rbtdb_t *rbtdb = (dns_rbtdb_t *)iterator->db;
+
+ REQUIRE(rbtdbiter->node != NULL);
+
+ if (rbtdbiter->result != ISC_R_SUCCESS) {
+ return (rbtdbiter->result);
+ }
+
+ if (rbtdbiter->paused) {
+ resume_iteration(rbtdbiter);
+ }
+
+ name = dns_fixedname_name(&rbtdbiter->name);
+ origin = dns_fixedname_name(&rbtdbiter->origin);
+ result = dns_rbtnodechain_prev(rbtdbiter->current, name, origin);
+ if (result == ISC_R_NOMORE && !rbtdbiter->nsec3only &&
+ !rbtdbiter->nonsec3 && &rbtdbiter->nsec3chain == rbtdbiter->current)
+ {
+ rbtdbiter->current = &rbtdbiter->chain;
+ dns_rbtnodechain_reset(rbtdbiter->current);
+ result = dns_rbtnodechain_last(rbtdbiter->current, rbtdb->tree,
+ name, origin);
+ if (result == ISC_R_NOTFOUND) {
+ result = ISC_R_NOMORE;
+ }
+ }
+
+ dereference_iter_node(rbtdbiter);
+
+ if (result == DNS_R_NEWORIGIN || result == ISC_R_SUCCESS) {
+ rbtdbiter->new_origin = (result == DNS_R_NEWORIGIN);
+ result = dns_rbtnodechain_current(rbtdbiter->current, NULL,
+ NULL, &rbtdbiter->node);
+ }
+
+ if (result == ISC_R_SUCCESS) {
+ reference_iter_node(rbtdbiter);
+ }
+
+ rbtdbiter->result = result;
+
+ return (result);
+}
+
+static isc_result_t
+dbiterator_next(dns_dbiterator_t *iterator) {
+ isc_result_t result;
+ rbtdb_dbiterator_t *rbtdbiter = (rbtdb_dbiterator_t *)iterator;
+ dns_name_t *name, *origin;
+ dns_rbtdb_t *rbtdb = (dns_rbtdb_t *)iterator->db;
+
+ REQUIRE(rbtdbiter->node != NULL);
+
+ if (rbtdbiter->result != ISC_R_SUCCESS) {
+ return (rbtdbiter->result);
+ }
+
+ if (rbtdbiter->paused) {
+ resume_iteration(rbtdbiter);
+ }
+
+ name = dns_fixedname_name(&rbtdbiter->name);
+ origin = dns_fixedname_name(&rbtdbiter->origin);
+ result = dns_rbtnodechain_next(rbtdbiter->current, name, origin);
+ if (result == ISC_R_NOMORE && !rbtdbiter->nsec3only &&
+ !rbtdbiter->nonsec3 && &rbtdbiter->chain == rbtdbiter->current)
+ {
+ rbtdbiter->current = &rbtdbiter->nsec3chain;
+ dns_rbtnodechain_reset(rbtdbiter->current);
+ result = dns_rbtnodechain_first(rbtdbiter->current,
+ rbtdb->nsec3, name, origin);
+ if (result == ISC_R_NOTFOUND) {
+ result = ISC_R_NOMORE;
+ }
+ }
+
+ dereference_iter_node(rbtdbiter);
+
+ if (result == DNS_R_NEWORIGIN || result == ISC_R_SUCCESS) {
+ rbtdbiter->new_origin = (result == DNS_R_NEWORIGIN);
+ result = dns_rbtnodechain_current(rbtdbiter->current, NULL,
+ NULL, &rbtdbiter->node);
+ }
+ if (result == ISC_R_SUCCESS) {
+ reference_iter_node(rbtdbiter);
+ }
+
+ rbtdbiter->result = result;
+
+ return (result);
+}
+
+static isc_result_t
+dbiterator_current(dns_dbiterator_t *iterator, dns_dbnode_t **nodep,
+ dns_name_t *name) {
+ dns_rbtdb_t *rbtdb = (dns_rbtdb_t *)iterator->db;
+ rbtdb_dbiterator_t *rbtdbiter = (rbtdb_dbiterator_t *)iterator;
+ dns_rbtnode_t *node = rbtdbiter->node;
+ isc_result_t result;
+ dns_name_t *nodename = dns_fixedname_name(&rbtdbiter->name);
+ dns_name_t *origin = dns_fixedname_name(&rbtdbiter->origin);
+
+ REQUIRE(rbtdbiter->result == ISC_R_SUCCESS);
+ REQUIRE(rbtdbiter->node != NULL);
+
+ if (rbtdbiter->paused) {
+ resume_iteration(rbtdbiter);
+ }
+
+ if (name != NULL) {
+ if (rbtdbiter->common.relative_names) {
+ origin = NULL;
+ }
+ result = dns_name_concatenate(nodename, origin, name, NULL);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+ if (rbtdbiter->common.relative_names && rbtdbiter->new_origin) {
+ result = DNS_R_NEWORIGIN;
+ }
+ } else {
+ result = ISC_R_SUCCESS;
+ }
+
+ new_reference(rbtdb, node, isc_rwlocktype_none);
+
+ *nodep = rbtdbiter->node;
+
+ if (iterator->cleaning && result == ISC_R_SUCCESS) {
+ isc_result_t expire_result;
+
+ /*
+ * If the deletion array is full, flush it before trying
+ * to expire the current node. The current node can't
+ * fully deleted while the iteration cursor is still on it.
+ */
+ if (rbtdbiter->delcnt == DELETION_BATCH_MAX) {
+ flush_deletions(rbtdbiter);
+ }
+
+ expire_result = expirenode(iterator->db, *nodep, 0);
+
+ /*
+ * expirenode() currently always returns success.
+ */
+ if (expire_result == ISC_R_SUCCESS && node->down == NULL) {
+ rbtdbiter->deletions[rbtdbiter->delcnt++] = node;
+ isc_refcount_increment(&node->references);
+ }
+ }
+
+ return (result);
+}
+
+static isc_result_t
+dbiterator_pause(dns_dbiterator_t *iterator) {
+ dns_rbtdb_t *rbtdb = (dns_rbtdb_t *)iterator->db;
+ rbtdb_dbiterator_t *rbtdbiter = (rbtdb_dbiterator_t *)iterator;
+
+ if (rbtdbiter->result != ISC_R_SUCCESS &&
+ rbtdbiter->result != ISC_R_NOTFOUND &&
+ rbtdbiter->result != DNS_R_PARTIALMATCH &&
+ rbtdbiter->result != ISC_R_NOMORE)
+ {
+ return (rbtdbiter->result);
+ }
+
+ if (rbtdbiter->paused) {
+ return (ISC_R_SUCCESS);
+ }
+
+ rbtdbiter->paused = true;
+
+ if (rbtdbiter->tree_locked != isc_rwlocktype_none) {
+ INSIST(rbtdbiter->tree_locked == isc_rwlocktype_read);
+ RWUNLOCK(&rbtdb->tree_lock, isc_rwlocktype_read);
+ rbtdbiter->tree_locked = isc_rwlocktype_none;
+ }
+
+ flush_deletions(rbtdbiter);
+
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+dbiterator_origin(dns_dbiterator_t *iterator, dns_name_t *name) {
+ rbtdb_dbiterator_t *rbtdbiter = (rbtdb_dbiterator_t *)iterator;
+ dns_name_t *origin = dns_fixedname_name(&rbtdbiter->origin);
+
+ if (rbtdbiter->result != ISC_R_SUCCESS) {
+ return (rbtdbiter->result);
+ }
+
+ dns_name_copy(origin, name);
+ return (ISC_R_SUCCESS);
+}
+
+static void
+setownercase(rdatasetheader_t *header, const dns_name_t *name) {
+ unsigned int i;
+ bool fully_lower;
+
+ /*
+ * We do not need to worry about label lengths as they are all
+ * less than or equal to 63.
+ */
+ memset(header->upper, 0, sizeof(header->upper));
+ fully_lower = true;
+ for (i = 0; i < name->length; i++) {
+ if (isupper(name->ndata[i])) {
+ header->upper[i / 8] |= 1 << (i % 8);
+ fully_lower = false;
+ }
+ }
+ RDATASET_ATTR_SET(header, RDATASET_ATTR_CASESET);
+ if (fully_lower) {
+ RDATASET_ATTR_SET(header, RDATASET_ATTR_CASEFULLYLOWER);
+ }
+}
+
+static void
+rdataset_setownercase(dns_rdataset_t *rdataset, const dns_name_t *name) {
+ dns_rbtdb_t *rbtdb = rdataset->private1;
+ dns_rbtnode_t *rbtnode = rdataset->private2;
+ unsigned char *raw = rdataset->private3; /* RDATASLAB */
+ rdatasetheader_t *header;
+
+ header = (struct rdatasetheader *)(raw - sizeof(*header));
+
+ NODE_LOCK(&rbtdb->node_locks[rbtnode->locknum].lock,
+ isc_rwlocktype_write);
+ setownercase(header, name);
+ NODE_UNLOCK(&rbtdb->node_locks[rbtnode->locknum].lock,
+ isc_rwlocktype_write);
+}
+
+static void
+rdataset_getownercase(const dns_rdataset_t *rdataset, dns_name_t *name) {
+ dns_rbtdb_t *rbtdb = rdataset->private1;
+ dns_rbtnode_t *rbtnode = rdataset->private2;
+ unsigned char *raw = rdataset->private3; /* RDATASLAB */
+ rdatasetheader_t *header = NULL;
+ uint8_t mask = (1 << 7);
+ uint8_t bits = 0;
+
+ header = (struct rdatasetheader *)(raw - sizeof(*header));
+
+ NODE_LOCK(&rbtdb->node_locks[rbtnode->locknum].lock,
+ isc_rwlocktype_read);
+
+ if (!CASESET(header)) {
+ goto unlock;
+ }
+
+ if (CASEFULLYLOWER(header)) {
+ for (size_t i = 0; i < name->length; i++) {
+ name->ndata[i] = tolower(name->ndata[i]);
+ }
+ } else {
+ for (size_t i = 0; i < name->length; i++) {
+ if (mask == (1 << 7)) {
+ bits = header->upper[i / 8];
+ mask = 1;
+ } else {
+ mask <<= 1;
+ }
+
+ name->ndata[i] = ((bits & mask) != 0)
+ ? toupper(name->ndata[i])
+ : tolower(name->ndata[i]);
+ }
+ }
+
+unlock:
+ NODE_UNLOCK(&rbtdb->node_locks[rbtnode->locknum].lock,
+ isc_rwlocktype_read);
+}
+
+struct rbtdb_glue {
+ struct rbtdb_glue *next;
+ dns_fixedname_t fixedname;
+ dns_rdataset_t rdataset_a;
+ dns_rdataset_t sigrdataset_a;
+ dns_rdataset_t rdataset_aaaa;
+ dns_rdataset_t sigrdataset_aaaa;
+};
+
+typedef struct {
+ rbtdb_glue_t *glue_list;
+ dns_rbtdb_t *rbtdb;
+ rbtdb_version_t *rbtversion;
+} rbtdb_glue_additionaldata_ctx_t;
+
+static void
+free_gluelist(rbtdb_glue_t *glue_list, dns_rbtdb_t *rbtdb) {
+ rbtdb_glue_t *cur, *cur_next;
+
+ if (glue_list == (void *)-1) {
+ return;
+ }
+
+ cur = glue_list;
+ while (cur != NULL) {
+ cur_next = cur->next;
+
+ if (dns_rdataset_isassociated(&cur->rdataset_a)) {
+ dns_rdataset_disassociate(&cur->rdataset_a);
+ }
+ if (dns_rdataset_isassociated(&cur->sigrdataset_a)) {
+ dns_rdataset_disassociate(&cur->sigrdataset_a);
+ }
+
+ if (dns_rdataset_isassociated(&cur->rdataset_aaaa)) {
+ dns_rdataset_disassociate(&cur->rdataset_aaaa);
+ }
+ if (dns_rdataset_isassociated(&cur->sigrdataset_aaaa)) {
+ dns_rdataset_disassociate(&cur->sigrdataset_aaaa);
+ }
+
+ dns_rdataset_invalidate(&cur->rdataset_a);
+ dns_rdataset_invalidate(&cur->sigrdataset_a);
+ dns_rdataset_invalidate(&cur->rdataset_aaaa);
+ dns_rdataset_invalidate(&cur->sigrdataset_aaaa);
+
+ isc_mem_put(rbtdb->common.mctx, cur, sizeof(*cur));
+ cur = cur_next;
+ }
+}
+
+static void
+free_gluetable(rbtdb_version_t *version) {
+ dns_rbtdb_t *rbtdb;
+ size_t size, i;
+
+ RWLOCK(&version->glue_rwlock, isc_rwlocktype_write);
+
+ rbtdb = version->rbtdb;
+
+ for (i = 0; i < HASHSIZE(version->glue_table_bits); i++) {
+ rbtdb_glue_table_node_t *cur, *cur_next;
+
+ cur = version->glue_table[i];
+ while (cur != NULL) {
+ cur_next = cur->next;
+ /* isc_refcount_decrement(&cur->node->references); */
+ cur->node = NULL;
+ free_gluelist(cur->glue_list, rbtdb);
+ cur->glue_list = NULL;
+ isc_mem_put(rbtdb->common.mctx, cur, sizeof(*cur));
+ cur = cur_next;
+ }
+ version->glue_table[i] = NULL;
+ }
+
+ size = HASHSIZE(version->glue_table_bits) *
+ sizeof(*version->glue_table);
+ isc_mem_put(rbtdb->common.mctx, version->glue_table, size);
+
+ RWUNLOCK(&version->glue_rwlock, isc_rwlocktype_write);
+}
+
+static uint32_t
+rehash_bits(rbtdb_version_t *version, size_t newcount) {
+ uint32_t oldbits = version->glue_table_bits;
+ uint32_t newbits = oldbits;
+
+ while (newcount >= HASHSIZE(newbits) &&
+ newbits <= RBTDB_GLUE_TABLE_MAX_BITS)
+ {
+ newbits += 1;
+ }
+
+ return (newbits);
+}
+
+/*%
+ * Write lock (version->glue_rwlock) must be held.
+ */
+static void
+rehash_gluetable(rbtdb_version_t *version) {
+ uint32_t oldbits, newbits;
+ size_t newsize, oldcount, i;
+ rbtdb_glue_table_node_t **oldtable;
+
+ oldbits = version->glue_table_bits;
+ oldcount = HASHSIZE(oldbits);
+ oldtable = version->glue_table;
+
+ newbits = rehash_bits(version, version->glue_table_nodecount);
+ newsize = HASHSIZE(newbits) * sizeof(version->glue_table[0]);
+
+ version->glue_table = isc_mem_get(version->rbtdb->common.mctx, newsize);
+ version->glue_table_bits = newbits;
+ memset(version->glue_table, 0, newsize);
+
+ for (i = 0; i < oldcount; i++) {
+ rbtdb_glue_table_node_t *gluenode;
+ rbtdb_glue_table_node_t *nextgluenode;
+ for (gluenode = oldtable[i]; gluenode != NULL;
+ gluenode = nextgluenode)
+ {
+ uint32_t hash = isc_hash32(
+ &gluenode->node, sizeof(gluenode->node), true);
+ uint32_t idx = hash_32(hash, newbits);
+ nextgluenode = gluenode->next;
+ gluenode->next = version->glue_table[idx];
+ version->glue_table[idx] = gluenode;
+ }
+ }
+
+ isc_mem_put(version->rbtdb->common.mctx, oldtable,
+ oldcount * sizeof(*version->glue_table));
+
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, DNS_LOGMODULE_ZONE,
+ ISC_LOG_DEBUG(3),
+ "rehash_gluetable(): "
+ "resized glue table from %zu to "
+ "%zu",
+ oldcount, newsize / sizeof(version->glue_table[0]));
+}
+
+static void
+maybe_rehash_gluetable(rbtdb_version_t *version) {
+ size_t overcommit = HASHSIZE(version->glue_table_bits) *
+ RBTDB_GLUE_TABLE_OVERCOMMIT;
+ if (version->glue_table_nodecount < overcommit) {
+ return;
+ }
+
+ rehash_gluetable(version);
+}
+
+static isc_result_t
+glue_nsdname_cb(void *arg, const dns_name_t *name, dns_rdatatype_t qtype,
+ dns_rdataset_t *unused) {
+ rbtdb_glue_additionaldata_ctx_t *ctx;
+ isc_result_t result;
+ dns_fixedname_t fixedname_a;
+ dns_name_t *name_a = NULL;
+ dns_rdataset_t rdataset_a, sigrdataset_a;
+ dns_rbtnode_t *node_a = NULL;
+ dns_fixedname_t fixedname_aaaa;
+ dns_name_t *name_aaaa = NULL;
+ dns_rdataset_t rdataset_aaaa, sigrdataset_aaaa;
+ dns_rbtnode_t *node_aaaa = NULL;
+ rbtdb_glue_t *glue = NULL;
+ dns_name_t *gluename = NULL;
+
+ UNUSED(unused);
+
+ /*
+ * NS records want addresses in additional records.
+ */
+ INSIST(qtype == dns_rdatatype_a);
+
+ ctx = (rbtdb_glue_additionaldata_ctx_t *)arg;
+
+ name_a = dns_fixedname_initname(&fixedname_a);
+ dns_rdataset_init(&rdataset_a);
+ dns_rdataset_init(&sigrdataset_a);
+
+ name_aaaa = dns_fixedname_initname(&fixedname_aaaa);
+ dns_rdataset_init(&rdataset_aaaa);
+ dns_rdataset_init(&sigrdataset_aaaa);
+
+ result = zone_find((dns_db_t *)ctx->rbtdb, name, ctx->rbtversion,
+ dns_rdatatype_a, DNS_DBFIND_GLUEOK, 0,
+ (dns_dbnode_t **)&node_a, name_a, &rdataset_a,
+ &sigrdataset_a);
+ if (result == DNS_R_GLUE) {
+ glue = isc_mem_get(ctx->rbtdb->common.mctx, sizeof(*glue));
+
+ gluename = dns_fixedname_initname(&glue->fixedname);
+ dns_name_copy(name_a, gluename);
+
+ dns_rdataset_init(&glue->rdataset_a);
+ dns_rdataset_init(&glue->sigrdataset_a);
+ dns_rdataset_init(&glue->rdataset_aaaa);
+ dns_rdataset_init(&glue->sigrdataset_aaaa);
+
+ dns_rdataset_clone(&rdataset_a, &glue->rdataset_a);
+ if (dns_rdataset_isassociated(&sigrdataset_a)) {
+ dns_rdataset_clone(&sigrdataset_a,
+ &glue->sigrdataset_a);
+ }
+ }
+
+ result = zone_find((dns_db_t *)ctx->rbtdb, name, ctx->rbtversion,
+ dns_rdatatype_aaaa, DNS_DBFIND_GLUEOK, 0,
+ (dns_dbnode_t **)&node_aaaa, name_aaaa,
+ &rdataset_aaaa, &sigrdataset_aaaa);
+ if (result == DNS_R_GLUE) {
+ if (glue == NULL) {
+ glue = isc_mem_get(ctx->rbtdb->common.mctx,
+ sizeof(*glue));
+
+ gluename = dns_fixedname_initname(&glue->fixedname);
+ dns_name_copy(name_aaaa, gluename);
+
+ dns_rdataset_init(&glue->rdataset_a);
+ dns_rdataset_init(&glue->sigrdataset_a);
+ dns_rdataset_init(&glue->rdataset_aaaa);
+ dns_rdataset_init(&glue->sigrdataset_aaaa);
+ } else {
+ INSIST(node_a == node_aaaa);
+ INSIST(dns_name_equal(name_a, name_aaaa));
+ }
+
+ dns_rdataset_clone(&rdataset_aaaa, &glue->rdataset_aaaa);
+ if (dns_rdataset_isassociated(&sigrdataset_aaaa)) {
+ dns_rdataset_clone(&sigrdataset_aaaa,
+ &glue->sigrdataset_aaaa);
+ }
+ }
+
+ if (glue != NULL) {
+ glue->next = ctx->glue_list;
+ ctx->glue_list = glue;
+ }
+
+ result = ISC_R_SUCCESS;
+
+ if (dns_rdataset_isassociated(&rdataset_a)) {
+ rdataset_disassociate(&rdataset_a);
+ }
+ if (dns_rdataset_isassociated(&sigrdataset_a)) {
+ rdataset_disassociate(&sigrdataset_a);
+ }
+
+ if (dns_rdataset_isassociated(&rdataset_aaaa)) {
+ rdataset_disassociate(&rdataset_aaaa);
+ }
+ if (dns_rdataset_isassociated(&sigrdataset_aaaa)) {
+ rdataset_disassociate(&sigrdataset_aaaa);
+ }
+
+ if (node_a != NULL) {
+ detachnode((dns_db_t *)ctx->rbtdb, (dns_dbnode_t *)&node_a);
+ }
+ if (node_aaaa != NULL) {
+ detachnode((dns_db_t *)ctx->rbtdb, (dns_dbnode_t *)&node_aaaa);
+ }
+
+ return (result);
+}
+
+static isc_result_t
+rdataset_addglue(dns_rdataset_t *rdataset, dns_dbversion_t *version,
+ dns_message_t *msg) {
+ dns_rbtdb_t *rbtdb = rdataset->private1;
+ dns_rbtnode_t *node = rdataset->private2;
+ rbtdb_version_t *rbtversion = version;
+ uint32_t idx;
+ rbtdb_glue_table_node_t *cur;
+ bool found = false;
+ bool restarted = false;
+ rbtdb_glue_t *ge;
+ rbtdb_glue_additionaldata_ctx_t ctx;
+ isc_result_t result;
+ uint64_t hash;
+
+ REQUIRE(rdataset->type == dns_rdatatype_ns);
+ REQUIRE(rbtdb == rbtversion->rbtdb);
+ REQUIRE(!IS_CACHE(rbtdb) && !IS_STUB(rbtdb));
+
+ /*
+ * The glue table cache that forms a part of the DB version
+ * structure is not explicitly bounded and there's no cache
+ * cleaning. The zone data size itself is an implicit bound.
+ *
+ * The key into the glue hashtable is the node pointer. This is
+ * because the glue hashtable is a property of the DB version,
+ * and the glue is keyed for the ownername/NS tuple. We don't
+ * bother with using an expensive dns_name_t comparison here as
+ * the node pointer is a fixed value that won't change for a DB
+ * version and can be compared directly.
+ */
+ hash = isc_hash_function(&node, sizeof(node), true);
+
+restart:
+ /*
+ * First, check if we have the additional entries already cached
+ * in the glue table.
+ */
+ RWLOCK(&rbtversion->glue_rwlock, isc_rwlocktype_read);
+
+ idx = hash_32(hash, rbtversion->glue_table_bits);
+
+ for (cur = rbtversion->glue_table[idx]; cur != NULL; cur = cur->next) {
+ if (cur->node == node) {
+ break;
+ }
+ }
+
+ if (cur == NULL) {
+ goto no_glue;
+ }
+ /*
+ * We found a cached result. Add it to the message and
+ * return.
+ */
+ found = true;
+ ge = cur->glue_list;
+
+ /*
+ * (void *) -1 is a special value that means no glue is
+ * present in the zone.
+ */
+ if (ge == (void *)-1) {
+ if (!restarted && (rbtdb->gluecachestats != NULL)) {
+ isc_stats_increment(
+ rbtdb->gluecachestats,
+ dns_gluecachestatscounter_hits_absent);
+ }
+ goto no_glue;
+ } else {
+ if (!restarted && (rbtdb->gluecachestats != NULL)) {
+ isc_stats_increment(
+ rbtdb->gluecachestats,
+ dns_gluecachestatscounter_hits_present);
+ }
+ }
+
+ for (; ge != NULL; ge = ge->next) {
+ dns_name_t *name = NULL;
+ dns_rdataset_t *rdataset_a = NULL;
+ dns_rdataset_t *sigrdataset_a = NULL;
+ dns_rdataset_t *rdataset_aaaa = NULL;
+ dns_rdataset_t *sigrdataset_aaaa = NULL;
+ dns_name_t *gluename = dns_fixedname_name(&ge->fixedname);
+
+ result = dns_message_gettempname(msg, &name);
+ if (result != ISC_R_SUCCESS) {
+ goto no_glue;
+ }
+
+ dns_name_copy(gluename, name);
+
+ if (dns_rdataset_isassociated(&ge->rdataset_a)) {
+ result = dns_message_gettemprdataset(msg, &rdataset_a);
+ if (result != ISC_R_SUCCESS) {
+ dns_message_puttempname(msg, &name);
+ goto no_glue;
+ }
+ }
+
+ if (dns_rdataset_isassociated(&ge->sigrdataset_a)) {
+ result = dns_message_gettemprdataset(msg,
+ &sigrdataset_a);
+ if (result != ISC_R_SUCCESS) {
+ if (rdataset_a != NULL) {
+ dns_message_puttemprdataset(
+ msg, &rdataset_a);
+ }
+ dns_message_puttempname(msg, &name);
+ goto no_glue;
+ }
+ }
+
+ if (dns_rdataset_isassociated(&ge->rdataset_aaaa)) {
+ result = dns_message_gettemprdataset(msg,
+ &rdataset_aaaa);
+ if (result != ISC_R_SUCCESS) {
+ dns_message_puttempname(msg, &name);
+ if (rdataset_a != NULL) {
+ dns_message_puttemprdataset(
+ msg, &rdataset_a);
+ }
+ if (sigrdataset_a != NULL) {
+ dns_message_puttemprdataset(
+ msg, &sigrdataset_a);
+ }
+ goto no_glue;
+ }
+ }
+
+ if (dns_rdataset_isassociated(&ge->sigrdataset_aaaa)) {
+ result = dns_message_gettemprdataset(msg,
+ &sigrdataset_aaaa);
+ if (result != ISC_R_SUCCESS) {
+ dns_message_puttempname(msg, &name);
+ if (rdataset_a != NULL) {
+ dns_message_puttemprdataset(
+ msg, &rdataset_a);
+ }
+ if (sigrdataset_a != NULL) {
+ dns_message_puttemprdataset(
+ msg, &sigrdataset_a);
+ }
+ if (rdataset_aaaa != NULL) {
+ dns_message_puttemprdataset(
+ msg, &rdataset_aaaa);
+ }
+ goto no_glue;
+ }
+ }
+
+ if (rdataset_a != NULL) {
+ dns_rdataset_clone(&ge->rdataset_a, rdataset_a);
+ ISC_LIST_APPEND(name->list, rdataset_a, link);
+ }
+
+ if (sigrdataset_a != NULL) {
+ dns_rdataset_clone(&ge->sigrdataset_a, sigrdataset_a);
+ ISC_LIST_APPEND(name->list, sigrdataset_a, link);
+ }
+
+ if (rdataset_aaaa != NULL) {
+ dns_rdataset_clone(&ge->rdataset_aaaa, rdataset_aaaa);
+ ISC_LIST_APPEND(name->list, rdataset_aaaa, link);
+ }
+ if (sigrdataset_aaaa != NULL) {
+ dns_rdataset_clone(&ge->sigrdataset_aaaa,
+ sigrdataset_aaaa);
+ ISC_LIST_APPEND(name->list, sigrdataset_aaaa, link);
+ }
+
+ dns_message_addname(msg, name, DNS_SECTION_ADDITIONAL);
+ }
+
+no_glue:
+ RWUNLOCK(&rbtversion->glue_rwlock, isc_rwlocktype_read);
+
+ if (found) {
+ return (ISC_R_SUCCESS);
+ }
+
+ if (restarted) {
+ return (ISC_R_FAILURE);
+ }
+
+ /*
+ * No cached glue was found in the table. Cache it and restart
+ * this function.
+ *
+ * Due to the gap between the read lock and the write lock, it's
+ * possible that we may cache a duplicate glue table entry, but
+ * we don't care.
+ */
+
+ ctx.glue_list = NULL;
+ ctx.rbtdb = rbtdb;
+ ctx.rbtversion = rbtversion;
+
+ RWLOCK(&rbtversion->glue_rwlock, isc_rwlocktype_write);
+
+ maybe_rehash_gluetable(rbtversion);
+ idx = hash_32(hash, rbtversion->glue_table_bits);
+
+ (void)dns_rdataset_additionaldata(rdataset, dns_rootname,
+ glue_nsdname_cb, &ctx);
+
+ cur = isc_mem_get(rbtdb->common.mctx, sizeof(*cur));
+
+ /*
+ * XXXMUKS: it looks like the dns_dbversion is not destroyed
+ * when named is terminated by a keyboard break. This doesn't
+ * cleanup the node reference and keeps the process dangling.
+ */
+ /* isc_refcount_increment0(&node->references); */
+ cur->node = node;
+
+ if (ctx.glue_list == NULL) {
+ /*
+ * No glue was found. Cache it so.
+ */
+ cur->glue_list = (void *)-1;
+ if (rbtdb->gluecachestats != NULL) {
+ isc_stats_increment(
+ rbtdb->gluecachestats,
+ dns_gluecachestatscounter_inserts_absent);
+ }
+ } else {
+ cur->glue_list = ctx.glue_list;
+ if (rbtdb->gluecachestats != NULL) {
+ isc_stats_increment(
+ rbtdb->gluecachestats,
+ dns_gluecachestatscounter_inserts_present);
+ }
+ }
+
+ cur->next = rbtversion->glue_table[idx];
+ rbtversion->glue_table[idx] = cur;
+ rbtversion->glue_table_nodecount++;
+
+ RWUNLOCK(&rbtversion->glue_rwlock, isc_rwlocktype_write);
+
+ restarted = true;
+ goto restart;
+
+ /* UNREACHABLE */
+}
+
+/*%
+ * Routines for LRU-based cache management.
+ */
+
+/*%
+ * See if a given cache entry that is being reused needs to be updated
+ * in the LRU-list. From the LRU management point of view, this function is
+ * expected to return true for almost all cases. When used with threads,
+ * however, this may cause a non-negligible performance penalty because a
+ * writer lock will have to be acquired before updating the list.
+ * If DNS_RBTDB_LIMITLRUUPDATE is defined to be non 0 at compilation time, this
+ * function returns true if the entry has not been updated for some period of
+ * time. We differentiate the NS or glue address case and the others since
+ * experiments have shown that the former tends to be accessed relatively
+ * infrequently and the cost of cache miss is higher (e.g., a missing NS records
+ * may cause external queries at a higher level zone, involving more
+ * transactions).
+ *
+ * Caller must hold the node (read or write) lock.
+ */
+static bool
+need_headerupdate(rdatasetheader_t *header, isc_stdtime_t now) {
+ if (RDATASET_ATTR_GET(header, (RDATASET_ATTR_NONEXISTENT |
+ RDATASET_ATTR_ANCIENT |
+ RDATASET_ATTR_ZEROTTL)) != 0)
+ {
+ return (false);
+ }
+
+#if DNS_RBTDB_LIMITLRUUPDATE
+ if (header->type == dns_rdatatype_ns ||
+ (header->trust == dns_trust_glue &&
+ (header->type == dns_rdatatype_a ||
+ header->type == dns_rdatatype_aaaa)))
+ {
+ /*
+ * Glue records are updated if at least DNS_RBTDB_LRUUPDATE_GLUE
+ * seconds have passed since the previous update time.
+ */
+ return (header->last_used + DNS_RBTDB_LRUUPDATE_GLUE <= now);
+ }
+
+ /*
+ * Other records are updated if DNS_RBTDB_LRUUPDATE_REGULAR seconds
+ * have passed.
+ */
+ return (header->last_used + DNS_RBTDB_LRUUPDATE_REGULAR <= now);
+#else
+ UNUSED(now);
+
+ return (true);
+#endif /* if DNS_RBTDB_LIMITLRUUPDATE */
+}
+
+/*%
+ * Update the timestamp of a given cache entry and move it to the head
+ * of the corresponding LRU list.
+ *
+ * Caller must hold the node (write) lock.
+ *
+ * Note that the we do NOT touch the heap here, as the TTL has not changed.
+ */
+static void
+update_header(dns_rbtdb_t *rbtdb, rdatasetheader_t *header, isc_stdtime_t now) {
+ INSIST(IS_CACHE(rbtdb));
+
+ /* To be checked: can we really assume this? XXXMLG */
+ INSIST(ISC_LINK_LINKED(header, link));
+
+ ISC_LIST_UNLINK(rbtdb->rdatasets[header->node->locknum], header, link);
+ header->last_used = now;
+ ISC_LIST_PREPEND(rbtdb->rdatasets[header->node->locknum], header, link);
+}
+
+static size_t
+expire_lru_headers(dns_rbtdb_t *rbtdb, unsigned int locknum, size_t purgesize,
+ bool tree_locked) {
+ rdatasetheader_t *header, *header_prev;
+ size_t purged = 0;
+
+ for (header = ISC_LIST_TAIL(rbtdb->rdatasets[locknum]);
+ header != NULL && purged <= purgesize; header = header_prev)
+ {
+ header_prev = ISC_LIST_PREV(header, link);
+ /*
+ * Unlink the entry at this point to avoid checking it
+ * again even if it's currently used someone else and
+ * cannot be purged at this moment. This entry won't be
+ * referenced any more (so unlinking is safe) since the
+ * TTL was reset to 0.
+ */
+ ISC_LIST_UNLINK(rbtdb->rdatasets[locknum], header, link);
+ size_t header_size = rdataset_size(header);
+ expire_header(rbtdb, header, tree_locked, expire_lru);
+ purged += header_size;
+ }
+
+ return (purged);
+}
+
+/*%
+ * Purge some stale (i.e. unused for some period - LRU based cleaning) cache
+ * entries under the overmem condition. To recover from this condition quickly,
+ * we cleanup entries up to the size of newly added rdata (passed as purgesize).
+ *
+ * This process is triggered while adding a new entry, and we specifically avoid
+ * purging entries in the same LRU bucket as the one to which the new entry will
+ * belong. Otherwise, we might purge entries of the same name of different RR
+ * types while adding RRsets from a single response (consider the case where
+ * we're adding A and AAAA glue records of the same NS name).
+ */
+static void
+overmem_purge(dns_rbtdb_t *rbtdb, unsigned int locknum_start, size_t purgesize,
+ bool tree_locked) {
+ unsigned int locknum;
+ size_t purged = 0;
+
+ for (locknum = (locknum_start + 1) % rbtdb->node_lock_count;
+ locknum != locknum_start && purged <= purgesize;
+ locknum = (locknum + 1) % rbtdb->node_lock_count)
+ {
+ NODE_LOCK(&rbtdb->node_locks[locknum].lock,
+ isc_rwlocktype_write);
+
+ purged += expire_lru_headers(rbtdb, locknum, purgesize - purged,
+ tree_locked);
+
+ NODE_UNLOCK(&rbtdb->node_locks[locknum].lock,
+ isc_rwlocktype_write);
+ }
+}
+
+static void
+expire_header(dns_rbtdb_t *rbtdb, rdatasetheader_t *header, bool tree_locked,
+ expire_t reason) {
+ set_ttl(rbtdb, header, 0);
+ mark_header_ancient(rbtdb, header);
+
+ /*
+ * Caller must hold the node (write) lock.
+ */
+
+ if (isc_refcount_current(&header->node->references) == 0) {
+ /*
+ * If no one else is using the node, we can clean it up now.
+ * We first need to gain a new reference to the node to meet a
+ * requirement of decrement_reference().
+ */
+ new_reference(rbtdb, header->node, isc_rwlocktype_write);
+ decrement_reference(rbtdb, header->node, 0,
+ isc_rwlocktype_write,
+ tree_locked ? isc_rwlocktype_write
+ : isc_rwlocktype_none,
+ false);
+
+ if (rbtdb->cachestats == NULL) {
+ return;
+ }
+
+ switch (reason) {
+ case expire_ttl:
+ isc_stats_increment(rbtdb->cachestats,
+ dns_cachestatscounter_deletettl);
+ break;
+ case expire_lru:
+ isc_stats_increment(rbtdb->cachestats,
+ dns_cachestatscounter_deletelru);
+ break;
+ default:
+ break;
+ }
+ }
+}
diff --git a/lib/dns/rbtdb.h b/lib/dns/rbtdb.h
new file mode 100644
index 0000000..44e5102
--- /dev/null
+++ b/lib/dns/rbtdb.h
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#pragma once
+
+#include <isc/lang.h>
+
+#include <dns/types.h>
+
+/*****
+***** Module Info
+*****/
+
+/*! \file
+ * \brief
+ * DNS Red-Black Tree DB Implementation
+ */
+
+ISC_LANG_BEGINDECLS
+
+isc_result_t
+dns_rbtdb_create(isc_mem_t *mctx, const dns_name_t *base, dns_dbtype_t type,
+ dns_rdataclass_t rdclass, unsigned int argc, char *argv[],
+ void *driverarg, dns_db_t **dbp);
+
+/*%<
+ * Create a new database of type "rbt" (or "rbt64"). Called via
+ * dns_db_create(); see documentation for that function for more details.
+ *
+ * If argv[0] is set, it points to a valid memory context to be used for
+ * allocation of heap memory. Generally this is used for cache databases
+ * only.
+ *
+ * Requires:
+ *
+ * \li argc == 0 or argv[0] is a valid memory context.
+ */
+
+ISC_LANG_ENDDECLS
diff --git a/lib/dns/rcode.c b/lib/dns/rcode.c
new file mode 100644
index 0000000..78897e9
--- /dev/null
+++ b/lib/dns/rcode.c
@@ -0,0 +1,585 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#include <ctype.h>
+#include <inttypes.h>
+#include <stdbool.h>
+#include <stdlib.h>
+
+#include <isc/buffer.h>
+#include <isc/parseint.h>
+#include <isc/print.h>
+#include <isc/region.h>
+#include <isc/result.h>
+#include <isc/stdio.h>
+#include <isc/string.h>
+#include <isc/types.h>
+#include <isc/util.h>
+
+#include <dns/cert.h>
+#include <dns/ds.h>
+#include <dns/dsdigest.h>
+#include <dns/keyflags.h>
+#include <dns/keyvalues.h>
+#include <dns/rcode.h>
+#include <dns/rdataclass.h>
+#include <dns/secalg.h>
+#include <dns/secproto.h>
+
+#define RETERR(x) \
+ do { \
+ isc_result_t _r = (x); \
+ if (_r != ISC_R_SUCCESS) \
+ return ((_r)); \
+ } while (0)
+
+#define NUMBERSIZE sizeof("037777777777") /* 2^32-1 octal + NUL */
+
+#define TOTEXTONLY 0x01
+
+#define RCODENAMES \
+ /* standard rcodes */ \
+ { dns_rcode_noerror, "NOERROR", 0 }, \
+ { dns_rcode_formerr, "FORMERR", 0 }, \
+ { dns_rcode_servfail, "SERVFAIL", 0 }, \
+ { dns_rcode_nxdomain, "NXDOMAIN", 0 }, \
+ { dns_rcode_notimp, "NOTIMP", 0 }, \
+ { dns_rcode_refused, "REFUSED", 0 }, \
+ { dns_rcode_yxdomain, "YXDOMAIN", 0 }, \
+ { dns_rcode_yxrrset, "YXRRSET", 0 }, \
+ { dns_rcode_nxrrset, "NXRRSET", 0 }, \
+ { dns_rcode_notauth, "NOTAUTH", 0 }, \
+ { dns_rcode_notzone, "NOTZONE", 0 }, \
+ { 11, "RESERVED11", TOTEXTONLY }, \
+ { 12, "RESERVED12", TOTEXTONLY }, \
+ { 13, "RESERVED13", TOTEXTONLY }, \
+ { 14, "RESERVED14", TOTEXTONLY }, \
+ { 15, "RESERVED15", TOTEXTONLY },
+
+#define ERCODENAMES \
+ /* extended rcodes */ \
+ { dns_rcode_badvers, "BADVERS", 0 }, \
+ { dns_rcode_badcookie, "BADCOOKIE", 0 }, { \
+ 0, NULL, 0 \
+ }
+
+#define TSIGRCODENAMES \
+ /* extended rcodes */ \
+ { dns_tsigerror_badsig, "BADSIG", 0 }, \
+ { dns_tsigerror_badkey, "BADKEY", 0 }, \
+ { dns_tsigerror_badtime, "BADTIME", 0 }, \
+ { dns_tsigerror_badmode, "BADMODE", 0 }, \
+ { dns_tsigerror_badname, "BADNAME", 0 }, \
+ { dns_tsigerror_badalg, "BADALG", 0 }, \
+ { dns_tsigerror_badtrunc, "BADTRUNC", 0 }, { \
+ 0, NULL, 0 \
+ }
+
+/* RFC4398 section 2.1 */
+
+#define CERTNAMES \
+ { 1, "PKIX", 0 }, { 2, "SPKI", 0 }, { 3, "PGP", 0 }, \
+ { 4, "IPKIX", 0 }, { 5, "ISPKI", 0 }, { 6, "IPGP", 0 }, \
+ { 7, "ACPKIX", 0 }, { 8, "IACPKIX", 0 }, { 253, "URI", 0 }, \
+ { 254, "OID", 0 }, { \
+ 0, NULL, 0 \
+ }
+
+/* RFC2535 section 7, RFC3110 */
+
+#define SECALGNAMES \
+ { DNS_KEYALG_RSAMD5, "RSAMD5", 0 }, { DNS_KEYALG_DH, "DH", 0 }, \
+ { DNS_KEYALG_DSA, "DSA", 0 }, \
+ { DNS_KEYALG_RSASHA1, "RSASHA1", 0 }, \
+ { DNS_KEYALG_NSEC3DSA, "NSEC3DSA", 0 }, \
+ { DNS_KEYALG_NSEC3RSASHA1, "NSEC3RSASHA1", 0 }, \
+ { DNS_KEYALG_RSASHA256, "RSASHA256", 0 }, \
+ { DNS_KEYALG_RSASHA512, "RSASHA512", 0 }, \
+ { DNS_KEYALG_ECCGOST, "ECCGOST", 0 }, \
+ { DNS_KEYALG_ECDSA256, "ECDSAP256SHA256", 0 }, \
+ { DNS_KEYALG_ECDSA256, "ECDSA256", 0 }, \
+ { DNS_KEYALG_ECDSA384, "ECDSAP384SHA384", 0 }, \
+ { DNS_KEYALG_ECDSA384, "ECDSA384", 0 }, \
+ { DNS_KEYALG_ED25519, "ED25519", 0 }, \
+ { DNS_KEYALG_ED448, "ED448", 0 }, \
+ { DNS_KEYALG_INDIRECT, "INDIRECT", 0 }, \
+ { DNS_KEYALG_PRIVATEDNS, "PRIVATEDNS", 0 }, \
+ { DNS_KEYALG_PRIVATEOID, "PRIVATEOID", 0 }, { \
+ 0, NULL, 0 \
+ }
+
+/* RFC2535 section 7.1 */
+
+#define SECPROTONAMES \
+ { 0, "NONE", 0 }, { 1, "TLS", 0 }, { 2, "EMAIL", 0 }, \
+ { 3, "DNSSEC", 0 }, { 4, "IPSEC", 0 }, { 255, "ALL", 0 }, { \
+ 0, NULL, 0 \
+ }
+
+#define HASHALGNAMES \
+ { 1, "SHA-1", 0 }, { 0, NULL, 0 }
+
+/* RFC3658, RFC4509, RFC5933, RFC6605 */
+
+#define DSDIGESTNAMES \
+ { DNS_DSDIGEST_SHA1, "SHA-1", 0 }, { DNS_DSDIGEST_SHA1, "SHA1", 0 }, \
+ { DNS_DSDIGEST_SHA256, "SHA-256", 0 }, \
+ { DNS_DSDIGEST_SHA256, "SHA256", 0 }, \
+ { DNS_DSDIGEST_GOST, "GOST", 0 }, \
+ { DNS_DSDIGEST_SHA384, "SHA-384", 0 }, \
+ { DNS_DSDIGEST_SHA384, "SHA384", 0 }, { \
+ 0, NULL, 0 \
+ }
+
+struct tbl {
+ unsigned int value;
+ const char *name;
+ int flags;
+};
+
+static struct tbl rcodes[] = { RCODENAMES ERCODENAMES };
+static struct tbl tsigrcodes[] = { RCODENAMES TSIGRCODENAMES };
+static struct tbl certs[] = { CERTNAMES };
+static struct tbl secalgs[] = { SECALGNAMES };
+static struct tbl secprotos[] = { SECPROTONAMES };
+static struct tbl hashalgs[] = { HASHALGNAMES };
+static struct tbl dsdigests[] = { DSDIGESTNAMES };
+
+static struct keyflag {
+ const char *name;
+ unsigned int value;
+ unsigned int mask;
+} keyflags[] = { { "NOCONF", 0x4000, 0xC000 },
+ { "NOAUTH", 0x8000, 0xC000 },
+ { "NOKEY", 0xC000, 0xC000 },
+ { "FLAG2", 0x2000, 0x2000 },
+ { "EXTEND", 0x1000, 0x1000 },
+ { "FLAG4", 0x0800, 0x0800 },
+ { "FLAG5", 0x0400, 0x0400 },
+ { "USER", 0x0000, 0x0300 },
+ { "ZONE", 0x0100, 0x0300 },
+ { "HOST", 0x0200, 0x0300 },
+ { "NTYP3", 0x0300, 0x0300 },
+ { "FLAG8", 0x0080, 0x0080 },
+ { "FLAG9", 0x0040, 0x0040 },
+ { "FLAG10", 0x0020, 0x0020 },
+ { "FLAG11", 0x0010, 0x0010 },
+ { "SIG0", 0x0000, 0x000F },
+ { "SIG1", 0x0001, 0x000F },
+ { "SIG2", 0x0002, 0x000F },
+ { "SIG3", 0x0003, 0x000F },
+ { "SIG4", 0x0004, 0x000F },
+ { "SIG5", 0x0005, 0x000F },
+ { "SIG6", 0x0006, 0x000F },
+ { "SIG7", 0x0007, 0x000F },
+ { "SIG8", 0x0008, 0x000F },
+ { "SIG9", 0x0009, 0x000F },
+ { "SIG10", 0x000A, 0x000F },
+ { "SIG11", 0x000B, 0x000F },
+ { "SIG12", 0x000C, 0x000F },
+ { "SIG13", 0x000D, 0x000F },
+ { "SIG14", 0x000E, 0x000F },
+ { "SIG15", 0x000F, 0x000F },
+ { "KSK", DNS_KEYFLAG_KSK, DNS_KEYFLAG_KSK },
+ { NULL, 0, 0 } };
+
+static isc_result_t
+str_totext(const char *source, isc_buffer_t *target) {
+ unsigned int l;
+ isc_region_t region;
+
+ isc_buffer_availableregion(target, &region);
+ l = strlen(source);
+
+ if (l > region.length) {
+ return (ISC_R_NOSPACE);
+ }
+
+ memmove(region.base, source, l);
+ isc_buffer_add(target, l);
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+maybe_numeric(unsigned int *valuep, isc_textregion_t *source, unsigned int max,
+ bool hex_allowed) {
+ isc_result_t result;
+ uint32_t n;
+ char buffer[NUMBERSIZE];
+ int v;
+
+ if (!isdigit((unsigned char)source->base[0]) ||
+ source->length > NUMBERSIZE - 1)
+ {
+ return (ISC_R_BADNUMBER);
+ }
+
+ /*
+ * We have a potential number. Try to parse it with
+ * isc_parse_uint32(). isc_parse_uint32() requires
+ * null termination, so we must make a copy.
+ */
+ v = snprintf(buffer, sizeof(buffer), "%.*s", (int)source->length,
+ source->base);
+ if (v < 0 || (unsigned)v != source->length) {
+ return (ISC_R_BADNUMBER);
+ }
+ INSIST(buffer[source->length] == '\0');
+
+ result = isc_parse_uint32(&n, buffer, 10);
+ if (result == ISC_R_BADNUMBER && hex_allowed) {
+ result = isc_parse_uint32(&n, buffer, 16);
+ }
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+ if (n > max) {
+ return (ISC_R_RANGE);
+ }
+ *valuep = n;
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+dns_mnemonic_fromtext(unsigned int *valuep, isc_textregion_t *source,
+ struct tbl *table, unsigned int max) {
+ isc_result_t result;
+ int i;
+
+ result = maybe_numeric(valuep, source, max, false);
+ if (result != ISC_R_BADNUMBER) {
+ return (result);
+ }
+
+ for (i = 0; table[i].name != NULL; i++) {
+ unsigned int n;
+ n = strlen(table[i].name);
+ if (n == source->length && (table[i].flags & TOTEXTONLY) == 0 &&
+ strncasecmp(source->base, table[i].name, n) == 0)
+ {
+ *valuep = table[i].value;
+ return (ISC_R_SUCCESS);
+ }
+ }
+ return (DNS_R_UNKNOWN);
+}
+
+static isc_result_t
+dns_mnemonic_totext(unsigned int value, isc_buffer_t *target,
+ struct tbl *table) {
+ int i = 0;
+ char buf[sizeof("4294967296")];
+ while (table[i].name != NULL) {
+ if (table[i].value == value) {
+ return (str_totext(table[i].name, target));
+ }
+ i++;
+ }
+ snprintf(buf, sizeof(buf), "%u", value);
+ return (str_totext(buf, target));
+}
+
+isc_result_t
+dns_rcode_fromtext(dns_rcode_t *rcodep, isc_textregion_t *source) {
+ unsigned int value;
+ RETERR(dns_mnemonic_fromtext(&value, source, rcodes, 0xffff));
+ *rcodep = value;
+ return (ISC_R_SUCCESS);
+}
+
+isc_result_t
+dns_rcode_totext(dns_rcode_t rcode, isc_buffer_t *target) {
+ return (dns_mnemonic_totext(rcode, target, rcodes));
+}
+
+isc_result_t
+dns_tsigrcode_fromtext(dns_rcode_t *rcodep, isc_textregion_t *source) {
+ unsigned int value;
+ RETERR(dns_mnemonic_fromtext(&value, source, tsigrcodes, 0xffff));
+ *rcodep = value;
+ return (ISC_R_SUCCESS);
+}
+
+isc_result_t
+dns_tsigrcode_totext(dns_rcode_t rcode, isc_buffer_t *target) {
+ return (dns_mnemonic_totext(rcode, target, tsigrcodes));
+}
+
+isc_result_t
+dns_cert_fromtext(dns_cert_t *certp, isc_textregion_t *source) {
+ unsigned int value;
+ RETERR(dns_mnemonic_fromtext(&value, source, certs, 0xffff));
+ *certp = value;
+ return (ISC_R_SUCCESS);
+}
+
+isc_result_t
+dns_cert_totext(dns_cert_t cert, isc_buffer_t *target) {
+ return (dns_mnemonic_totext(cert, target, certs));
+}
+
+isc_result_t
+dns_secalg_fromtext(dns_secalg_t *secalgp, isc_textregion_t *source) {
+ unsigned int value;
+ RETERR(dns_mnemonic_fromtext(&value, source, secalgs, 0xff));
+ *secalgp = value;
+ return (ISC_R_SUCCESS);
+}
+
+isc_result_t
+dns_secalg_totext(dns_secalg_t secalg, isc_buffer_t *target) {
+ return (dns_mnemonic_totext(secalg, target, secalgs));
+}
+
+void
+dns_secalg_format(dns_secalg_t alg, char *cp, unsigned int size) {
+ isc_buffer_t b;
+ isc_region_t r;
+ isc_result_t result;
+
+ REQUIRE(cp != NULL && size > 0);
+ isc_buffer_init(&b, cp, size - 1);
+ result = dns_secalg_totext(alg, &b);
+ isc_buffer_usedregion(&b, &r);
+ r.base[r.length] = 0;
+ if (result != ISC_R_SUCCESS) {
+ r.base[0] = 0;
+ }
+}
+
+isc_result_t
+dns_secproto_fromtext(dns_secproto_t *secprotop, isc_textregion_t *source) {
+ unsigned int value;
+ RETERR(dns_mnemonic_fromtext(&value, source, secprotos, 0xff));
+ *secprotop = value;
+ return (ISC_R_SUCCESS);
+}
+
+isc_result_t
+dns_secproto_totext(dns_secproto_t secproto, isc_buffer_t *target) {
+ return (dns_mnemonic_totext(secproto, target, secprotos));
+}
+
+isc_result_t
+dns_hashalg_fromtext(unsigned char *hashalg, isc_textregion_t *source) {
+ unsigned int value;
+ RETERR(dns_mnemonic_fromtext(&value, source, hashalgs, 0xff));
+ *hashalg = value;
+ return (ISC_R_SUCCESS);
+}
+
+isc_result_t
+dns_keyflags_fromtext(dns_keyflags_t *flagsp, isc_textregion_t *source) {
+ isc_result_t result;
+ char *text, *end;
+ unsigned int value = 0;
+#ifdef notyet
+ unsigned int mask = 0;
+#endif /* ifdef notyet */
+
+ result = maybe_numeric(&value, source, 0xffff, true);
+ if (result == ISC_R_SUCCESS) {
+ *flagsp = value;
+ return (ISC_R_SUCCESS);
+ }
+ if (result != ISC_R_BADNUMBER) {
+ return (result);
+ }
+
+ text = source->base;
+ end = source->base + source->length;
+
+ while (text < end) {
+ struct keyflag *p;
+ unsigned int len;
+ char *delim = memchr(text, '|', end - text);
+ if (delim != NULL) {
+ len = (unsigned int)(delim - text);
+ } else {
+ len = (unsigned int)(end - text);
+ }
+ for (p = keyflags; p->name != NULL; p++) {
+ if (strncasecmp(p->name, text, len) == 0) {
+ break;
+ }
+ }
+ if (p->name == NULL) {
+ return (DNS_R_UNKNOWNFLAG);
+ }
+ value |= p->value;
+#ifdef notyet
+ if ((mask & p->mask) != 0) {
+ warn("overlapping key flags");
+ }
+ mask |= p->mask;
+#endif /* ifdef notyet */
+ text += len;
+ if (delim != NULL) {
+ text++; /* Skip "|" */
+ }
+ }
+ *flagsp = value;
+ return (ISC_R_SUCCESS);
+}
+
+isc_result_t
+dns_dsdigest_fromtext(dns_dsdigest_t *dsdigestp, isc_textregion_t *source) {
+ unsigned int value;
+ RETERR(dns_mnemonic_fromtext(&value, source, dsdigests, 0xff));
+ *dsdigestp = value;
+ return (ISC_R_SUCCESS);
+}
+
+isc_result_t
+dns_dsdigest_totext(dns_dsdigest_t dsdigest, isc_buffer_t *target) {
+ return (dns_mnemonic_totext(dsdigest, target, dsdigests));
+}
+
+void
+dns_dsdigest_format(dns_dsdigest_t typ, char *cp, unsigned int size) {
+ isc_buffer_t b;
+ isc_region_t r;
+ isc_result_t result;
+
+ REQUIRE(cp != NULL && size > 0);
+ isc_buffer_init(&b, cp, size - 1);
+ result = dns_dsdigest_totext(typ, &b);
+ isc_buffer_usedregion(&b, &r);
+ r.base[r.length] = 0;
+ if (result != ISC_R_SUCCESS) {
+ r.base[0] = 0;
+ }
+}
+
+/*
+ * This uses lots of hard coded values, but how often do we actually
+ * add classes?
+ */
+isc_result_t
+dns_rdataclass_fromtext(dns_rdataclass_t *classp, isc_textregion_t *source) {
+#define COMPARE(string, rdclass) \
+ if (((sizeof(string) - 1) == source->length) && \
+ (strncasecmp(source->base, string, source->length) == 0)) \
+ { \
+ *classp = rdclass; \
+ return (ISC_R_SUCCESS); \
+ }
+
+ switch (tolower((unsigned char)source->base[0])) {
+ case 'a':
+ COMPARE("any", dns_rdataclass_any);
+ break;
+ case 'c':
+ /*
+ * RFC1035 says the mnemonic for the CHAOS class is CH,
+ * but historical BIND practice is to call it CHAOS.
+ * We will accept both forms, but only generate CH.
+ */
+ COMPARE("ch", dns_rdataclass_chaos);
+ COMPARE("chaos", dns_rdataclass_chaos);
+
+ if (source->length > 5 &&
+ source->length < (5 + sizeof("65000")) &&
+ strncasecmp("class", source->base, 5) == 0)
+ {
+ char buf[sizeof("65000")];
+ char *endp;
+ unsigned int val;
+
+ /*
+ * source->base is not required to be NUL terminated.
+ * Copy up to remaining bytes and NUL terminate.
+ */
+ snprintf(buf, sizeof(buf), "%.*s",
+ (int)(source->length - 5), source->base + 5);
+ val = strtoul(buf, &endp, 10);
+ if (*endp == '\0' && val <= 0xffff) {
+ *classp = (dns_rdataclass_t)val;
+ return (ISC_R_SUCCESS);
+ }
+ }
+ break;
+ case 'h':
+ COMPARE("hs", dns_rdataclass_hs);
+ COMPARE("hesiod", dns_rdataclass_hs);
+ break;
+ case 'i':
+ COMPARE("in", dns_rdataclass_in);
+ break;
+ case 'n':
+ COMPARE("none", dns_rdataclass_none);
+ break;
+ case 'r':
+ COMPARE("reserved0", dns_rdataclass_reserved0);
+ break;
+ }
+
+#undef COMPARE
+
+ return (DNS_R_UNKNOWN);
+}
+
+isc_result_t
+dns_rdataclass_totext(dns_rdataclass_t rdclass, isc_buffer_t *target) {
+ switch (rdclass) {
+ case dns_rdataclass_any:
+ return (str_totext("ANY", target));
+ case dns_rdataclass_chaos:
+ return (str_totext("CH", target));
+ case dns_rdataclass_hs:
+ return (str_totext("HS", target));
+ case dns_rdataclass_in:
+ return (str_totext("IN", target));
+ case dns_rdataclass_none:
+ return (str_totext("NONE", target));
+ case dns_rdataclass_reserved0:
+ return (str_totext("RESERVED0", target));
+ default:
+ return (dns_rdataclass_tounknowntext(rdclass, target));
+ }
+}
+
+isc_result_t
+dns_rdataclass_tounknowntext(dns_rdataclass_t rdclass, isc_buffer_t *target) {
+ char buf[sizeof("CLASS65535")];
+
+ snprintf(buf, sizeof(buf), "CLASS%u", rdclass);
+ return (str_totext(buf, target));
+}
+
+void
+dns_rdataclass_format(dns_rdataclass_t rdclass, char *array,
+ unsigned int size) {
+ isc_result_t result;
+ isc_buffer_t buf;
+
+ if (size == 0U) {
+ return;
+ }
+
+ isc_buffer_init(&buf, array, size);
+ result = dns_rdataclass_totext(rdclass, &buf);
+ /*
+ * Null terminate.
+ */
+ if (result == ISC_R_SUCCESS) {
+ if (isc_buffer_availablelength(&buf) >= 1) {
+ isc_buffer_putuint8(&buf, 0);
+ } else {
+ result = ISC_R_NOSPACE;
+ }
+ }
+ if (result != ISC_R_SUCCESS) {
+ strlcpy(array, "<unknown>", size);
+ }
+}
diff --git a/lib/dns/rdata.c b/lib/dns/rdata.c
new file mode 100644
index 0000000..b7f9ed2
--- /dev/null
+++ b/lib/dns/rdata.c
@@ -0,0 +1,2367 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/*! \file */
+
+#include <ctype.h>
+#include <inttypes.h>
+#include <stdbool.h>
+
+#include <isc/base64.h>
+#include <isc/hex.h>
+#include <isc/lex.h>
+#include <isc/mem.h>
+#include <isc/parseint.h>
+#include <isc/print.h>
+#include <isc/result.h>
+#include <isc/string.h>
+#include <isc/util.h>
+
+#include <dns/callbacks.h>
+#include <dns/cert.h>
+#include <dns/compress.h>
+#include <dns/dsdigest.h>
+#include <dns/enumtype.h>
+#include <dns/fixedname.h>
+#include <dns/keyflags.h>
+#include <dns/keyvalues.h>
+#include <dns/message.h>
+#include <dns/rcode.h>
+#include <dns/rdata.h>
+#include <dns/rdataclass.h>
+#include <dns/rdataset.h>
+#include <dns/rdatastruct.h>
+#include <dns/rdatatype.h>
+#include <dns/secalg.h>
+#include <dns/secproto.h>
+#include <dns/time.h>
+#include <dns/ttl.h>
+
+#define RETERR(x) \
+ do { \
+ isc_result_t _r = (x); \
+ if (_r != ISC_R_SUCCESS) \
+ return ((_r)); \
+ } while (0)
+
+#define RETTOK(x) \
+ do { \
+ isc_result_t _r = (x); \
+ if (_r != ISC_R_SUCCESS) { \
+ isc_lex_ungettoken(lexer, &token); \
+ return (_r); \
+ } \
+ } while (0)
+
+#define CHECK(op) \
+ do { \
+ result = (op); \
+ if (result != ISC_R_SUCCESS) \
+ goto cleanup; \
+ } while (0)
+
+#define CHECKTOK(op) \
+ do { \
+ result = (op); \
+ if (result != ISC_R_SUCCESS) { \
+ isc_lex_ungettoken(lexer, &token); \
+ goto cleanup; \
+ } \
+ } while (0)
+
+#define DNS_AS_STR(t) ((t).value.as_textregion.base)
+
+#define ARGS_FROMTEXT \
+ int rdclass, dns_rdatatype_t type, isc_lex_t *lexer, \
+ const dns_name_t *origin, unsigned int options, \
+ isc_buffer_t *target, dns_rdatacallbacks_t *callbacks
+
+#define CALL_FROMTEXT rdclass, type, lexer, origin, options, target, callbacks
+
+#define ARGS_TOTEXT \
+ dns_rdata_t *rdata, dns_rdata_textctx_t *tctx, isc_buffer_t *target
+
+#define CALL_TOTEXT rdata, tctx, target
+
+#define ARGS_FROMWIRE \
+ int rdclass, dns_rdatatype_t type, isc_buffer_t *source, \
+ dns_decompress_t *dctx, unsigned int options, \
+ isc_buffer_t *target
+
+#define CALL_FROMWIRE rdclass, type, source, dctx, options, target
+
+#define ARGS_TOWIRE \
+ dns_rdata_t *rdata, dns_compress_t *cctx, isc_buffer_t *target
+
+#define CALL_TOWIRE rdata, cctx, target
+
+#define ARGS_COMPARE const dns_rdata_t *rdata1, const dns_rdata_t *rdata2
+
+#define CALL_COMPARE rdata1, rdata2
+
+#define ARGS_FROMSTRUCT \
+ int rdclass, dns_rdatatype_t type, void *source, isc_buffer_t *target
+
+#define CALL_FROMSTRUCT rdclass, type, source, target
+
+#define ARGS_TOSTRUCT const dns_rdata_t *rdata, void *target, isc_mem_t *mctx
+
+#define CALL_TOSTRUCT rdata, target, mctx
+
+#define ARGS_FREESTRUCT void *source
+
+#define CALL_FREESTRUCT source
+
+#define ARGS_ADDLDATA \
+ dns_rdata_t *rdata, const dns_name_t *owner, \
+ dns_additionaldatafunc_t add, void *arg
+
+#define CALL_ADDLDATA rdata, owner, add, arg
+
+#define ARGS_DIGEST dns_rdata_t *rdata, dns_digestfunc_t digest, void *arg
+
+#define CALL_DIGEST rdata, digest, arg
+
+#define ARGS_CHECKOWNER \
+ const dns_name_t *name, dns_rdataclass_t rdclass, \
+ dns_rdatatype_t type, bool wildcard
+
+#define CALL_CHECKOWNER name, rdclass, type, wildcard
+
+#define ARGS_CHECKNAMES \
+ dns_rdata_t *rdata, const dns_name_t *owner, dns_name_t *bad
+
+#define CALL_CHECKNAMES rdata, owner, bad
+
+/*%
+ * Context structure for the totext_ functions.
+ * Contains formatting options for rdata-to-text
+ * conversion.
+ */
+typedef struct dns_rdata_textctx {
+ const dns_name_t *origin; /*%< Current origin, or NULL. */
+ dns_masterstyle_flags_t flags; /*%< DNS_STYLEFLAG_* */
+ unsigned int width; /*%< Width of rdata column. */
+ const char *linebreak; /*%< Line break string. */
+} dns_rdata_textctx_t;
+
+static isc_result_t
+txt_totext(isc_region_t *source, bool quote, isc_buffer_t *target);
+
+static isc_result_t
+txt_fromtext(isc_textregion_t *source, isc_buffer_t *target);
+
+static isc_result_t
+txt_fromwire(isc_buffer_t *source, isc_buffer_t *target);
+
+static isc_result_t
+commatxt_fromtext(isc_textregion_t *source, bool comma, isc_buffer_t *target);
+
+static isc_result_t
+commatxt_totext(isc_region_t *source, bool quote, bool comma,
+ isc_buffer_t *target);
+
+static isc_result_t
+multitxt_totext(isc_region_t *source, isc_buffer_t *target);
+
+static isc_result_t
+multitxt_fromtext(isc_textregion_t *source, isc_buffer_t *target);
+
+static bool
+name_prefix(dns_name_t *name, const dns_name_t *origin, dns_name_t *target);
+
+static unsigned int
+name_length(const dns_name_t *name);
+
+static isc_result_t
+str_totext(const char *source, isc_buffer_t *target);
+
+static isc_result_t
+inet_totext(int af, uint32_t flags, isc_region_t *src, isc_buffer_t *target);
+
+static bool
+buffer_empty(isc_buffer_t *source);
+
+static void
+buffer_fromregion(isc_buffer_t *buffer, isc_region_t *region);
+
+static isc_result_t
+uint32_tobuffer(uint32_t, isc_buffer_t *target);
+
+static isc_result_t
+uint16_tobuffer(uint32_t, isc_buffer_t *target);
+
+static isc_result_t
+uint8_tobuffer(uint32_t, isc_buffer_t *target);
+
+static isc_result_t
+name_tobuffer(const dns_name_t *name, isc_buffer_t *target);
+
+static uint32_t
+uint32_fromregion(isc_region_t *region);
+
+static uint16_t
+uint16_fromregion(isc_region_t *region);
+
+static uint8_t
+uint8_fromregion(isc_region_t *region);
+
+static uint8_t
+uint8_consume_fromregion(isc_region_t *region);
+
+static isc_result_t
+mem_tobuffer(isc_buffer_t *target, void *base, unsigned int length);
+
+static int
+hexvalue(char value);
+
+static int
+decvalue(char value);
+
+static void
+default_fromtext_callback(dns_rdatacallbacks_t *callbacks, const char *, ...)
+ ISC_FORMAT_PRINTF(2, 3);
+
+static void
+fromtext_error(void (*callback)(dns_rdatacallbacks_t *, const char *, ...),
+ dns_rdatacallbacks_t *callbacks, const char *name,
+ unsigned long line, isc_token_t *token, isc_result_t result);
+
+static void
+fromtext_warneof(isc_lex_t *lexer, dns_rdatacallbacks_t *callbacks);
+
+static isc_result_t
+rdata_totext(dns_rdata_t *rdata, dns_rdata_textctx_t *tctx,
+ isc_buffer_t *target);
+
+static void
+warn_badname(const dns_name_t *name, isc_lex_t *lexer,
+ dns_rdatacallbacks_t *callbacks);
+
+static void
+warn_badmx(isc_token_t *token, isc_lex_t *lexer,
+ dns_rdatacallbacks_t *callbacks);
+
+static uint16_t
+uint16_consume_fromregion(isc_region_t *region);
+
+static isc_result_t
+unknown_totext(dns_rdata_t *rdata, dns_rdata_textctx_t *tctx,
+ isc_buffer_t *target);
+
+static isc_result_t generic_fromtext_key(ARGS_FROMTEXT);
+
+static isc_result_t generic_totext_key(ARGS_TOTEXT);
+
+static isc_result_t generic_fromwire_key(ARGS_FROMWIRE);
+
+static isc_result_t generic_fromstruct_key(ARGS_FROMSTRUCT);
+
+static isc_result_t generic_tostruct_key(ARGS_TOSTRUCT);
+
+static void generic_freestruct_key(ARGS_FREESTRUCT);
+
+static isc_result_t generic_fromtext_txt(ARGS_FROMTEXT);
+
+static isc_result_t generic_totext_txt(ARGS_TOTEXT);
+
+static isc_result_t generic_fromwire_txt(ARGS_FROMWIRE);
+
+static isc_result_t generic_fromstruct_txt(ARGS_FROMSTRUCT);
+
+static isc_result_t generic_tostruct_txt(ARGS_TOSTRUCT);
+
+static void generic_freestruct_txt(ARGS_FREESTRUCT);
+
+static isc_result_t
+generic_txt_first(dns_rdata_txt_t *txt);
+
+static isc_result_t
+generic_txt_next(dns_rdata_txt_t *txt);
+
+static isc_result_t
+generic_txt_current(dns_rdata_txt_t *txt, dns_rdata_txt_string_t *string);
+
+static isc_result_t generic_totext_ds(ARGS_TOTEXT);
+
+static isc_result_t generic_tostruct_ds(ARGS_TOSTRUCT);
+
+static isc_result_t generic_fromtext_ds(ARGS_FROMTEXT);
+
+static isc_result_t generic_fromwire_ds(ARGS_FROMWIRE);
+
+static isc_result_t generic_fromstruct_ds(ARGS_FROMSTRUCT);
+
+static isc_result_t generic_fromtext_tlsa(ARGS_FROMTEXT);
+
+static isc_result_t generic_totext_tlsa(ARGS_TOTEXT);
+
+static isc_result_t generic_fromwire_tlsa(ARGS_FROMWIRE);
+
+static isc_result_t generic_fromstruct_tlsa(ARGS_FROMSTRUCT);
+
+static isc_result_t generic_tostruct_tlsa(ARGS_TOSTRUCT);
+
+static void generic_freestruct_tlsa(ARGS_FREESTRUCT);
+
+static isc_result_t generic_fromtext_in_svcb(ARGS_FROMTEXT);
+static isc_result_t generic_totext_in_svcb(ARGS_TOTEXT);
+static isc_result_t generic_fromwire_in_svcb(ARGS_FROMWIRE);
+static isc_result_t generic_towire_in_svcb(ARGS_TOWIRE);
+static isc_result_t generic_fromstruct_in_svcb(ARGS_FROMSTRUCT);
+static isc_result_t generic_tostruct_in_svcb(ARGS_TOSTRUCT);
+static void generic_freestruct_in_svcb(ARGS_FREESTRUCT);
+static isc_result_t generic_additionaldata_in_svcb(ARGS_ADDLDATA);
+static bool generic_checknames_in_svcb(ARGS_CHECKNAMES);
+static isc_result_t
+generic_rdata_in_svcb_first(dns_rdata_in_svcb_t *);
+static isc_result_t
+generic_rdata_in_svcb_next(dns_rdata_in_svcb_t *);
+static void
+generic_rdata_in_svcb_current(dns_rdata_in_svcb_t *, isc_region_t *);
+
+/*% INT16 Size */
+#define NS_INT16SZ 2
+/*% IPv6 Address Size */
+#define NS_LOCATORSZ 8
+
+/*
+ * Active Directory gc._msdcs.<forest> prefix.
+ */
+static unsigned char gc_msdcs_data[] = "\002gc\006_msdcs";
+static unsigned char gc_msdcs_offset[] = { 0, 3 };
+
+static dns_name_t const gc_msdcs = DNS_NAME_INITNONABSOLUTE(gc_msdcs_data,
+ gc_msdcs_offset);
+
+/*%
+ * convert presentation level address to network order binary form.
+ * \return
+ * 1 if `src' is a valid [RFC1884 2.2] address, else 0.
+ * \note
+ * (1) does not touch `dst' unless it's returning 1.
+ */
+static int
+locator_pton(const char *src, unsigned char *dst) {
+ static const char xdigits_l[] = "0123456789abcdef",
+ xdigits_u[] = "0123456789ABCDEF";
+ unsigned char tmp[NS_LOCATORSZ];
+ unsigned char *tp = tmp, *endp;
+ const char *xdigits;
+ int ch, seen_xdigits;
+ unsigned int val;
+
+ memset(tp, '\0', NS_LOCATORSZ);
+ endp = tp + NS_LOCATORSZ;
+ seen_xdigits = 0;
+ val = 0;
+ while ((ch = *src++) != '\0') {
+ const char *pch;
+
+ pch = strchr((xdigits = xdigits_l), ch);
+ if (pch == NULL) {
+ pch = strchr((xdigits = xdigits_u), ch);
+ }
+ if (pch != NULL) {
+ val <<= 4;
+ val |= (pch - xdigits);
+ if (++seen_xdigits > 4) {
+ return (0);
+ }
+ continue;
+ }
+ if (ch == ':') {
+ if (!seen_xdigits) {
+ return (0);
+ }
+ if (tp + NS_INT16SZ > endp) {
+ return (0);
+ }
+ *tp++ = (unsigned char)(val >> 8) & 0xff;
+ *tp++ = (unsigned char)val & 0xff;
+ seen_xdigits = 0;
+ val = 0;
+ continue;
+ }
+ return (0);
+ }
+ if (seen_xdigits) {
+ if (tp + NS_INT16SZ > endp) {
+ return (0);
+ }
+ *tp++ = (unsigned char)(val >> 8) & 0xff;
+ *tp++ = (unsigned char)val & 0xff;
+ }
+ if (tp != endp) {
+ return (0);
+ }
+ memmove(dst, tmp, NS_LOCATORSZ);
+ return (1);
+}
+
+static void
+name_duporclone(const dns_name_t *source, isc_mem_t *mctx, dns_name_t *target) {
+ if (mctx != NULL) {
+ dns_name_dup(source, mctx, target);
+ } else {
+ dns_name_clone(source, target);
+ }
+}
+
+static void *
+mem_maybedup(isc_mem_t *mctx, void *source, size_t length) {
+ void *copy;
+
+ if (mctx == NULL) {
+ return (source);
+ }
+ copy = isc_mem_allocate(mctx, length);
+ memmove(copy, source, length);
+
+ return (copy);
+}
+
+static isc_result_t
+typemap_fromtext(isc_lex_t *lexer, isc_buffer_t *target, bool allow_empty) {
+ isc_token_t token;
+ unsigned char bm[8 * 1024]; /* 64k bits */
+ dns_rdatatype_t covered, max_used;
+ int octet;
+ unsigned int max_octet, newend, end;
+ int window;
+ bool first = true;
+
+ max_used = 0;
+ bm[0] = 0;
+ end = 0;
+
+ do {
+ RETERR(isc_lex_getmastertoken(lexer, &token,
+ isc_tokentype_string, true));
+ if (token.type != isc_tokentype_string) {
+ break;
+ }
+ RETTOK(dns_rdatatype_fromtext(&covered,
+ &token.value.as_textregion));
+ if (covered > max_used) {
+ newend = covered / 8;
+ if (newend > end) {
+ memset(&bm[end + 1], 0, newend - end);
+ end = newend;
+ }
+ max_used = covered;
+ }
+ bm[covered / 8] |= (0x80 >> (covered % 8));
+ first = false;
+ } while (1);
+ isc_lex_ungettoken(lexer, &token);
+ if (!allow_empty && first) {
+ return (DNS_R_FORMERR);
+ }
+
+ for (window = 0; window < 256; window++) {
+ if (max_used < window * 256) {
+ break;
+ }
+
+ max_octet = max_used - (window * 256);
+ if (max_octet >= 256) {
+ max_octet = 31;
+ } else {
+ max_octet /= 8;
+ }
+
+ /*
+ * Find if we have a type in this window.
+ */
+ for (octet = max_octet; octet >= 0; octet--) {
+ if (bm[window * 32 + octet] != 0) {
+ break;
+ }
+ }
+ if (octet < 0) {
+ continue;
+ }
+ RETERR(uint8_tobuffer(window, target));
+ RETERR(uint8_tobuffer(octet + 1, target));
+ RETERR(mem_tobuffer(target, &bm[window * 32], octet + 1));
+ }
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+typemap_totext(isc_region_t *sr, dns_rdata_textctx_t *tctx,
+ isc_buffer_t *target) {
+ unsigned int i, j, k;
+ unsigned int window, len;
+ bool first = true;
+
+ for (i = 0; i < sr->length; i += len) {
+ if (tctx != NULL &&
+ (tctx->flags & DNS_STYLEFLAG_MULTILINE) != 0)
+ {
+ RETERR(str_totext(tctx->linebreak, target));
+ first = true;
+ }
+ INSIST(i + 2 <= sr->length);
+ window = sr->base[i];
+ len = sr->base[i + 1];
+ INSIST(len > 0 && len <= 32);
+ i += 2;
+ INSIST(i + len <= sr->length);
+ for (j = 0; j < len; j++) {
+ dns_rdatatype_t t;
+ if (sr->base[i + j] == 0) {
+ continue;
+ }
+ for (k = 0; k < 8; k++) {
+ if ((sr->base[i + j] & (0x80 >> k)) == 0) {
+ continue;
+ }
+ t = window * 256 + j * 8 + k;
+ if (!first) {
+ RETERR(str_totext(" ", target));
+ }
+ first = false;
+ if (dns_rdatatype_isknown(t)) {
+ RETERR(dns_rdatatype_totext(t, target));
+ } else {
+ char buf[sizeof("TYPE65535")];
+ snprintf(buf, sizeof(buf), "TYPE%u", t);
+ RETERR(str_totext(buf, target));
+ }
+ }
+ }
+ }
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+typemap_test(isc_region_t *sr, bool allow_empty) {
+ unsigned int window, lastwindow = 0;
+ unsigned int len;
+ bool first = true;
+ unsigned int i;
+
+ for (i = 0; i < sr->length; i += len) {
+ /*
+ * Check for overflow.
+ */
+ if (i + 2 > sr->length) {
+ RETERR(DNS_R_FORMERR);
+ }
+ window = sr->base[i];
+ len = sr->base[i + 1];
+ i += 2;
+ /*
+ * Check that bitmap windows are in the correct order.
+ */
+ if (!first && window <= lastwindow) {
+ RETERR(DNS_R_FORMERR);
+ }
+ /*
+ * Check for legal lengths.
+ */
+ if (len < 1 || len > 32) {
+ RETERR(DNS_R_FORMERR);
+ }
+ /*
+ * Check for overflow.
+ */
+ if (i + len > sr->length) {
+ RETERR(DNS_R_FORMERR);
+ }
+ /*
+ * The last octet of the bitmap must be non zero.
+ */
+ if (sr->base[i + len - 1] == 0) {
+ RETERR(DNS_R_FORMERR);
+ }
+ lastwindow = window;
+ first = false;
+ }
+ if (i != sr->length) {
+ return (DNS_R_EXTRADATA);
+ }
+ if (!allow_empty && first) {
+ RETERR(DNS_R_FORMERR);
+ }
+ return (ISC_R_SUCCESS);
+}
+
+static const char hexdigits[] = "0123456789abcdef";
+static const char decdigits[] = "0123456789";
+
+#include "code.h"
+
+#define META 0x0001
+#define RESERVED 0x0002
+
+/***
+ *** Initialization
+ ***/
+
+void
+dns_rdata_init(dns_rdata_t *rdata) {
+ REQUIRE(rdata != NULL);
+
+ rdata->data = NULL;
+ rdata->length = 0;
+ rdata->rdclass = 0;
+ rdata->type = 0;
+ rdata->flags = 0;
+ ISC_LINK_INIT(rdata, link);
+ /* ISC_LIST_INIT(rdata->list); */
+}
+
+void
+dns_rdata_reset(dns_rdata_t *rdata) {
+ REQUIRE(rdata != NULL);
+
+ REQUIRE(!ISC_LINK_LINKED(rdata, link));
+ REQUIRE(DNS_RDATA_VALIDFLAGS(rdata));
+
+ rdata->data = NULL;
+ rdata->length = 0;
+ rdata->rdclass = 0;
+ rdata->type = 0;
+ rdata->flags = 0;
+}
+
+/***
+ ***
+ ***/
+
+void
+dns_rdata_clone(const dns_rdata_t *src, dns_rdata_t *target) {
+ REQUIRE(src != NULL);
+ REQUIRE(target != NULL);
+
+ REQUIRE(DNS_RDATA_INITIALIZED(target));
+
+ REQUIRE(DNS_RDATA_VALIDFLAGS(src));
+ REQUIRE(DNS_RDATA_VALIDFLAGS(target));
+
+ target->data = src->data;
+ target->length = src->length;
+ target->rdclass = src->rdclass;
+ target->type = src->type;
+ target->flags = src->flags;
+}
+
+/***
+ *** Comparisons
+ ***/
+
+int
+dns_rdata_compare(const dns_rdata_t *rdata1, const dns_rdata_t *rdata2) {
+ int result = 0;
+ bool use_default = false;
+
+ REQUIRE(rdata1 != NULL);
+ REQUIRE(rdata2 != NULL);
+ REQUIRE(rdata1->length == 0 || rdata1->data != NULL);
+ REQUIRE(rdata2->length == 0 || rdata2->data != NULL);
+ REQUIRE(DNS_RDATA_VALIDFLAGS(rdata1));
+ REQUIRE(DNS_RDATA_VALIDFLAGS(rdata2));
+
+ if (rdata1->rdclass != rdata2->rdclass) {
+ return (rdata1->rdclass < rdata2->rdclass ? -1 : 1);
+ }
+
+ if (rdata1->type != rdata2->type) {
+ return (rdata1->type < rdata2->type ? -1 : 1);
+ }
+
+ COMPARESWITCH
+
+ if (use_default) {
+ isc_region_t r1;
+ isc_region_t r2;
+
+ dns_rdata_toregion(rdata1, &r1);
+ dns_rdata_toregion(rdata2, &r2);
+ result = isc_region_compare(&r1, &r2);
+ }
+ return (result);
+}
+
+int
+dns_rdata_casecompare(const dns_rdata_t *rdata1, const dns_rdata_t *rdata2) {
+ int result = 0;
+ bool use_default = false;
+
+ REQUIRE(rdata1 != NULL);
+ REQUIRE(rdata2 != NULL);
+ REQUIRE(rdata1->length == 0 || rdata1->data != NULL);
+ REQUIRE(rdata2->length == 0 || rdata2->data != NULL);
+ REQUIRE(DNS_RDATA_VALIDFLAGS(rdata1));
+ REQUIRE(DNS_RDATA_VALIDFLAGS(rdata2));
+
+ if (rdata1->rdclass != rdata2->rdclass) {
+ return (rdata1->rdclass < rdata2->rdclass ? -1 : 1);
+ }
+
+ if (rdata1->type != rdata2->type) {
+ return (rdata1->type < rdata2->type ? -1 : 1);
+ }
+
+ CASECOMPARESWITCH
+
+ if (use_default) {
+ isc_region_t r1;
+ isc_region_t r2;
+
+ dns_rdata_toregion(rdata1, &r1);
+ dns_rdata_toregion(rdata2, &r2);
+ result = isc_region_compare(&r1, &r2);
+ }
+ return (result);
+}
+
+/***
+ *** Conversions
+ ***/
+
+void
+dns_rdata_fromregion(dns_rdata_t *rdata, dns_rdataclass_t rdclass,
+ dns_rdatatype_t type, isc_region_t *r) {
+ REQUIRE(rdata != NULL);
+ REQUIRE(DNS_RDATA_INITIALIZED(rdata));
+ REQUIRE(r != NULL);
+
+ REQUIRE(DNS_RDATA_VALIDFLAGS(rdata));
+
+ rdata->data = r->base;
+ rdata->length = r->length;
+ rdata->rdclass = rdclass;
+ rdata->type = type;
+ rdata->flags = 0;
+}
+
+void
+dns_rdata_toregion(const dns_rdata_t *rdata, isc_region_t *r) {
+ REQUIRE(rdata != NULL);
+ REQUIRE(r != NULL);
+ REQUIRE(DNS_RDATA_VALIDFLAGS(rdata));
+
+ r->base = rdata->data;
+ r->length = rdata->length;
+}
+
+isc_result_t
+dns_rdata_fromwire(dns_rdata_t *rdata, dns_rdataclass_t rdclass,
+ dns_rdatatype_t type, isc_buffer_t *source,
+ dns_decompress_t *dctx, unsigned int options,
+ isc_buffer_t *target) {
+ isc_result_t result = ISC_R_NOTIMPLEMENTED;
+ isc_region_t region;
+ isc_buffer_t ss;
+ isc_buffer_t st;
+ bool use_default = false;
+ uint32_t activelength;
+ unsigned int length;
+
+ REQUIRE(dctx != NULL);
+ if (rdata != NULL) {
+ REQUIRE(DNS_RDATA_INITIALIZED(rdata));
+ REQUIRE(DNS_RDATA_VALIDFLAGS(rdata));
+ }
+ REQUIRE(source != NULL);
+ REQUIRE(target != NULL);
+
+ if (type == 0) {
+ return (DNS_R_FORMERR);
+ }
+
+ ss = *source;
+ st = *target;
+
+ activelength = isc_buffer_activelength(source);
+ INSIST(activelength < 65536);
+
+ FROMWIRESWITCH
+
+ if (use_default) {
+ if (activelength > isc_buffer_availablelength(target)) {
+ result = ISC_R_NOSPACE;
+ } else {
+ isc_buffer_putmem(target, isc_buffer_current(source),
+ activelength);
+ isc_buffer_forward(source, activelength);
+ result = ISC_R_SUCCESS;
+ }
+ }
+
+ /*
+ * Reject any rdata that expands out to more than DNS_RDATA_MAXLENGTH
+ * as we cannot transmit it.
+ */
+ length = isc_buffer_usedlength(target) - isc_buffer_usedlength(&st);
+ if (result == ISC_R_SUCCESS && length > DNS_RDATA_MAXLENGTH) {
+ result = DNS_R_FORMERR;
+ }
+
+ /*
+ * We should have consumed all of our buffer.
+ */
+ if (result == ISC_R_SUCCESS && !buffer_empty(source)) {
+ result = DNS_R_EXTRADATA;
+ }
+
+ if (rdata != NULL && result == ISC_R_SUCCESS) {
+ region.base = isc_buffer_used(&st);
+ region.length = length;
+ dns_rdata_fromregion(rdata, rdclass, type, &region);
+ }
+
+ if (result != ISC_R_SUCCESS) {
+ *source = ss;
+ *target = st;
+ }
+ return (result);
+}
+
+isc_result_t
+dns_rdata_towire(dns_rdata_t *rdata, dns_compress_t *cctx,
+ isc_buffer_t *target) {
+ isc_result_t result = ISC_R_NOTIMPLEMENTED;
+ bool use_default = false;
+ isc_region_t tr;
+ isc_buffer_t st;
+
+ REQUIRE(rdata != NULL);
+ REQUIRE(DNS_RDATA_VALIDFLAGS(rdata));
+
+ /*
+ * Some DynDNS meta-RRs have empty rdata.
+ */
+ if ((rdata->flags & DNS_RDATA_UPDATE) != 0) {
+ INSIST(rdata->length == 0);
+ return (ISC_R_SUCCESS);
+ }
+
+ st = *target;
+
+ TOWIRESWITCH
+
+ if (use_default) {
+ isc_buffer_availableregion(target, &tr);
+ if (tr.length < rdata->length) {
+ return (ISC_R_NOSPACE);
+ }
+ memmove(tr.base, rdata->data, rdata->length);
+ isc_buffer_add(target, rdata->length);
+ return (ISC_R_SUCCESS);
+ }
+ if (result != ISC_R_SUCCESS) {
+ *target = st;
+ INSIST(target->used < 65536);
+ dns_compress_rollback(cctx, (uint16_t)target->used);
+ }
+ return (result);
+}
+
+/*
+ * If the binary data in 'src' is valid uncompressed wire format
+ * rdata of class 'rdclass' and type 'type', return ISC_R_SUCCESS
+ * and copy the validated rdata to 'dest'. Otherwise return an error.
+ */
+static isc_result_t
+rdata_validate(isc_buffer_t *src, isc_buffer_t *dest, dns_rdataclass_t rdclass,
+ dns_rdatatype_t type) {
+ dns_decompress_t dctx;
+ isc_result_t result;
+
+ dns_decompress_init(&dctx, -1, DNS_DECOMPRESS_NONE);
+ isc_buffer_setactive(src, isc_buffer_usedlength(src));
+ result = dns_rdata_fromwire(NULL, rdclass, type, src, &dctx, 0, dest);
+ dns_decompress_invalidate(&dctx);
+
+ return (result);
+}
+
+static isc_result_t
+unknown_fromtext(dns_rdataclass_t rdclass, dns_rdatatype_t type,
+ isc_lex_t *lexer, isc_mem_t *mctx, isc_buffer_t *target) {
+ isc_result_t result;
+ isc_buffer_t *buf = NULL;
+ isc_token_t token;
+
+ if (type == 0 || dns_rdatatype_ismeta(type)) {
+ return (DNS_R_METATYPE);
+ }
+
+ RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_number,
+ false));
+ if (token.value.as_ulong > 65535U) {
+ return (ISC_R_RANGE);
+ }
+ isc_buffer_allocate(mctx, &buf, token.value.as_ulong);
+
+ if (token.value.as_ulong != 0U) {
+ result = isc_hex_tobuffer(lexer, buf,
+ (unsigned int)token.value.as_ulong);
+ if (result != ISC_R_SUCCESS) {
+ goto failure;
+ }
+ if (isc_buffer_usedlength(buf) != token.value.as_ulong) {
+ result = ISC_R_UNEXPECTEDEND;
+ goto failure;
+ }
+ }
+
+ if (dns_rdatatype_isknown(type)) {
+ result = rdata_validate(buf, target, rdclass, type);
+ } else {
+ isc_region_t r;
+ isc_buffer_usedregion(buf, &r);
+ result = isc_buffer_copyregion(target, &r);
+ }
+ if (result != ISC_R_SUCCESS) {
+ goto failure;
+ }
+
+ isc_buffer_free(&buf);
+ return (ISC_R_SUCCESS);
+
+failure:
+ isc_buffer_free(&buf);
+ return (result);
+}
+
+isc_result_t
+dns_rdata_fromtext(dns_rdata_t *rdata, dns_rdataclass_t rdclass,
+ dns_rdatatype_t type, isc_lex_t *lexer,
+ const dns_name_t *origin, unsigned int options,
+ isc_mem_t *mctx, isc_buffer_t *target,
+ dns_rdatacallbacks_t *callbacks) {
+ isc_result_t result = ISC_R_NOTIMPLEMENTED;
+ isc_region_t region;
+ isc_buffer_t st;
+ isc_token_t token;
+ unsigned int lexoptions = ISC_LEXOPT_EOL | ISC_LEXOPT_EOF |
+ ISC_LEXOPT_DNSMULTILINE | ISC_LEXOPT_ESCAPE;
+ char *name;
+ unsigned long line;
+ void (*callback)(dns_rdatacallbacks_t *, const char *, ...);
+ isc_result_t tresult;
+ unsigned int length;
+ bool unknown;
+
+ REQUIRE(origin == NULL || dns_name_isabsolute(origin));
+ if (rdata != NULL) {
+ REQUIRE(DNS_RDATA_INITIALIZED(rdata));
+ REQUIRE(DNS_RDATA_VALIDFLAGS(rdata));
+ }
+ if (callbacks != NULL) {
+ REQUIRE(callbacks->warn != NULL);
+ REQUIRE(callbacks->error != NULL);
+ }
+
+ st = *target;
+
+ if (callbacks != NULL) {
+ callback = callbacks->error;
+ } else {
+ callback = default_fromtext_callback;
+ }
+
+ result = isc_lex_getmastertoken(lexer, &token, isc_tokentype_qstring,
+ true);
+ if (result != ISC_R_SUCCESS) {
+ name = isc_lex_getsourcename(lexer);
+ line = isc_lex_getsourceline(lexer);
+ fromtext_error(callback, callbacks, name, line, NULL, result);
+ return (result);
+ }
+
+ unknown = false;
+ if (token.type == isc_tokentype_string &&
+ strcmp(DNS_AS_STR(token), "\\#") == 0)
+ {
+ /*
+ * If this is a TXT record '\#' could be a escaped '#'.
+ * Look to see if the next token is a number and if so
+ * treat it as a unknown record format.
+ */
+ if (type == dns_rdatatype_txt) {
+ result = isc_lex_getmastertoken(
+ lexer, &token, isc_tokentype_number, false);
+ if (result == ISC_R_SUCCESS) {
+ isc_lex_ungettoken(lexer, &token);
+ }
+ }
+
+ if (result == ISC_R_SUCCESS) {
+ unknown = true;
+ result = unknown_fromtext(rdclass, type, lexer, mctx,
+ target);
+ } else {
+ options |= DNS_RDATA_UNKNOWNESCAPE;
+ }
+ } else {
+ isc_lex_ungettoken(lexer, &token);
+ }
+
+ if (!unknown) {
+ FROMTEXTSWITCH
+
+ /*
+ * Consume to end of line / file.
+ * If not at end of line initially set error code.
+ * Call callback via fromtext_error once if there was an error.
+ */
+ }
+ do {
+ name = isc_lex_getsourcename(lexer);
+ line = isc_lex_getsourceline(lexer);
+ tresult = isc_lex_gettoken(lexer, lexoptions, &token);
+ if (tresult != ISC_R_SUCCESS) {
+ if (result == ISC_R_SUCCESS) {
+ result = tresult;
+ }
+ if (callback != NULL) {
+ fromtext_error(callback, callbacks, name, line,
+ NULL, result);
+ }
+ break;
+ } else if (token.type != isc_tokentype_eol &&
+ token.type != isc_tokentype_eof)
+ {
+ if (result == ISC_R_SUCCESS) {
+ result = DNS_R_EXTRATOKEN;
+ }
+ if (callback != NULL) {
+ fromtext_error(callback, callbacks, name, line,
+ &token, result);
+ callback = NULL;
+ }
+ } else if (result != ISC_R_SUCCESS && callback != NULL) {
+ fromtext_error(callback, callbacks, name, line, &token,
+ result);
+ break;
+ } else {
+ if (token.type == isc_tokentype_eof) {
+ fromtext_warneof(lexer, callbacks);
+ }
+ break;
+ }
+ } while (1);
+
+ length = isc_buffer_usedlength(target) - isc_buffer_usedlength(&st);
+ if (result == ISC_R_SUCCESS && length > DNS_RDATA_MAXLENGTH) {
+ result = ISC_R_NOSPACE;
+ }
+
+ if (rdata != NULL && result == ISC_R_SUCCESS) {
+ region.base = isc_buffer_used(&st);
+ region.length = length;
+ dns_rdata_fromregion(rdata, rdclass, type, &region);
+ }
+ if (result != ISC_R_SUCCESS) {
+ *target = st;
+ }
+ return (result);
+}
+
+static isc_result_t
+unknown_totext(dns_rdata_t *rdata, dns_rdata_textctx_t *tctx,
+ isc_buffer_t *target) {
+ isc_result_t result;
+ char buf[sizeof("65535")];
+ isc_region_t sr;
+
+ strlcpy(buf, "\\# ", sizeof(buf));
+ result = str_totext(buf, target);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+
+ dns_rdata_toregion(rdata, &sr);
+ INSIST(sr.length < 65536);
+ snprintf(buf, sizeof(buf), "%u", sr.length);
+ result = str_totext(buf, target);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+
+ if (sr.length != 0U) {
+ if ((tctx->flags & DNS_STYLEFLAG_MULTILINE) != 0) {
+ result = str_totext(" ( ", target);
+ } else {
+ result = str_totext(" ", target);
+ }
+
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+
+ if (tctx->width == 0) { /* No splitting */
+ result = isc_hex_totext(&sr, 0, "", target);
+ } else {
+ result = isc_hex_totext(&sr, tctx->width - 2,
+ tctx->linebreak, target);
+ }
+ if (result == ISC_R_SUCCESS &&
+ (tctx->flags & DNS_STYLEFLAG_MULTILINE) != 0)
+ {
+ result = str_totext(" )", target);
+ }
+ }
+ return (result);
+}
+
+static isc_result_t
+rdata_totext(dns_rdata_t *rdata, dns_rdata_textctx_t *tctx,
+ isc_buffer_t *target) {
+ isc_result_t result = ISC_R_NOTIMPLEMENTED;
+ bool use_default = false;
+ unsigned int cur;
+
+ REQUIRE(rdata != NULL);
+ REQUIRE(tctx->origin == NULL || dns_name_isabsolute(tctx->origin));
+
+ /*
+ * Some DynDNS meta-RRs have empty rdata.
+ */
+ if ((rdata->flags & DNS_RDATA_UPDATE) != 0) {
+ INSIST(rdata->length == 0);
+ return (ISC_R_SUCCESS);
+ }
+
+ if ((tctx->flags & DNS_STYLEFLAG_UNKNOWNFORMAT) != 0) {
+ return (unknown_totext(rdata, tctx, target));
+ }
+
+ cur = isc_buffer_usedlength(target);
+
+ TOTEXTSWITCH
+
+ if (use_default || (result == ISC_R_NOTIMPLEMENTED)) {
+ unsigned int u = isc_buffer_usedlength(target);
+
+ INSIST(u >= cur);
+ isc_buffer_subtract(target, u - cur);
+ result = unknown_totext(rdata, tctx, target);
+ }
+
+ return (result);
+}
+
+isc_result_t
+dns_rdata_totext(dns_rdata_t *rdata, const dns_name_t *origin,
+ isc_buffer_t *target) {
+ dns_rdata_textctx_t tctx;
+
+ REQUIRE(DNS_RDATA_VALIDFLAGS(rdata));
+
+ /*
+ * Set up formatting options for single-line output.
+ */
+ tctx.origin = origin;
+ tctx.flags = 0;
+ tctx.width = 60;
+ tctx.linebreak = " ";
+ return (rdata_totext(rdata, &tctx, target));
+}
+
+isc_result_t
+dns_rdata_tofmttext(dns_rdata_t *rdata, const dns_name_t *origin,
+ dns_masterstyle_flags_t flags, unsigned int width,
+ unsigned int split_width, const char *linebreak,
+ isc_buffer_t *target) {
+ dns_rdata_textctx_t tctx;
+
+ REQUIRE(DNS_RDATA_VALIDFLAGS(rdata));
+
+ /*
+ * Set up formatting options for formatted output.
+ */
+ tctx.origin = origin;
+ tctx.flags = flags;
+ if (split_width == 0xffffffff) {
+ tctx.width = width;
+ } else {
+ tctx.width = split_width;
+ }
+
+ if ((flags & DNS_STYLEFLAG_MULTILINE) != 0) {
+ tctx.linebreak = linebreak;
+ } else {
+ if (split_width == 0xffffffff) {
+ tctx.width = 60; /* Used for hex word length only. */
+ }
+ tctx.linebreak = " ";
+ }
+ return (rdata_totext(rdata, &tctx, target));
+}
+
+isc_result_t
+dns_rdata_fromstruct(dns_rdata_t *rdata, dns_rdataclass_t rdclass,
+ dns_rdatatype_t type, void *source, isc_buffer_t *target) {
+ isc_result_t result = ISC_R_NOTIMPLEMENTED;
+ isc_buffer_t st;
+ isc_region_t region;
+ bool use_default = false;
+ unsigned int length;
+
+ REQUIRE(source != NULL);
+ if (rdata != NULL) {
+ REQUIRE(DNS_RDATA_INITIALIZED(rdata));
+ REQUIRE(DNS_RDATA_VALIDFLAGS(rdata));
+ }
+
+ st = *target;
+
+ FROMSTRUCTSWITCH
+
+ if (use_default) {
+ (void)NULL;
+ }
+
+ length = isc_buffer_usedlength(target) - isc_buffer_usedlength(&st);
+ if (result == ISC_R_SUCCESS && length > DNS_RDATA_MAXLENGTH) {
+ result = ISC_R_NOSPACE;
+ }
+
+ if (rdata != NULL && result == ISC_R_SUCCESS) {
+ region.base = isc_buffer_used(&st);
+ region.length = length;
+ dns_rdata_fromregion(rdata, rdclass, type, &region);
+ }
+ if (result != ISC_R_SUCCESS) {
+ *target = st;
+ }
+ return (result);
+}
+
+isc_result_t
+dns_rdata_tostruct(const dns_rdata_t *rdata, void *target, isc_mem_t *mctx) {
+ isc_result_t result = ISC_R_NOTIMPLEMENTED;
+ bool use_default = false;
+
+ REQUIRE(rdata != NULL);
+ REQUIRE(DNS_RDATA_VALIDFLAGS(rdata));
+ REQUIRE((rdata->flags & DNS_RDATA_UPDATE) == 0);
+
+ TOSTRUCTSWITCH
+
+ if (use_default) {
+ (void)NULL;
+ }
+
+ return (result);
+}
+
+void
+dns_rdata_freestruct(void *source) {
+ dns_rdatacommon_t *common = source;
+ REQUIRE(common != NULL);
+
+ FREESTRUCTSWITCH
+}
+
+isc_result_t
+dns_rdata_additionaldata(dns_rdata_t *rdata, const dns_name_t *owner,
+ dns_additionaldatafunc_t add, void *arg) {
+ isc_result_t result = ISC_R_NOTIMPLEMENTED;
+ bool use_default = false;
+
+ /*
+ * Call 'add' for each name and type from 'rdata' which is subject to
+ * additional section processing.
+ */
+
+ REQUIRE(rdata != NULL);
+ REQUIRE(add != NULL);
+ REQUIRE(DNS_RDATA_VALIDFLAGS(rdata));
+
+ ADDITIONALDATASWITCH
+
+ /* No additional processing for unknown types */
+ if (use_default) {
+ result = ISC_R_SUCCESS;
+ }
+
+ return (result);
+}
+
+isc_result_t
+dns_rdata_digest(dns_rdata_t *rdata, dns_digestfunc_t digest, void *arg) {
+ isc_result_t result = ISC_R_NOTIMPLEMENTED;
+ bool use_default = false;
+ isc_region_t r;
+
+ /*
+ * Send 'rdata' in DNSSEC canonical form to 'digest'.
+ */
+
+ REQUIRE(rdata != NULL);
+ REQUIRE(digest != NULL);
+ REQUIRE(DNS_RDATA_VALIDFLAGS(rdata));
+
+ DIGESTSWITCH
+
+ if (use_default) {
+ dns_rdata_toregion(rdata, &r);
+ result = (digest)(arg, &r);
+ }
+
+ return (result);
+}
+
+bool
+dns_rdata_checkowner(const dns_name_t *name, dns_rdataclass_t rdclass,
+ dns_rdatatype_t type, bool wildcard) {
+ bool result;
+
+ CHECKOWNERSWITCH
+ return (result);
+}
+
+bool
+dns_rdata_checknames(dns_rdata_t *rdata, const dns_name_t *owner,
+ dns_name_t *bad) {
+ bool result;
+
+ CHECKNAMESSWITCH
+ return (result);
+}
+
+unsigned int
+dns_rdatatype_attributes(dns_rdatatype_t type) {
+ RDATATYPE_ATTRIBUTE_SW
+ if (type >= (dns_rdatatype_t)128 && type <= (dns_rdatatype_t)255) {
+ return (DNS_RDATATYPEATTR_UNKNOWN | DNS_RDATATYPEATTR_META);
+ }
+ return (DNS_RDATATYPEATTR_UNKNOWN);
+}
+
+isc_result_t
+dns_rdatatype_fromtext(dns_rdatatype_t *typep, isc_textregion_t *source) {
+ unsigned int hash;
+ unsigned int n;
+ unsigned char a, b;
+
+ n = source->length;
+
+ if (n == 0) {
+ return (DNS_R_UNKNOWN);
+ }
+
+ a = tolower((unsigned char)source->base[0]);
+ b = tolower((unsigned char)source->base[n - 1]);
+
+ hash = ((a + n) * b) % 256;
+
+ /*
+ * This switch block is inlined via \#define, and will use "return"
+ * to return a result to the caller if it is a valid (known)
+ * rdatatype name.
+ */
+ RDATATYPE_FROMTEXT_SW(hash, source->base, n, typep);
+
+ if (source->length > 4 && source->length < (4 + sizeof("65000")) &&
+ strncasecmp("type", source->base, 4) == 0)
+ {
+ char buf[sizeof("65000")];
+ char *endp;
+ unsigned int val;
+
+ /*
+ * source->base is not required to be NUL terminated.
+ * Copy up to remaining bytes and NUL terminate.
+ */
+ snprintf(buf, sizeof(buf), "%.*s", (int)(source->length - 4),
+ source->base + 4);
+ val = strtoul(buf, &endp, 10);
+ if (*endp == '\0' && val <= 0xffff) {
+ *typep = (dns_rdatatype_t)val;
+ return (ISC_R_SUCCESS);
+ }
+ }
+
+ return (DNS_R_UNKNOWN);
+}
+
+isc_result_t
+dns_rdatatype_totext(dns_rdatatype_t type, isc_buffer_t *target) {
+ RDATATYPE_TOTEXT_SW
+
+ return (dns_rdatatype_tounknowntext(type, target));
+}
+
+isc_result_t
+dns_rdatatype_tounknowntext(dns_rdatatype_t type, isc_buffer_t *target) {
+ char buf[sizeof("TYPE65535")];
+
+ snprintf(buf, sizeof(buf), "TYPE%u", type);
+ return (str_totext(buf, target));
+}
+
+void
+dns_rdatatype_format(dns_rdatatype_t rdtype, char *array, unsigned int size) {
+ isc_result_t result;
+ isc_buffer_t buf;
+
+ if (size == 0U) {
+ return;
+ }
+
+ isc_buffer_init(&buf, array, size);
+ result = dns_rdatatype_totext(rdtype, &buf);
+ /*
+ * Null terminate.
+ */
+ if (result == ISC_R_SUCCESS) {
+ if (isc_buffer_availablelength(&buf) >= 1) {
+ isc_buffer_putuint8(&buf, 0);
+ } else {
+ result = ISC_R_NOSPACE;
+ }
+ }
+ if (result != ISC_R_SUCCESS) {
+ strlcpy(array, "<unknown>", size);
+ }
+}
+
+/*
+ * Private function.
+ */
+
+static unsigned int
+name_length(const dns_name_t *name) {
+ return (name->length);
+}
+
+static isc_result_t
+commatxt_totext(isc_region_t *source, bool quote, bool comma,
+ isc_buffer_t *target) {
+ unsigned int tl;
+ unsigned int n;
+ unsigned char *sp;
+ char *tp;
+ isc_region_t region;
+
+ isc_buffer_availableregion(target, &region);
+ sp = source->base;
+ tp = (char *)region.base;
+ tl = region.length;
+
+ n = *sp++;
+
+ REQUIRE(n + 1 <= source->length);
+ if (n == 0U) {
+ REQUIRE(quote);
+ }
+
+ if (quote) {
+ if (tl < 1) {
+ return (ISC_R_NOSPACE);
+ }
+ *tp++ = '"';
+ tl--;
+ }
+ while (n--) {
+ /*
+ * \DDD space (0x20) if not quoting.
+ */
+ if (*sp < (quote ? ' ' : '!') || *sp >= 0x7f) {
+ if (tl < 4) {
+ return (ISC_R_NOSPACE);
+ }
+ *tp++ = '\\';
+ *tp++ = '0' + ((*sp / 100) % 10);
+ *tp++ = '0' + ((*sp / 10) % 10);
+ *tp++ = '0' + (*sp % 10);
+ sp++;
+ tl -= 4;
+ continue;
+ }
+ /*
+ * Escape double quote and backslash. If we are not
+ * enclosing the string in double quotes, also escape
+ * at sign (@) and semicolon (;) unless comma is set.
+ * If comma is set, then only escape commas (,).
+ */
+ if (*sp == '"' || *sp == '\\' || (comma && *sp == ',') ||
+ (!comma && !quote && (*sp == '@' || *sp == ';')))
+ {
+ if (tl < 2) {
+ return (ISC_R_NOSPACE);
+ }
+ *tp++ = '\\';
+ tl--;
+ /*
+ * Perform comma escape processing.
+ * ',' => '\\,'
+ * '\' => '\\\\'
+ */
+ if (comma && (*sp == ',' || *sp == '\\')) {
+ if (tl < ((*sp == '\\') ? 3 : 2)) {
+ return (ISC_R_NOSPACE);
+ }
+ *tp++ = '\\';
+ tl--;
+ if (*sp == '\\') {
+ *tp++ = '\\';
+ tl--;
+ }
+ }
+ }
+ if (tl < 1) {
+ return (ISC_R_NOSPACE);
+ }
+ *tp++ = *sp++;
+ tl--;
+ }
+ if (quote) {
+ if (tl < 1) {
+ return (ISC_R_NOSPACE);
+ }
+ *tp++ = '"';
+ tl--;
+ POST(tl);
+ }
+ isc_buffer_add(target, (unsigned int)(tp - (char *)region.base));
+ isc_region_consume(source, *source->base + 1);
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+txt_totext(isc_region_t *source, bool quote, isc_buffer_t *target) {
+ return (commatxt_totext(source, quote, false, target));
+}
+
+static isc_result_t
+commatxt_fromtext(isc_textregion_t *source, bool comma, isc_buffer_t *target) {
+ isc_region_t tregion;
+ bool escape = false, comma_escape = false, seen_comma = false;
+ unsigned int n, nrem;
+ char *s;
+ unsigned char *t;
+ int d;
+ int c;
+
+ isc_buffer_availableregion(target, &tregion);
+ s = source->base;
+ n = source->length;
+ t = tregion.base;
+ nrem = tregion.length;
+ if (nrem < 1) {
+ return (ISC_R_NOSPACE);
+ }
+ /*
+ * Length byte.
+ */
+ nrem--;
+ t++;
+ /*
+ * Maximum text string length.
+ */
+ if (nrem > 255) {
+ nrem = 255;
+ }
+ while (n-- != 0) {
+ c = (*s++) & 0xff;
+ if (escape && (d = decvalue((char)c)) != -1) {
+ c = d;
+ if (n == 0) {
+ return (DNS_R_SYNTAX);
+ }
+ n--;
+ if ((d = decvalue(*s++)) != -1) {
+ c = c * 10 + d;
+ } else {
+ return (DNS_R_SYNTAX);
+ }
+ if (n == 0) {
+ return (DNS_R_SYNTAX);
+ }
+ n--;
+ if ((d = decvalue(*s++)) != -1) {
+ c = c * 10 + d;
+ } else {
+ return (DNS_R_SYNTAX);
+ }
+ if (c > 255) {
+ return (DNS_R_SYNTAX);
+ }
+ } else if (!escape && c == '\\') {
+ escape = true;
+ continue;
+ }
+ escape = false;
+ /*
+ * Level 1 escape processing complete.
+ * If comma is set perform comma escape processing.
+ *
+ * Level 1 Level 2 ALPN's
+ * h1\,h2 => h1,h2 => h1 and h2
+ * h1\\,h2 => h1\,h2 => h1,h2
+ * h1\\h2 => h1\h2 => h1h2
+ * h1\\\\h2 => h1\\h2 => h1\h2
+ */
+ if (comma && !comma_escape && c == ',') {
+ seen_comma = true;
+ break;
+ }
+ if (comma && !comma_escape && c == '\\') {
+ comma_escape = true;
+ continue;
+ }
+ comma_escape = false;
+ if (nrem == 0) {
+ return ((tregion.length <= 256U) ? ISC_R_NOSPACE
+ : DNS_R_SYNTAX);
+ }
+ *t++ = c;
+ nrem--;
+ }
+
+ /*
+ * Incomplete escape processing?
+ */
+ if (escape || (comma && comma_escape)) {
+ return (DNS_R_SYNTAX);
+ }
+
+ if (comma) {
+ /*
+ * Disallow empty ALPN at start (",h1") or in the
+ * middle ("h1,,h2").
+ */
+ if (s == source->base || (seen_comma && s == source->base + 1))
+ {
+ return (DNS_R_SYNTAX);
+ }
+ isc_textregion_consume(source, s - source->base);
+ /*
+ * Disallow empty ALPN at end ("h1,").
+ */
+ if (seen_comma && source->length == 0) {
+ return (DNS_R_SYNTAX);
+ }
+ }
+ *tregion.base = (unsigned char)(t - tregion.base - 1);
+ isc_buffer_add(target, *tregion.base + 1);
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+txt_fromtext(isc_textregion_t *source, isc_buffer_t *target) {
+ return (commatxt_fromtext(source, false, target));
+}
+
+static isc_result_t
+txt_fromwire(isc_buffer_t *source, isc_buffer_t *target) {
+ unsigned int n;
+ isc_region_t sregion;
+ isc_region_t tregion;
+
+ isc_buffer_activeregion(source, &sregion);
+ if (sregion.length == 0) {
+ return (ISC_R_UNEXPECTEDEND);
+ }
+ n = *sregion.base + 1;
+ if (n > sregion.length) {
+ return (ISC_R_UNEXPECTEDEND);
+ }
+
+ isc_buffer_availableregion(target, &tregion);
+ if (n > tregion.length) {
+ return (ISC_R_NOSPACE);
+ }
+
+ if (tregion.base != sregion.base) {
+ memmove(tregion.base, sregion.base, n);
+ }
+ isc_buffer_forward(source, n);
+ isc_buffer_add(target, n);
+ return (ISC_R_SUCCESS);
+}
+
+/*
+ * Conversion of TXT-like rdata fields without length limits.
+ */
+static isc_result_t
+multitxt_totext(isc_region_t *source, isc_buffer_t *target) {
+ unsigned int tl;
+ unsigned int n0, n;
+ unsigned char *sp;
+ char *tp;
+ isc_region_t region;
+
+ isc_buffer_availableregion(target, &region);
+ sp = source->base;
+ tp = (char *)region.base;
+ tl = region.length;
+
+ if (tl < 1) {
+ return (ISC_R_NOSPACE);
+ }
+ *tp++ = '"';
+ tl--;
+ do {
+ n = source->length;
+ n0 = source->length - 1;
+
+ while (n--) {
+ if (*sp < ' ' || *sp >= 0x7f) {
+ if (tl < 4) {
+ return (ISC_R_NOSPACE);
+ }
+ *tp++ = '\\';
+ *tp++ = '0' + ((*sp / 100) % 10);
+ *tp++ = '0' + ((*sp / 10) % 10);
+ *tp++ = '0' + (*sp % 10);
+ sp++;
+ tl -= 4;
+ continue;
+ }
+ /* double quote, backslash */
+ if (*sp == '"' || *sp == '\\') {
+ if (tl < 2) {
+ return (ISC_R_NOSPACE);
+ }
+ *tp++ = '\\';
+ tl--;
+ }
+ if (tl < 1) {
+ return (ISC_R_NOSPACE);
+ }
+ *tp++ = *sp++;
+ tl--;
+ }
+ isc_region_consume(source, n0 + 1);
+ } while (source->length != 0);
+ if (tl < 1) {
+ return (ISC_R_NOSPACE);
+ }
+ *tp++ = '"';
+ tl--;
+ POST(tl);
+ isc_buffer_add(target, (unsigned int)(tp - (char *)region.base));
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+multitxt_fromtext(isc_textregion_t *source, isc_buffer_t *target) {
+ isc_region_t tregion;
+ bool escape;
+ unsigned int n, nrem;
+ char *s;
+ unsigned char *t0, *t;
+ int d;
+ int c;
+
+ s = source->base;
+ n = source->length;
+ escape = false;
+
+ do {
+ isc_buffer_availableregion(target, &tregion);
+ t0 = t = tregion.base;
+ nrem = tregion.length;
+ if (nrem < 1) {
+ return (ISC_R_NOSPACE);
+ }
+
+ while (n != 0) {
+ --n;
+ c = (*s++) & 0xff;
+ if (escape && (d = decvalue((char)c)) != -1) {
+ c = d;
+ if (n == 0) {
+ return (DNS_R_SYNTAX);
+ }
+ n--;
+ if ((d = decvalue(*s++)) != -1) {
+ c = c * 10 + d;
+ } else {
+ return (DNS_R_SYNTAX);
+ }
+ if (n == 0) {
+ return (DNS_R_SYNTAX);
+ }
+ n--;
+ if ((d = decvalue(*s++)) != -1) {
+ c = c * 10 + d;
+ } else {
+ return (DNS_R_SYNTAX);
+ }
+ if (c > 255) {
+ return (DNS_R_SYNTAX);
+ }
+ } else if (!escape && c == '\\') {
+ escape = true;
+ continue;
+ }
+ escape = false;
+ *t++ = c;
+ nrem--;
+ if (nrem == 0) {
+ break;
+ }
+ }
+ if (escape) {
+ return (DNS_R_SYNTAX);
+ }
+
+ isc_buffer_add(target, (unsigned int)(t - t0));
+ } while (n != 0);
+ return (ISC_R_SUCCESS);
+}
+
+static bool
+name_prefix(dns_name_t *name, const dns_name_t *origin, dns_name_t *target) {
+ int l1, l2;
+
+ if (origin == NULL) {
+ goto return_false;
+ }
+
+ if (dns_name_compare(origin, dns_rootname) == 0) {
+ goto return_false;
+ }
+
+ if (!dns_name_issubdomain(name, origin)) {
+ goto return_false;
+ }
+
+ l1 = dns_name_countlabels(name);
+ l2 = dns_name_countlabels(origin);
+
+ if (l1 == l2) {
+ goto return_false;
+ }
+
+ /* Master files should be case preserving. */
+ dns_name_getlabelsequence(name, l1 - l2, l2, target);
+ if (!dns_name_caseequal(origin, target)) {
+ goto return_false;
+ }
+
+ dns_name_getlabelsequence(name, 0, l1 - l2, target);
+ return (true);
+
+return_false:
+ *target = *name;
+ return (false);
+}
+
+static isc_result_t
+str_totext(const char *source, isc_buffer_t *target) {
+ unsigned int l;
+ isc_region_t region;
+
+ isc_buffer_availableregion(target, &region);
+ l = strlen(source);
+
+ if (l > region.length) {
+ return (ISC_R_NOSPACE);
+ }
+
+ memmove(region.base, source, l);
+ isc_buffer_add(target, l);
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+inet_totext(int af, uint32_t flags, isc_region_t *src, isc_buffer_t *target) {
+ char tmpbuf[64];
+
+ /* Note - inet_ntop doesn't do size checking on its input. */
+ if (inet_ntop(af, src->base, tmpbuf, sizeof(tmpbuf)) == NULL) {
+ return (ISC_R_NOSPACE);
+ }
+ if (strlen(tmpbuf) > isc_buffer_availablelength(target)) {
+ return (ISC_R_NOSPACE);
+ }
+ isc_buffer_putstr(target, tmpbuf);
+
+ /*
+ * An IPv6 address ending in "::" breaks YAML
+ * parsing, so append 0 in that case.
+ */
+ if (af == AF_INET6 && (flags & DNS_STYLEFLAG_YAML) != 0) {
+ isc_region_t r;
+ isc_buffer_usedregion(target, &r);
+ if (r.length > 0 && r.base[r.length - 1] == ':') {
+ if (isc_buffer_availablelength(target) == 0) {
+ return (ISC_R_NOSPACE);
+ }
+ isc_buffer_putmem(target, (const unsigned char *)"0",
+ 1);
+ }
+ }
+
+ return (ISC_R_SUCCESS);
+}
+
+static bool
+buffer_empty(isc_buffer_t *source) {
+ return ((source->current == source->active) ? true : false);
+}
+
+static void
+buffer_fromregion(isc_buffer_t *buffer, isc_region_t *region) {
+ isc_buffer_init(buffer, region->base, region->length);
+ isc_buffer_add(buffer, region->length);
+ isc_buffer_setactive(buffer, region->length);
+}
+
+static isc_result_t
+uint32_tobuffer(uint32_t value, isc_buffer_t *target) {
+ isc_region_t region;
+
+ isc_buffer_availableregion(target, &region);
+ 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, &region);
+ 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, &region);
+ if (region.length < 1) {
+ return (ISC_R_NOSPACE);
+ }
+ isc_buffer_putuint8(target, (uint8_t)value);
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+name_tobuffer(const dns_name_t *name, isc_buffer_t *target) {
+ isc_region_t r;
+ dns_name_toregion(name, &r);
+ return (isc_buffer_copyregion(target, &r));
+}
+
+static uint32_t
+uint32_fromregion(isc_region_t *region) {
+ uint32_t value;
+
+ REQUIRE(region->length >= 4);
+ value = (uint32_t)region->base[0] << 24;
+ value |= (uint32_t)region->base[1] << 16;
+ value |= (uint32_t)region->base[2] << 8;
+ value |= (uint32_t)region->base[3];
+ return (value);
+}
+
+static uint16_t
+uint16_consume_fromregion(isc_region_t *region) {
+ uint16_t r = uint16_fromregion(region);
+
+ isc_region_consume(region, 2);
+ return (r);
+}
+
+static uint16_t
+uint16_fromregion(isc_region_t *region) {
+ REQUIRE(region->length >= 2);
+
+ return ((region->base[0] << 8) | region->base[1]);
+}
+
+static uint8_t
+uint8_fromregion(isc_region_t *region) {
+ REQUIRE(region->length >= 1);
+
+ return (region->base[0]);
+}
+
+static uint8_t
+uint8_consume_fromregion(isc_region_t *region) {
+ uint8_t r = uint8_fromregion(region);
+
+ isc_region_consume(region, 1);
+ return (r);
+}
+
+static isc_result_t
+mem_tobuffer(isc_buffer_t *target, void *base, unsigned int length) {
+ isc_region_t tr;
+
+ if (length == 0U) {
+ return (ISC_R_SUCCESS);
+ }
+
+ isc_buffer_availableregion(target, &tr);
+ if (length > tr.length) {
+ return (ISC_R_NOSPACE);
+ }
+ if (tr.base != base) {
+ memmove(tr.base, base, length);
+ }
+ isc_buffer_add(target, length);
+ return (ISC_R_SUCCESS);
+}
+
+static int
+hexvalue(char value) {
+ const char *s;
+ unsigned char c;
+
+ c = (unsigned char)value;
+
+ if (!isascii(c)) {
+ return (-1);
+ }
+ if (isupper(c)) {
+ c = tolower(c);
+ }
+ if ((s = strchr(hexdigits, c)) == NULL) {
+ return (-1);
+ }
+ return ((int)(s - hexdigits));
+}
+
+static int
+decvalue(char value) {
+ const char *s;
+
+ /*
+ * isascii() is valid for full range of int values, no need to
+ * mask or cast.
+ */
+ if (!isascii(value)) {
+ return (-1);
+ }
+ if ((s = strchr(decdigits, value)) == NULL) {
+ return (-1);
+ }
+ return ((int)(s - decdigits));
+}
+
+static void
+default_fromtext_callback(dns_rdatacallbacks_t *callbacks, const char *fmt,
+ ...) {
+ va_list ap;
+
+ UNUSED(callbacks);
+
+ va_start(ap, fmt);
+ vfprintf(stderr, fmt, ap);
+ va_end(ap);
+ fprintf(stderr, "\n");
+}
+
+static void
+fromtext_warneof(isc_lex_t *lexer, dns_rdatacallbacks_t *callbacks) {
+ if (isc_lex_isfile(lexer) && callbacks != NULL) {
+ const char *name = isc_lex_getsourcename(lexer);
+ if (name == NULL) {
+ name = "UNKNOWN";
+ }
+ (*callbacks->warn)(callbacks,
+ "%s:%lu: file does not end with newline",
+ name, isc_lex_getsourceline(lexer));
+ }
+}
+
+static void
+warn_badmx(isc_token_t *token, isc_lex_t *lexer,
+ dns_rdatacallbacks_t *callbacks) {
+ const char *file;
+ unsigned long line;
+
+ if (lexer != NULL) {
+ file = isc_lex_getsourcename(lexer);
+ line = isc_lex_getsourceline(lexer);
+ (*callbacks->warn)(callbacks, "%s:%u: warning: '%s': %s", file,
+ line, DNS_AS_STR(*token),
+ isc_result_totext(DNS_R_MXISADDRESS));
+ }
+}
+
+static void
+warn_badname(const dns_name_t *name, isc_lex_t *lexer,
+ dns_rdatacallbacks_t *callbacks) {
+ const char *file;
+ unsigned long line;
+ char namebuf[DNS_NAME_FORMATSIZE];
+
+ if (lexer != NULL) {
+ file = isc_lex_getsourcename(lexer);
+ line = isc_lex_getsourceline(lexer);
+ dns_name_format(name, namebuf, sizeof(namebuf));
+ (*callbacks->warn)(callbacks, "%s:%u: warning: %s: %s", file,
+ line, namebuf,
+ isc_result_totext(DNS_R_BADNAME));
+ }
+}
+
+static void
+fromtext_error(void (*callback)(dns_rdatacallbacks_t *, const char *, ...),
+ dns_rdatacallbacks_t *callbacks, const char *name,
+ unsigned long line, isc_token_t *token, isc_result_t result) {
+ if (name == NULL) {
+ name = "UNKNOWN";
+ }
+
+ if (token != NULL) {
+ switch (token->type) {
+ case isc_tokentype_eol:
+ (*callback)(callbacks, "%s: %s:%lu: near eol: %s",
+ "dns_rdata_fromtext", name, line,
+ isc_result_totext(result));
+ break;
+ case isc_tokentype_eof:
+ (*callback)(callbacks, "%s: %s:%lu: near eof: %s",
+ "dns_rdata_fromtext", name, line,
+ isc_result_totext(result));
+ break;
+ case isc_tokentype_number:
+ (*callback)(callbacks, "%s: %s:%lu: near %lu: %s",
+ "dns_rdata_fromtext", name, line,
+ token->value.as_ulong,
+ isc_result_totext(result));
+ break;
+ case isc_tokentype_string:
+ case isc_tokentype_qstring:
+ (*callback)(callbacks, "%s: %s:%lu: near '%s': %s",
+ "dns_rdata_fromtext", name, line,
+ DNS_AS_STR(*token),
+ isc_result_totext(result));
+ break;
+ default:
+ (*callback)(callbacks, "%s: %s:%lu: %s",
+ "dns_rdata_fromtext", name, line,
+ isc_result_totext(result));
+ break;
+ }
+ } else {
+ (*callback)(callbacks, "dns_rdata_fromtext: %s:%lu: %s", name,
+ line, isc_result_totext(result));
+ }
+}
+
+dns_rdatatype_t
+dns_rdata_covers(dns_rdata_t *rdata) {
+ if (rdata->type == dns_rdatatype_rrsig) {
+ return (covers_rrsig(rdata));
+ }
+ return (covers_sig(rdata));
+}
+
+bool
+dns_rdatatype_ismeta(dns_rdatatype_t type) {
+ if ((dns_rdatatype_attributes(type) & DNS_RDATATYPEATTR_META) != 0) {
+ return (true);
+ }
+ return (false);
+}
+
+bool
+dns_rdatatype_issingleton(dns_rdatatype_t type) {
+ if ((dns_rdatatype_attributes(type) & DNS_RDATATYPEATTR_SINGLETON) != 0)
+ {
+ return (true);
+ }
+ return (false);
+}
+
+bool
+dns_rdatatype_notquestion(dns_rdatatype_t type) {
+ if ((dns_rdatatype_attributes(type) & DNS_RDATATYPEATTR_NOTQUESTION) !=
+ 0)
+ {
+ return (true);
+ }
+ return (false);
+}
+
+bool
+dns_rdatatype_questiononly(dns_rdatatype_t type) {
+ if ((dns_rdatatype_attributes(type) & DNS_RDATATYPEATTR_QUESTIONONLY) !=
+ 0)
+ {
+ return (true);
+ }
+ return (false);
+}
+
+bool
+dns_rdatatype_atcname(dns_rdatatype_t type) {
+ if ((dns_rdatatype_attributes(type) & DNS_RDATATYPEATTR_ATCNAME) != 0) {
+ return (true);
+ }
+ return (false);
+}
+
+bool
+dns_rdatatype_atparent(dns_rdatatype_t type) {
+ if ((dns_rdatatype_attributes(type) & DNS_RDATATYPEATTR_ATPARENT) != 0)
+ {
+ return (true);
+ }
+ return (false);
+}
+
+bool
+dns_rdatatype_followadditional(dns_rdatatype_t type) {
+ if ((dns_rdatatype_attributes(type) &
+ DNS_RDATATYPEATTR_FOLLOWADDITIONAL) != 0)
+ {
+ return (true);
+ }
+ return (false);
+}
+
+bool
+dns_rdataclass_ismeta(dns_rdataclass_t rdclass) {
+ if (rdclass == dns_rdataclass_reserved0 ||
+ rdclass == dns_rdataclass_none || rdclass == dns_rdataclass_any)
+ {
+ return (true);
+ }
+
+ return (false); /* Assume it is not a meta class. */
+}
+
+bool
+dns_rdatatype_isdnssec(dns_rdatatype_t type) {
+ if ((dns_rdatatype_attributes(type) & DNS_RDATATYPEATTR_DNSSEC) != 0) {
+ return (true);
+ }
+ return (false);
+}
+
+bool
+dns_rdatatype_iszonecutauth(dns_rdatatype_t type) {
+ if ((dns_rdatatype_attributes(type) & DNS_RDATATYPEATTR_ZONECUTAUTH) !=
+ 0)
+ {
+ return (true);
+ }
+ return (false);
+}
+
+bool
+dns_rdatatype_isknown(dns_rdatatype_t type) {
+ if ((dns_rdatatype_attributes(type) & DNS_RDATATYPEATTR_UNKNOWN) == 0) {
+ return (true);
+ }
+ return (false);
+}
+
+void
+dns_rdata_exists(dns_rdata_t *rdata, dns_rdatatype_t type) {
+ REQUIRE(rdata != NULL);
+ REQUIRE(DNS_RDATA_INITIALIZED(rdata));
+
+ rdata->data = NULL;
+ rdata->length = 0;
+ rdata->flags = DNS_RDATA_UPDATE;
+ rdata->type = type;
+ rdata->rdclass = dns_rdataclass_any;
+}
+
+void
+dns_rdata_notexist(dns_rdata_t *rdata, dns_rdatatype_t type) {
+ REQUIRE(rdata != NULL);
+ REQUIRE(DNS_RDATA_INITIALIZED(rdata));
+
+ rdata->data = NULL;
+ rdata->length = 0;
+ rdata->flags = DNS_RDATA_UPDATE;
+ rdata->type = type;
+ rdata->rdclass = dns_rdataclass_none;
+}
+
+void
+dns_rdata_deleterrset(dns_rdata_t *rdata, dns_rdatatype_t type) {
+ REQUIRE(rdata != NULL);
+ REQUIRE(DNS_RDATA_INITIALIZED(rdata));
+
+ rdata->data = NULL;
+ rdata->length = 0;
+ rdata->flags = DNS_RDATA_UPDATE;
+ rdata->type = type;
+ rdata->rdclass = dns_rdataclass_any;
+}
+
+void
+dns_rdata_makedelete(dns_rdata_t *rdata) {
+ REQUIRE(rdata != NULL);
+
+ rdata->rdclass = dns_rdataclass_none;
+}
+
+const char *
+dns_rdata_updateop(dns_rdata_t *rdata, dns_section_t section) {
+ REQUIRE(rdata != NULL);
+ REQUIRE(DNS_RDATA_INITIALIZED(rdata));
+
+ switch (section) {
+ case DNS_SECTION_PREREQUISITE:
+ switch (rdata->rdclass) {
+ case dns_rdataclass_none:
+ switch (rdata->type) {
+ case dns_rdatatype_any:
+ return ("domain doesn't exist");
+ default:
+ return ("rrset doesn't exist");
+ }
+ case dns_rdataclass_any:
+ switch (rdata->type) {
+ case dns_rdatatype_any:
+ return ("domain exists");
+ default:
+ return ("rrset exists (value independent)");
+ }
+ default:
+ return ("rrset exists (value dependent)");
+ }
+ case DNS_SECTION_UPDATE:
+ switch (rdata->rdclass) {
+ case dns_rdataclass_none:
+ return ("delete");
+ case dns_rdataclass_any:
+ switch (rdata->type) {
+ case dns_rdatatype_any:
+ return ("delete all rrsets");
+ default:
+ return ("delete rrset");
+ }
+ default:
+ return ("add");
+ }
+ }
+ return ("invalid");
+}
diff --git a/lib/dns/rdata/any_255/tsig_250.c b/lib/dns/rdata/any_255/tsig_250.c
new file mode 100644
index 0000000..5fe9254
--- /dev/null
+++ b/lib/dns/rdata/any_255/tsig_250.c
@@ -0,0 +1,622 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#ifndef RDATA_ANY_255_TSIG_250_C
+#define RDATA_ANY_255_TSIG_250_C
+
+#define RRTYPE_TSIG_ATTRIBUTES \
+ (DNS_RDATATYPEATTR_META | DNS_RDATATYPEATTR_NOTQUESTION)
+
+static isc_result_t
+fromtext_any_tsig(ARGS_FROMTEXT) {
+ isc_token_t token;
+ dns_name_t name;
+ uint64_t sigtime;
+ isc_buffer_t buffer;
+ dns_rcode_t rcode;
+ long i;
+ char *e;
+
+ REQUIRE(type == dns_rdatatype_tsig);
+ REQUIRE(rdclass == dns_rdataclass_any);
+
+ UNUSED(type);
+ UNUSED(rdclass);
+ UNUSED(callbacks);
+
+ /*
+ * Algorithm Name.
+ */
+ RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_string,
+ false));
+ dns_name_init(&name, NULL);
+ buffer_fromregion(&buffer, &token.value.as_region);
+ if (origin == NULL) {
+ origin = dns_rootname;
+ }
+ RETTOK(dns_name_fromtext(&name, &buffer, origin, options, target));
+
+ /*
+ * Time Signed: 48 bits.
+ */
+ RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_string,
+ false));
+ sigtime = strtoull(DNS_AS_STR(token), &e, 10);
+ if (*e != 0) {
+ RETTOK(DNS_R_SYNTAX);
+ }
+ if ((sigtime >> 48) != 0) {
+ RETTOK(ISC_R_RANGE);
+ }
+ RETERR(uint16_tobuffer((uint16_t)(sigtime >> 32), target));
+ RETERR(uint32_tobuffer((uint32_t)(sigtime & 0xffffffffU), target));
+
+ /*
+ * Fudge.
+ */
+ RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_number,
+ false));
+ if (token.value.as_ulong > 0xffffU) {
+ RETTOK(ISC_R_RANGE);
+ }
+ RETERR(uint16_tobuffer(token.value.as_ulong, target));
+
+ /*
+ * Signature Size.
+ */
+ RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_number,
+ false));
+ if (token.value.as_ulong > 0xffffU) {
+ RETTOK(ISC_R_RANGE);
+ }
+ RETERR(uint16_tobuffer(token.value.as_ulong, target));
+
+ /*
+ * Signature.
+ */
+ RETERR(isc_base64_tobuffer(lexer, target, (int)token.value.as_ulong));
+
+ /*
+ * Original ID.
+ */
+ RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_number,
+ false));
+ if (token.value.as_ulong > 0xffffU) {
+ RETTOK(ISC_R_RANGE);
+ }
+ RETERR(uint16_tobuffer(token.value.as_ulong, target));
+
+ /*
+ * Error.
+ */
+ RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_string,
+ false));
+ if (dns_tsigrcode_fromtext(&rcode, &token.value.as_textregion) !=
+ ISC_R_SUCCESS)
+ {
+ i = strtol(DNS_AS_STR(token), &e, 10);
+ if (*e != 0) {
+ RETTOK(DNS_R_UNKNOWN);
+ }
+ if (i < 0 || i > 0xffff) {
+ RETTOK(ISC_R_RANGE);
+ }
+ rcode = (dns_rcode_t)i;
+ }
+ RETERR(uint16_tobuffer(rcode, target));
+
+ /*
+ * Other Len.
+ */
+ RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_number,
+ false));
+ if (token.value.as_ulong > 0xffffU) {
+ RETTOK(ISC_R_RANGE);
+ }
+ RETERR(uint16_tobuffer(token.value.as_ulong, target));
+
+ /*
+ * Other Data.
+ */
+ return (isc_base64_tobuffer(lexer, target, (int)token.value.as_ulong));
+}
+
+static isc_result_t
+totext_any_tsig(ARGS_TOTEXT) {
+ isc_region_t sr;
+ isc_region_t sigr;
+ char buf[sizeof(" 281474976710655 ")];
+ char *bufp;
+ dns_name_t name;
+ dns_name_t prefix;
+ bool sub;
+ uint64_t sigtime;
+ unsigned short n;
+
+ REQUIRE(rdata->type == dns_rdatatype_tsig);
+ REQUIRE(rdata->rdclass == dns_rdataclass_any);
+ REQUIRE(rdata->length != 0);
+
+ dns_rdata_toregion(rdata, &sr);
+ /*
+ * Algorithm Name.
+ */
+ dns_name_init(&name, NULL);
+ dns_name_init(&prefix, NULL);
+ dns_name_fromregion(&name, &sr);
+ sub = name_prefix(&name, tctx->origin, &prefix);
+ RETERR(dns_name_totext(&prefix, sub, target));
+ RETERR(str_totext(" ", target));
+ isc_region_consume(&sr, name_length(&name));
+
+ /*
+ * Time Signed.
+ */
+ sigtime = ((uint64_t)sr.base[0] << 40) | ((uint64_t)sr.base[1] << 32) |
+ ((uint64_t)sr.base[2] << 24) | ((uint64_t)sr.base[3] << 16) |
+ ((uint64_t)sr.base[4] << 8) | (uint64_t)sr.base[5];
+ isc_region_consume(&sr, 6);
+ bufp = &buf[sizeof(buf) - 1];
+ *bufp-- = 0;
+ *bufp-- = ' ';
+ do {
+ *bufp-- = decdigits[sigtime % 10];
+ sigtime /= 10;
+ } while (sigtime != 0);
+ bufp++;
+ RETERR(str_totext(bufp, target));
+
+ /*
+ * Fudge.
+ */
+ n = uint16_fromregion(&sr);
+ isc_region_consume(&sr, 2);
+ snprintf(buf, sizeof(buf), "%u ", n);
+ RETERR(str_totext(buf, target));
+
+ /*
+ * Signature Size.
+ */
+ n = uint16_fromregion(&sr);
+ isc_region_consume(&sr, 2);
+ snprintf(buf, sizeof(buf), "%u", n);
+ RETERR(str_totext(buf, target));
+
+ /*
+ * Signature.
+ */
+ if (n != 0U) {
+ REQUIRE(n <= sr.length);
+ sigr = sr;
+ sigr.length = n;
+ if ((tctx->flags & DNS_STYLEFLAG_MULTILINE) != 0) {
+ RETERR(str_totext(" (", target));
+ }
+ RETERR(str_totext(tctx->linebreak, target));
+ if (tctx->width == 0) { /* No splitting */
+ RETERR(isc_base64_totext(&sigr, 60, "", target));
+ } else {
+ RETERR(isc_base64_totext(&sigr, tctx->width - 2,
+ tctx->linebreak, target));
+ }
+ if ((tctx->flags & DNS_STYLEFLAG_MULTILINE) != 0) {
+ RETERR(str_totext(" ) ", target));
+ } else {
+ RETERR(str_totext(" ", target));
+ }
+ isc_region_consume(&sr, n);
+ } else {
+ RETERR(str_totext(" ", target));
+ }
+
+ /*
+ * Original ID.
+ */
+ n = uint16_fromregion(&sr);
+ isc_region_consume(&sr, 2);
+ snprintf(buf, sizeof(buf), "%u ", n);
+ RETERR(str_totext(buf, target));
+
+ /*
+ * Error.
+ */
+ n = uint16_fromregion(&sr);
+ isc_region_consume(&sr, 2);
+ RETERR(dns_tsigrcode_totext((dns_rcode_t)n, target));
+
+ /*
+ * Other Size.
+ */
+ n = uint16_fromregion(&sr);
+ isc_region_consume(&sr, 2);
+ snprintf(buf, sizeof(buf), " %u ", n);
+ RETERR(str_totext(buf, target));
+
+ /*
+ * Other.
+ */
+ if (tctx->width == 0) { /* No splitting */
+ return (isc_base64_totext(&sr, 60, "", target));
+ } else {
+ return (isc_base64_totext(&sr, 60, " ", target));
+ }
+}
+
+static isc_result_t
+fromwire_any_tsig(ARGS_FROMWIRE) {
+ isc_region_t sr;
+ dns_name_t name;
+ unsigned long n;
+
+ REQUIRE(type == dns_rdatatype_tsig);
+ REQUIRE(rdclass == dns_rdataclass_any);
+
+ UNUSED(type);
+ UNUSED(rdclass);
+
+ dns_decompress_setmethods(dctx, DNS_COMPRESS_NONE);
+
+ /*
+ * Algorithm Name.
+ */
+ dns_name_init(&name, NULL);
+ RETERR(dns_name_fromwire(&name, source, dctx, options, target));
+
+ isc_buffer_activeregion(source, &sr);
+ /*
+ * Time Signed + Fudge.
+ */
+ if (sr.length < 8) {
+ return (ISC_R_UNEXPECTEDEND);
+ }
+ RETERR(mem_tobuffer(target, sr.base, 8));
+ isc_region_consume(&sr, 8);
+ isc_buffer_forward(source, 8);
+
+ /*
+ * Signature Length + Signature.
+ */
+ if (sr.length < 2) {
+ return (ISC_R_UNEXPECTEDEND);
+ }
+ n = uint16_fromregion(&sr);
+ if (sr.length < n + 2) {
+ return (ISC_R_UNEXPECTEDEND);
+ }
+ RETERR(mem_tobuffer(target, sr.base, n + 2));
+ isc_region_consume(&sr, n + 2);
+ isc_buffer_forward(source, n + 2);
+
+ /*
+ * Original ID + Error.
+ */
+ if (sr.length < 4) {
+ return (ISC_R_UNEXPECTEDEND);
+ }
+ RETERR(mem_tobuffer(target, sr.base, 4));
+ isc_region_consume(&sr, 4);
+ isc_buffer_forward(source, 4);
+
+ /*
+ * Other Length + Other.
+ */
+ if (sr.length < 2) {
+ return (ISC_R_UNEXPECTEDEND);
+ }
+ n = uint16_fromregion(&sr);
+ if (sr.length < n + 2) {
+ return (ISC_R_UNEXPECTEDEND);
+ }
+ isc_buffer_forward(source, n + 2);
+ return (mem_tobuffer(target, sr.base, n + 2));
+}
+
+static isc_result_t
+towire_any_tsig(ARGS_TOWIRE) {
+ isc_region_t sr;
+ dns_name_t name;
+ dns_offsets_t offsets;
+
+ REQUIRE(rdata->type == dns_rdatatype_tsig);
+ REQUIRE(rdata->rdclass == dns_rdataclass_any);
+ REQUIRE(rdata->length != 0);
+
+ dns_compress_setmethods(cctx, DNS_COMPRESS_NONE);
+ dns_rdata_toregion(rdata, &sr);
+ dns_name_init(&name, offsets);
+ dns_name_fromregion(&name, &sr);
+ RETERR(dns_name_towire(&name, cctx, target));
+ isc_region_consume(&sr, name_length(&name));
+ return (mem_tobuffer(target, sr.base, sr.length));
+}
+
+static int
+compare_any_tsig(ARGS_COMPARE) {
+ isc_region_t r1;
+ isc_region_t r2;
+ dns_name_t name1;
+ dns_name_t name2;
+ int order;
+
+ REQUIRE(rdata1->type == rdata2->type);
+ REQUIRE(rdata1->rdclass == rdata2->rdclass);
+ REQUIRE(rdata1->type == dns_rdatatype_tsig);
+ REQUIRE(rdata1->rdclass == dns_rdataclass_any);
+ REQUIRE(rdata1->length != 0);
+ REQUIRE(rdata2->length != 0);
+
+ dns_rdata_toregion(rdata1, &r1);
+ dns_rdata_toregion(rdata2, &r2);
+ dns_name_init(&name1, NULL);
+ dns_name_init(&name2, NULL);
+ dns_name_fromregion(&name1, &r1);
+ dns_name_fromregion(&name2, &r2);
+ order = dns_name_rdatacompare(&name1, &name2);
+ if (order != 0) {
+ return (order);
+ }
+ isc_region_consume(&r1, name_length(&name1));
+ isc_region_consume(&r2, name_length(&name2));
+ return (isc_region_compare(&r1, &r2));
+}
+
+static isc_result_t
+fromstruct_any_tsig(ARGS_FROMSTRUCT) {
+ dns_rdata_any_tsig_t *tsig = source;
+ isc_region_t tr;
+
+ REQUIRE(type == dns_rdatatype_tsig);
+ REQUIRE(rdclass == dns_rdataclass_any);
+ REQUIRE(tsig != NULL);
+ REQUIRE(tsig->common.rdclass == rdclass);
+ REQUIRE(tsig->common.rdtype == type);
+
+ UNUSED(type);
+ UNUSED(rdclass);
+
+ /*
+ * Algorithm Name.
+ */
+ RETERR(name_tobuffer(&tsig->algorithm, target));
+
+ isc_buffer_availableregion(target, &tr);
+ if (tr.length < 6 + 2 + 2) {
+ return (ISC_R_NOSPACE);
+ }
+
+ /*
+ * Time Signed: 48 bits.
+ */
+ RETERR(uint16_tobuffer((uint16_t)(tsig->timesigned >> 32), target));
+ RETERR(uint32_tobuffer((uint32_t)(tsig->timesigned & 0xffffffffU),
+ target));
+
+ /*
+ * Fudge.
+ */
+ RETERR(uint16_tobuffer(tsig->fudge, target));
+
+ /*
+ * Signature Size.
+ */
+ RETERR(uint16_tobuffer(tsig->siglen, target));
+
+ /*
+ * Signature.
+ */
+ RETERR(mem_tobuffer(target, tsig->signature, tsig->siglen));
+
+ isc_buffer_availableregion(target, &tr);
+ if (tr.length < 2 + 2 + 2) {
+ return (ISC_R_NOSPACE);
+ }
+
+ /*
+ * Original ID.
+ */
+ RETERR(uint16_tobuffer(tsig->originalid, target));
+
+ /*
+ * Error.
+ */
+ RETERR(uint16_tobuffer(tsig->error, target));
+
+ /*
+ * Other Len.
+ */
+ RETERR(uint16_tobuffer(tsig->otherlen, target));
+
+ /*
+ * Other Data.
+ */
+ return (mem_tobuffer(target, tsig->other, tsig->otherlen));
+}
+
+static isc_result_t
+tostruct_any_tsig(ARGS_TOSTRUCT) {
+ dns_rdata_any_tsig_t *tsig;
+ dns_name_t alg;
+ isc_region_t sr;
+
+ REQUIRE(rdata->type == dns_rdatatype_tsig);
+ REQUIRE(rdata->rdclass == dns_rdataclass_any);
+ REQUIRE(rdata->length != 0);
+
+ tsig = (dns_rdata_any_tsig_t *)target;
+ tsig->common.rdclass = rdata->rdclass;
+ tsig->common.rdtype = rdata->type;
+ ISC_LINK_INIT(&tsig->common, link);
+
+ dns_rdata_toregion(rdata, &sr);
+
+ /*
+ * Algorithm Name.
+ */
+ dns_name_init(&alg, NULL);
+ dns_name_fromregion(&alg, &sr);
+ dns_name_init(&tsig->algorithm, NULL);
+ name_duporclone(&alg, mctx, &tsig->algorithm);
+
+ isc_region_consume(&sr, name_length(&tsig->algorithm));
+
+ /*
+ * Time Signed.
+ */
+ INSIST(sr.length >= 6);
+ tsig->timesigned = ((uint64_t)sr.base[0] << 40) |
+ ((uint64_t)sr.base[1] << 32) |
+ ((uint64_t)sr.base[2] << 24) |
+ ((uint64_t)sr.base[3] << 16) |
+ ((uint64_t)sr.base[4] << 8) | (uint64_t)sr.base[5];
+ isc_region_consume(&sr, 6);
+
+ /*
+ * Fudge.
+ */
+ tsig->fudge = uint16_fromregion(&sr);
+ isc_region_consume(&sr, 2);
+
+ /*
+ * Signature Size.
+ */
+ tsig->siglen = uint16_fromregion(&sr);
+ isc_region_consume(&sr, 2);
+
+ /*
+ * Signature.
+ */
+ INSIST(sr.length >= tsig->siglen);
+ tsig->signature = mem_maybedup(mctx, sr.base, tsig->siglen);
+ if (tsig->signature == NULL) {
+ goto cleanup;
+ }
+ isc_region_consume(&sr, tsig->siglen);
+
+ /*
+ * Original ID.
+ */
+ tsig->originalid = uint16_fromregion(&sr);
+ isc_region_consume(&sr, 2);
+
+ /*
+ * Error.
+ */
+ tsig->error = uint16_fromregion(&sr);
+ isc_region_consume(&sr, 2);
+
+ /*
+ * Other Size.
+ */
+ tsig->otherlen = uint16_fromregion(&sr);
+ isc_region_consume(&sr, 2);
+
+ /*
+ * Other.
+ */
+ INSIST(sr.length == tsig->otherlen);
+ tsig->other = mem_maybedup(mctx, sr.base, tsig->otherlen);
+ if (tsig->other == NULL) {
+ goto cleanup;
+ }
+
+ tsig->mctx = mctx;
+ return (ISC_R_SUCCESS);
+
+cleanup:
+ if (mctx != NULL) {
+ dns_name_free(&tsig->algorithm, tsig->mctx);
+ }
+ if (mctx != NULL && tsig->signature != NULL) {
+ isc_mem_free(mctx, tsig->signature);
+ }
+ return (ISC_R_NOMEMORY);
+}
+
+static void
+freestruct_any_tsig(ARGS_FREESTRUCT) {
+ dns_rdata_any_tsig_t *tsig = (dns_rdata_any_tsig_t *)source;
+
+ REQUIRE(tsig != NULL);
+ REQUIRE(tsig->common.rdtype == dns_rdatatype_tsig);
+ REQUIRE(tsig->common.rdclass == dns_rdataclass_any);
+
+ if (tsig->mctx == NULL) {
+ return;
+ }
+
+ dns_name_free(&tsig->algorithm, tsig->mctx);
+ if (tsig->signature != NULL) {
+ isc_mem_free(tsig->mctx, tsig->signature);
+ }
+ if (tsig->other != NULL) {
+ isc_mem_free(tsig->mctx, tsig->other);
+ }
+ tsig->mctx = NULL;
+}
+
+static isc_result_t
+additionaldata_any_tsig(ARGS_ADDLDATA) {
+ REQUIRE(rdata->type == dns_rdatatype_tsig);
+ REQUIRE(rdata->rdclass == dns_rdataclass_any);
+
+ UNUSED(rdata);
+ UNUSED(owner);
+ UNUSED(add);
+ UNUSED(arg);
+
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+digest_any_tsig(ARGS_DIGEST) {
+ REQUIRE(rdata->type == dns_rdatatype_tsig);
+ REQUIRE(rdata->rdclass == dns_rdataclass_any);
+
+ UNUSED(rdata);
+ UNUSED(digest);
+ UNUSED(arg);
+
+ return (ISC_R_NOTIMPLEMENTED);
+}
+
+static bool
+checkowner_any_tsig(ARGS_CHECKOWNER) {
+ REQUIRE(type == dns_rdatatype_tsig);
+ REQUIRE(rdclass == dns_rdataclass_any);
+
+ UNUSED(name);
+ UNUSED(type);
+ UNUSED(rdclass);
+ UNUSED(wildcard);
+
+ return (true);
+}
+
+static bool
+checknames_any_tsig(ARGS_CHECKNAMES) {
+ REQUIRE(rdata->type == dns_rdatatype_tsig);
+ REQUIRE(rdata->rdclass == dns_rdataclass_any);
+
+ UNUSED(rdata);
+ UNUSED(owner);
+ UNUSED(bad);
+
+ return (true);
+}
+
+static int
+casecompare_any_tsig(ARGS_COMPARE) {
+ return (compare_any_tsig(rdata1, rdata2));
+}
+
+#endif /* RDATA_ANY_255_TSIG_250_C */
diff --git a/lib/dns/rdata/any_255/tsig_250.h b/lib/dns/rdata/any_255/tsig_250.h
new file mode 100644
index 0000000..ca7806e
--- /dev/null
+++ b/lib/dns/rdata/any_255/tsig_250.h
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#pragma once
+
+/*% RFC2845 */
+typedef struct dns_rdata_any_tsig {
+ dns_rdatacommon_t common;
+ isc_mem_t *mctx;
+ dns_name_t algorithm;
+ uint64_t timesigned;
+ uint16_t fudge;
+ uint16_t siglen;
+ unsigned char *signature;
+ uint16_t originalid;
+ uint16_t error;
+ uint16_t otherlen;
+ unsigned char *other;
+} dns_rdata_any_tsig_t;
diff --git a/lib/dns/rdata/ch_3/a_1.c b/lib/dns/rdata/ch_3/a_1.c
new file mode 100644
index 0000000..a3303a8
--- /dev/null
+++ b/lib/dns/rdata/ch_3/a_1.c
@@ -0,0 +1,326 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/* by Bjorn.Victor@it.uu.se, 2005-05-07 */
+/* Based on generic/soa_6.c and generic/mx_15.c */
+
+#ifndef RDATA_CH_3_A_1_C
+#define RDATA_CH_3_A_1_C
+
+#include <isc/net.h>
+
+#define RRTYPE_A_ATTRIBUTES (0)
+
+static isc_result_t
+fromtext_ch_a(ARGS_FROMTEXT) {
+ isc_token_t token;
+ dns_name_t name;
+ isc_buffer_t buffer;
+
+ REQUIRE(type == dns_rdatatype_a);
+ REQUIRE(rdclass == dns_rdataclass_ch); /* 3 */
+
+ UNUSED(type);
+ UNUSED(callbacks);
+
+ RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_string,
+ false));
+
+ /* get domain name */
+ dns_name_init(&name, NULL);
+ buffer_fromregion(&buffer, &token.value.as_region);
+ if (origin == NULL) {
+ origin = dns_rootname;
+ }
+ RETTOK(dns_name_fromtext(&name, &buffer, origin, options, target));
+ if ((options & DNS_RDATA_CHECKNAMES) != 0 &&
+ (options & DNS_RDATA_CHECKREVERSE) != 0)
+ {
+ bool ok;
+ ok = dns_name_ishostname(&name, false);
+ if (!ok && (options & DNS_RDATA_CHECKNAMESFAIL) != 0) {
+ RETTOK(DNS_R_BADNAME);
+ }
+ if (!ok && callbacks != NULL) {
+ warn_badname(&name, lexer, callbacks);
+ }
+ }
+
+ /* 16-bit octal address */
+ RETERR(isc_lex_getoctaltoken(lexer, &token, false));
+ if (token.value.as_ulong > 0xffffU) {
+ RETTOK(ISC_R_RANGE);
+ }
+ return (uint16_tobuffer(token.value.as_ulong, target));
+}
+
+static isc_result_t
+totext_ch_a(ARGS_TOTEXT) {
+ isc_region_t region;
+ dns_name_t name;
+ dns_name_t prefix;
+ bool sub;
+ char buf[sizeof("0177777")];
+ uint16_t addr;
+
+ REQUIRE(rdata->type == dns_rdatatype_a);
+ REQUIRE(rdata->rdclass == dns_rdataclass_ch); /* 3 */
+ REQUIRE(rdata->length != 0);
+
+ dns_name_init(&name, NULL);
+ dns_name_init(&prefix, NULL);
+
+ dns_rdata_toregion(rdata, &region);
+ dns_name_fromregion(&name, &region);
+ isc_region_consume(&region, name_length(&name));
+ addr = uint16_fromregion(&region);
+
+ sub = name_prefix(&name, tctx->origin, &prefix);
+ RETERR(dns_name_totext(&prefix, sub, target));
+
+ snprintf(buf, sizeof(buf), "%o", addr); /* note octal */
+ RETERR(str_totext(" ", target));
+ return (str_totext(buf, target));
+}
+
+static isc_result_t
+fromwire_ch_a(ARGS_FROMWIRE) {
+ isc_region_t sregion;
+ isc_region_t tregion;
+ dns_name_t name;
+
+ REQUIRE(type == dns_rdatatype_a);
+ REQUIRE(rdclass == dns_rdataclass_ch);
+
+ UNUSED(type);
+ UNUSED(rdclass);
+
+ dns_decompress_setmethods(dctx, DNS_COMPRESS_GLOBAL14);
+
+ dns_name_init(&name, NULL);
+
+ RETERR(dns_name_fromwire(&name, source, dctx, options, target));
+
+ isc_buffer_activeregion(source, &sregion);
+ isc_buffer_availableregion(target, &tregion);
+ if (sregion.length < 2) {
+ return (ISC_R_UNEXPECTEDEND);
+ }
+ if (tregion.length < 2) {
+ return (ISC_R_NOSPACE);
+ }
+
+ memmove(tregion.base, sregion.base, 2);
+ isc_buffer_forward(source, 2);
+ isc_buffer_add(target, 2);
+
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+towire_ch_a(ARGS_TOWIRE) {
+ dns_name_t name;
+ dns_offsets_t offsets;
+ isc_region_t sregion;
+ isc_region_t tregion;
+
+ REQUIRE(rdata->type == dns_rdatatype_a);
+ REQUIRE(rdata->rdclass == dns_rdataclass_ch);
+ REQUIRE(rdata->length != 0);
+
+ dns_compress_setmethods(cctx, DNS_COMPRESS_GLOBAL14);
+
+ dns_name_init(&name, offsets);
+
+ dns_rdata_toregion(rdata, &sregion);
+
+ dns_name_fromregion(&name, &sregion);
+ isc_region_consume(&sregion, name_length(&name));
+ RETERR(dns_name_towire(&name, cctx, target));
+
+ isc_buffer_availableregion(target, &tregion);
+ if (tregion.length < 2) {
+ return (ISC_R_NOSPACE);
+ }
+
+ memmove(tregion.base, sregion.base, 2);
+ isc_buffer_add(target, 2);
+ return (ISC_R_SUCCESS);
+}
+
+static int
+compare_ch_a(ARGS_COMPARE) {
+ dns_name_t name1;
+ dns_name_t name2;
+ isc_region_t region1;
+ isc_region_t region2;
+ int order;
+
+ REQUIRE(rdata1->type == rdata2->type);
+ REQUIRE(rdata1->rdclass == rdata2->rdclass);
+ REQUIRE(rdata1->type == dns_rdatatype_a);
+ REQUIRE(rdata1->rdclass == dns_rdataclass_ch);
+ REQUIRE(rdata1->length != 0);
+ REQUIRE(rdata2->length != 0);
+
+ dns_name_init(&name1, NULL);
+ dns_name_init(&name2, NULL);
+
+ dns_rdata_toregion(rdata1, &region1);
+ dns_rdata_toregion(rdata2, &region2);
+
+ dns_name_fromregion(&name1, &region1);
+ dns_name_fromregion(&name2, &region2);
+ isc_region_consume(&region1, name_length(&name1));
+ isc_region_consume(&region2, name_length(&name2));
+
+ order = dns_name_rdatacompare(&name1, &name2);
+ if (order != 0) {
+ return (order);
+ }
+
+ order = memcmp(region1.base, region2.base, 2);
+ if (order != 0) {
+ order = (order < 0) ? -1 : 1;
+ }
+ return (order);
+}
+
+static isc_result_t
+fromstruct_ch_a(ARGS_FROMSTRUCT) {
+ dns_rdata_ch_a_t *a = source;
+ isc_region_t region;
+
+ REQUIRE(type == dns_rdatatype_a);
+ REQUIRE(a != NULL);
+ REQUIRE(a->common.rdtype == type);
+ REQUIRE(a->common.rdclass == rdclass);
+
+ UNUSED(type);
+ UNUSED(rdclass);
+
+ dns_name_toregion(&a->ch_addr_dom, &region);
+ RETERR(isc_buffer_copyregion(target, &region));
+
+ return (uint16_tobuffer(ntohs(a->ch_addr), target));
+}
+
+static isc_result_t
+tostruct_ch_a(ARGS_TOSTRUCT) {
+ dns_rdata_ch_a_t *a = target;
+ isc_region_t region;
+ dns_name_t name;
+
+ REQUIRE(rdata->type == dns_rdatatype_a);
+ REQUIRE(rdata->rdclass == dns_rdataclass_ch);
+ REQUIRE(rdata->length != 0);
+
+ a->common.rdclass = rdata->rdclass;
+ a->common.rdtype = rdata->type;
+ ISC_LINK_INIT(&a->common, link);
+
+ dns_rdata_toregion(rdata, &region);
+
+ dns_name_init(&name, NULL);
+ dns_name_fromregion(&name, &region);
+ isc_region_consume(&region, name_length(&name));
+
+ dns_name_init(&a->ch_addr_dom, NULL);
+ name_duporclone(&name, mctx, &a->ch_addr_dom);
+ a->ch_addr = htons(uint16_fromregion(&region));
+ a->mctx = mctx;
+ return (ISC_R_SUCCESS);
+}
+
+static void
+freestruct_ch_a(ARGS_FREESTRUCT) {
+ dns_rdata_ch_a_t *a = source;
+
+ REQUIRE(a != NULL);
+ REQUIRE(a->common.rdtype == dns_rdatatype_a);
+
+ if (a->mctx == NULL) {
+ return;
+ }
+
+ dns_name_free(&a->ch_addr_dom, a->mctx);
+ a->mctx = NULL;
+}
+
+static isc_result_t
+additionaldata_ch_a(ARGS_ADDLDATA) {
+ REQUIRE(rdata->type == dns_rdatatype_a);
+ REQUIRE(rdata->rdclass == dns_rdataclass_ch);
+
+ UNUSED(rdata);
+ UNUSED(owner);
+ UNUSED(add);
+ UNUSED(arg);
+
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+digest_ch_a(ARGS_DIGEST) {
+ isc_region_t r;
+ dns_name_t name;
+
+ REQUIRE(rdata->type == dns_rdatatype_a);
+ REQUIRE(rdata->rdclass == dns_rdataclass_ch);
+
+ dns_rdata_toregion(rdata, &r);
+ dns_name_init(&name, NULL);
+ dns_name_fromregion(&name, &r);
+ isc_region_consume(&r, name_length(&name));
+ RETERR(dns_name_digest(&name, digest, arg));
+ return ((digest)(arg, &r));
+}
+
+static bool
+checkowner_ch_a(ARGS_CHECKOWNER) {
+ REQUIRE(type == dns_rdatatype_a);
+ REQUIRE(rdclass == dns_rdataclass_ch);
+
+ UNUSED(type);
+
+ return (dns_name_ishostname(name, wildcard));
+}
+
+static bool
+checknames_ch_a(ARGS_CHECKNAMES) {
+ isc_region_t region;
+ dns_name_t name;
+
+ REQUIRE(rdata->type == dns_rdatatype_a);
+ REQUIRE(rdata->rdclass == dns_rdataclass_ch);
+
+ UNUSED(owner);
+
+ dns_rdata_toregion(rdata, &region);
+ dns_name_init(&name, NULL);
+ dns_name_fromregion(&name, &region);
+ if (!dns_name_ishostname(&name, false)) {
+ if (bad != NULL) {
+ dns_name_clone(&name, bad);
+ }
+ return (false);
+ }
+
+ return (true);
+}
+
+static int
+casecompare_ch_a(ARGS_COMPARE) {
+ return (compare_ch_a(rdata1, rdata2));
+}
+#endif /* RDATA_CH_3_A_1_C */
diff --git a/lib/dns/rdata/ch_3/a_1.h b/lib/dns/rdata/ch_3/a_1.h
new file mode 100644
index 0000000..4ac6169
--- /dev/null
+++ b/lib/dns/rdata/ch_3/a_1.h
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/* by Bjorn.Victor@it.uu.se, 2005-05-07 */
+/* Based on generic/mx_15.h */
+
+#pragma once
+
+typedef uint16_t ch_addr_t;
+
+typedef struct dns_rdata_ch_a {
+ dns_rdatacommon_t common;
+ isc_mem_t *mctx;
+ dns_name_t ch_addr_dom; /* ch-addr domain for back mapping
+ * */
+ ch_addr_t ch_addr; /* chaos address (16 bit) network
+ * order */
+} dns_rdata_ch_a_t;
diff --git a/lib/dns/rdata/generic/afsdb_18.c b/lib/dns/rdata/generic/afsdb_18.c
new file mode 100644
index 0000000..077e0ac
--- /dev/null
+++ b/lib/dns/rdata/generic/afsdb_18.c
@@ -0,0 +1,318 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/* RFC1183 */
+
+#ifndef RDATA_GENERIC_AFSDB_18_C
+#define RDATA_GENERIC_AFSDB_18_C
+
+#define RRTYPE_AFSDB_ATTRIBUTES (0)
+
+static isc_result_t
+fromtext_afsdb(ARGS_FROMTEXT) {
+ isc_token_t token;
+ isc_buffer_t buffer;
+ dns_name_t name;
+ bool ok;
+
+ REQUIRE(type == dns_rdatatype_afsdb);
+
+ UNUSED(type);
+ UNUSED(rdclass);
+ UNUSED(callbacks);
+
+ /*
+ * Subtype.
+ */
+ RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_number,
+ false));
+ if (token.value.as_ulong > 0xffffU) {
+ RETTOK(ISC_R_RANGE);
+ }
+ RETERR(uint16_tobuffer(token.value.as_ulong, target));
+
+ /*
+ * Hostname.
+ */
+ RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_string,
+ false));
+ dns_name_init(&name, NULL);
+ buffer_fromregion(&buffer, &token.value.as_region);
+ if (origin == NULL) {
+ origin = dns_rootname;
+ }
+ RETTOK(dns_name_fromtext(&name, &buffer, origin, options, target));
+ ok = true;
+ if ((options & DNS_RDATA_CHECKNAMES) != 0) {
+ ok = dns_name_ishostname(&name, false);
+ }
+ if (!ok && (options & DNS_RDATA_CHECKNAMESFAIL) != 0) {
+ RETTOK(DNS_R_BADNAME);
+ }
+ if (!ok && callbacks != NULL) {
+ warn_badname(&name, lexer, callbacks);
+ }
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+totext_afsdb(ARGS_TOTEXT) {
+ dns_name_t name;
+ dns_name_t prefix;
+ isc_region_t region;
+ char buf[sizeof("64000 ")];
+ bool sub;
+ unsigned int num;
+
+ REQUIRE(rdata->type == dns_rdatatype_afsdb);
+ REQUIRE(rdata->length != 0);
+
+ dns_name_init(&name, NULL);
+ dns_name_init(&prefix, NULL);
+
+ dns_rdata_toregion(rdata, &region);
+ num = uint16_fromregion(&region);
+ isc_region_consume(&region, 2);
+ snprintf(buf, sizeof(buf), "%u ", num);
+ RETERR(str_totext(buf, target));
+ dns_name_fromregion(&name, &region);
+ sub = name_prefix(&name, tctx->origin, &prefix);
+ return (dns_name_totext(&prefix, sub, target));
+}
+
+static isc_result_t
+fromwire_afsdb(ARGS_FROMWIRE) {
+ dns_name_t name;
+ isc_region_t sr;
+ isc_region_t tr;
+
+ REQUIRE(type == dns_rdatatype_afsdb);
+
+ UNUSED(type);
+ UNUSED(rdclass);
+
+ dns_decompress_setmethods(dctx, DNS_COMPRESS_NONE);
+
+ dns_name_init(&name, NULL);
+
+ isc_buffer_activeregion(source, &sr);
+ isc_buffer_availableregion(target, &tr);
+ if (tr.length < 2) {
+ return (ISC_R_NOSPACE);
+ }
+ if (sr.length < 2) {
+ return (ISC_R_UNEXPECTEDEND);
+ }
+ memmove(tr.base, sr.base, 2);
+ isc_buffer_forward(source, 2);
+ isc_buffer_add(target, 2);
+ return (dns_name_fromwire(&name, source, dctx, options, target));
+}
+
+static isc_result_t
+towire_afsdb(ARGS_TOWIRE) {
+ isc_region_t tr;
+ isc_region_t sr;
+ dns_name_t name;
+ dns_offsets_t offsets;
+
+ REQUIRE(rdata->type == dns_rdatatype_afsdb);
+ REQUIRE(rdata->length != 0);
+
+ dns_compress_setmethods(cctx, DNS_COMPRESS_NONE);
+ isc_buffer_availableregion(target, &tr);
+ dns_rdata_toregion(rdata, &sr);
+ if (tr.length < 2) {
+ return (ISC_R_NOSPACE);
+ }
+ memmove(tr.base, sr.base, 2);
+ isc_region_consume(&sr, 2);
+ isc_buffer_add(target, 2);
+
+ dns_name_init(&name, offsets);
+ dns_name_fromregion(&name, &sr);
+
+ return (dns_name_towire(&name, cctx, target));
+}
+
+static int
+compare_afsdb(ARGS_COMPARE) {
+ int result;
+ dns_name_t name1;
+ dns_name_t name2;
+ isc_region_t region1;
+ isc_region_t region2;
+
+ REQUIRE(rdata1->type == rdata2->type);
+ REQUIRE(rdata1->rdclass == rdata2->rdclass);
+ REQUIRE(rdata1->type == dns_rdatatype_afsdb);
+ REQUIRE(rdata1->length != 0);
+ REQUIRE(rdata2->length != 0);
+
+ result = memcmp(rdata1->data, rdata2->data, 2);
+ if (result != 0) {
+ return (result < 0 ? -1 : 1);
+ }
+
+ dns_name_init(&name1, NULL);
+ dns_name_init(&name2, NULL);
+
+ dns_rdata_toregion(rdata1, &region1);
+ dns_rdata_toregion(rdata2, &region2);
+
+ isc_region_consume(&region1, 2);
+ isc_region_consume(&region2, 2);
+
+ dns_name_fromregion(&name1, &region1);
+ dns_name_fromregion(&name2, &region2);
+
+ return (dns_name_rdatacompare(&name1, &name2));
+}
+
+static isc_result_t
+fromstruct_afsdb(ARGS_FROMSTRUCT) {
+ dns_rdata_afsdb_t *afsdb = source;
+ isc_region_t region;
+
+ REQUIRE(type == dns_rdatatype_afsdb);
+ REQUIRE(afsdb != NULL);
+ REQUIRE(afsdb->common.rdclass == rdclass);
+ REQUIRE(afsdb->common.rdtype == type);
+
+ UNUSED(type);
+ UNUSED(rdclass);
+
+ RETERR(uint16_tobuffer(afsdb->subtype, target));
+ dns_name_toregion(&afsdb->server, &region);
+ return (isc_buffer_copyregion(target, &region));
+}
+
+static isc_result_t
+tostruct_afsdb(ARGS_TOSTRUCT) {
+ isc_region_t region;
+ dns_rdata_afsdb_t *afsdb = target;
+ dns_name_t name;
+
+ REQUIRE(rdata->type == dns_rdatatype_afsdb);
+ REQUIRE(afsdb != NULL);
+ REQUIRE(rdata->length != 0);
+
+ afsdb->common.rdclass = rdata->rdclass;
+ afsdb->common.rdtype = rdata->type;
+ ISC_LINK_INIT(&afsdb->common, link);
+
+ dns_name_init(&afsdb->server, NULL);
+
+ dns_rdata_toregion(rdata, &region);
+
+ afsdb->subtype = uint16_fromregion(&region);
+ isc_region_consume(&region, 2);
+
+ dns_name_init(&name, NULL);
+ dns_name_fromregion(&name, &region);
+
+ name_duporclone(&name, mctx, &afsdb->server);
+ afsdb->mctx = mctx;
+ return (ISC_R_SUCCESS);
+}
+
+static void
+freestruct_afsdb(ARGS_FREESTRUCT) {
+ dns_rdata_afsdb_t *afsdb = source;
+
+ REQUIRE(afsdb != NULL);
+ REQUIRE(afsdb->common.rdtype == dns_rdatatype_afsdb);
+
+ if (afsdb->mctx == NULL) {
+ return;
+ }
+
+ dns_name_free(&afsdb->server, afsdb->mctx);
+ afsdb->mctx = NULL;
+}
+
+static isc_result_t
+additionaldata_afsdb(ARGS_ADDLDATA) {
+ dns_name_t name;
+ dns_offsets_t offsets;
+ isc_region_t region;
+
+ REQUIRE(rdata->type == dns_rdatatype_afsdb);
+
+ UNUSED(owner);
+
+ dns_name_init(&name, offsets);
+ dns_rdata_toregion(rdata, &region);
+ isc_region_consume(&region, 2);
+ dns_name_fromregion(&name, &region);
+
+ return ((add)(arg, &name, dns_rdatatype_a, NULL));
+}
+
+static isc_result_t
+digest_afsdb(ARGS_DIGEST) {
+ isc_region_t r1, r2;
+ dns_name_t name;
+
+ REQUIRE(rdata->type == dns_rdatatype_afsdb);
+
+ dns_rdata_toregion(rdata, &r1);
+ r2 = r1;
+ isc_region_consume(&r2, 2);
+ r1.length = 2;
+ RETERR((digest)(arg, &r1));
+ dns_name_init(&name, NULL);
+ dns_name_fromregion(&name, &r2);
+
+ return (dns_name_digest(&name, digest, arg));
+}
+
+static bool
+checkowner_afsdb(ARGS_CHECKOWNER) {
+ REQUIRE(type == dns_rdatatype_afsdb);
+
+ UNUSED(name);
+ UNUSED(type);
+ UNUSED(rdclass);
+ UNUSED(wildcard);
+
+ return (true);
+}
+
+static bool
+checknames_afsdb(ARGS_CHECKNAMES) {
+ isc_region_t region;
+ dns_name_t name;
+
+ REQUIRE(rdata->type == dns_rdatatype_afsdb);
+
+ UNUSED(owner);
+
+ dns_rdata_toregion(rdata, &region);
+ isc_region_consume(&region, 2);
+ dns_name_init(&name, NULL);
+ dns_name_fromregion(&name, &region);
+ if (!dns_name_ishostname(&name, false)) {
+ if (bad != NULL) {
+ dns_name_clone(&name, bad);
+ }
+ return (false);
+ }
+ return (true);
+}
+
+static int
+casecompare_afsdb(ARGS_COMPARE) {
+ return (compare_afsdb(rdata1, rdata2));
+}
+#endif /* RDATA_GENERIC_AFSDB_18_C */
diff --git a/lib/dns/rdata/generic/afsdb_18.h b/lib/dns/rdata/generic/afsdb_18.h
new file mode 100644
index 0000000..16b390c
--- /dev/null
+++ b/lib/dns/rdata/generic/afsdb_18.h
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#pragma once
+
+/*!
+ * \brief Per RFC1183 */
+
+typedef struct dns_rdata_afsdb {
+ dns_rdatacommon_t common;
+ isc_mem_t *mctx;
+ uint16_t subtype;
+ dns_name_t server;
+} dns_rdata_afsdb_t;
diff --git a/lib/dns/rdata/generic/amtrelay_260.c b/lib/dns/rdata/generic/amtrelay_260.c
new file mode 100644
index 0000000..a6d30e1
--- /dev/null
+++ b/lib/dns/rdata/generic/amtrelay_260.c
@@ -0,0 +1,472 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#ifndef RDATA_GENERIC_AMTRELAY_260_C
+#define RDATA_GENERIC_AMTRELAY_260_C
+
+#include <string.h>
+
+#include <isc/net.h>
+
+#define RRTYPE_AMTRELAY_ATTRIBUTES (0)
+
+static isc_result_t
+fromtext_amtrelay(ARGS_FROMTEXT) {
+ isc_token_t token;
+ dns_name_t name;
+ isc_buffer_t buffer;
+ unsigned int discovery;
+ unsigned int gateway;
+ struct in_addr addr;
+ unsigned char addr6[16];
+ isc_region_t region;
+
+ REQUIRE(type == dns_rdatatype_amtrelay);
+
+ UNUSED(type);
+ UNUSED(rdclass);
+ UNUSED(callbacks);
+
+ /*
+ * Precedence.
+ */
+ RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_number,
+ false));
+ if (token.value.as_ulong > 0xffU) {
+ RETTOK(ISC_R_RANGE);
+ }
+ RETERR(uint8_tobuffer(token.value.as_ulong, target));
+
+ /*
+ * Discovery.
+ */
+ RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_number,
+ false));
+ if (token.value.as_ulong > 1U) {
+ RETTOK(ISC_R_RANGE);
+ }
+ discovery = token.value.as_ulong;
+
+ /*
+ * Gateway type.
+ */
+ RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_number,
+ false));
+ if (token.value.as_ulong > 0x7fU) {
+ RETTOK(ISC_R_RANGE);
+ }
+ RETERR(uint8_tobuffer(token.value.as_ulong | (discovery << 7), target));
+ gateway = token.value.as_ulong;
+
+ if (gateway == 0) {
+ return (ISC_R_SUCCESS);
+ }
+
+ if (gateway > 3) {
+ return (ISC_R_NOTIMPLEMENTED);
+ }
+
+ /*
+ * Gateway.
+ */
+ RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_string,
+ false));
+
+ switch (gateway) {
+ case 1:
+ if (inet_pton(AF_INET, DNS_AS_STR(token), &addr) != 1) {
+ RETTOK(DNS_R_BADDOTTEDQUAD);
+ }
+ isc_buffer_availableregion(target, &region);
+ if (region.length < 4) {
+ return (ISC_R_NOSPACE);
+ }
+ memmove(region.base, &addr, 4);
+ isc_buffer_add(target, 4);
+ return (ISC_R_SUCCESS);
+
+ case 2:
+ if (inet_pton(AF_INET6, DNS_AS_STR(token), addr6) != 1) {
+ RETTOK(DNS_R_BADAAAA);
+ }
+ isc_buffer_availableregion(target, &region);
+ if (region.length < 16) {
+ return (ISC_R_NOSPACE);
+ }
+ memmove(region.base, addr6, 16);
+ isc_buffer_add(target, 16);
+ return (ISC_R_SUCCESS);
+
+ case 3:
+ dns_name_init(&name, NULL);
+ buffer_fromregion(&buffer, &token.value.as_region);
+ if (origin == NULL) {
+ origin = dns_rootname;
+ }
+ return (dns_name_fromtext(&name, &buffer, origin, options,
+ target));
+ default:
+ UNREACHABLE();
+ }
+}
+
+static isc_result_t
+totext_amtrelay(ARGS_TOTEXT) {
+ isc_region_t region;
+ dns_name_t name;
+ char buf[sizeof("0 255 ")];
+ unsigned char precedence;
+ unsigned char discovery;
+ unsigned char gateway;
+ const char *space;
+
+ UNUSED(tctx);
+
+ REQUIRE(rdata->type == dns_rdatatype_amtrelay);
+ REQUIRE(rdata->length >= 2);
+
+ if ((rdata->data[1] & 0x7f) > 3U) {
+ return (ISC_R_NOTIMPLEMENTED);
+ }
+
+ /*
+ * Precedence.
+ */
+ dns_rdata_toregion(rdata, &region);
+ precedence = uint8_fromregion(&region);
+ isc_region_consume(&region, 1);
+ snprintf(buf, sizeof(buf), "%u ", precedence);
+ RETERR(str_totext(buf, target));
+
+ /*
+ * Discovery and Gateway type.
+ */
+ gateway = uint8_fromregion(&region);
+ discovery = gateway >> 7;
+ gateway &= 0x7f;
+ space = (gateway != 0U) ? " " : "";
+ isc_region_consume(&region, 1);
+ snprintf(buf, sizeof(buf), "%u %u%s", discovery, gateway, space);
+ RETERR(str_totext(buf, target));
+
+ /*
+ * Gateway.
+ */
+ switch (gateway) {
+ case 0:
+ break;
+ case 1:
+ return (inet_totext(AF_INET, tctx->flags, &region, target));
+
+ case 2:
+ return (inet_totext(AF_INET6, tctx->flags, &region, target));
+
+ case 3:
+ dns_name_init(&name, NULL);
+ dns_name_fromregion(&name, &region);
+ return (dns_name_totext(&name, false, target));
+
+ default:
+ UNREACHABLE();
+ }
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+fromwire_amtrelay(ARGS_FROMWIRE) {
+ dns_name_t name;
+ isc_region_t region;
+
+ REQUIRE(type == dns_rdatatype_amtrelay);
+
+ UNUSED(type);
+ UNUSED(rdclass);
+
+ dns_decompress_setmethods(dctx, DNS_COMPRESS_NONE);
+
+ isc_buffer_activeregion(source, &region);
+ if (region.length < 2) {
+ return (ISC_R_UNEXPECTEDEND);
+ }
+
+ switch (region.base[1] & 0x7f) {
+ case 0:
+ if (region.length != 2) {
+ return (DNS_R_FORMERR);
+ }
+ isc_buffer_forward(source, region.length);
+ return (mem_tobuffer(target, region.base, region.length));
+
+ case 1:
+ if (region.length != 6) {
+ return (DNS_R_FORMERR);
+ }
+ isc_buffer_forward(source, region.length);
+ return (mem_tobuffer(target, region.base, region.length));
+
+ case 2:
+ if (region.length != 18) {
+ return (DNS_R_FORMERR);
+ }
+ isc_buffer_forward(source, region.length);
+ return (mem_tobuffer(target, region.base, region.length));
+
+ case 3:
+ RETERR(mem_tobuffer(target, region.base, 2));
+ isc_buffer_forward(source, 2);
+ dns_name_init(&name, NULL);
+ return (dns_name_fromwire(&name, source, dctx, options,
+ target));
+
+ default:
+ isc_buffer_forward(source, region.length);
+ return (mem_tobuffer(target, region.base, region.length));
+ }
+}
+
+static isc_result_t
+towire_amtrelay(ARGS_TOWIRE) {
+ isc_region_t region;
+
+ REQUIRE(rdata->type == dns_rdatatype_amtrelay);
+ REQUIRE(rdata->length != 0);
+
+ UNUSED(cctx);
+
+ dns_rdata_toregion(rdata, &region);
+ return (mem_tobuffer(target, region.base, region.length));
+}
+
+static int
+compare_amtrelay(ARGS_COMPARE) {
+ isc_region_t region1;
+ isc_region_t region2;
+
+ REQUIRE(rdata1->type == rdata2->type);
+ REQUIRE(rdata1->rdclass == rdata2->rdclass);
+ REQUIRE(rdata1->type == dns_rdatatype_amtrelay);
+ REQUIRE(rdata1->length >= 2);
+ REQUIRE(rdata2->length >= 2);
+
+ dns_rdata_toregion(rdata1, &region1);
+ dns_rdata_toregion(rdata2, &region2);
+
+ return (isc_region_compare(&region1, &region2));
+}
+
+static isc_result_t
+fromstruct_amtrelay(ARGS_FROMSTRUCT) {
+ dns_rdata_amtrelay_t *amtrelay = source;
+ isc_region_t region;
+ uint32_t n;
+
+ REQUIRE(type == dns_rdatatype_amtrelay);
+ REQUIRE(amtrelay != NULL);
+ REQUIRE(amtrelay->common.rdtype == type);
+ REQUIRE(amtrelay->common.rdclass == rdclass);
+
+ UNUSED(type);
+ UNUSED(rdclass);
+
+ RETERR(uint8_tobuffer(amtrelay->precedence, target));
+ n = (amtrelay->discovery ? 0x80 : 0) | amtrelay->gateway_type;
+ RETERR(uint8_tobuffer(n, target));
+
+ switch (amtrelay->gateway_type) {
+ case 0:
+ return (ISC_R_SUCCESS);
+
+ case 1:
+ n = ntohl(amtrelay->in_addr.s_addr);
+ return (uint32_tobuffer(n, target));
+
+ case 2:
+ return (mem_tobuffer(target, amtrelay->in6_addr.s6_addr, 16));
+ break;
+
+ case 3:
+ dns_name_toregion(&amtrelay->gateway, &region);
+ return (isc_buffer_copyregion(target, &region));
+ break;
+
+ default:
+ return (mem_tobuffer(target, amtrelay->data, amtrelay->length));
+ }
+}
+
+static isc_result_t
+tostruct_amtrelay(ARGS_TOSTRUCT) {
+ isc_region_t region;
+ dns_rdata_amtrelay_t *amtrelay = target;
+ dns_name_t name;
+ uint32_t n;
+
+ REQUIRE(rdata->type == dns_rdatatype_amtrelay);
+ REQUIRE(amtrelay != NULL);
+ REQUIRE(rdata->length >= 2);
+
+ amtrelay->common.rdclass = rdata->rdclass;
+ amtrelay->common.rdtype = rdata->type;
+ ISC_LINK_INIT(&amtrelay->common, link);
+
+ dns_name_init(&amtrelay->gateway, NULL);
+ amtrelay->data = NULL;
+
+ dns_name_init(&name, NULL);
+ dns_rdata_toregion(rdata, &region);
+
+ amtrelay->precedence = uint8_fromregion(&region);
+ isc_region_consume(&region, 1);
+
+ amtrelay->gateway_type = uint8_fromregion(&region);
+ amtrelay->discovery = (amtrelay->gateway_type & 0x80) != 0;
+ amtrelay->gateway_type &= 0x7f;
+ isc_region_consume(&region, 1);
+
+ switch (amtrelay->gateway_type) {
+ case 0:
+ break;
+
+ case 1:
+ n = uint32_fromregion(&region);
+ amtrelay->in_addr.s_addr = htonl(n);
+ isc_region_consume(&region, 4);
+ break;
+
+ case 2:
+ memmove(amtrelay->in6_addr.s6_addr, region.base, 16);
+ isc_region_consume(&region, 16);
+ break;
+
+ case 3:
+ dns_name_fromregion(&name, &region);
+ name_duporclone(&name, mctx, &amtrelay->gateway);
+ isc_region_consume(&region, name_length(&name));
+ break;
+
+ default:
+ if (region.length != 0) {
+ amtrelay->data = mem_maybedup(mctx, region.base,
+ region.length);
+ if (amtrelay->data == NULL) {
+ return (ISC_R_NOMEMORY);
+ }
+ }
+ amtrelay->length = region.length;
+ }
+ amtrelay->mctx = mctx;
+ return (ISC_R_SUCCESS);
+}
+
+static void
+freestruct_amtrelay(ARGS_FREESTRUCT) {
+ dns_rdata_amtrelay_t *amtrelay = source;
+
+ REQUIRE(amtrelay != NULL);
+ REQUIRE(amtrelay->common.rdtype == dns_rdatatype_amtrelay);
+
+ if (amtrelay->mctx == NULL) {
+ return;
+ }
+
+ if (amtrelay->gateway_type == 3) {
+ dns_name_free(&amtrelay->gateway, amtrelay->mctx);
+ }
+
+ if (amtrelay->data != NULL) {
+ isc_mem_free(amtrelay->mctx, amtrelay->data);
+ }
+
+ amtrelay->mctx = NULL;
+}
+
+static isc_result_t
+additionaldata_amtrelay(ARGS_ADDLDATA) {
+ REQUIRE(rdata->type == dns_rdatatype_amtrelay);
+
+ UNUSED(rdata);
+ UNUSED(owner);
+ UNUSED(add);
+ UNUSED(arg);
+
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+digest_amtrelay(ARGS_DIGEST) {
+ isc_region_t region;
+
+ REQUIRE(rdata->type == dns_rdatatype_amtrelay);
+
+ dns_rdata_toregion(rdata, &region);
+ return ((digest)(arg, &region));
+}
+
+static bool
+checkowner_amtrelay(ARGS_CHECKOWNER) {
+ REQUIRE(type == dns_rdatatype_amtrelay);
+
+ UNUSED(name);
+ UNUSED(type);
+ UNUSED(rdclass);
+ UNUSED(wildcard);
+
+ return (true);
+}
+
+static bool
+checknames_amtrelay(ARGS_CHECKNAMES) {
+ REQUIRE(rdata->type == dns_rdatatype_amtrelay);
+
+ UNUSED(rdata);
+ UNUSED(owner);
+ UNUSED(bad);
+
+ return (true);
+}
+
+static int
+casecompare_amtrelay(ARGS_COMPARE) {
+ isc_region_t region1;
+ isc_region_t region2;
+ dns_name_t name1;
+ dns_name_t name2;
+
+ REQUIRE(rdata1->type == rdata2->type);
+ REQUIRE(rdata1->rdclass == rdata2->rdclass);
+ REQUIRE(rdata1->type == dns_rdatatype_amtrelay);
+ REQUIRE(rdata1->length >= 2);
+ REQUIRE(rdata2->length >= 2);
+
+ dns_rdata_toregion(rdata1, &region1);
+ dns_rdata_toregion(rdata2, &region2);
+
+ if (memcmp(region1.base, region2.base, 2) != 0 ||
+ (region1.base[1] & 0x7f) != 3)
+ {
+ return (isc_region_compare(&region1, &region2));
+ }
+
+ dns_name_init(&name1, NULL);
+ dns_name_init(&name2, NULL);
+
+ isc_region_consume(&region1, 2);
+ isc_region_consume(&region2, 2);
+
+ dns_name_fromregion(&name1, &region1);
+ dns_name_fromregion(&name2, &region2);
+
+ return (dns_name_rdatacompare(&name1, &name2));
+}
+
+#endif /* RDATA_GENERIC_AMTRELAY_260_C */
diff --git a/lib/dns/rdata/generic/amtrelay_260.h b/lib/dns/rdata/generic/amtrelay_260.h
new file mode 100644
index 0000000..8680c35
--- /dev/null
+++ b/lib/dns/rdata/generic/amtrelay_260.h
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#pragma once
+
+typedef struct dns_rdata_amtrelay {
+ dns_rdatacommon_t common;
+ isc_mem_t *mctx;
+ uint8_t precedence;
+ bool discovery;
+ uint8_t gateway_type;
+ struct in_addr in_addr; /* gateway type 1 */
+ struct in6_addr in6_addr; /* gateway type 2 */
+ dns_name_t gateway; /* gateway type 3 */
+ unsigned char *data; /* gateway type > 3 */
+ uint16_t length;
+} dns_rdata_amtrelay_t;
diff --git a/lib/dns/rdata/generic/avc_258.c b/lib/dns/rdata/generic/avc_258.c
new file mode 100644
index 0000000..8c7c8d0
--- /dev/null
+++ b/lib/dns/rdata/generic/avc_258.c
@@ -0,0 +1,145 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#ifndef RDATA_GENERIC_AVC_258_C
+#define RDATA_GENERIC_AVC_258_C
+
+#define RRTYPE_AVC_ATTRIBUTES (0)
+
+static isc_result_t
+fromtext_avc(ARGS_FROMTEXT) {
+ REQUIRE(type == dns_rdatatype_avc);
+
+ return (generic_fromtext_txt(CALL_FROMTEXT));
+}
+
+static isc_result_t
+totext_avc(ARGS_TOTEXT) {
+ REQUIRE(rdata != NULL);
+ REQUIRE(rdata->type == dns_rdatatype_avc);
+
+ return (generic_totext_txt(CALL_TOTEXT));
+}
+
+static isc_result_t
+fromwire_avc(ARGS_FROMWIRE) {
+ REQUIRE(type == dns_rdatatype_avc);
+
+ return (generic_fromwire_txt(CALL_FROMWIRE));
+}
+
+static isc_result_t
+towire_avc(ARGS_TOWIRE) {
+ REQUIRE(rdata->type == dns_rdatatype_avc);
+
+ UNUSED(cctx);
+
+ return (mem_tobuffer(target, rdata->data, rdata->length));
+}
+
+static int
+compare_avc(ARGS_COMPARE) {
+ isc_region_t r1;
+ isc_region_t r2;
+
+ REQUIRE(rdata1->type == rdata2->type);
+ REQUIRE(rdata1->rdclass == rdata2->rdclass);
+ REQUIRE(rdata1->type == dns_rdatatype_avc);
+
+ dns_rdata_toregion(rdata1, &r1);
+ dns_rdata_toregion(rdata2, &r2);
+ return (isc_region_compare(&r1, &r2));
+}
+
+static isc_result_t
+fromstruct_avc(ARGS_FROMSTRUCT) {
+ REQUIRE(type == dns_rdatatype_avc);
+
+ return (generic_fromstruct_txt(CALL_FROMSTRUCT));
+}
+
+static isc_result_t
+tostruct_avc(ARGS_TOSTRUCT) {
+ dns_rdata_avc_t *avc = target;
+
+ REQUIRE(rdata->type == dns_rdatatype_avc);
+ REQUIRE(avc != NULL);
+
+ avc->common.rdclass = rdata->rdclass;
+ avc->common.rdtype = rdata->type;
+ ISC_LINK_INIT(&avc->common, link);
+
+ return (generic_tostruct_txt(CALL_TOSTRUCT));
+}
+
+static void
+freestruct_avc(ARGS_FREESTRUCT) {
+ dns_rdata_avc_t *avc = source;
+
+ REQUIRE(avc != NULL);
+ REQUIRE(avc->common.rdtype == dns_rdatatype_avc);
+
+ generic_freestruct_txt(source);
+}
+
+static isc_result_t
+additionaldata_avc(ARGS_ADDLDATA) {
+ REQUIRE(rdata->type == dns_rdatatype_avc);
+
+ UNUSED(rdata);
+ UNUSED(owner);
+ UNUSED(add);
+ UNUSED(arg);
+
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+digest_avc(ARGS_DIGEST) {
+ isc_region_t r;
+
+ REQUIRE(rdata->type == dns_rdatatype_avc);
+
+ dns_rdata_toregion(rdata, &r);
+
+ return ((digest)(arg, &r));
+}
+
+static bool
+checkowner_avc(ARGS_CHECKOWNER) {
+ REQUIRE(type == dns_rdatatype_avc);
+
+ UNUSED(name);
+ UNUSED(type);
+ UNUSED(rdclass);
+ UNUSED(wildcard);
+
+ return (true);
+}
+
+static bool
+checknames_avc(ARGS_CHECKNAMES) {
+ REQUIRE(rdata->type == dns_rdatatype_avc);
+
+ UNUSED(rdata);
+ UNUSED(owner);
+ UNUSED(bad);
+
+ return (true);
+}
+
+static int
+casecompare_avc(ARGS_COMPARE) {
+ return (compare_avc(rdata1, rdata2));
+}
+#endif /* RDATA_GENERIC_AVC_258_C */
diff --git a/lib/dns/rdata/generic/avc_258.h b/lib/dns/rdata/generic/avc_258.h
new file mode 100644
index 0000000..828d7f0
--- /dev/null
+++ b/lib/dns/rdata/generic/avc_258.h
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#pragma once
+
+typedef dns_rdata_txt_string_t dns_rdata_avc_string_t;
+
+typedef struct dns_rdata_avc {
+ dns_rdatacommon_t common;
+ isc_mem_t *mctx;
+ unsigned char *data;
+ uint16_t length;
+ /* private */
+ uint16_t offset;
+} dns_rdata_avc_t;
+
+/*
+ * ISC_LANG_BEGINDECLS and ISC_LANG_ENDDECLS are already done
+ * via rdatastructpre.h and rdatastructsuf.h.
+ */
diff --git a/lib/dns/rdata/generic/caa_257.c b/lib/dns/rdata/generic/caa_257.c
new file mode 100644
index 0000000..70bf9f2
--- /dev/null
+++ b/lib/dns/rdata/generic/caa_257.c
@@ -0,0 +1,628 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#ifndef GENERIC_CAA_257_C
+#define GENERIC_CAA_257_C 1
+
+#define RRTYPE_CAA_ATTRIBUTES (0)
+
+static unsigned char const alphanumeric[256] = {
+ /* 0x00-0x0f */ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ /* 0x10-0x1f */ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ /* 0x20-0x2f */ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ /* 0x30-0x3f */ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ /* 0x40-0x4f */ 0,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ /* 0x50-0x5f */ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ /* 0x60-0x6f */ 0,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ /* 0x70-0x7f */ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ /* 0x80-0x8f */ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ /* 0x90-0x9f */ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ /* 0xa0-0xaf */ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ /* 0xb0-0xbf */ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ /* 0xc0-0xcf */ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ /* 0xd0-0xdf */ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ /* 0xe0-0xef */ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ /* 0xf0-0xff */ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+};
+
+static isc_result_t
+fromtext_caa(ARGS_FROMTEXT) {
+ isc_token_t token;
+ isc_textregion_t tr;
+ uint8_t flags;
+ unsigned int i;
+
+ REQUIRE(type == dns_rdatatype_caa);
+
+ UNUSED(type);
+ UNUSED(rdclass);
+ UNUSED(origin);
+ UNUSED(options);
+ UNUSED(callbacks);
+
+ /* Flags. */
+ RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_number,
+ false));
+ if (token.value.as_ulong > 255U) {
+ RETTOK(ISC_R_RANGE);
+ }
+ flags = (uint8_t)(token.value.as_ulong & 255U);
+ RETERR(uint8_tobuffer(flags, target));
+
+ /*
+ * Tag
+ */
+ RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_string,
+ false));
+ tr = token.value.as_textregion;
+ for (i = 0; i < tr.length; i++) {
+ if (!alphanumeric[(unsigned char)tr.base[i]]) {
+ RETTOK(DNS_R_SYNTAX);
+ }
+ }
+ RETERR(uint8_tobuffer(tr.length, target));
+ RETERR(mem_tobuffer(target, tr.base, tr.length));
+
+ /*
+ * Value
+ */
+ RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_qstring,
+ false));
+ if (token.type != isc_tokentype_qstring &&
+ token.type != isc_tokentype_string)
+ {
+ RETERR(DNS_R_SYNTAX);
+ }
+ RETERR(multitxt_fromtext(&token.value.as_textregion, target));
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+totext_caa(ARGS_TOTEXT) {
+ isc_region_t region;
+ uint8_t flags;
+ char buf[256];
+
+ UNUSED(tctx);
+
+ REQUIRE(rdata->type == dns_rdatatype_caa);
+ REQUIRE(rdata->length >= 3U);
+ REQUIRE(rdata->data != NULL);
+
+ dns_rdata_toregion(rdata, &region);
+
+ /*
+ * Flags
+ */
+ flags = uint8_consume_fromregion(&region);
+ snprintf(buf, sizeof(buf), "%u ", flags);
+ RETERR(str_totext(buf, target));
+
+ /*
+ * Tag
+ */
+ RETERR(txt_totext(&region, false, target));
+ RETERR(str_totext(" ", target));
+
+ /*
+ * Value
+ */
+ RETERR(multitxt_totext(&region, target));
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+fromwire_caa(ARGS_FROMWIRE) {
+ isc_region_t sr;
+ unsigned int len, i;
+
+ REQUIRE(type == dns_rdatatype_caa);
+
+ UNUSED(type);
+ UNUSED(rdclass);
+ UNUSED(dctx);
+ UNUSED(options);
+
+ /*
+ * Flags
+ */
+ isc_buffer_activeregion(source, &sr);
+ if (sr.length < 2) {
+ return (ISC_R_UNEXPECTEDEND);
+ }
+
+ /*
+ * Flags, tag length
+ */
+ RETERR(mem_tobuffer(target, sr.base, 2));
+ len = sr.base[1];
+ isc_region_consume(&sr, 2);
+ isc_buffer_forward(source, 2);
+
+ /*
+ * Zero length tag fields are illegal.
+ */
+ if (sr.length < len || len == 0) {
+ RETERR(DNS_R_FORMERR);
+ }
+
+ /* Check the Tag's value */
+ for (i = 0; i < len; i++) {
+ if (!alphanumeric[sr.base[i]]) {
+ RETERR(DNS_R_FORMERR);
+ /*
+ * Tag + Value
+ */
+ }
+ }
+ /*
+ * Tag + Value
+ */
+ isc_buffer_forward(source, sr.length);
+ return (mem_tobuffer(target, sr.base, sr.length));
+}
+
+static isc_result_t
+towire_caa(ARGS_TOWIRE) {
+ isc_region_t region;
+
+ REQUIRE(rdata->type == dns_rdatatype_caa);
+ REQUIRE(rdata->length >= 3U);
+ REQUIRE(rdata->data != NULL);
+
+ UNUSED(cctx);
+
+ dns_rdata_toregion(rdata, &region);
+ return (mem_tobuffer(target, region.base, region.length));
+}
+
+static int
+compare_caa(ARGS_COMPARE) {
+ isc_region_t r1, r2;
+
+ REQUIRE(rdata1->type == rdata2->type);
+ REQUIRE(rdata1->rdclass == rdata2->rdclass);
+ REQUIRE(rdata1->type == dns_rdatatype_caa);
+ REQUIRE(rdata1->length >= 3U);
+ REQUIRE(rdata2->length >= 3U);
+ REQUIRE(rdata1->data != NULL);
+ REQUIRE(rdata2->data != NULL);
+
+ dns_rdata_toregion(rdata1, &r1);
+ dns_rdata_toregion(rdata2, &r2);
+ return (isc_region_compare(&r1, &r2));
+}
+
+static isc_result_t
+fromstruct_caa(ARGS_FROMSTRUCT) {
+ dns_rdata_caa_t *caa = source;
+ isc_region_t region;
+ unsigned int i;
+
+ REQUIRE(type == dns_rdatatype_caa);
+ REQUIRE(caa != NULL);
+ REQUIRE(caa->common.rdtype == type);
+ REQUIRE(caa->common.rdclass == rdclass);
+ REQUIRE(caa->tag != NULL && caa->tag_len != 0);
+ REQUIRE(caa->value != NULL);
+
+ UNUSED(type);
+ UNUSED(rdclass);
+
+ /*
+ * Flags
+ */
+ RETERR(uint8_tobuffer(caa->flags, target));
+
+ /*
+ * Tag length
+ */
+ RETERR(uint8_tobuffer(caa->tag_len, target));
+
+ /*
+ * Tag
+ */
+ region.base = caa->tag;
+ region.length = caa->tag_len;
+ for (i = 0; i < region.length; i++) {
+ if (!alphanumeric[region.base[i]]) {
+ RETERR(DNS_R_SYNTAX);
+ }
+ }
+ RETERR(isc_buffer_copyregion(target, &region));
+
+ /*
+ * Value
+ */
+ region.base = caa->value;
+ region.length = caa->value_len;
+ return (isc_buffer_copyregion(target, &region));
+}
+
+static isc_result_t
+tostruct_caa(ARGS_TOSTRUCT) {
+ dns_rdata_caa_t *caa = target;
+ isc_region_t sr;
+
+ REQUIRE(rdata->type == dns_rdatatype_caa);
+ REQUIRE(caa != NULL);
+ REQUIRE(rdata->length >= 3U);
+ REQUIRE(rdata->data != NULL);
+
+ caa->common.rdclass = rdata->rdclass;
+ caa->common.rdtype = rdata->type;
+ ISC_LINK_INIT(&caa->common, link);
+
+ dns_rdata_toregion(rdata, &sr);
+
+ /*
+ * Flags
+ */
+ if (sr.length < 1) {
+ return (ISC_R_UNEXPECTEDEND);
+ }
+ caa->flags = uint8_fromregion(&sr);
+ isc_region_consume(&sr, 1);
+
+ /*
+ * Tag length
+ */
+ if (sr.length < 1) {
+ return (ISC_R_UNEXPECTEDEND);
+ }
+ caa->tag_len = uint8_fromregion(&sr);
+ isc_region_consume(&sr, 1);
+
+ /*
+ * Tag
+ */
+ if (sr.length < caa->tag_len) {
+ return (ISC_R_UNEXPECTEDEND);
+ }
+ caa->tag = mem_maybedup(mctx, sr.base, caa->tag_len);
+ if (caa->tag == NULL) {
+ return (ISC_R_NOMEMORY);
+ }
+ isc_region_consume(&sr, caa->tag_len);
+
+ /*
+ * Value
+ */
+ caa->value_len = sr.length;
+ caa->value = mem_maybedup(mctx, sr.base, sr.length);
+ if (caa->value == NULL) {
+ return (ISC_R_NOMEMORY);
+ }
+
+ caa->mctx = mctx;
+ return (ISC_R_SUCCESS);
+}
+
+static void
+freestruct_caa(ARGS_FREESTRUCT) {
+ dns_rdata_caa_t *caa = (dns_rdata_caa_t *)source;
+
+ REQUIRE(caa != NULL);
+ REQUIRE(caa->common.rdtype == dns_rdatatype_caa);
+
+ if (caa->mctx == NULL) {
+ return;
+ }
+
+ if (caa->tag != NULL) {
+ isc_mem_free(caa->mctx, caa->tag);
+ }
+ if (caa->value != NULL) {
+ isc_mem_free(caa->mctx, caa->value);
+ }
+ caa->mctx = NULL;
+}
+
+static isc_result_t
+additionaldata_caa(ARGS_ADDLDATA) {
+ REQUIRE(rdata->type == dns_rdatatype_caa);
+ REQUIRE(rdata->data != NULL);
+ REQUIRE(rdata->length >= 3U);
+
+ UNUSED(rdata);
+ UNUSED(owner);
+ UNUSED(add);
+ UNUSED(arg);
+
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+digest_caa(ARGS_DIGEST) {
+ isc_region_t r;
+
+ REQUIRE(rdata->type == dns_rdatatype_caa);
+ REQUIRE(rdata->data != NULL);
+ REQUIRE(rdata->length >= 3U);
+
+ dns_rdata_toregion(rdata, &r);
+
+ return ((digest)(arg, &r));
+}
+
+static bool
+checkowner_caa(ARGS_CHECKOWNER) {
+ REQUIRE(type == dns_rdatatype_caa);
+
+ UNUSED(name);
+ UNUSED(type);
+ UNUSED(rdclass);
+ UNUSED(wildcard);
+
+ return (true);
+}
+
+static bool
+checknames_caa(ARGS_CHECKNAMES) {
+ REQUIRE(rdata->type == dns_rdatatype_caa);
+ REQUIRE(rdata->data != NULL);
+ REQUIRE(rdata->length >= 3U);
+
+ UNUSED(rdata);
+ UNUSED(owner);
+ UNUSED(bad);
+
+ return (true);
+}
+
+static int
+casecompare_caa(ARGS_COMPARE) {
+ return (compare_caa(rdata1, rdata2));
+}
+
+#endif /* GENERIC_CAA_257_C */
diff --git a/lib/dns/rdata/generic/caa_257.h b/lib/dns/rdata/generic/caa_257.h
new file mode 100644
index 0000000..f5f348d
--- /dev/null
+++ b/lib/dns/rdata/generic/caa_257.h
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#pragma once
+
+typedef struct dns_rdata_caa {
+ dns_rdatacommon_t common;
+ isc_mem_t *mctx;
+ uint8_t flags;
+ unsigned char *tag;
+ uint8_t tag_len;
+ unsigned char *value;
+ uint16_t value_len;
+} dns_rdata_caa_t;
diff --git a/lib/dns/rdata/generic/cdnskey_60.c b/lib/dns/rdata/generic/cdnskey_60.c
new file mode 100644
index 0000000..3da66d6
--- /dev/null
+++ b/lib/dns/rdata/generic/cdnskey_60.c
@@ -0,0 +1,164 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/* draft-ietf-dnsop-delegation-trust-maintainance-14 */
+
+#ifndef RDATA_GENERIC_CDNSKEY_60_C
+#define RDATA_GENERIC_CDNSKEY_60_C
+
+#include <dst/dst.h>
+
+#define RRTYPE_CDNSKEY_ATTRIBUTES 0
+
+static isc_result_t
+fromtext_cdnskey(ARGS_FROMTEXT) {
+ REQUIRE(type == dns_rdatatype_cdnskey);
+
+ return (generic_fromtext_key(CALL_FROMTEXT));
+}
+
+static isc_result_t
+totext_cdnskey(ARGS_TOTEXT) {
+ REQUIRE(rdata != NULL);
+ REQUIRE(rdata->type == dns_rdatatype_cdnskey);
+
+ return (generic_totext_key(CALL_TOTEXT));
+}
+
+static isc_result_t
+fromwire_cdnskey(ARGS_FROMWIRE) {
+ REQUIRE(type == dns_rdatatype_cdnskey);
+
+ return (generic_fromwire_key(CALL_FROMWIRE));
+}
+
+static isc_result_t
+towire_cdnskey(ARGS_TOWIRE) {
+ isc_region_t sr;
+
+ REQUIRE(rdata->type == dns_rdatatype_cdnskey);
+ REQUIRE(rdata->length != 0);
+
+ UNUSED(cctx);
+
+ dns_rdata_toregion(rdata, &sr);
+ return (mem_tobuffer(target, sr.base, sr.length));
+}
+
+static int
+compare_cdnskey(ARGS_COMPARE) {
+ isc_region_t r1;
+ isc_region_t r2;
+
+ REQUIRE(rdata1 != NULL);
+ REQUIRE(rdata2 != NULL);
+ REQUIRE(rdata1->type == rdata2->type);
+ REQUIRE(rdata1->rdclass == rdata2->rdclass);
+ REQUIRE(rdata1->type == dns_rdatatype_cdnskey);
+ REQUIRE(rdata1->length != 0);
+ REQUIRE(rdata2->length != 0);
+
+ dns_rdata_toregion(rdata1, &r1);
+ dns_rdata_toregion(rdata2, &r2);
+ return (isc_region_compare(&r1, &r2));
+}
+
+static isc_result_t
+fromstruct_cdnskey(ARGS_FROMSTRUCT) {
+ REQUIRE(type == dns_rdatatype_cdnskey);
+
+ return (generic_fromstruct_key(CALL_FROMSTRUCT));
+}
+
+static isc_result_t
+tostruct_cdnskey(ARGS_TOSTRUCT) {
+ dns_rdata_cdnskey_t *dnskey = target;
+
+ REQUIRE(dnskey != NULL);
+ REQUIRE(rdata != NULL);
+ REQUIRE(rdata->type == dns_rdatatype_cdnskey);
+
+ dnskey->common.rdclass = rdata->rdclass;
+ dnskey->common.rdtype = rdata->type;
+ ISC_LINK_INIT(&dnskey->common, link);
+
+ return (generic_tostruct_key(CALL_TOSTRUCT));
+}
+
+static void
+freestruct_cdnskey(ARGS_FREESTRUCT) {
+ dns_rdata_cdnskey_t *dnskey = (dns_rdata_cdnskey_t *)source;
+
+ REQUIRE(dnskey != NULL);
+ REQUIRE(dnskey->common.rdtype == dns_rdatatype_cdnskey);
+
+ generic_freestruct_key(source);
+}
+
+static isc_result_t
+additionaldata_cdnskey(ARGS_ADDLDATA) {
+ REQUIRE(rdata->type == dns_rdatatype_cdnskey);
+
+ UNUSED(rdata);
+ UNUSED(owner);
+ UNUSED(add);
+ UNUSED(arg);
+
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+digest_cdnskey(ARGS_DIGEST) {
+ isc_region_t r;
+
+ REQUIRE(rdata != NULL);
+ REQUIRE(rdata->type == dns_rdatatype_cdnskey);
+
+ dns_rdata_toregion(rdata, &r);
+
+ return ((digest)(arg, &r));
+}
+
+static bool
+checkowner_cdnskey(ARGS_CHECKOWNER) {
+ REQUIRE(type == dns_rdatatype_cdnskey);
+
+ UNUSED(name);
+ UNUSED(type);
+ UNUSED(rdclass);
+ UNUSED(wildcard);
+
+ return (true);
+}
+
+static bool
+checknames_cdnskey(ARGS_CHECKNAMES) {
+ REQUIRE(rdata != NULL);
+ REQUIRE(rdata->type == dns_rdatatype_cdnskey);
+
+ UNUSED(rdata);
+ UNUSED(owner);
+ UNUSED(bad);
+
+ return (true);
+}
+
+static int
+casecompare_cdnskey(ARGS_COMPARE) {
+ /*
+ * Treat ALG 253 (private DNS) subtype name case sensitively.
+ */
+ return (compare_cdnskey(rdata1, rdata2));
+}
+
+#endif /* RDATA_GENERIC_CDNSKEY_60_C */
diff --git a/lib/dns/rdata/generic/cdnskey_60.h b/lib/dns/rdata/generic/cdnskey_60.h
new file mode 100644
index 0000000..b2c8771
--- /dev/null
+++ b/lib/dns/rdata/generic/cdnskey_60.h
@@ -0,0 +1,17 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#pragma once
+
+/* CDNSKEY records have the same RDATA fields as DNSKEY records. */
+typedef struct dns_rdata_key dns_rdata_cdnskey_t;
diff --git a/lib/dns/rdata/generic/cds_59.c b/lib/dns/rdata/generic/cds_59.c
new file mode 100644
index 0000000..7c64158
--- /dev/null
+++ b/lib/dns/rdata/generic/cds_59.c
@@ -0,0 +1,167 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/* draft-ietf-dnsop-delegation-trust-maintainance-14 */
+
+#ifndef RDATA_GENERIC_CDS_59_C
+#define RDATA_GENERIC_CDS_59_C
+
+#define RRTYPE_CDS_ATTRIBUTES 0
+
+#include <dns/ds.h>
+
+static isc_result_t
+fromtext_cds(ARGS_FROMTEXT) {
+ REQUIRE(type == dns_rdatatype_cds);
+
+ return (generic_fromtext_ds(CALL_FROMTEXT));
+}
+
+static isc_result_t
+totext_cds(ARGS_TOTEXT) {
+ REQUIRE(rdata != NULL);
+ REQUIRE(rdata->type == dns_rdatatype_cds);
+
+ return (generic_totext_ds(CALL_TOTEXT));
+}
+
+static isc_result_t
+fromwire_cds(ARGS_FROMWIRE) {
+ REQUIRE(type == dns_rdatatype_cds);
+
+ return (generic_fromwire_ds(CALL_FROMWIRE));
+}
+
+static isc_result_t
+towire_cds(ARGS_TOWIRE) {
+ isc_region_t sr;
+
+ REQUIRE(rdata->type == dns_rdatatype_cds);
+ REQUIRE(rdata->length != 0);
+
+ UNUSED(cctx);
+
+ dns_rdata_toregion(rdata, &sr);
+ return (mem_tobuffer(target, sr.base, sr.length));
+}
+
+static int
+compare_cds(ARGS_COMPARE) {
+ isc_region_t r1;
+ isc_region_t r2;
+
+ REQUIRE(rdata1->type == rdata2->type);
+ REQUIRE(rdata1->rdclass == rdata2->rdclass);
+ REQUIRE(rdata1->type == dns_rdatatype_cds);
+ REQUIRE(rdata1->length != 0);
+ REQUIRE(rdata2->length != 0);
+
+ dns_rdata_toregion(rdata1, &r1);
+ dns_rdata_toregion(rdata2, &r2);
+ return (isc_region_compare(&r1, &r2));
+}
+
+static isc_result_t
+fromstruct_cds(ARGS_FROMSTRUCT) {
+ REQUIRE(type == dns_rdatatype_cds);
+
+ return (generic_fromstruct_ds(CALL_FROMSTRUCT));
+}
+
+static isc_result_t
+tostruct_cds(ARGS_TOSTRUCT) {
+ dns_rdata_cds_t *cds = target;
+
+ REQUIRE(rdata->type == dns_rdatatype_cds);
+ REQUIRE(cds != NULL);
+ REQUIRE(rdata->length != 0);
+
+ /*
+ * Checked by generic_tostruct_ds().
+ */
+ cds->common.rdclass = rdata->rdclass;
+ cds->common.rdtype = rdata->type;
+ ISC_LINK_INIT(&cds->common, link);
+
+ return (generic_tostruct_ds(CALL_TOSTRUCT));
+}
+
+static void
+freestruct_cds(ARGS_FREESTRUCT) {
+ dns_rdata_cds_t *cds = source;
+
+ REQUIRE(cds != NULL);
+ REQUIRE(cds->common.rdtype == dns_rdatatype_cds);
+
+ if (cds->mctx == NULL) {
+ return;
+ }
+
+ if (cds->digest != NULL) {
+ isc_mem_free(cds->mctx, cds->digest);
+ }
+ cds->mctx = NULL;
+}
+
+static isc_result_t
+additionaldata_cds(ARGS_ADDLDATA) {
+ REQUIRE(rdata->type == dns_rdatatype_cds);
+
+ UNUSED(rdata);
+ UNUSED(owner);
+ UNUSED(add);
+ UNUSED(arg);
+
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+digest_cds(ARGS_DIGEST) {
+ isc_region_t r;
+
+ REQUIRE(rdata->type == dns_rdatatype_cds);
+
+ dns_rdata_toregion(rdata, &r);
+
+ return ((digest)(arg, &r));
+}
+
+static bool
+checkowner_cds(ARGS_CHECKOWNER) {
+ REQUIRE(type == dns_rdatatype_cds);
+
+ UNUSED(name);
+ UNUSED(type);
+ UNUSED(rdclass);
+ UNUSED(wildcard);
+
+ return (true);
+}
+
+static bool
+checknames_cds(ARGS_CHECKNAMES) {
+ REQUIRE(rdata->type == dns_rdatatype_cds);
+
+ UNUSED(rdata);
+ UNUSED(owner);
+ UNUSED(bad);
+
+ return (true);
+}
+
+static int
+casecompare_cds(ARGS_COMPARE) {
+ return (compare_cds(rdata1, rdata2));
+}
+
+#endif /* RDATA_GENERIC_CDS_59_C */
diff --git a/lib/dns/rdata/generic/cds_59.h b/lib/dns/rdata/generic/cds_59.h
new file mode 100644
index 0000000..b5b409a
--- /dev/null
+++ b/lib/dns/rdata/generic/cds_59.h
@@ -0,0 +1,17 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#pragma once
+
+/* CDS records have the same RDATA fields as DS records. */
+typedef struct dns_rdata_ds dns_rdata_cds_t;
diff --git a/lib/dns/rdata/generic/cert_37.c b/lib/dns/rdata/generic/cert_37.c
new file mode 100644
index 0000000..eb307a7
--- /dev/null
+++ b/lib/dns/rdata/generic/cert_37.c
@@ -0,0 +1,285 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/* RFC2538 */
+
+#ifndef RDATA_GENERIC_CERT_37_C
+#define RDATA_GENERIC_CERT_37_C
+
+#define RRTYPE_CERT_ATTRIBUTES (0)
+
+static isc_result_t
+fromtext_cert(ARGS_FROMTEXT) {
+ isc_token_t token;
+ dns_secalg_t secalg;
+ dns_cert_t cert;
+
+ REQUIRE(type == dns_rdatatype_cert);
+
+ UNUSED(type);
+ UNUSED(rdclass);
+ UNUSED(origin);
+ UNUSED(options);
+ UNUSED(callbacks);
+
+ /*
+ * Cert type.
+ */
+ RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_string,
+ false));
+ RETTOK(dns_cert_fromtext(&cert, &token.value.as_textregion));
+ RETERR(uint16_tobuffer(cert, target));
+
+ /*
+ * Key tag.
+ */
+ RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_number,
+ false));
+ if (token.value.as_ulong > 0xffffU) {
+ RETTOK(ISC_R_RANGE);
+ }
+ RETERR(uint16_tobuffer(token.value.as_ulong, target));
+
+ /*
+ * Algorithm.
+ */
+ RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_string,
+ false));
+ RETTOK(dns_secalg_fromtext(&secalg, &token.value.as_textregion));
+ RETERR(mem_tobuffer(target, &secalg, 1));
+
+ return (isc_base64_tobuffer(lexer, target, -2));
+}
+
+static isc_result_t
+totext_cert(ARGS_TOTEXT) {
+ isc_region_t sr;
+ char buf[sizeof("64000 ")];
+ unsigned int n;
+
+ REQUIRE(rdata->type == dns_rdatatype_cert);
+ REQUIRE(rdata->length != 0);
+
+ UNUSED(tctx);
+
+ dns_rdata_toregion(rdata, &sr);
+
+ /*
+ * Type.
+ */
+ n = uint16_fromregion(&sr);
+ isc_region_consume(&sr, 2);
+ RETERR(dns_cert_totext((dns_cert_t)n, target));
+ RETERR(str_totext(" ", target));
+
+ /*
+ * Key tag.
+ */
+ n = uint16_fromregion(&sr);
+ isc_region_consume(&sr, 2);
+ snprintf(buf, sizeof(buf), "%u ", n);
+ RETERR(str_totext(buf, target));
+
+ /*
+ * Algorithm.
+ */
+ RETERR(dns_secalg_totext(sr.base[0], target));
+ isc_region_consume(&sr, 1);
+
+ /*
+ * Cert.
+ */
+ if ((tctx->flags & DNS_STYLEFLAG_MULTILINE) != 0) {
+ RETERR(str_totext(" (", target));
+ }
+ RETERR(str_totext(tctx->linebreak, target));
+ if (tctx->width == 0) { /* No splitting */
+ RETERR(isc_base64_totext(&sr, 60, "", target));
+ } else {
+ RETERR(isc_base64_totext(&sr, tctx->width - 2, tctx->linebreak,
+ target));
+ }
+ if ((tctx->flags & DNS_STYLEFLAG_MULTILINE) != 0) {
+ RETERR(str_totext(" )", target));
+ }
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+fromwire_cert(ARGS_FROMWIRE) {
+ isc_region_t sr;
+
+ REQUIRE(type == dns_rdatatype_cert);
+
+ UNUSED(type);
+ UNUSED(rdclass);
+ UNUSED(dctx);
+ UNUSED(options);
+
+ isc_buffer_activeregion(source, &sr);
+ if (sr.length < 6) {
+ return (ISC_R_UNEXPECTEDEND);
+ }
+
+ isc_buffer_forward(source, sr.length);
+ return (mem_tobuffer(target, sr.base, sr.length));
+}
+
+static isc_result_t
+towire_cert(ARGS_TOWIRE) {
+ isc_region_t sr;
+
+ REQUIRE(rdata->type == dns_rdatatype_cert);
+ REQUIRE(rdata->length != 0);
+
+ UNUSED(cctx);
+
+ dns_rdata_toregion(rdata, &sr);
+ return (mem_tobuffer(target, sr.base, sr.length));
+}
+
+static int
+compare_cert(ARGS_COMPARE) {
+ isc_region_t r1;
+ isc_region_t r2;
+
+ REQUIRE(rdata1->type == rdata2->type);
+ REQUIRE(rdata1->rdclass == rdata2->rdclass);
+ REQUIRE(rdata1->type == dns_rdatatype_cert);
+ REQUIRE(rdata1->length != 0);
+ REQUIRE(rdata2->length != 0);
+
+ dns_rdata_toregion(rdata1, &r1);
+ dns_rdata_toregion(rdata2, &r2);
+ return (isc_region_compare(&r1, &r2));
+}
+
+static isc_result_t
+fromstruct_cert(ARGS_FROMSTRUCT) {
+ dns_rdata_cert_t *cert = source;
+
+ REQUIRE(type == dns_rdatatype_cert);
+ REQUIRE(cert != NULL);
+ REQUIRE(cert->common.rdtype == type);
+ REQUIRE(cert->common.rdclass == rdclass);
+
+ UNUSED(type);
+ UNUSED(rdclass);
+
+ RETERR(uint16_tobuffer(cert->type, target));
+ RETERR(uint16_tobuffer(cert->key_tag, target));
+ RETERR(uint8_tobuffer(cert->algorithm, target));
+
+ return (mem_tobuffer(target, cert->certificate, cert->length));
+}
+
+static isc_result_t
+tostruct_cert(ARGS_TOSTRUCT) {
+ dns_rdata_cert_t *cert = target;
+ isc_region_t region;
+
+ REQUIRE(rdata->type == dns_rdatatype_cert);
+ REQUIRE(cert != NULL);
+ REQUIRE(rdata->length != 0);
+
+ cert->common.rdclass = rdata->rdclass;
+ cert->common.rdtype = rdata->type;
+ ISC_LINK_INIT(&cert->common, link);
+
+ dns_rdata_toregion(rdata, &region);
+
+ cert->type = uint16_fromregion(&region);
+ isc_region_consume(&region, 2);
+ cert->key_tag = uint16_fromregion(&region);
+ isc_region_consume(&region, 2);
+ cert->algorithm = uint8_fromregion(&region);
+ isc_region_consume(&region, 1);
+ cert->length = region.length;
+
+ cert->certificate = mem_maybedup(mctx, region.base, region.length);
+ if (cert->certificate == NULL) {
+ return (ISC_R_NOMEMORY);
+ }
+
+ cert->mctx = mctx;
+ return (ISC_R_SUCCESS);
+}
+
+static void
+freestruct_cert(ARGS_FREESTRUCT) {
+ dns_rdata_cert_t *cert = source;
+
+ REQUIRE(cert != NULL);
+ REQUIRE(cert->common.rdtype == dns_rdatatype_cert);
+
+ if (cert->mctx == NULL) {
+ return;
+ }
+
+ if (cert->certificate != NULL) {
+ isc_mem_free(cert->mctx, cert->certificate);
+ }
+ cert->mctx = NULL;
+}
+
+static isc_result_t
+additionaldata_cert(ARGS_ADDLDATA) {
+ REQUIRE(rdata->type == dns_rdatatype_cert);
+
+ UNUSED(rdata);
+ UNUSED(owner);
+ UNUSED(add);
+ UNUSED(arg);
+
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+digest_cert(ARGS_DIGEST) {
+ isc_region_t r;
+
+ REQUIRE(rdata->type == dns_rdatatype_cert);
+
+ dns_rdata_toregion(rdata, &r);
+
+ return ((digest)(arg, &r));
+}
+
+static bool
+checkowner_cert(ARGS_CHECKOWNER) {
+ REQUIRE(type == dns_rdatatype_cert);
+
+ UNUSED(name);
+ UNUSED(type);
+ UNUSED(rdclass);
+ UNUSED(wildcard);
+
+ return (true);
+}
+
+static bool
+checknames_cert(ARGS_CHECKNAMES) {
+ REQUIRE(rdata->type == dns_rdatatype_cert);
+
+ UNUSED(rdata);
+ UNUSED(owner);
+ UNUSED(bad);
+
+ return (true);
+}
+
+static int
+casecompare_cert(ARGS_COMPARE) {
+ return (compare_cert(rdata1, rdata2));
+}
+#endif /* RDATA_GENERIC_CERT_37_C */
diff --git a/lib/dns/rdata/generic/cert_37.h b/lib/dns/rdata/generic/cert_37.h
new file mode 100644
index 0000000..a905a2b
--- /dev/null
+++ b/lib/dns/rdata/generic/cert_37.h
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#pragma once
+
+/*% RFC2538 */
+typedef struct dns_rdata_cert {
+ dns_rdatacommon_t common;
+ isc_mem_t *mctx;
+ uint16_t type;
+ uint16_t key_tag;
+ uint8_t algorithm;
+ uint16_t length;
+ unsigned char *certificate;
+} dns_rdata_cert_t;
diff --git a/lib/dns/rdata/generic/cname_5.c b/lib/dns/rdata/generic/cname_5.c
new file mode 100644
index 0000000..f3f9378
--- /dev/null
+++ b/lib/dns/rdata/generic/cname_5.c
@@ -0,0 +1,231 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#ifndef RDATA_GENERIC_CNAME_5_C
+#define RDATA_GENERIC_CNAME_5_C
+
+#define RRTYPE_CNAME_ATTRIBUTES \
+ (DNS_RDATATYPEATTR_EXCLUSIVE | DNS_RDATATYPEATTR_SINGLETON)
+
+static isc_result_t
+fromtext_cname(ARGS_FROMTEXT) {
+ isc_token_t token;
+ dns_name_t name;
+ isc_buffer_t buffer;
+
+ REQUIRE(type == dns_rdatatype_cname);
+
+ UNUSED(type);
+ UNUSED(rdclass);
+ UNUSED(callbacks);
+
+ RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_string,
+ false));
+
+ dns_name_init(&name, NULL);
+ buffer_fromregion(&buffer, &token.value.as_region);
+ if (origin == NULL) {
+ origin = dns_rootname;
+ }
+ RETTOK(dns_name_fromtext(&name, &buffer, origin, options, target));
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+totext_cname(ARGS_TOTEXT) {
+ isc_region_t region;
+ dns_name_t name;
+ dns_name_t prefix;
+ bool sub;
+
+ REQUIRE(rdata->type == dns_rdatatype_cname);
+ REQUIRE(rdata->length != 0);
+
+ dns_name_init(&name, NULL);
+ dns_name_init(&prefix, NULL);
+
+ dns_rdata_toregion(rdata, &region);
+ dns_name_fromregion(&name, &region);
+
+ sub = name_prefix(&name, tctx->origin, &prefix);
+
+ return (dns_name_totext(&prefix, sub, target));
+}
+
+static isc_result_t
+fromwire_cname(ARGS_FROMWIRE) {
+ dns_name_t name;
+
+ REQUIRE(type == dns_rdatatype_cname);
+
+ UNUSED(type);
+ UNUSED(rdclass);
+
+ dns_decompress_setmethods(dctx, DNS_COMPRESS_GLOBAL14);
+
+ dns_name_init(&name, NULL);
+ return (dns_name_fromwire(&name, source, dctx, options, target));
+}
+
+static isc_result_t
+towire_cname(ARGS_TOWIRE) {
+ dns_name_t name;
+ dns_offsets_t offsets;
+ isc_region_t region;
+
+ REQUIRE(rdata->type == dns_rdatatype_cname);
+ REQUIRE(rdata->length != 0);
+
+ dns_compress_setmethods(cctx, DNS_COMPRESS_GLOBAL14);
+
+ dns_name_init(&name, offsets);
+ dns_rdata_toregion(rdata, &region);
+ dns_name_fromregion(&name, &region);
+
+ return (dns_name_towire(&name, cctx, target));
+}
+
+static int
+compare_cname(ARGS_COMPARE) {
+ dns_name_t name1;
+ dns_name_t name2;
+ isc_region_t region1;
+ isc_region_t region2;
+
+ REQUIRE(rdata1->type == rdata2->type);
+ REQUIRE(rdata1->rdclass == rdata2->rdclass);
+ REQUIRE(rdata1->type == dns_rdatatype_cname);
+ REQUIRE(rdata1->length != 0);
+ REQUIRE(rdata2->length != 0);
+
+ dns_name_init(&name1, NULL);
+ dns_name_init(&name2, NULL);
+
+ dns_rdata_toregion(rdata1, &region1);
+ dns_rdata_toregion(rdata2, &region2);
+
+ dns_name_fromregion(&name1, &region1);
+ dns_name_fromregion(&name2, &region2);
+
+ return (dns_name_rdatacompare(&name1, &name2));
+}
+
+static isc_result_t
+fromstruct_cname(ARGS_FROMSTRUCT) {
+ dns_rdata_cname_t *cname = source;
+ isc_region_t region;
+
+ REQUIRE(type == dns_rdatatype_cname);
+ REQUIRE(cname != NULL);
+ REQUIRE(cname->common.rdtype == type);
+ REQUIRE(cname->common.rdclass == rdclass);
+
+ UNUSED(type);
+ UNUSED(rdclass);
+
+ dns_name_toregion(&cname->cname, &region);
+ return (isc_buffer_copyregion(target, &region));
+}
+
+static isc_result_t
+tostruct_cname(ARGS_TOSTRUCT) {
+ isc_region_t region;
+ dns_rdata_cname_t *cname = target;
+ dns_name_t name;
+
+ REQUIRE(rdata->type == dns_rdatatype_cname);
+ REQUIRE(cname != NULL);
+ REQUIRE(rdata->length != 0);
+
+ cname->common.rdclass = rdata->rdclass;
+ cname->common.rdtype = rdata->type;
+ ISC_LINK_INIT(&cname->common, link);
+
+ dns_name_init(&name, NULL);
+ dns_rdata_toregion(rdata, &region);
+ dns_name_fromregion(&name, &region);
+ dns_name_init(&cname->cname, NULL);
+ name_duporclone(&name, mctx, &cname->cname);
+ cname->mctx = mctx;
+ return (ISC_R_SUCCESS);
+}
+
+static void
+freestruct_cname(ARGS_FREESTRUCT) {
+ dns_rdata_cname_t *cname = source;
+
+ REQUIRE(cname != NULL);
+
+ if (cname->mctx == NULL) {
+ return;
+ }
+
+ dns_name_free(&cname->cname, cname->mctx);
+ cname->mctx = NULL;
+}
+
+static isc_result_t
+additionaldata_cname(ARGS_ADDLDATA) {
+ UNUSED(rdata);
+ UNUSED(owner);
+ UNUSED(add);
+ UNUSED(arg);
+
+ REQUIRE(rdata->type == dns_rdatatype_cname);
+
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+digest_cname(ARGS_DIGEST) {
+ isc_region_t r;
+ dns_name_t name;
+
+ REQUIRE(rdata->type == dns_rdatatype_cname);
+
+ dns_rdata_toregion(rdata, &r);
+ dns_name_init(&name, NULL);
+ dns_name_fromregion(&name, &r);
+
+ return (dns_name_digest(&name, digest, arg));
+}
+
+static bool
+checkowner_cname(ARGS_CHECKOWNER) {
+ REQUIRE(type == dns_rdatatype_cname);
+
+ UNUSED(name);
+ UNUSED(type);
+ UNUSED(rdclass);
+ UNUSED(wildcard);
+
+ return (true);
+}
+
+static bool
+checknames_cname(ARGS_CHECKNAMES) {
+ REQUIRE(rdata->type == dns_rdatatype_cname);
+
+ UNUSED(rdata);
+ UNUSED(owner);
+ UNUSED(bad);
+
+ return (true);
+}
+
+static int
+casecompare_cname(ARGS_COMPARE) {
+ return (compare_cname(rdata1, rdata2));
+}
+
+#endif /* RDATA_GENERIC_CNAME_5_C */
diff --git a/lib/dns/rdata/generic/cname_5.h b/lib/dns/rdata/generic/cname_5.h
new file mode 100644
index 0000000..1525fa0
--- /dev/null
+++ b/lib/dns/rdata/generic/cname_5.h
@@ -0,0 +1,20 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#pragma once
+
+typedef struct dns_rdata_cname {
+ dns_rdatacommon_t common;
+ isc_mem_t *mctx;
+ dns_name_t cname;
+} dns_rdata_cname_t;
diff --git a/lib/dns/rdata/generic/csync_62.c b/lib/dns/rdata/generic/csync_62.c
new file mode 100644
index 0000000..285f22b
--- /dev/null
+++ b/lib/dns/rdata/generic/csync_62.c
@@ -0,0 +1,274 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/* RFC 7477 */
+
+#ifndef RDATA_GENERIC_CSYNC_62_C
+#define RDATA_GENERIC_CSYNC_62_C
+
+#define RRTYPE_CSYNC_ATTRIBUTES 0
+
+static isc_result_t
+fromtext_csync(ARGS_FROMTEXT) {
+ isc_token_t token;
+
+ REQUIRE(type == dns_rdatatype_csync);
+
+ UNUSED(type);
+ UNUSED(rdclass);
+ UNUSED(origin);
+ UNUSED(options);
+ UNUSED(callbacks);
+
+ /* Serial. */
+ RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_number,
+ false));
+ RETERR(uint32_tobuffer(token.value.as_ulong, target));
+
+ /* Flags. */
+ RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_number,
+ false));
+ if (token.value.as_ulong > 0xffffU) {
+ RETTOK(ISC_R_RANGE);
+ }
+ RETERR(uint16_tobuffer(token.value.as_ulong, target));
+
+ /* Type Map */
+ return (typemap_fromtext(lexer, target, true));
+}
+
+static isc_result_t
+totext_csync(ARGS_TOTEXT) {
+ unsigned long num;
+ char buf[sizeof("0123456789")]; /* Also TYPE65535 */
+ isc_region_t sr;
+
+ REQUIRE(rdata->type == dns_rdatatype_csync);
+ REQUIRE(rdata->length >= 6);
+
+ UNUSED(tctx);
+
+ dns_rdata_toregion(rdata, &sr);
+
+ num = uint32_fromregion(&sr);
+ isc_region_consume(&sr, 4);
+ snprintf(buf, sizeof(buf), "%lu", num);
+ RETERR(str_totext(buf, target));
+
+ RETERR(str_totext(" ", target));
+
+ num = uint16_fromregion(&sr);
+ isc_region_consume(&sr, 2);
+ snprintf(buf, sizeof(buf), "%lu", num);
+ RETERR(str_totext(buf, target));
+
+ /*
+ * Don't leave a trailing space when there's no typemap present.
+ */
+ if (sr.length > 0) {
+ RETERR(str_totext(" ", target));
+ }
+ return (typemap_totext(&sr, NULL, target));
+}
+
+static isc_result_t
+fromwire_csync(ARGS_FROMWIRE) {
+ isc_region_t sr;
+
+ REQUIRE(type == dns_rdatatype_csync);
+
+ UNUSED(type);
+ UNUSED(rdclass);
+ UNUSED(options);
+ UNUSED(dctx);
+
+ /*
+ * Serial + Flags
+ */
+ isc_buffer_activeregion(source, &sr);
+ if (sr.length < 6) {
+ return (ISC_R_UNEXPECTEDEND);
+ }
+
+ RETERR(mem_tobuffer(target, sr.base, 6));
+ isc_buffer_forward(source, 6);
+ isc_region_consume(&sr, 6);
+
+ RETERR(typemap_test(&sr, true));
+
+ RETERR(mem_tobuffer(target, sr.base, sr.length));
+ isc_buffer_forward(source, sr.length);
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+towire_csync(ARGS_TOWIRE) {
+ REQUIRE(rdata->type == dns_rdatatype_csync);
+ REQUIRE(rdata->length >= 6);
+
+ UNUSED(cctx);
+
+ return (mem_tobuffer(target, rdata->data, rdata->length));
+}
+
+static int
+compare_csync(ARGS_COMPARE) {
+ isc_region_t r1;
+ isc_region_t r2;
+
+ REQUIRE(rdata1->type == rdata2->type);
+ REQUIRE(rdata1->rdclass == rdata2->rdclass);
+ REQUIRE(rdata1->type == dns_rdatatype_csync);
+ REQUIRE(rdata1->length >= 6);
+ REQUIRE(rdata2->length >= 6);
+
+ dns_rdata_toregion(rdata1, &r1);
+ dns_rdata_toregion(rdata2, &r2);
+ return (isc_region_compare(&r1, &r2));
+}
+
+static isc_result_t
+fromstruct_csync(ARGS_FROMSTRUCT) {
+ dns_rdata_csync_t *csync = source;
+ isc_region_t region;
+
+ REQUIRE(type == dns_rdatatype_csync);
+ REQUIRE(csync != NULL);
+ REQUIRE(csync->common.rdtype == type);
+ REQUIRE(csync->common.rdclass == rdclass);
+ REQUIRE(csync->typebits != NULL || csync->len == 0);
+
+ UNUSED(type);
+ UNUSED(rdclass);
+
+ RETERR(uint32_tobuffer(csync->serial, target));
+ RETERR(uint16_tobuffer(csync->flags, target));
+
+ region.base = csync->typebits;
+ region.length = csync->len;
+ RETERR(typemap_test(&region, true));
+ return (mem_tobuffer(target, csync->typebits, csync->len));
+}
+
+static isc_result_t
+tostruct_csync(ARGS_TOSTRUCT) {
+ isc_region_t region;
+ dns_rdata_csync_t *csync = target;
+
+ REQUIRE(rdata->type == dns_rdatatype_csync);
+ REQUIRE(csync != NULL);
+ REQUIRE(rdata->length != 0);
+
+ csync->common.rdclass = rdata->rdclass;
+ csync->common.rdtype = rdata->type;
+ ISC_LINK_INIT(&csync->common, link);
+
+ dns_rdata_toregion(rdata, &region);
+
+ csync->serial = uint32_fromregion(&region);
+ isc_region_consume(&region, 4);
+
+ csync->flags = uint16_fromregion(&region);
+ isc_region_consume(&region, 2);
+
+ csync->len = region.length;
+ csync->typebits = mem_maybedup(mctx, region.base, region.length);
+ if (csync->typebits == NULL) {
+ goto cleanup;
+ }
+
+ csync->mctx = mctx;
+ return (ISC_R_SUCCESS);
+
+cleanup:
+ return (ISC_R_NOMEMORY);
+}
+
+static void
+freestruct_csync(ARGS_FREESTRUCT) {
+ dns_rdata_csync_t *csync = source;
+
+ REQUIRE(csync != NULL);
+ REQUIRE(csync->common.rdtype == dns_rdatatype_csync);
+
+ if (csync->mctx == NULL) {
+ return;
+ }
+
+ if (csync->typebits != NULL) {
+ isc_mem_free(csync->mctx, csync->typebits);
+ }
+ csync->mctx = NULL;
+}
+
+static isc_result_t
+additionaldata_csync(ARGS_ADDLDATA) {
+ REQUIRE(rdata->type == dns_rdatatype_csync);
+
+ UNUSED(rdata);
+ UNUSED(owner);
+ UNUSED(add);
+ UNUSED(arg);
+
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+digest_csync(ARGS_DIGEST) {
+ isc_region_t r;
+
+ REQUIRE(rdata->type == dns_rdatatype_csync);
+
+ dns_rdata_toregion(rdata, &r);
+ return ((digest)(arg, &r));
+}
+
+static bool
+checkowner_csync(ARGS_CHECKOWNER) {
+ REQUIRE(type == dns_rdatatype_csync);
+
+ UNUSED(name);
+ UNUSED(type);
+ UNUSED(rdclass);
+ UNUSED(wildcard);
+
+ return (true);
+}
+
+static bool
+checknames_csync(ARGS_CHECKNAMES) {
+ REQUIRE(rdata->type == dns_rdatatype_csync);
+
+ UNUSED(rdata);
+ UNUSED(owner);
+ UNUSED(bad);
+
+ return (true);
+}
+
+static int
+casecompare_csync(ARGS_COMPARE) {
+ isc_region_t region1;
+ isc_region_t region2;
+
+ REQUIRE(rdata1->type == rdata2->type);
+ REQUIRE(rdata1->rdclass == rdata2->rdclass);
+ REQUIRE(rdata1->type == dns_rdatatype_csync);
+ REQUIRE(rdata1->length >= 6);
+ REQUIRE(rdata2->length >= 6);
+
+ dns_rdata_toregion(rdata1, &region1);
+ dns_rdata_toregion(rdata2, &region2);
+ return (isc_region_compare(&region1, &region2));
+}
+#endif /* RDATA_GENERIC_CSYNC_62_C */
diff --git a/lib/dns/rdata/generic/csync_62.h b/lib/dns/rdata/generic/csync_62.h
new file mode 100644
index 0000000..a7b3c1e
--- /dev/null
+++ b/lib/dns/rdata/generic/csync_62.h
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#pragma once
+
+/*!
+ * \brief Per RFC 7477
+ */
+
+typedef struct dns_rdata_csync {
+ dns_rdatacommon_t common;
+ isc_mem_t *mctx;
+ uint32_t serial;
+ uint16_t flags;
+ unsigned char *typebits;
+ uint16_t len;
+} dns_rdata_csync_t;
diff --git a/lib/dns/rdata/generic/dlv_32769.c b/lib/dns/rdata/generic/dlv_32769.c
new file mode 100644
index 0000000..faf9500
--- /dev/null
+++ b/lib/dns/rdata/generic/dlv_32769.c
@@ -0,0 +1,163 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/* RFC3658 */
+
+#ifndef RDATA_GENERIC_DLV_32769_C
+#define RDATA_GENERIC_DLV_32769_C
+
+#define RRTYPE_DLV_ATTRIBUTES 0
+
+#include <dns/ds.h>
+
+static isc_result_t
+fromtext_dlv(ARGS_FROMTEXT) {
+ REQUIRE(type == dns_rdatatype_dlv);
+
+ return (generic_fromtext_ds(CALL_FROMTEXT));
+}
+
+static isc_result_t
+totext_dlv(ARGS_TOTEXT) {
+ REQUIRE(rdata != NULL);
+ REQUIRE(rdata->type == dns_rdatatype_dlv);
+
+ return (generic_totext_ds(CALL_TOTEXT));
+}
+
+static isc_result_t
+fromwire_dlv(ARGS_FROMWIRE) {
+ REQUIRE(type == dns_rdatatype_dlv);
+
+ return (generic_fromwire_ds(CALL_FROMWIRE));
+}
+
+static isc_result_t
+towire_dlv(ARGS_TOWIRE) {
+ isc_region_t sr;
+
+ REQUIRE(rdata->type == dns_rdatatype_dlv);
+ REQUIRE(rdata->length != 0);
+
+ UNUSED(cctx);
+
+ dns_rdata_toregion(rdata, &sr);
+ return (mem_tobuffer(target, sr.base, sr.length));
+}
+
+static int
+compare_dlv(ARGS_COMPARE) {
+ isc_region_t r1;
+ isc_region_t r2;
+
+ REQUIRE(rdata1->type == rdata2->type);
+ REQUIRE(rdata1->rdclass == rdata2->rdclass);
+ REQUIRE(rdata1->type == dns_rdatatype_dlv);
+ REQUIRE(rdata1->length != 0);
+ REQUIRE(rdata2->length != 0);
+
+ dns_rdata_toregion(rdata1, &r1);
+ dns_rdata_toregion(rdata2, &r2);
+ return (isc_region_compare(&r1, &r2));
+}
+
+static isc_result_t
+fromstruct_dlv(ARGS_FROMSTRUCT) {
+ REQUIRE(type == dns_rdatatype_dlv);
+
+ return (generic_fromstruct_ds(CALL_FROMSTRUCT));
+}
+
+static isc_result_t
+tostruct_dlv(ARGS_TOSTRUCT) {
+ dns_rdata_dlv_t *dlv = target;
+
+ REQUIRE(rdata->type == dns_rdatatype_dlv);
+ REQUIRE(dlv != NULL);
+
+ dlv->common.rdclass = rdata->rdclass;
+ dlv->common.rdtype = rdata->type;
+ ISC_LINK_INIT(&dlv->common, link);
+
+ return (generic_tostruct_ds(CALL_TOSTRUCT));
+}
+
+static void
+freestruct_dlv(ARGS_FREESTRUCT) {
+ dns_rdata_dlv_t *dlv = source;
+
+ REQUIRE(dlv != NULL);
+ REQUIRE(dlv->common.rdtype == dns_rdatatype_dlv);
+
+ if (dlv->mctx == NULL) {
+ return;
+ }
+
+ if (dlv->digest != NULL) {
+ isc_mem_free(dlv->mctx, dlv->digest);
+ }
+ dlv->mctx = NULL;
+}
+
+static isc_result_t
+additionaldata_dlv(ARGS_ADDLDATA) {
+ REQUIRE(rdata->type == dns_rdatatype_dlv);
+
+ UNUSED(rdata);
+ UNUSED(owner);
+ UNUSED(add);
+ UNUSED(arg);
+
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+digest_dlv(ARGS_DIGEST) {
+ isc_region_t r;
+
+ REQUIRE(rdata->type == dns_rdatatype_dlv);
+
+ dns_rdata_toregion(rdata, &r);
+
+ return ((digest)(arg, &r));
+}
+
+static bool
+checkowner_dlv(ARGS_CHECKOWNER) {
+ REQUIRE(type == dns_rdatatype_dlv);
+
+ UNUSED(name);
+ UNUSED(type);
+ UNUSED(rdclass);
+ UNUSED(wildcard);
+
+ return (true);
+}
+
+static bool
+checknames_dlv(ARGS_CHECKNAMES) {
+ REQUIRE(rdata->type == dns_rdatatype_dlv);
+
+ UNUSED(rdata);
+ UNUSED(owner);
+ UNUSED(bad);
+
+ return (true);
+}
+
+static int
+casecompare_dlv(ARGS_COMPARE) {
+ return (compare_dlv(rdata1, rdata2));
+}
+
+#endif /* RDATA_GENERIC_DLV_32769_C */
diff --git a/lib/dns/rdata/generic/dlv_32769.h b/lib/dns/rdata/generic/dlv_32769.h
new file mode 100644
index 0000000..ec3709f
--- /dev/null
+++ b/lib/dns/rdata/generic/dlv_32769.h
@@ -0,0 +1,17 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/* draft-ietf-dnsext-delegation-signer-05.txt */
+#pragma once
+
+typedef struct dns_rdata_ds dns_rdata_dlv_t;
diff --git a/lib/dns/rdata/generic/dname_39.c b/lib/dns/rdata/generic/dname_39.c
new file mode 100644
index 0000000..2eb1dc8
--- /dev/null
+++ b/lib/dns/rdata/generic/dname_39.c
@@ -0,0 +1,231 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/* RFC2672 */
+
+#ifndef RDATA_GENERIC_DNAME_39_C
+#define RDATA_GENERIC_DNAME_39_C
+
+#define RRTYPE_DNAME_ATTRIBUTES (DNS_RDATATYPEATTR_SINGLETON)
+
+static isc_result_t
+fromtext_dname(ARGS_FROMTEXT) {
+ isc_token_t token;
+ dns_name_t name;
+ isc_buffer_t buffer;
+
+ REQUIRE(type == dns_rdatatype_dname);
+
+ UNUSED(type);
+ UNUSED(rdclass);
+ UNUSED(callbacks);
+
+ RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_string,
+ false));
+
+ dns_name_init(&name, NULL);
+ buffer_fromregion(&buffer, &token.value.as_region);
+ if (origin == NULL) {
+ origin = dns_rootname;
+ }
+ RETTOK(dns_name_fromtext(&name, &buffer, origin, options, target));
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+totext_dname(ARGS_TOTEXT) {
+ isc_region_t region;
+ dns_name_t name;
+ dns_name_t prefix;
+ bool sub;
+
+ REQUIRE(rdata->type == dns_rdatatype_dname);
+ REQUIRE(rdata->length != 0);
+
+ dns_name_init(&name, NULL);
+ dns_name_init(&prefix, NULL);
+
+ dns_rdata_toregion(rdata, &region);
+ dns_name_fromregion(&name, &region);
+
+ sub = name_prefix(&name, tctx->origin, &prefix);
+
+ return (dns_name_totext(&prefix, sub, target));
+}
+
+static isc_result_t
+fromwire_dname(ARGS_FROMWIRE) {
+ dns_name_t name;
+
+ REQUIRE(type == dns_rdatatype_dname);
+
+ UNUSED(type);
+ UNUSED(rdclass);
+
+ dns_decompress_setmethods(dctx, DNS_COMPRESS_NONE);
+
+ dns_name_init(&name, NULL);
+ return (dns_name_fromwire(&name, source, dctx, options, target));
+}
+
+static isc_result_t
+towire_dname(ARGS_TOWIRE) {
+ dns_name_t name;
+ dns_offsets_t offsets;
+ isc_region_t region;
+
+ REQUIRE(rdata->type == dns_rdatatype_dname);
+ REQUIRE(rdata->length != 0);
+
+ dns_compress_setmethods(cctx, DNS_COMPRESS_NONE);
+ dns_name_init(&name, offsets);
+ dns_rdata_toregion(rdata, &region);
+ dns_name_fromregion(&name, &region);
+
+ return (dns_name_towire(&name, cctx, target));
+}
+
+static int
+compare_dname(ARGS_COMPARE) {
+ dns_name_t name1;
+ dns_name_t name2;
+ isc_region_t region1;
+ isc_region_t region2;
+
+ REQUIRE(rdata1->type == rdata2->type);
+ REQUIRE(rdata1->rdclass == rdata2->rdclass);
+ REQUIRE(rdata1->type == dns_rdatatype_dname);
+ REQUIRE(rdata1->length != 0);
+ REQUIRE(rdata2->length != 0);
+
+ dns_name_init(&name1, NULL);
+ dns_name_init(&name2, NULL);
+
+ dns_rdata_toregion(rdata1, &region1);
+ dns_rdata_toregion(rdata2, &region2);
+
+ dns_name_fromregion(&name1, &region1);
+ dns_name_fromregion(&name2, &region2);
+
+ return (dns_name_rdatacompare(&name1, &name2));
+}
+
+static isc_result_t
+fromstruct_dname(ARGS_FROMSTRUCT) {
+ dns_rdata_dname_t *dname = source;
+ isc_region_t region;
+
+ REQUIRE(type == dns_rdatatype_dname);
+ REQUIRE(dname != NULL);
+ REQUIRE(dname->common.rdtype == type);
+ REQUIRE(dname->common.rdclass == rdclass);
+
+ UNUSED(type);
+ UNUSED(rdclass);
+
+ dns_name_toregion(&dname->dname, &region);
+ return (isc_buffer_copyregion(target, &region));
+}
+
+static isc_result_t
+tostruct_dname(ARGS_TOSTRUCT) {
+ isc_region_t region;
+ dns_rdata_dname_t *dname = target;
+ dns_name_t name;
+
+ REQUIRE(rdata->type == dns_rdatatype_dname);
+ REQUIRE(dname != NULL);
+ REQUIRE(rdata->length != 0);
+
+ dname->common.rdclass = rdata->rdclass;
+ dname->common.rdtype = rdata->type;
+ ISC_LINK_INIT(&dname->common, link);
+
+ dns_name_init(&name, NULL);
+ dns_rdata_toregion(rdata, &region);
+ dns_name_fromregion(&name, &region);
+ dns_name_init(&dname->dname, NULL);
+ name_duporclone(&name, mctx, &dname->dname);
+ dname->mctx = mctx;
+ return (ISC_R_SUCCESS);
+}
+
+static void
+freestruct_dname(ARGS_FREESTRUCT) {
+ dns_rdata_dname_t *dname = source;
+
+ REQUIRE(dname != NULL);
+ REQUIRE(dname->common.rdtype == dns_rdatatype_dname);
+
+ if (dname->mctx == NULL) {
+ return;
+ }
+
+ dns_name_free(&dname->dname, dname->mctx);
+ dname->mctx = NULL;
+}
+
+static isc_result_t
+additionaldata_dname(ARGS_ADDLDATA) {
+ REQUIRE(rdata->type == dns_rdatatype_dname);
+
+ UNUSED(rdata);
+ UNUSED(owner);
+ UNUSED(add);
+ UNUSED(arg);
+
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+digest_dname(ARGS_DIGEST) {
+ isc_region_t r;
+ dns_name_t name;
+
+ REQUIRE(rdata->type == dns_rdatatype_dname);
+
+ dns_rdata_toregion(rdata, &r);
+ dns_name_init(&name, NULL);
+ dns_name_fromregion(&name, &r);
+
+ return (dns_name_digest(&name, digest, arg));
+}
+
+static bool
+checkowner_dname(ARGS_CHECKOWNER) {
+ REQUIRE(type == dns_rdatatype_dname);
+
+ UNUSED(name);
+ UNUSED(type);
+ UNUSED(rdclass);
+ UNUSED(wildcard);
+
+ return (true);
+}
+
+static bool
+checknames_dname(ARGS_CHECKNAMES) {
+ REQUIRE(rdata->type == dns_rdatatype_dname);
+
+ UNUSED(rdata);
+ UNUSED(owner);
+ UNUSED(bad);
+
+ return (true);
+}
+
+static int
+casecompare_dname(ARGS_COMPARE) {
+ return (compare_dname(rdata1, rdata2));
+}
+#endif /* RDATA_GENERIC_DNAME_39_C */
diff --git a/lib/dns/rdata/generic/dname_39.h b/lib/dns/rdata/generic/dname_39.h
new file mode 100644
index 0000000..14c71fb
--- /dev/null
+++ b/lib/dns/rdata/generic/dname_39.h
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#pragma once
+
+/*!
+ * \brief per RFC2672 */
+
+typedef struct dns_rdata_dname {
+ dns_rdatacommon_t common;
+ isc_mem_t *mctx;
+ dns_name_t dname;
+} dns_rdata_dname_t;
diff --git a/lib/dns/rdata/generic/dnskey_48.c b/lib/dns/rdata/generic/dnskey_48.c
new file mode 100644
index 0000000..2df1e77
--- /dev/null
+++ b/lib/dns/rdata/generic/dnskey_48.c
@@ -0,0 +1,165 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/* RFC2535 */
+
+#ifndef RDATA_GENERIC_DNSKEY_48_C
+#define RDATA_GENERIC_DNSKEY_48_C
+
+#include <dst/dst.h>
+
+#define RRTYPE_DNSKEY_ATTRIBUTES (DNS_RDATATYPEATTR_DNSSEC)
+
+static isc_result_t
+fromtext_dnskey(ARGS_FROMTEXT) {
+ REQUIRE(type == dns_rdatatype_dnskey);
+
+ return (generic_fromtext_key(CALL_FROMTEXT));
+}
+
+static isc_result_t
+totext_dnskey(ARGS_TOTEXT) {
+ REQUIRE(rdata != NULL);
+ REQUIRE(rdata->type == dns_rdatatype_dnskey);
+
+ return (generic_totext_key(CALL_TOTEXT));
+}
+
+static isc_result_t
+fromwire_dnskey(ARGS_FROMWIRE) {
+ REQUIRE(type == dns_rdatatype_dnskey);
+
+ return (generic_fromwire_key(CALL_FROMWIRE));
+}
+
+static isc_result_t
+towire_dnskey(ARGS_TOWIRE) {
+ isc_region_t sr;
+
+ REQUIRE(rdata != NULL);
+ REQUIRE(rdata->type == dns_rdatatype_dnskey);
+ REQUIRE(rdata->length != 0);
+
+ UNUSED(cctx);
+
+ dns_rdata_toregion(rdata, &sr);
+ return (mem_tobuffer(target, sr.base, sr.length));
+}
+
+static int
+compare_dnskey(ARGS_COMPARE) {
+ isc_region_t r1;
+ isc_region_t r2;
+
+ REQUIRE(rdata1 != NULL);
+ REQUIRE(rdata2 != NULL);
+ REQUIRE(rdata1->type == rdata2->type);
+ REQUIRE(rdata1->rdclass == rdata2->rdclass);
+ REQUIRE(rdata1->type == dns_rdatatype_dnskey);
+ REQUIRE(rdata1->length != 0);
+ REQUIRE(rdata2->length != 0);
+
+ dns_rdata_toregion(rdata1, &r1);
+ dns_rdata_toregion(rdata2, &r2);
+ return (isc_region_compare(&r1, &r2));
+}
+
+static isc_result_t
+fromstruct_dnskey(ARGS_FROMSTRUCT) {
+ REQUIRE(type == dns_rdatatype_dnskey);
+
+ return (generic_fromstruct_key(CALL_FROMSTRUCT));
+}
+
+static isc_result_t
+tostruct_dnskey(ARGS_TOSTRUCT) {
+ dns_rdata_dnskey_t *dnskey = target;
+
+ REQUIRE(dnskey != NULL);
+ REQUIRE(rdata != NULL);
+ REQUIRE(rdata->type == dns_rdatatype_dnskey);
+
+ dnskey->common.rdclass = rdata->rdclass;
+ dnskey->common.rdtype = rdata->type;
+ ISC_LINK_INIT(&dnskey->common, link);
+
+ return (generic_tostruct_key(CALL_TOSTRUCT));
+}
+
+static void
+freestruct_dnskey(ARGS_FREESTRUCT) {
+ dns_rdata_dnskey_t *dnskey = (dns_rdata_dnskey_t *)source;
+
+ REQUIRE(dnskey != NULL);
+ REQUIRE(dnskey->common.rdtype == dns_rdatatype_dnskey);
+
+ generic_freestruct_key(source);
+}
+
+static isc_result_t
+additionaldata_dnskey(ARGS_ADDLDATA) {
+ REQUIRE(rdata->type == dns_rdatatype_dnskey);
+
+ UNUSED(rdata);
+ UNUSED(owner);
+ UNUSED(add);
+ UNUSED(arg);
+
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+digest_dnskey(ARGS_DIGEST) {
+ isc_region_t r;
+
+ REQUIRE(rdata != NULL);
+ REQUIRE(rdata->type == dns_rdatatype_dnskey);
+
+ dns_rdata_toregion(rdata, &r);
+
+ return ((digest)(arg, &r));
+}
+
+static bool
+checkowner_dnskey(ARGS_CHECKOWNER) {
+ REQUIRE(type == dns_rdatatype_dnskey);
+
+ UNUSED(name);
+ UNUSED(type);
+ UNUSED(rdclass);
+ UNUSED(wildcard);
+
+ return (true);
+}
+
+static bool
+checknames_dnskey(ARGS_CHECKNAMES) {
+ REQUIRE(rdata != NULL);
+ REQUIRE(rdata->type == dns_rdatatype_dnskey);
+
+ UNUSED(rdata);
+ UNUSED(owner);
+ UNUSED(bad);
+
+ return (true);
+}
+
+static int
+casecompare_dnskey(ARGS_COMPARE) {
+ /*
+ * Treat ALG 253 (private DNS) subtype name case sensitively.
+ */
+ return (compare_dnskey(rdata1, rdata2));
+}
+
+#endif /* RDATA_GENERIC_DNSKEY_48_C */
diff --git a/lib/dns/rdata/generic/dnskey_48.h b/lib/dns/rdata/generic/dnskey_48.h
new file mode 100644
index 0000000..7a0bc6f
--- /dev/null
+++ b/lib/dns/rdata/generic/dnskey_48.h
@@ -0,0 +1,20 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#pragma once
+
+/*!
+ * \brief per RFC2535
+ */
+
+typedef struct dns_rdata_key dns_rdata_dnskey_t;
diff --git a/lib/dns/rdata/generic/doa_259.c b/lib/dns/rdata/generic/doa_259.c
new file mode 100644
index 0000000..45f2439
--- /dev/null
+++ b/lib/dns/rdata/generic/doa_259.c
@@ -0,0 +1,362 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#ifndef RDATA_GENERIC_DOA_259_C
+#define RDATA_GENERIC_DOA_259_C
+
+#define RRTYPE_DOA_ATTRIBUTES (0)
+
+static isc_result_t
+fromtext_doa(ARGS_FROMTEXT) {
+ isc_token_t token;
+
+ REQUIRE(type == dns_rdatatype_doa);
+
+ UNUSED(rdclass);
+ UNUSED(origin);
+ UNUSED(options);
+ UNUSED(callbacks);
+
+ /*
+ * DOA-ENTERPRISE
+ */
+ RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_number,
+ false));
+ RETERR(uint32_tobuffer(token.value.as_ulong, target));
+
+ /*
+ * DOA-TYPE
+ */
+ RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_number,
+ false));
+ RETERR(uint32_tobuffer(token.value.as_ulong, target));
+
+ /*
+ * DOA-LOCATION
+ */
+ RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_number,
+ false));
+ if (token.value.as_ulong > 0xffU) {
+ RETTOK(ISC_R_RANGE);
+ }
+ RETERR(uint8_tobuffer(token.value.as_ulong, target));
+
+ /*
+ * DOA-MEDIA-TYPE
+ */
+ RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_qstring,
+ false));
+ RETTOK(txt_fromtext(&token.value.as_textregion, target));
+
+ /*
+ * DOA-DATA
+ */
+ RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_string,
+ false));
+ if (strcmp(DNS_AS_STR(token), "-") == 0) {
+ return (ISC_R_SUCCESS);
+ } else {
+ isc_lex_ungettoken(lexer, &token);
+ return (isc_base64_tobuffer(lexer, target, -1));
+ }
+}
+
+static isc_result_t
+totext_doa(ARGS_TOTEXT) {
+ char buf[sizeof("4294967295 ")];
+ isc_region_t region;
+ uint32_t n;
+
+ REQUIRE(rdata != NULL);
+ REQUIRE(rdata->type == dns_rdatatype_doa);
+ REQUIRE(rdata->length != 0);
+
+ UNUSED(tctx);
+
+ dns_rdata_toregion(rdata, &region);
+
+ /*
+ * DOA-ENTERPRISE
+ */
+ n = uint32_fromregion(&region);
+ isc_region_consume(&region, 4);
+ snprintf(buf, sizeof(buf), "%u ", n);
+ RETERR(str_totext(buf, target));
+
+ /*
+ * DOA-TYPE
+ */
+ n = uint32_fromregion(&region);
+ isc_region_consume(&region, 4);
+ snprintf(buf, sizeof(buf), "%u ", n);
+ RETERR(str_totext(buf, target));
+
+ /*
+ * DOA-LOCATION
+ */
+ n = uint8_fromregion(&region);
+ isc_region_consume(&region, 1);
+ snprintf(buf, sizeof(buf), "%u ", n);
+ RETERR(str_totext(buf, target));
+
+ /*
+ * DOA-MEDIA-TYPE
+ */
+ RETERR(txt_totext(&region, true, target));
+ RETERR(str_totext(" ", target));
+
+ /*
+ * DOA-DATA
+ */
+ if (region.length == 0) {
+ return (str_totext("-", target));
+ } else {
+ return (isc_base64_totext(&region, 60, "", target));
+ }
+}
+
+static isc_result_t
+fromwire_doa(ARGS_FROMWIRE) {
+ isc_region_t region;
+
+ UNUSED(rdclass);
+ UNUSED(dctx);
+ UNUSED(options);
+
+ REQUIRE(type == dns_rdatatype_doa);
+
+ isc_buffer_activeregion(source, &region);
+ /*
+ * DOA-MEDIA-TYPE may be an empty <character-string> (i.e.,
+ * comprising of just the length octet) and DOA-DATA can have
+ * zero length.
+ */
+ if (region.length < 4 + 4 + 1 + 1) {
+ return (ISC_R_UNEXPECTEDEND);
+ }
+
+ /*
+ * Check whether DOA-MEDIA-TYPE length is not malformed.
+ */
+ if (region.base[9] > region.length - 10) {
+ return (ISC_R_UNEXPECTEDEND);
+ }
+
+ isc_buffer_forward(source, region.length);
+ return (mem_tobuffer(target, region.base, region.length));
+}
+
+static isc_result_t
+towire_doa(ARGS_TOWIRE) {
+ isc_region_t region;
+
+ UNUSED(cctx);
+
+ REQUIRE(rdata != NULL);
+ REQUIRE(rdata->type == dns_rdatatype_doa);
+ REQUIRE(rdata->length != 0);
+
+ dns_rdata_toregion(rdata, &region);
+ return (mem_tobuffer(target, region.base, region.length));
+}
+
+static int
+compare_doa(ARGS_COMPARE) {
+ isc_region_t r1;
+ isc_region_t r2;
+
+ REQUIRE(rdata1 != NULL);
+ REQUIRE(rdata2 != NULL);
+ REQUIRE(rdata1->type == rdata2->type);
+ REQUIRE(rdata1->type == dns_rdatatype_doa);
+ REQUIRE(rdata1->rdclass == rdata2->rdclass);
+ REQUIRE(rdata1->length != 0);
+ REQUIRE(rdata2->length != 0);
+
+ dns_rdata_toregion(rdata1, &r1);
+ dns_rdata_toregion(rdata2, &r2);
+ return (isc_region_compare(&r1, &r2));
+}
+
+static isc_result_t
+fromstruct_doa(ARGS_FROMSTRUCT) {
+ dns_rdata_doa_t *doa = source;
+
+ REQUIRE(type == dns_rdatatype_doa);
+ REQUIRE(doa != NULL);
+ REQUIRE(doa->common.rdtype == dns_rdatatype_doa);
+ REQUIRE(doa->common.rdclass == rdclass);
+
+ RETERR(uint32_tobuffer(doa->enterprise, target));
+ RETERR(uint32_tobuffer(doa->type, target));
+ RETERR(uint8_tobuffer(doa->location, target));
+ RETERR(uint8_tobuffer(doa->mediatype_len, target));
+ RETERR(mem_tobuffer(target, doa->mediatype, doa->mediatype_len));
+ return (mem_tobuffer(target, doa->data, doa->data_len));
+}
+
+static isc_result_t
+tostruct_doa(ARGS_TOSTRUCT) {
+ dns_rdata_doa_t *doa = target;
+ isc_region_t region;
+
+ REQUIRE(rdata != NULL);
+ REQUIRE(rdata->type == dns_rdatatype_doa);
+ REQUIRE(doa != NULL);
+ REQUIRE(rdata->length != 0);
+
+ doa->common.rdclass = rdata->rdclass;
+ doa->common.rdtype = rdata->type;
+ ISC_LINK_INIT(&doa->common, link);
+
+ dns_rdata_toregion(rdata, &region);
+
+ /*
+ * DOA-ENTERPRISE
+ */
+ if (region.length < 4) {
+ return (ISC_R_UNEXPECTEDEND);
+ }
+ doa->enterprise = uint32_fromregion(&region);
+ isc_region_consume(&region, 4);
+
+ /*
+ * DOA-TYPE
+ */
+ if (region.length < 4) {
+ return (ISC_R_UNEXPECTEDEND);
+ }
+ doa->type = uint32_fromregion(&region);
+ isc_region_consume(&region, 4);
+
+ /*
+ * DOA-LOCATION
+ */
+ if (region.length < 1) {
+ return (ISC_R_UNEXPECTEDEND);
+ }
+ doa->location = uint8_fromregion(&region);
+ isc_region_consume(&region, 1);
+
+ /*
+ * DOA-MEDIA-TYPE
+ */
+ if (region.length < 1) {
+ return (ISC_R_UNEXPECTEDEND);
+ }
+ doa->mediatype_len = uint8_fromregion(&region);
+ isc_region_consume(&region, 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(&region, 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(&region, doa->data_len);
+ }
+
+ doa->mctx = mctx;
+
+ return (ISC_R_SUCCESS);
+
+cleanup:
+ if (mctx != NULL && doa->mediatype != NULL) {
+ isc_mem_free(mctx, doa->mediatype);
+ }
+ return (ISC_R_NOMEMORY);
+}
+
+static void
+freestruct_doa(ARGS_FREESTRUCT) {
+ dns_rdata_doa_t *doa = source;
+
+ REQUIRE(doa != NULL);
+ REQUIRE(doa->common.rdtype == dns_rdatatype_doa);
+
+ if (doa->mctx == NULL) {
+ return;
+ }
+
+ if (doa->mediatype != NULL) {
+ isc_mem_free(doa->mctx, doa->mediatype);
+ }
+ if (doa->data != NULL) {
+ isc_mem_free(doa->mctx, doa->data);
+ }
+
+ doa->mctx = NULL;
+}
+
+static isc_result_t
+additionaldata_doa(ARGS_ADDLDATA) {
+ REQUIRE(rdata->type == dns_rdatatype_doa);
+
+ UNUSED(rdata);
+ UNUSED(owner);
+ UNUSED(add);
+ UNUSED(arg);
+
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+digest_doa(ARGS_DIGEST) {
+ isc_region_t r;
+
+ REQUIRE(rdata->type == dns_rdatatype_doa);
+
+ dns_rdata_toregion(rdata, &r);
+
+ return ((digest)(arg, &r));
+}
+
+static bool
+checkowner_doa(ARGS_CHECKOWNER) {
+ UNUSED(name);
+ UNUSED(type);
+ UNUSED(rdclass);
+ UNUSED(wildcard);
+
+ REQUIRE(type == dns_rdatatype_doa);
+
+ return (true);
+}
+
+static bool
+checknames_doa(ARGS_CHECKNAMES) {
+ UNUSED(rdata);
+ UNUSED(owner);
+ UNUSED(bad);
+
+ REQUIRE(rdata->type == dns_rdatatype_doa);
+
+ return (true);
+}
+
+static int
+casecompare_doa(ARGS_COMPARE) {
+ return (compare_doa(rdata1, rdata2));
+}
+
+#endif /* RDATA_GENERIC_DOA_259_C */
diff --git a/lib/dns/rdata/generic/doa_259.h b/lib/dns/rdata/generic/doa_259.h
new file mode 100644
index 0000000..320bd7a
--- /dev/null
+++ b/lib/dns/rdata/generic/doa_259.h
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#pragma once
+
+typedef struct dns_rdata_doa {
+ dns_rdatacommon_t common;
+ isc_mem_t *mctx;
+ unsigned char *mediatype;
+ unsigned char *data;
+ uint32_t enterprise;
+ uint32_t type;
+ uint16_t data_len;
+ uint8_t location;
+ uint8_t mediatype_len;
+} dns_rdata_doa_t;
diff --git a/lib/dns/rdata/generic/ds_43.c b/lib/dns/rdata/generic/ds_43.c
new file mode 100644
index 0000000..482f32b
--- /dev/null
+++ b/lib/dns/rdata/generic/ds_43.c
@@ -0,0 +1,386 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/* RFC3658 */
+
+#ifndef RDATA_GENERIC_DS_43_C
+#define RDATA_GENERIC_DS_43_C
+
+#define RRTYPE_DS_ATTRIBUTES \
+ (DNS_RDATATYPEATTR_DNSSEC | DNS_RDATATYPEATTR_ZONECUTAUTH | \
+ DNS_RDATATYPEATTR_ATPARENT)
+
+#include <isc/md.h>
+
+#include <dns/ds.h>
+
+static isc_result_t
+generic_fromtext_ds(ARGS_FROMTEXT) {
+ isc_token_t token;
+ unsigned char c;
+ int length;
+
+ UNUSED(type);
+ UNUSED(rdclass);
+ UNUSED(origin);
+ UNUSED(options);
+ UNUSED(callbacks);
+
+ /*
+ * Key tag.
+ */
+ RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_number,
+ false));
+ if (token.value.as_ulong > 0xffffU) {
+ RETTOK(ISC_R_RANGE);
+ }
+ RETERR(uint16_tobuffer(token.value.as_ulong, target));
+
+ /*
+ * Algorithm.
+ */
+ RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_string,
+ false));
+ RETTOK(dns_secalg_fromtext(&c, &token.value.as_textregion));
+ RETERR(mem_tobuffer(target, &c, 1));
+
+ /*
+ * Digest type.
+ */
+ RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_string,
+ false));
+ RETTOK(dns_dsdigest_fromtext(&c, &token.value.as_textregion));
+ RETERR(mem_tobuffer(target, &c, 1));
+
+ /*
+ * Digest.
+ */
+ switch (c) {
+ case DNS_DSDIGEST_SHA1:
+ length = ISC_SHA1_DIGESTLENGTH;
+ break;
+ case DNS_DSDIGEST_SHA256:
+ length = ISC_SHA256_DIGESTLENGTH;
+ break;
+ case DNS_DSDIGEST_SHA384:
+ length = ISC_SHA384_DIGESTLENGTH;
+ break;
+ default:
+ length = -2;
+ break;
+ }
+ return (isc_hex_tobuffer(lexer, target, length));
+}
+
+static isc_result_t
+fromtext_ds(ARGS_FROMTEXT) {
+ REQUIRE(type == dns_rdatatype_ds);
+
+ return (generic_fromtext_ds(CALL_FROMTEXT));
+}
+
+static isc_result_t
+generic_totext_ds(ARGS_TOTEXT) {
+ isc_region_t sr;
+ char buf[sizeof("64000 ")];
+ unsigned int n;
+
+ REQUIRE(rdata->length != 0);
+
+ UNUSED(tctx);
+
+ dns_rdata_toregion(rdata, &sr);
+
+ /*
+ * Key tag.
+ */
+ n = uint16_fromregion(&sr);
+ isc_region_consume(&sr, 2);
+ snprintf(buf, sizeof(buf), "%u ", n);
+ RETERR(str_totext(buf, target));
+
+ /*
+ * Algorithm.
+ */
+ n = uint8_fromregion(&sr);
+ isc_region_consume(&sr, 1);
+ snprintf(buf, sizeof(buf), "%u ", n);
+ RETERR(str_totext(buf, target));
+
+ /*
+ * Digest type.
+ */
+ n = uint8_fromregion(&sr);
+ isc_region_consume(&sr, 1);
+ snprintf(buf, sizeof(buf), "%u", n);
+ RETERR(str_totext(buf, target));
+
+ /*
+ * Digest.
+ */
+ if ((tctx->flags & DNS_STYLEFLAG_MULTILINE) != 0) {
+ RETERR(str_totext(" (", target));
+ }
+ RETERR(str_totext(tctx->linebreak, target));
+ if ((tctx->flags & DNS_STYLEFLAG_NOCRYPTO) == 0) {
+ if (tctx->width == 0) { /* No splitting */
+ RETERR(isc_hex_totext(&sr, 0, "", target));
+ } else {
+ RETERR(isc_hex_totext(&sr, tctx->width - 2,
+ tctx->linebreak, target));
+ }
+ } else {
+ RETERR(str_totext("[omitted]", target));
+ }
+ if ((tctx->flags & DNS_STYLEFLAG_MULTILINE) != 0) {
+ RETERR(str_totext(" )", target));
+ }
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+totext_ds(ARGS_TOTEXT) {
+ REQUIRE(rdata != NULL);
+ REQUIRE(rdata->type == dns_rdatatype_ds);
+
+ return (generic_totext_ds(CALL_TOTEXT));
+}
+
+static isc_result_t
+generic_fromwire_ds(ARGS_FROMWIRE) {
+ isc_region_t sr;
+
+ UNUSED(type);
+ UNUSED(rdclass);
+ UNUSED(dctx);
+ UNUSED(options);
+
+ isc_buffer_activeregion(source, &sr);
+
+ /*
+ * Check digest lengths if we know them.
+ */
+ if (sr.length < 5 ||
+ (sr.base[3] == DNS_DSDIGEST_SHA1 &&
+ sr.length < 4 + ISC_SHA1_DIGESTLENGTH) ||
+ (sr.base[3] == DNS_DSDIGEST_SHA256 &&
+ sr.length < 4 + ISC_SHA256_DIGESTLENGTH) ||
+ (sr.base[3] == DNS_DSDIGEST_SHA384 &&
+ sr.length < 4 + ISC_SHA384_DIGESTLENGTH))
+ {
+ return (ISC_R_UNEXPECTEDEND);
+ }
+
+ /*
+ * Only copy digest lengths if we know them.
+ * If there is extra data dns_rdata_fromwire() will
+ * detect that.
+ */
+ if (sr.base[3] == DNS_DSDIGEST_SHA1) {
+ sr.length = 4 + ISC_SHA1_DIGESTLENGTH;
+ } else if (sr.base[3] == DNS_DSDIGEST_SHA256) {
+ sr.length = 4 + ISC_SHA256_DIGESTLENGTH;
+ } else if (sr.base[3] == DNS_DSDIGEST_SHA384) {
+ sr.length = 4 + ISC_SHA384_DIGESTLENGTH;
+ }
+
+ isc_buffer_forward(source, sr.length);
+ return (mem_tobuffer(target, sr.base, sr.length));
+}
+
+static isc_result_t
+fromwire_ds(ARGS_FROMWIRE) {
+ REQUIRE(type == dns_rdatatype_ds);
+
+ return (generic_fromwire_ds(CALL_FROMWIRE));
+}
+
+static isc_result_t
+towire_ds(ARGS_TOWIRE) {
+ isc_region_t sr;
+
+ REQUIRE(rdata->type == dns_rdatatype_ds);
+ REQUIRE(rdata->length != 0);
+
+ UNUSED(cctx);
+
+ dns_rdata_toregion(rdata, &sr);
+ return (mem_tobuffer(target, sr.base, sr.length));
+}
+
+static int
+compare_ds(ARGS_COMPARE) {
+ isc_region_t r1;
+ isc_region_t r2;
+
+ REQUIRE(rdata1->type == rdata2->type);
+ REQUIRE(rdata1->rdclass == rdata2->rdclass);
+ REQUIRE(rdata1->type == dns_rdatatype_ds);
+ REQUIRE(rdata1->length != 0);
+ REQUIRE(rdata2->length != 0);
+
+ dns_rdata_toregion(rdata1, &r1);
+ dns_rdata_toregion(rdata2, &r2);
+ return (isc_region_compare(&r1, &r2));
+}
+
+static isc_result_t
+generic_fromstruct_ds(ARGS_FROMSTRUCT) {
+ dns_rdata_ds_t *ds = source;
+
+ REQUIRE(ds != NULL);
+ REQUIRE(ds->common.rdtype == type);
+ REQUIRE(ds->common.rdclass == rdclass);
+
+ UNUSED(type);
+ UNUSED(rdclass);
+
+ switch (ds->digest_type) {
+ case DNS_DSDIGEST_SHA1:
+ REQUIRE(ds->length == ISC_SHA1_DIGESTLENGTH);
+ break;
+ case DNS_DSDIGEST_SHA256:
+ REQUIRE(ds->length == ISC_SHA256_DIGESTLENGTH);
+ break;
+ case DNS_DSDIGEST_SHA384:
+ REQUIRE(ds->length == ISC_SHA384_DIGESTLENGTH);
+ break;
+ }
+
+ RETERR(uint16_tobuffer(ds->key_tag, target));
+ RETERR(uint8_tobuffer(ds->algorithm, target));
+ RETERR(uint8_tobuffer(ds->digest_type, target));
+
+ return (mem_tobuffer(target, ds->digest, ds->length));
+}
+
+static isc_result_t
+fromstruct_ds(ARGS_FROMSTRUCT) {
+ REQUIRE(type == dns_rdatatype_ds);
+
+ return (generic_fromstruct_ds(CALL_FROMSTRUCT));
+}
+
+static isc_result_t
+generic_tostruct_ds(ARGS_TOSTRUCT) {
+ dns_rdata_ds_t *ds = target;
+ isc_region_t region;
+
+ REQUIRE(ds != NULL);
+ REQUIRE(rdata->length != 0);
+ REQUIRE(ds->common.rdtype == rdata->type);
+ REQUIRE(ds->common.rdclass == rdata->rdclass);
+ REQUIRE(!ISC_LINK_LINKED(&ds->common, link));
+
+ dns_rdata_toregion(rdata, &region);
+
+ ds->key_tag = uint16_fromregion(&region);
+ isc_region_consume(&region, 2);
+ ds->algorithm = uint8_fromregion(&region);
+ isc_region_consume(&region, 1);
+ ds->digest_type = uint8_fromregion(&region);
+ isc_region_consume(&region, 1);
+ ds->length = region.length;
+
+ ds->digest = mem_maybedup(mctx, region.base, region.length);
+ if (ds->digest == NULL) {
+ return (ISC_R_NOMEMORY);
+ }
+
+ ds->mctx = mctx;
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+tostruct_ds(ARGS_TOSTRUCT) {
+ dns_rdata_ds_t *ds = target;
+
+ REQUIRE(rdata->type == dns_rdatatype_ds);
+ REQUIRE(ds != NULL);
+
+ ds->common.rdclass = rdata->rdclass;
+ ds->common.rdtype = rdata->type;
+ ISC_LINK_INIT(&ds->common, link);
+
+ return (generic_tostruct_ds(CALL_TOSTRUCT));
+}
+
+static void
+freestruct_ds(ARGS_FREESTRUCT) {
+ dns_rdata_ds_t *ds = source;
+
+ REQUIRE(ds != NULL);
+ REQUIRE(ds->common.rdtype == dns_rdatatype_ds);
+
+ if (ds->mctx == NULL) {
+ return;
+ }
+
+ if (ds->digest != NULL) {
+ isc_mem_free(ds->mctx, ds->digest);
+ }
+ ds->mctx = NULL;
+}
+
+static isc_result_t
+additionaldata_ds(ARGS_ADDLDATA) {
+ REQUIRE(rdata->type == dns_rdatatype_ds);
+
+ UNUSED(rdata);
+ UNUSED(owner);
+ UNUSED(add);
+ UNUSED(arg);
+
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+digest_ds(ARGS_DIGEST) {
+ isc_region_t r;
+
+ REQUIRE(rdata->type == dns_rdatatype_ds);
+
+ dns_rdata_toregion(rdata, &r);
+
+ return ((digest)(arg, &r));
+}
+
+static bool
+checkowner_ds(ARGS_CHECKOWNER) {
+ REQUIRE(type == dns_rdatatype_ds);
+
+ UNUSED(name);
+ UNUSED(type);
+ UNUSED(rdclass);
+ UNUSED(wildcard);
+
+ return (true);
+}
+
+static bool
+checknames_ds(ARGS_CHECKNAMES) {
+ REQUIRE(rdata->type == dns_rdatatype_ds);
+
+ UNUSED(rdata);
+ UNUSED(owner);
+ UNUSED(bad);
+
+ return (true);
+}
+
+static int
+casecompare_ds(ARGS_COMPARE) {
+ return (compare_ds(rdata1, rdata2));
+}
+
+#endif /* RDATA_GENERIC_DS_43_C */
diff --git a/lib/dns/rdata/generic/ds_43.h b/lib/dns/rdata/generic/ds_43.h
new file mode 100644
index 0000000..57e8494
--- /dev/null
+++ b/lib/dns/rdata/generic/ds_43.h
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#pragma once
+
+/*!
+ * \brief per draft-ietf-dnsext-delegation-signer-05.txt */
+typedef struct dns_rdata_ds {
+ dns_rdatacommon_t common;
+ isc_mem_t *mctx;
+ uint16_t key_tag;
+ dns_secalg_t algorithm;
+ dns_dsdigest_t digest_type;
+ uint16_t length;
+ unsigned char *digest;
+} dns_rdata_ds_t;
diff --git a/lib/dns/rdata/generic/eui48_108.c b/lib/dns/rdata/generic/eui48_108.c
new file mode 100644
index 0000000..25603b2
--- /dev/null
+++ b/lib/dns/rdata/generic/eui48_108.c
@@ -0,0 +1,212 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#ifndef RDATA_GENERIC_EUI48_108_C
+#define RDATA_GENERIC_EUI48_108_C
+
+#include <string.h>
+
+#define RRTYPE_EUI48_ATTRIBUTES (0)
+
+static isc_result_t
+fromtext_eui48(ARGS_FROMTEXT) {
+ isc_token_t token;
+ unsigned char eui48[6];
+ unsigned int l0, l1, l2, l3, l4, l5;
+ int n;
+
+ REQUIRE(type == dns_rdatatype_eui48);
+
+ UNUSED(type);
+ UNUSED(rdclass);
+ UNUSED(origin);
+ UNUSED(options);
+ UNUSED(callbacks);
+
+ RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_string,
+ false));
+ n = sscanf(DNS_AS_STR(token), "%2x-%2x-%2x-%2x-%2x-%2x", &l0, &l1, &l2,
+ &l3, &l4, &l5);
+ if (n != 6 || l0 > 255U || l1 > 255U || l2 > 255U || l3 > 255U ||
+ l4 > 255U || l5 > 255U)
+ {
+ return (DNS_R_BADEUI);
+ }
+
+ eui48[0] = l0;
+ eui48[1] = l1;
+ eui48[2] = l2;
+ eui48[3] = l3;
+ eui48[4] = l4;
+ eui48[5] = l5;
+ return (mem_tobuffer(target, eui48, sizeof(eui48)));
+}
+
+static isc_result_t
+totext_eui48(ARGS_TOTEXT) {
+ char buf[sizeof("xx-xx-xx-xx-xx-xx")];
+
+ REQUIRE(rdata->type == dns_rdatatype_eui48);
+ REQUIRE(rdata->length == 6);
+
+ UNUSED(tctx);
+
+ (void)snprintf(buf, sizeof(buf), "%02x-%02x-%02x-%02x-%02x-%02x",
+ rdata->data[0], rdata->data[1], rdata->data[2],
+ rdata->data[3], rdata->data[4], rdata->data[5]);
+ return (str_totext(buf, target));
+}
+
+static isc_result_t
+fromwire_eui48(ARGS_FROMWIRE) {
+ isc_region_t sregion;
+
+ REQUIRE(type == dns_rdatatype_eui48);
+
+ UNUSED(type);
+ UNUSED(options);
+ UNUSED(rdclass);
+ UNUSED(dctx);
+
+ isc_buffer_activeregion(source, &sregion);
+ if (sregion.length != 6) {
+ return (DNS_R_FORMERR);
+ }
+ isc_buffer_forward(source, sregion.length);
+ return (mem_tobuffer(target, sregion.base, sregion.length));
+}
+
+static isc_result_t
+towire_eui48(ARGS_TOWIRE) {
+ REQUIRE(rdata->type == dns_rdatatype_eui48);
+ REQUIRE(rdata->length == 6);
+
+ UNUSED(cctx);
+
+ return (mem_tobuffer(target, rdata->data, rdata->length));
+}
+
+static int
+compare_eui48(ARGS_COMPARE) {
+ isc_region_t region1;
+ isc_region_t region2;
+
+ REQUIRE(rdata1->type == rdata2->type);
+ REQUIRE(rdata1->rdclass == rdata2->rdclass);
+ REQUIRE(rdata1->type == dns_rdatatype_eui48);
+ REQUIRE(rdata1->length == 6);
+ REQUIRE(rdata2->length == 6);
+
+ dns_rdata_toregion(rdata1, &region1);
+ dns_rdata_toregion(rdata2, &region2);
+ return (isc_region_compare(&region1, &region2));
+}
+
+static isc_result_t
+fromstruct_eui48(ARGS_FROMSTRUCT) {
+ dns_rdata_eui48_t *eui48 = source;
+
+ REQUIRE(type == dns_rdatatype_eui48);
+ REQUIRE(eui48 != NULL);
+ REQUIRE(eui48->common.rdtype == type);
+ REQUIRE(eui48->common.rdclass == rdclass);
+
+ UNUSED(type);
+ UNUSED(rdclass);
+
+ return (mem_tobuffer(target, eui48->eui48, sizeof(eui48->eui48)));
+}
+
+static isc_result_t
+tostruct_eui48(ARGS_TOSTRUCT) {
+ dns_rdata_eui48_t *eui48 = target;
+
+ REQUIRE(rdata->type == dns_rdatatype_eui48);
+ REQUIRE(eui48 != NULL);
+ REQUIRE(rdata->length == 6);
+
+ UNUSED(mctx);
+
+ eui48->common.rdclass = rdata->rdclass;
+ eui48->common.rdtype = rdata->type;
+ ISC_LINK_INIT(&eui48->common, link);
+
+ memmove(eui48->eui48, rdata->data, rdata->length);
+ return (ISC_R_SUCCESS);
+}
+
+static void
+freestruct_eui48(ARGS_FREESTRUCT) {
+ dns_rdata_eui48_t *eui48 = source;
+
+ REQUIRE(eui48 != NULL);
+ REQUIRE(eui48->common.rdtype == dns_rdatatype_eui48);
+
+ return;
+}
+
+static isc_result_t
+additionaldata_eui48(ARGS_ADDLDATA) {
+ REQUIRE(rdata->type == dns_rdatatype_eui48);
+ REQUIRE(rdata->length == 6);
+
+ UNUSED(rdata);
+ UNUSED(owner);
+ UNUSED(add);
+ UNUSED(arg);
+
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+digest_eui48(ARGS_DIGEST) {
+ isc_region_t r;
+
+ REQUIRE(rdata->type == dns_rdatatype_eui48);
+ REQUIRE(rdata->length == 6);
+
+ dns_rdata_toregion(rdata, &r);
+
+ return ((digest)(arg, &r));
+}
+
+static bool
+checkowner_eui48(ARGS_CHECKOWNER) {
+ REQUIRE(type == dns_rdatatype_eui48);
+
+ UNUSED(name);
+ UNUSED(type);
+ UNUSED(rdclass);
+ UNUSED(wildcard);
+
+ return (true);
+}
+
+static bool
+checknames_eui48(ARGS_CHECKNAMES) {
+ REQUIRE(rdata->type == dns_rdatatype_eui48);
+ REQUIRE(rdata->length == 6);
+
+ UNUSED(rdata);
+ UNUSED(owner);
+ UNUSED(bad);
+
+ return (true);
+}
+
+static int
+casecompare_eui48(ARGS_COMPARE) {
+ return (compare_eui48(rdata1, rdata2));
+}
+
+#endif /* RDATA_GENERIC_EUI48_108_C */
diff --git a/lib/dns/rdata/generic/eui48_108.h b/lib/dns/rdata/generic/eui48_108.h
new file mode 100644
index 0000000..fb3cebd
--- /dev/null
+++ b/lib/dns/rdata/generic/eui48_108.h
@@ -0,0 +1,20 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/* */
+#pragma once
+
+typedef struct dns_rdata_eui48 {
+ dns_rdatacommon_t common;
+ unsigned char eui48[6];
+} dns_rdata_eui48_t;
diff --git a/lib/dns/rdata/generic/eui64_109.c b/lib/dns/rdata/generic/eui64_109.c
new file mode 100644
index 0000000..93978d2
--- /dev/null
+++ b/lib/dns/rdata/generic/eui64_109.c
@@ -0,0 +1,215 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#ifndef RDATA_GENERIC_EUI64_109_C
+#define RDATA_GENERIC_EUI64_109_C
+
+#include <string.h>
+
+#define RRTYPE_EUI64_ATTRIBUTES (0)
+
+static isc_result_t
+fromtext_eui64(ARGS_FROMTEXT) {
+ isc_token_t token;
+ unsigned char eui64[8];
+ unsigned int l0, l1, l2, l3, l4, l5, l6, l7;
+ int n;
+
+ REQUIRE(type == dns_rdatatype_eui64);
+
+ UNUSED(type);
+ UNUSED(rdclass);
+ UNUSED(origin);
+ UNUSED(options);
+ UNUSED(callbacks);
+
+ RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_string,
+ false));
+ n = sscanf(DNS_AS_STR(token), "%2x-%2x-%2x-%2x-%2x-%2x-%2x-%2x", &l0,
+ &l1, &l2, &l3, &l4, &l5, &l6, &l7);
+ if (n != 8 || l0 > 255U || l1 > 255U || l2 > 255U || l3 > 255U ||
+ l4 > 255U || l5 > 255U || l6 > 255U || l7 > 255U)
+ {
+ return (DNS_R_BADEUI);
+ }
+
+ eui64[0] = l0;
+ eui64[1] = l1;
+ eui64[2] = l2;
+ eui64[3] = l3;
+ eui64[4] = l4;
+ eui64[5] = l5;
+ eui64[6] = l6;
+ eui64[7] = l7;
+ return (mem_tobuffer(target, eui64, sizeof(eui64)));
+}
+
+static isc_result_t
+totext_eui64(ARGS_TOTEXT) {
+ char buf[sizeof("xx-xx-xx-xx-xx-xx-xx-xx")];
+
+ REQUIRE(rdata->type == dns_rdatatype_eui64);
+ REQUIRE(rdata->length == 8);
+
+ UNUSED(tctx);
+
+ (void)snprintf(
+ buf, sizeof(buf), "%02x-%02x-%02x-%02x-%02x-%02x-%02x-%02x",
+ rdata->data[0], rdata->data[1], rdata->data[2], rdata->data[3],
+ rdata->data[4], rdata->data[5], rdata->data[6], rdata->data[7]);
+ return (str_totext(buf, target));
+}
+
+static isc_result_t
+fromwire_eui64(ARGS_FROMWIRE) {
+ isc_region_t sregion;
+
+ REQUIRE(type == dns_rdatatype_eui64);
+
+ UNUSED(type);
+ UNUSED(options);
+ UNUSED(rdclass);
+ UNUSED(dctx);
+
+ isc_buffer_activeregion(source, &sregion);
+ if (sregion.length != 8) {
+ return (DNS_R_FORMERR);
+ }
+ isc_buffer_forward(source, sregion.length);
+ return (mem_tobuffer(target, sregion.base, sregion.length));
+}
+
+static isc_result_t
+towire_eui64(ARGS_TOWIRE) {
+ REQUIRE(rdata->type == dns_rdatatype_eui64);
+ REQUIRE(rdata->length == 8);
+
+ UNUSED(cctx);
+
+ return (mem_tobuffer(target, rdata->data, rdata->length));
+}
+
+static int
+compare_eui64(ARGS_COMPARE) {
+ isc_region_t region1;
+ isc_region_t region2;
+
+ REQUIRE(rdata1->type == rdata2->type);
+ REQUIRE(rdata1->rdclass == rdata2->rdclass);
+ REQUIRE(rdata1->type == dns_rdatatype_eui64);
+ REQUIRE(rdata1->length == 8);
+ REQUIRE(rdata2->length == 8);
+
+ dns_rdata_toregion(rdata1, &region1);
+ dns_rdata_toregion(rdata2, &region2);
+ return (isc_region_compare(&region1, &region2));
+}
+
+static isc_result_t
+fromstruct_eui64(ARGS_FROMSTRUCT) {
+ dns_rdata_eui64_t *eui64 = source;
+
+ REQUIRE(type == dns_rdatatype_eui64);
+ REQUIRE(eui64 != NULL);
+ REQUIRE(eui64->common.rdtype == type);
+ REQUIRE(eui64->common.rdclass == rdclass);
+
+ UNUSED(type);
+ UNUSED(rdclass);
+
+ return (mem_tobuffer(target, eui64->eui64, sizeof(eui64->eui64)));
+}
+
+static isc_result_t
+tostruct_eui64(ARGS_TOSTRUCT) {
+ dns_rdata_eui64_t *eui64 = target;
+
+ REQUIRE(rdata->type == dns_rdatatype_eui64);
+ REQUIRE(eui64 != NULL);
+ REQUIRE(rdata->length == 8);
+
+ UNUSED(mctx);
+
+ eui64->common.rdclass = rdata->rdclass;
+ eui64->common.rdtype = rdata->type;
+ ISC_LINK_INIT(&eui64->common, link);
+
+ memmove(eui64->eui64, rdata->data, rdata->length);
+ return (ISC_R_SUCCESS);
+}
+
+static void
+freestruct_eui64(ARGS_FREESTRUCT) {
+ dns_rdata_eui64_t *eui64 = source;
+
+ REQUIRE(eui64 != NULL);
+ REQUIRE(eui64->common.rdtype == dns_rdatatype_eui64);
+
+ return;
+}
+
+static isc_result_t
+additionaldata_eui64(ARGS_ADDLDATA) {
+ REQUIRE(rdata->type == dns_rdatatype_eui64);
+ REQUIRE(rdata->length == 8);
+
+ UNUSED(rdata);
+ UNUSED(owner);
+ UNUSED(add);
+ UNUSED(arg);
+
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+digest_eui64(ARGS_DIGEST) {
+ isc_region_t r;
+
+ REQUIRE(rdata->type == dns_rdatatype_eui64);
+ REQUIRE(rdata->length == 8);
+
+ dns_rdata_toregion(rdata, &r);
+
+ return ((digest)(arg, &r));
+}
+
+static bool
+checkowner_eui64(ARGS_CHECKOWNER) {
+ REQUIRE(type == dns_rdatatype_eui64);
+
+ UNUSED(name);
+ UNUSED(type);
+ UNUSED(rdclass);
+ UNUSED(wildcard);
+
+ return (true);
+}
+
+static bool
+checknames_eui64(ARGS_CHECKNAMES) {
+ REQUIRE(rdata->type == dns_rdatatype_eui64);
+ REQUIRE(rdata->length == 8);
+
+ UNUSED(rdata);
+ UNUSED(owner);
+ UNUSED(bad);
+
+ return (true);
+}
+
+static int
+casecompare_eui64(ARGS_COMPARE) {
+ return (compare_eui64(rdata1, rdata2));
+}
+
+#endif /* RDATA_GENERIC_EUI64_109_C */
diff --git a/lib/dns/rdata/generic/eui64_109.h b/lib/dns/rdata/generic/eui64_109.h
new file mode 100644
index 0000000..c5f6d89
--- /dev/null
+++ b/lib/dns/rdata/generic/eui64_109.h
@@ -0,0 +1,20 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/* */
+#pragma once
+
+typedef struct dns_rdata_eui64 {
+ dns_rdatacommon_t common;
+ unsigned char eui64[8];
+} dns_rdata_eui64_t;
diff --git a/lib/dns/rdata/generic/gpos_27.c b/lib/dns/rdata/generic/gpos_27.c
new file mode 100644
index 0000000..4f2f3fd
--- /dev/null
+++ b/lib/dns/rdata/generic/gpos_27.c
@@ -0,0 +1,257 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/* RFC1712 */
+
+#ifndef RDATA_GENERIC_GPOS_27_C
+#define RDATA_GENERIC_GPOS_27_C
+
+#define RRTYPE_GPOS_ATTRIBUTES (0)
+
+static isc_result_t
+fromtext_gpos(ARGS_FROMTEXT) {
+ isc_token_t token;
+ int i;
+
+ REQUIRE(type == dns_rdatatype_gpos);
+
+ UNUSED(type);
+ UNUSED(rdclass);
+ UNUSED(origin);
+ UNUSED(options);
+ UNUSED(callbacks);
+
+ for (i = 0; i < 3; i++) {
+ RETERR(isc_lex_getmastertoken(lexer, &token,
+ isc_tokentype_qstring, false));
+ RETTOK(txt_fromtext(&token.value.as_textregion, target));
+ }
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+totext_gpos(ARGS_TOTEXT) {
+ isc_region_t region;
+ int i;
+
+ REQUIRE(rdata->type == dns_rdatatype_gpos);
+ REQUIRE(rdata->length != 0);
+
+ UNUSED(tctx);
+
+ dns_rdata_toregion(rdata, &region);
+
+ for (i = 0; i < 3; i++) {
+ RETERR(txt_totext(&region, true, target));
+ if (i != 2) {
+ RETERR(str_totext(" ", target));
+ }
+ }
+
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+fromwire_gpos(ARGS_FROMWIRE) {
+ int i;
+
+ REQUIRE(type == dns_rdatatype_gpos);
+
+ UNUSED(type);
+ UNUSED(dctx);
+ UNUSED(rdclass);
+ UNUSED(options);
+
+ for (i = 0; i < 3; i++) {
+ RETERR(txt_fromwire(source, target));
+ }
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+towire_gpos(ARGS_TOWIRE) {
+ REQUIRE(rdata->type == dns_rdatatype_gpos);
+ REQUIRE(rdata->length != 0);
+
+ UNUSED(cctx);
+
+ return (mem_tobuffer(target, rdata->data, rdata->length));
+}
+
+static int
+compare_gpos(ARGS_COMPARE) {
+ isc_region_t r1;
+ isc_region_t r2;
+
+ REQUIRE(rdata1->type == rdata2->type);
+ REQUIRE(rdata1->rdclass == rdata2->rdclass);
+ REQUIRE(rdata1->type == dns_rdatatype_gpos);
+ REQUIRE(rdata1->length != 0);
+ REQUIRE(rdata2->length != 0);
+
+ dns_rdata_toregion(rdata1, &r1);
+ dns_rdata_toregion(rdata2, &r2);
+ return (isc_region_compare(&r1, &r2));
+}
+
+static isc_result_t
+fromstruct_gpos(ARGS_FROMSTRUCT) {
+ dns_rdata_gpos_t *gpos = source;
+
+ REQUIRE(type == dns_rdatatype_gpos);
+ REQUIRE(gpos != NULL);
+ REQUIRE(gpos->common.rdtype == type);
+ REQUIRE(gpos->common.rdclass == rdclass);
+
+ UNUSED(type);
+ UNUSED(rdclass);
+
+ RETERR(uint8_tobuffer(gpos->long_len, target));
+ RETERR(mem_tobuffer(target, gpos->longitude, gpos->long_len));
+ RETERR(uint8_tobuffer(gpos->lat_len, target));
+ RETERR(mem_tobuffer(target, gpos->latitude, gpos->lat_len));
+ RETERR(uint8_tobuffer(gpos->alt_len, target));
+ return (mem_tobuffer(target, gpos->altitude, gpos->alt_len));
+}
+
+static isc_result_t
+tostruct_gpos(ARGS_TOSTRUCT) {
+ dns_rdata_gpos_t *gpos = target;
+ isc_region_t region;
+
+ REQUIRE(rdata->type == dns_rdatatype_gpos);
+ REQUIRE(gpos != NULL);
+ REQUIRE(rdata->length != 0);
+
+ gpos->common.rdclass = rdata->rdclass;
+ gpos->common.rdtype = rdata->type;
+ ISC_LINK_INIT(&gpos->common, link);
+
+ dns_rdata_toregion(rdata, &region);
+ gpos->long_len = uint8_fromregion(&region);
+ isc_region_consume(&region, 1);
+ gpos->longitude = mem_maybedup(mctx, region.base, gpos->long_len);
+ if (gpos->longitude == NULL) {
+ return (ISC_R_NOMEMORY);
+ }
+ isc_region_consume(&region, gpos->long_len);
+
+ gpos->lat_len = uint8_fromregion(&region);
+ isc_region_consume(&region, 1);
+ gpos->latitude = mem_maybedup(mctx, region.base, gpos->lat_len);
+ if (gpos->latitude == NULL) {
+ goto cleanup_longitude;
+ }
+ isc_region_consume(&region, gpos->lat_len);
+
+ gpos->alt_len = uint8_fromregion(&region);
+ isc_region_consume(&region, 1);
+ if (gpos->lat_len > 0) {
+ gpos->altitude = mem_maybedup(mctx, region.base, gpos->alt_len);
+ if (gpos->altitude == NULL) {
+ goto cleanup_latitude;
+ }
+ } else {
+ gpos->altitude = NULL;
+ }
+
+ gpos->mctx = mctx;
+ return (ISC_R_SUCCESS);
+
+cleanup_latitude:
+ if (mctx != NULL && gpos->longitude != NULL) {
+ isc_mem_free(mctx, gpos->longitude);
+ }
+
+cleanup_longitude:
+ if (mctx != NULL && gpos->latitude != NULL) {
+ isc_mem_free(mctx, gpos->latitude);
+ }
+ return (ISC_R_NOMEMORY);
+}
+
+static void
+freestruct_gpos(ARGS_FREESTRUCT) {
+ dns_rdata_gpos_t *gpos = source;
+
+ REQUIRE(gpos != NULL);
+ REQUIRE(gpos->common.rdtype == dns_rdatatype_gpos);
+
+ if (gpos->mctx == NULL) {
+ return;
+ }
+
+ if (gpos->longitude != NULL) {
+ isc_mem_free(gpos->mctx, gpos->longitude);
+ }
+ if (gpos->latitude != NULL) {
+ isc_mem_free(gpos->mctx, gpos->latitude);
+ }
+ if (gpos->altitude != NULL) {
+ isc_mem_free(gpos->mctx, gpos->altitude);
+ }
+ gpos->mctx = NULL;
+}
+
+static isc_result_t
+additionaldata_gpos(ARGS_ADDLDATA) {
+ REQUIRE(rdata->type == dns_rdatatype_gpos);
+
+ UNUSED(rdata);
+ UNUSED(owner);
+ UNUSED(add);
+ UNUSED(arg);
+
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+digest_gpos(ARGS_DIGEST) {
+ isc_region_t r;
+
+ REQUIRE(rdata->type == dns_rdatatype_gpos);
+
+ dns_rdata_toregion(rdata, &r);
+
+ return ((digest)(arg, &r));
+}
+
+static bool
+checkowner_gpos(ARGS_CHECKOWNER) {
+ REQUIRE(type == dns_rdatatype_gpos);
+
+ UNUSED(name);
+ UNUSED(type);
+ UNUSED(rdclass);
+ UNUSED(wildcard);
+
+ return (true);
+}
+
+static bool
+checknames_gpos(ARGS_CHECKNAMES) {
+ REQUIRE(rdata->type == dns_rdatatype_gpos);
+
+ UNUSED(rdata);
+ UNUSED(owner);
+ UNUSED(bad);
+
+ return (true);
+}
+
+static int
+casecompare_gpos(ARGS_COMPARE) {
+ return (compare_gpos(rdata1, rdata2));
+}
+
+#endif /* RDATA_GENERIC_GPOS_27_C */
diff --git a/lib/dns/rdata/generic/gpos_27.h b/lib/dns/rdata/generic/gpos_27.h
new file mode 100644
index 0000000..b09a69f
--- /dev/null
+++ b/lib/dns/rdata/generic/gpos_27.h
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#pragma once
+
+/*!
+ * \brief per RFC1712 */
+
+typedef struct dns_rdata_gpos {
+ dns_rdatacommon_t common;
+ isc_mem_t *mctx;
+ char *longitude;
+ char *latitude;
+ char *altitude;
+ uint8_t long_len;
+ uint8_t lat_len;
+ uint8_t alt_len;
+} dns_rdata_gpos_t;
diff --git a/lib/dns/rdata/generic/hinfo_13.c b/lib/dns/rdata/generic/hinfo_13.c
new file mode 100644
index 0000000..d3978ed
--- /dev/null
+++ b/lib/dns/rdata/generic/hinfo_13.c
@@ -0,0 +1,218 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#pragma once
+
+#define RRTYPE_HINFO_ATTRIBUTES (0)
+
+static isc_result_t
+fromtext_hinfo(ARGS_FROMTEXT) {
+ isc_token_t token;
+ int i;
+
+ UNUSED(type);
+ UNUSED(rdclass);
+ UNUSED(origin);
+ UNUSED(options);
+ UNUSED(callbacks);
+
+ REQUIRE(type == dns_rdatatype_hinfo);
+
+ for (i = 0; i < 2; i++) {
+ RETERR(isc_lex_getmastertoken(lexer, &token,
+ isc_tokentype_qstring, false));
+ RETTOK(txt_fromtext(&token.value.as_textregion, target));
+ }
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+totext_hinfo(ARGS_TOTEXT) {
+ isc_region_t region;
+
+ UNUSED(tctx);
+
+ REQUIRE(rdata->type == dns_rdatatype_hinfo);
+ REQUIRE(rdata->length != 0);
+
+ dns_rdata_toregion(rdata, &region);
+ RETERR(txt_totext(&region, true, target));
+ RETERR(str_totext(" ", target));
+ return (txt_totext(&region, true, target));
+}
+
+static isc_result_t
+fromwire_hinfo(ARGS_FROMWIRE) {
+ REQUIRE(type == dns_rdatatype_hinfo);
+
+ UNUSED(type);
+ UNUSED(dctx);
+ UNUSED(rdclass);
+ UNUSED(options);
+
+ RETERR(txt_fromwire(source, target));
+ return (txt_fromwire(source, target));
+}
+
+static isc_result_t
+towire_hinfo(ARGS_TOWIRE) {
+ UNUSED(cctx);
+
+ REQUIRE(rdata->type == dns_rdatatype_hinfo);
+ REQUIRE(rdata->length != 0);
+
+ return (mem_tobuffer(target, rdata->data, rdata->length));
+}
+
+static int
+compare_hinfo(ARGS_COMPARE) {
+ isc_region_t r1;
+ isc_region_t r2;
+
+ REQUIRE(rdata1->type == rdata2->type);
+ REQUIRE(rdata1->rdclass == rdata2->rdclass);
+ REQUIRE(rdata1->type == dns_rdatatype_hinfo);
+ REQUIRE(rdata1->length != 0);
+ REQUIRE(rdata2->length != 0);
+
+ dns_rdata_toregion(rdata1, &r1);
+ dns_rdata_toregion(rdata2, &r2);
+ return (isc_region_compare(&r1, &r2));
+}
+
+static isc_result_t
+fromstruct_hinfo(ARGS_FROMSTRUCT) {
+ dns_rdata_hinfo_t *hinfo = source;
+
+ REQUIRE(type == dns_rdatatype_hinfo);
+ REQUIRE(hinfo != NULL);
+ REQUIRE(hinfo->common.rdtype == type);
+ REQUIRE(hinfo->common.rdclass == rdclass);
+
+ UNUSED(type);
+ UNUSED(rdclass);
+
+ RETERR(uint8_tobuffer(hinfo->cpu_len, target));
+ RETERR(mem_tobuffer(target, hinfo->cpu, hinfo->cpu_len));
+ RETERR(uint8_tobuffer(hinfo->os_len, target));
+ return (mem_tobuffer(target, hinfo->os, hinfo->os_len));
+}
+
+static isc_result_t
+tostruct_hinfo(ARGS_TOSTRUCT) {
+ dns_rdata_hinfo_t *hinfo = target;
+ isc_region_t region;
+
+ REQUIRE(rdata->type == dns_rdatatype_hinfo);
+ REQUIRE(hinfo != NULL);
+ REQUIRE(rdata->length != 0);
+
+ hinfo->common.rdclass = rdata->rdclass;
+ hinfo->common.rdtype = rdata->type;
+ ISC_LINK_INIT(&hinfo->common, link);
+
+ dns_rdata_toregion(rdata, &region);
+ hinfo->cpu_len = uint8_fromregion(&region);
+ isc_region_consume(&region, 1);
+ hinfo->cpu = mem_maybedup(mctx, region.base, hinfo->cpu_len);
+ if (hinfo->cpu == NULL) {
+ return (ISC_R_NOMEMORY);
+ }
+ isc_region_consume(&region, hinfo->cpu_len);
+
+ hinfo->os_len = uint8_fromregion(&region);
+ isc_region_consume(&region, 1);
+ hinfo->os = mem_maybedup(mctx, region.base, hinfo->os_len);
+ if (hinfo->os == NULL) {
+ goto cleanup;
+ }
+
+ hinfo->mctx = mctx;
+ return (ISC_R_SUCCESS);
+
+cleanup:
+ if (mctx != NULL && hinfo->cpu != NULL) {
+ isc_mem_free(mctx, hinfo->cpu);
+ }
+ return (ISC_R_NOMEMORY);
+}
+
+static void
+freestruct_hinfo(ARGS_FREESTRUCT) {
+ dns_rdata_hinfo_t *hinfo = source;
+
+ REQUIRE(hinfo != NULL);
+
+ if (hinfo->mctx == NULL) {
+ return;
+ }
+
+ if (hinfo->cpu != NULL) {
+ isc_mem_free(hinfo->mctx, hinfo->cpu);
+ }
+ if (hinfo->os != NULL) {
+ isc_mem_free(hinfo->mctx, hinfo->os);
+ }
+ hinfo->mctx = NULL;
+}
+
+static isc_result_t
+additionaldata_hinfo(ARGS_ADDLDATA) {
+ REQUIRE(rdata->type == dns_rdatatype_hinfo);
+
+ UNUSED(rdata);
+ UNUSED(owner);
+ UNUSED(add);
+ UNUSED(arg);
+
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+digest_hinfo(ARGS_DIGEST) {
+ isc_region_t r;
+
+ REQUIRE(rdata->type == dns_rdatatype_hinfo);
+
+ dns_rdata_toregion(rdata, &r);
+
+ return ((digest)(arg, &r));
+}
+
+static bool
+checkowner_hinfo(ARGS_CHECKOWNER) {
+ REQUIRE(type == dns_rdatatype_hinfo);
+
+ UNUSED(name);
+ UNUSED(type);
+ UNUSED(rdclass);
+ UNUSED(wildcard);
+
+ return (true);
+}
+
+static bool
+checknames_hinfo(ARGS_CHECKNAMES) {
+ REQUIRE(rdata->type == dns_rdatatype_hinfo);
+
+ UNUSED(rdata);
+ UNUSED(owner);
+ UNUSED(bad);
+
+ return (true);
+}
+
+static int
+casecompare_hinfo(ARGS_COMPARE) {
+ return (compare_hinfo(rdata1, rdata2));
+}
diff --git a/lib/dns/rdata/generic/hinfo_13.h b/lib/dns/rdata/generic/hinfo_13.h
new file mode 100644
index 0000000..a477d47
--- /dev/null
+++ b/lib/dns/rdata/generic/hinfo_13.h
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#pragma once
+
+typedef struct dns_rdata_hinfo {
+ dns_rdatacommon_t common;
+ isc_mem_t *mctx;
+ char *cpu;
+ char *os;
+ uint8_t cpu_len;
+ uint8_t os_len;
+} dns_rdata_hinfo_t;
diff --git a/lib/dns/rdata/generic/hip_55.c b/lib/dns/rdata/generic/hip_55.c
new file mode 100644
index 0000000..e67df0e
--- /dev/null
+++ b/lib/dns/rdata/generic/hip_55.c
@@ -0,0 +1,523 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/* RFC 5205 */
+
+#pragma once
+
+#define RRTYPE_HIP_ATTRIBUTES (0)
+
+static isc_result_t
+fromtext_hip(ARGS_FROMTEXT) {
+ isc_token_t token;
+ dns_name_t name;
+ isc_buffer_t buffer;
+ isc_buffer_t hit_len;
+ isc_buffer_t key_len;
+ unsigned char *start;
+ size_t len;
+
+ REQUIRE(type == dns_rdatatype_hip);
+
+ UNUSED(type);
+ UNUSED(rdclass);
+ UNUSED(callbacks);
+
+ /*
+ * Dummy HIT len.
+ */
+ hit_len = *target;
+ RETERR(uint8_tobuffer(0, target));
+
+ /*
+ * Algorithm.
+ */
+ RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_number,
+ false));
+ if (token.value.as_ulong > 0xffU) {
+ RETTOK(ISC_R_RANGE);
+ }
+ RETERR(uint8_tobuffer(token.value.as_ulong, target));
+
+ /*
+ * Dummy KEY len.
+ */
+ key_len = *target;
+ RETERR(uint16_tobuffer(0, target));
+
+ /*
+ * HIT (base16).
+ */
+ start = isc_buffer_used(target);
+ RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_string,
+ false));
+ RETTOK(isc_hex_decodestring(DNS_AS_STR(token), target));
+
+ /*
+ * Fill in HIT len.
+ */
+ len = (unsigned char *)isc_buffer_used(target) - start;
+ if (len > 0xffU) {
+ RETTOK(ISC_R_RANGE);
+ }
+ RETERR(uint8_tobuffer((uint32_t)len, &hit_len));
+
+ /*
+ * Public key (base64).
+ */
+ start = isc_buffer_used(target);
+ RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_string,
+ false));
+ RETTOK(isc_base64_decodestring(DNS_AS_STR(token), target));
+
+ /*
+ * Fill in KEY len.
+ */
+ len = (unsigned char *)isc_buffer_used(target) - start;
+ if (len > 0xffffU) {
+ RETTOK(ISC_R_RANGE);
+ }
+ RETERR(uint16_tobuffer((uint32_t)len, &key_len));
+
+ if (origin == NULL) {
+ origin = dns_rootname;
+ }
+
+ /*
+ * Rendezvous Servers.
+ */
+ dns_name_init(&name, NULL);
+ do {
+ RETERR(isc_lex_getmastertoken(lexer, &token,
+ isc_tokentype_string, true));
+ if (token.type != isc_tokentype_string) {
+ break;
+ }
+ buffer_fromregion(&buffer, &token.value.as_region);
+ RETTOK(dns_name_fromtext(&name, &buffer, origin, options,
+ target));
+ } while (1);
+
+ /*
+ * Let upper layer handle eol/eof.
+ */
+ isc_lex_ungettoken(lexer, &token);
+
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+totext_hip(ARGS_TOTEXT) {
+ isc_region_t region;
+ dns_name_t name;
+ unsigned int length, key_len, hit_len;
+ unsigned char algorithm;
+ char buf[sizeof("225 ")];
+
+ REQUIRE(rdata->type == dns_rdatatype_hip);
+ REQUIRE(rdata->length != 0);
+
+ dns_rdata_toregion(rdata, &region);
+
+ hit_len = uint8_fromregion(&region);
+ isc_region_consume(&region, 1);
+
+ algorithm = uint8_fromregion(&region);
+ isc_region_consume(&region, 1);
+
+ key_len = uint16_fromregion(&region);
+ isc_region_consume(&region, 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(&region, 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(&region, 1, "", target));
+ region.length = length - key_len;
+ if (region.length > 0) {
+ RETERR(str_totext(tctx->linebreak, target));
+ }
+
+ /*
+ * Rendezvous Servers.
+ */
+ dns_name_init(&name, NULL);
+ while (region.length > 0) {
+ dns_name_fromregion(&name, &region);
+
+ RETERR(dns_name_totext(&name, false, target));
+ isc_region_consume(&region, name.length);
+ if (region.length > 0) {
+ RETERR(str_totext(tctx->linebreak, target));
+ }
+ }
+ if ((tctx->flags & DNS_STYLEFLAG_MULTILINE) != 0) {
+ RETERR(str_totext(" )", target));
+ }
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+fromwire_hip(ARGS_FROMWIRE) {
+ isc_region_t region, rr;
+ dns_name_t name;
+ uint8_t hit_len;
+ uint16_t key_len;
+ size_t len;
+
+ REQUIRE(type == dns_rdatatype_hip);
+
+ UNUSED(type);
+ UNUSED(rdclass);
+
+ isc_buffer_activeregion(source, &region);
+ if (region.length < 4U) {
+ RETERR(DNS_R_FORMERR);
+ }
+
+ rr = region;
+ hit_len = uint8_fromregion(&region);
+ if (hit_len == 0) {
+ RETERR(DNS_R_FORMERR);
+ }
+ isc_region_consume(&region, 2); /* hit length + algorithm */
+ key_len = uint16_fromregion(&region);
+ if (key_len == 0) {
+ RETERR(DNS_R_FORMERR);
+ }
+ isc_region_consume(&region, 2);
+ len = hit_len + key_len;
+ if (len > region.length) {
+ RETERR(DNS_R_FORMERR);
+ }
+
+ RETERR(mem_tobuffer(target, rr.base, 4 + len));
+ isc_buffer_forward(source, 4 + len);
+
+ dns_decompress_setmethods(dctx, DNS_COMPRESS_NONE);
+ while (isc_buffer_activelength(source) > 0) {
+ dns_name_init(&name, NULL);
+ RETERR(dns_name_fromwire(&name, source, dctx, options, target));
+ }
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+towire_hip(ARGS_TOWIRE) {
+ isc_region_t region;
+
+ REQUIRE(rdata->type == dns_rdatatype_hip);
+ REQUIRE(rdata->length != 0);
+
+ UNUSED(cctx);
+
+ dns_rdata_toregion(rdata, &region);
+ return (mem_tobuffer(target, region.base, region.length));
+}
+
+static int
+compare_hip(ARGS_COMPARE) {
+ isc_region_t region1;
+ isc_region_t region2;
+
+ REQUIRE(rdata1->type == rdata2->type);
+ REQUIRE(rdata1->rdclass == rdata2->rdclass);
+ REQUIRE(rdata1->type == dns_rdatatype_hip);
+ REQUIRE(rdata1->length != 0);
+ REQUIRE(rdata2->length != 0);
+
+ dns_rdata_toregion(rdata1, &region1);
+ dns_rdata_toregion(rdata2, &region2);
+ return (isc_region_compare(&region1, &region2));
+}
+
+static isc_result_t
+fromstruct_hip(ARGS_FROMSTRUCT) {
+ dns_rdata_hip_t *hip = source;
+ dns_rdata_hip_t myhip;
+ isc_result_t result;
+
+ REQUIRE(type == dns_rdatatype_hip);
+ REQUIRE(hip != NULL);
+ REQUIRE(hip->common.rdtype == type);
+ REQUIRE(hip->common.rdclass == rdclass);
+ REQUIRE(hip->hit_len > 0 && hip->hit != NULL);
+ REQUIRE(hip->key_len > 0 && hip->key != NULL);
+ REQUIRE((hip->servers == NULL && hip->servers_len == 0) ||
+ (hip->servers != NULL && hip->servers_len != 0));
+
+ UNUSED(type);
+ UNUSED(rdclass);
+
+ RETERR(uint8_tobuffer(hip->hit_len, target));
+ RETERR(uint8_tobuffer(hip->algorithm, target));
+ RETERR(uint16_tobuffer(hip->key_len, target));
+ RETERR(mem_tobuffer(target, hip->hit, hip->hit_len));
+ RETERR(mem_tobuffer(target, hip->key, hip->key_len));
+
+ myhip = *hip;
+ for (result = dns_rdata_hip_first(&myhip); result == ISC_R_SUCCESS;
+ result = dns_rdata_hip_next(&myhip))
+ {
+ /* initialize the names */
+ }
+
+ return (mem_tobuffer(target, hip->servers, hip->servers_len));
+}
+
+static isc_result_t
+tostruct_hip(ARGS_TOSTRUCT) {
+ isc_region_t region;
+ dns_rdata_hip_t *hip = target;
+
+ REQUIRE(rdata->type == dns_rdatatype_hip);
+ REQUIRE(hip != NULL);
+ REQUIRE(rdata->length != 0);
+
+ hip->common.rdclass = rdata->rdclass;
+ hip->common.rdtype = rdata->type;
+ ISC_LINK_INIT(&hip->common, link);
+
+ dns_rdata_toregion(rdata, &region);
+
+ hip->hit_len = uint8_fromregion(&region);
+ isc_region_consume(&region, 1);
+
+ hip->algorithm = uint8_fromregion(&region);
+ isc_region_consume(&region, 1);
+
+ hip->key_len = uint16_fromregion(&region);
+ isc_region_consume(&region, 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(&region, 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(&region, hip->key_len);
+
+ hip->servers_len = region.length;
+ if (hip->servers_len != 0) {
+ hip->servers = mem_maybedup(mctx, region.base, region.length);
+ if (hip->servers == NULL) {
+ goto cleanup;
+ }
+ }
+
+ hip->offset = hip->servers_len;
+ hip->mctx = mctx;
+ return (ISC_R_SUCCESS);
+
+cleanup:
+ if (hip->hit != NULL) {
+ isc_mem_free(mctx, hip->hit);
+ }
+ if (hip->key != NULL) {
+ isc_mem_free(mctx, hip->key);
+ }
+ if (hip->servers != NULL) {
+ isc_mem_free(mctx, hip->servers);
+ }
+ return (ISC_R_NOMEMORY);
+}
+
+static void
+freestruct_hip(ARGS_FREESTRUCT) {
+ dns_rdata_hip_t *hip = source;
+
+ REQUIRE(hip != NULL);
+
+ if (hip->mctx == NULL) {
+ return;
+ }
+
+ isc_mem_free(hip->mctx, hip->hit);
+ isc_mem_free(hip->mctx, hip->key);
+ if (hip->servers != NULL) {
+ isc_mem_free(hip->mctx, hip->servers);
+ }
+ hip->mctx = NULL;
+}
+
+static isc_result_t
+additionaldata_hip(ARGS_ADDLDATA) {
+ REQUIRE(rdata->type == dns_rdatatype_hip);
+
+ UNUSED(rdata);
+ UNUSED(owner);
+ UNUSED(add);
+ UNUSED(arg);
+
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+digest_hip(ARGS_DIGEST) {
+ isc_region_t r;
+
+ REQUIRE(rdata->type == dns_rdatatype_hip);
+
+ dns_rdata_toregion(rdata, &r);
+ return ((digest)(arg, &r));
+}
+
+static bool
+checkowner_hip(ARGS_CHECKOWNER) {
+ REQUIRE(type == dns_rdatatype_hip);
+
+ UNUSED(name);
+ UNUSED(type);
+ UNUSED(rdclass);
+ UNUSED(wildcard);
+
+ return (true);
+}
+
+static bool
+checknames_hip(ARGS_CHECKNAMES) {
+ REQUIRE(rdata->type == dns_rdatatype_hip);
+
+ UNUSED(rdata);
+ UNUSED(owner);
+ UNUSED(bad);
+
+ return (true);
+}
+
+isc_result_t
+dns_rdata_hip_first(dns_rdata_hip_t *hip) {
+ if (hip->servers_len == 0) {
+ return (ISC_R_NOMORE);
+ }
+ hip->offset = 0;
+ return (ISC_R_SUCCESS);
+}
+
+isc_result_t
+dns_rdata_hip_next(dns_rdata_hip_t *hip) {
+ isc_region_t region;
+ dns_name_t name;
+
+ if (hip->offset >= hip->servers_len) {
+ return (ISC_R_NOMORE);
+ }
+
+ region.base = hip->servers + hip->offset;
+ region.length = hip->servers_len - hip->offset;
+ dns_name_init(&name, NULL);
+ dns_name_fromregion(&name, &region);
+ hip->offset += name.length;
+ INSIST(hip->offset <= hip->servers_len);
+ return (hip->offset < hip->servers_len ? ISC_R_SUCCESS : ISC_R_NOMORE);
+}
+
+void
+dns_rdata_hip_current(dns_rdata_hip_t *hip, dns_name_t *name) {
+ isc_region_t region;
+
+ REQUIRE(hip->offset < hip->servers_len);
+
+ region.base = hip->servers + hip->offset;
+ region.length = hip->servers_len - hip->offset;
+ dns_name_fromregion(name, &region);
+
+ INSIST(name->length + hip->offset <= hip->servers_len);
+}
+
+static int
+casecompare_hip(ARGS_COMPARE) {
+ isc_region_t r1;
+ isc_region_t r2;
+ dns_name_t name1;
+ dns_name_t name2;
+ int order;
+ uint8_t hit_len;
+ uint16_t key_len;
+
+ REQUIRE(rdata1->type == rdata2->type);
+ REQUIRE(rdata1->rdclass == rdata2->rdclass);
+ REQUIRE(rdata1->type == dns_rdatatype_hip);
+ REQUIRE(rdata1->length != 0);
+ REQUIRE(rdata2->length != 0);
+
+ dns_rdata_toregion(rdata1, &r1);
+ dns_rdata_toregion(rdata2, &r2);
+
+ INSIST(r1.length > 4);
+ INSIST(r2.length > 4);
+ order = memcmp(r1.base, r2.base, 4);
+ if (order != 0) {
+ return (order);
+ }
+
+ hit_len = uint8_fromregion(&r1);
+ isc_region_consume(&r1, 2); /* hit length + algorithm */
+ key_len = uint16_fromregion(&r1);
+ isc_region_consume(&r1, 2); /* key length */
+ isc_region_consume(&r2, 4);
+
+ INSIST(r1.length >= (unsigned)(hit_len + key_len));
+ INSIST(r2.length >= (unsigned)(hit_len + key_len));
+ order = memcmp(r1.base, r2.base, hit_len + key_len);
+ if (order != 0) {
+ return (order);
+ }
+ isc_region_consume(&r1, hit_len + key_len);
+ isc_region_consume(&r2, hit_len + key_len);
+
+ dns_name_init(&name1, NULL);
+ dns_name_init(&name2, NULL);
+ while (r1.length != 0 && r2.length != 0) {
+ dns_name_fromregion(&name1, &r1);
+ dns_name_fromregion(&name2, &r2);
+ order = dns_name_rdatacompare(&name1, &name2);
+ if (order != 0) {
+ return (order);
+ }
+
+ isc_region_consume(&r1, name_length(&name1));
+ isc_region_consume(&r2, name_length(&name2));
+ }
+ return (isc_region_compare(&r1, &r2));
+}
diff --git a/lib/dns/rdata/generic/hip_55.h b/lib/dns/rdata/generic/hip_55.h
new file mode 100644
index 0000000..617e9c9
--- /dev/null
+++ b/lib/dns/rdata/generic/hip_55.h
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#pragma once
+
+/* RFC 5205 */
+
+typedef struct dns_rdata_hip {
+ dns_rdatacommon_t common;
+ isc_mem_t *mctx;
+ unsigned char *hit;
+ unsigned char *key;
+ unsigned char *servers;
+ uint8_t algorithm;
+ uint8_t hit_len;
+ uint16_t key_len;
+ uint16_t servers_len;
+ /* Private */
+ uint16_t offset;
+} dns_rdata_hip_t;
+
+isc_result_t
+dns_rdata_hip_first(dns_rdata_hip_t *);
+
+isc_result_t
+dns_rdata_hip_next(dns_rdata_hip_t *);
+
+void
+dns_rdata_hip_current(dns_rdata_hip_t *, dns_name_t *);
diff --git a/lib/dns/rdata/generic/ipseckey_45.c b/lib/dns/rdata/generic/ipseckey_45.c
new file mode 100644
index 0000000..a4232db
--- /dev/null
+++ b/lib/dns/rdata/generic/ipseckey_45.c
@@ -0,0 +1,527 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#ifndef RDATA_GENERIC_IPSECKEY_45_C
+#define RDATA_GENERIC_IPSECKEY_45_C
+
+#include <string.h>
+
+#include <isc/net.h>
+
+#define RRTYPE_IPSECKEY_ATTRIBUTES (0)
+
+static isc_result_t
+fromtext_ipseckey(ARGS_FROMTEXT) {
+ isc_token_t token;
+ dns_name_t name;
+ isc_buffer_t buffer;
+ unsigned int gateway;
+ struct in_addr addr;
+ unsigned char addr6[16];
+ isc_region_t region;
+
+ REQUIRE(type == dns_rdatatype_ipseckey);
+
+ UNUSED(type);
+ UNUSED(rdclass);
+ UNUSED(callbacks);
+
+ /*
+ * Precedence.
+ */
+ RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_number,
+ false));
+ if (token.value.as_ulong > 0xffU) {
+ RETTOK(ISC_R_RANGE);
+ }
+ RETERR(uint8_tobuffer(token.value.as_ulong, target));
+
+ /*
+ * Gateway type.
+ */
+ RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_number,
+ false));
+ if (token.value.as_ulong > 0x3U) {
+ RETTOK(ISC_R_RANGE);
+ }
+ RETERR(uint8_tobuffer(token.value.as_ulong, target));
+ gateway = token.value.as_ulong;
+
+ /*
+ * Algorithm.
+ */
+ RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_number,
+ false));
+ if (token.value.as_ulong > 0xffU) {
+ RETTOK(ISC_R_RANGE);
+ }
+ RETERR(uint8_tobuffer(token.value.as_ulong, target));
+
+ /*
+ * Gateway.
+ */
+ RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_string,
+ false));
+
+ switch (gateway) {
+ case 0:
+ if (strcmp(DNS_AS_STR(token), ".") != 0) {
+ RETTOK(DNS_R_SYNTAX);
+ }
+ break;
+
+ case 1:
+ if (inet_pton(AF_INET, DNS_AS_STR(token), &addr) != 1) {
+ RETTOK(DNS_R_BADDOTTEDQUAD);
+ }
+ isc_buffer_availableregion(target, &region);
+ 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, &region);
+ if (region.length < 16) {
+ return (ISC_R_NOSPACE);
+ }
+ memmove(region.base, addr6, 16);
+ isc_buffer_add(target, 16);
+ break;
+
+ case 3:
+ dns_name_init(&name, NULL);
+ buffer_fromregion(&buffer, &token.value.as_region);
+ if (origin == NULL) {
+ origin = dns_rootname;
+ }
+ RETTOK(dns_name_fromtext(&name, &buffer, origin, options,
+ target));
+ break;
+ }
+
+ /*
+ * Public key.
+ */
+ return (isc_base64_tobuffer(lexer, target, -2));
+}
+
+static isc_result_t
+totext_ipseckey(ARGS_TOTEXT) {
+ isc_region_t region;
+ dns_name_t name;
+ char buf[sizeof("255 ")];
+ unsigned short num;
+ unsigned short gateway;
+
+ REQUIRE(rdata->type == dns_rdatatype_ipseckey);
+ REQUIRE(rdata->length >= 3);
+
+ dns_name_init(&name, NULL);
+
+ if (rdata->data[1] > 3U) {
+ return (ISC_R_NOTIMPLEMENTED);
+ }
+
+ if ((tctx->flags & DNS_STYLEFLAG_MULTILINE) != 0) {
+ RETERR(str_totext("( ", target));
+ }
+
+ /*
+ * Precedence.
+ */
+ dns_rdata_toregion(rdata, &region);
+ num = uint8_fromregion(&region);
+ isc_region_consume(&region, 1);
+ snprintf(buf, sizeof(buf), "%u ", num);
+ RETERR(str_totext(buf, target));
+
+ /*
+ * Gateway type.
+ */
+ gateway = uint8_fromregion(&region);
+ isc_region_consume(&region, 1);
+ snprintf(buf, sizeof(buf), "%u ", gateway);
+ RETERR(str_totext(buf, target));
+
+ /*
+ * Algorithm.
+ */
+ num = uint8_fromregion(&region);
+ isc_region_consume(&region, 1);
+ snprintf(buf, sizeof(buf), "%u ", num);
+ RETERR(str_totext(buf, target));
+
+ /*
+ * Gateway.
+ */
+ switch (gateway) {
+ case 0:
+ RETERR(str_totext(".", target));
+ break;
+
+ case 1:
+ RETERR(inet_totext(AF_INET, tctx->flags, &region, target));
+ isc_region_consume(&region, 4);
+ break;
+
+ case 2:
+ RETERR(inet_totext(AF_INET6, tctx->flags, &region, target));
+ isc_region_consume(&region, 16);
+ break;
+
+ case 3:
+ dns_name_fromregion(&name, &region);
+ RETERR(dns_name_totext(&name, false, target));
+ isc_region_consume(&region, 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(&region, 60, "", target));
+ } else {
+ RETERR(isc_base64_totext(&region, tctx->width - 2,
+ tctx->linebreak, target));
+ }
+ }
+
+ if ((tctx->flags & DNS_STYLEFLAG_MULTILINE) != 0) {
+ RETERR(str_totext(" )", target));
+ }
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+fromwire_ipseckey(ARGS_FROMWIRE) {
+ dns_name_t name;
+ isc_region_t region;
+
+ REQUIRE(type == dns_rdatatype_ipseckey);
+
+ UNUSED(type);
+ UNUSED(rdclass);
+
+ dns_decompress_setmethods(dctx, DNS_COMPRESS_NONE);
+
+ dns_name_init(&name, NULL);
+
+ isc_buffer_activeregion(source, &region);
+ if (region.length < 3) {
+ return (ISC_R_UNEXPECTEDEND);
+ }
+
+ switch (region.base[1]) {
+ case 0:
+ if (region.length < 4) {
+ return (ISC_R_UNEXPECTEDEND);
+ }
+ isc_buffer_forward(source, region.length);
+ return (mem_tobuffer(target, region.base, region.length));
+
+ case 1:
+ if (region.length < 8) {
+ return (ISC_R_UNEXPECTEDEND);
+ }
+ isc_buffer_forward(source, region.length);
+ return (mem_tobuffer(target, region.base, region.length));
+
+ case 2:
+ if (region.length < 20) {
+ return (ISC_R_UNEXPECTEDEND);
+ }
+ isc_buffer_forward(source, region.length);
+ return (mem_tobuffer(target, region.base, region.length));
+
+ case 3:
+ RETERR(mem_tobuffer(target, region.base, 3));
+ isc_buffer_forward(source, 3);
+ RETERR(dns_name_fromwire(&name, source, dctx, options, target));
+ isc_buffer_activeregion(source, &region);
+ isc_buffer_forward(source, region.length);
+ if (region.length < 1) {
+ return (ISC_R_UNEXPECTEDEND);
+ }
+ return (mem_tobuffer(target, region.base, region.length));
+
+ default:
+ return (ISC_R_NOTIMPLEMENTED);
+ }
+}
+
+static isc_result_t
+towire_ipseckey(ARGS_TOWIRE) {
+ isc_region_t region;
+
+ REQUIRE(rdata->type == dns_rdatatype_ipseckey);
+ REQUIRE(rdata->length != 0);
+
+ UNUSED(cctx);
+
+ dns_rdata_toregion(rdata, &region);
+ return (mem_tobuffer(target, region.base, region.length));
+}
+
+static int
+compare_ipseckey(ARGS_COMPARE) {
+ isc_region_t region1;
+ isc_region_t region2;
+
+ REQUIRE(rdata1->type == rdata2->type);
+ REQUIRE(rdata1->rdclass == rdata2->rdclass);
+ REQUIRE(rdata1->type == dns_rdatatype_ipseckey);
+ REQUIRE(rdata1->length >= 3);
+ REQUIRE(rdata2->length >= 3);
+
+ dns_rdata_toregion(rdata1, &region1);
+ dns_rdata_toregion(rdata2, &region2);
+
+ return (isc_region_compare(&region1, &region2));
+}
+
+static isc_result_t
+fromstruct_ipseckey(ARGS_FROMSTRUCT) {
+ dns_rdata_ipseckey_t *ipseckey = source;
+ isc_region_t region;
+ uint32_t n;
+
+ REQUIRE(type == dns_rdatatype_ipseckey);
+ REQUIRE(ipseckey != NULL);
+ REQUIRE(ipseckey->common.rdtype == type);
+ REQUIRE(ipseckey->common.rdclass == rdclass);
+
+ UNUSED(type);
+ UNUSED(rdclass);
+
+ if (ipseckey->gateway_type > 3U) {
+ return (ISC_R_NOTIMPLEMENTED);
+ }
+
+ RETERR(uint8_tobuffer(ipseckey->precedence, target));
+ RETERR(uint8_tobuffer(ipseckey->gateway_type, target));
+ RETERR(uint8_tobuffer(ipseckey->algorithm, target));
+
+ switch (ipseckey->gateway_type) {
+ case 0:
+ break;
+
+ case 1:
+ n = ntohl(ipseckey->in_addr.s_addr);
+ RETERR(uint32_tobuffer(n, target));
+ break;
+
+ case 2:
+ RETERR(mem_tobuffer(target, ipseckey->in6_addr.s6_addr, 16));
+ break;
+
+ case 3:
+ dns_name_toregion(&ipseckey->gateway, &region);
+ RETERR(isc_buffer_copyregion(target, &region));
+ break;
+ }
+
+ return (mem_tobuffer(target, ipseckey->key, ipseckey->keylength));
+}
+
+static isc_result_t
+tostruct_ipseckey(ARGS_TOSTRUCT) {
+ isc_region_t region;
+ dns_rdata_ipseckey_t *ipseckey = target;
+ dns_name_t name;
+ uint32_t n;
+
+ REQUIRE(rdata->type == dns_rdatatype_ipseckey);
+ REQUIRE(ipseckey != NULL);
+ REQUIRE(rdata->length >= 3);
+
+ if (rdata->data[1] > 3U) {
+ return (ISC_R_NOTIMPLEMENTED);
+ }
+
+ ipseckey->common.rdclass = rdata->rdclass;
+ ipseckey->common.rdtype = rdata->type;
+ ISC_LINK_INIT(&ipseckey->common, link);
+
+ dns_name_init(&name, NULL);
+ dns_rdata_toregion(rdata, &region);
+
+ ipseckey->precedence = uint8_fromregion(&region);
+ isc_region_consume(&region, 1);
+
+ ipseckey->gateway_type = uint8_fromregion(&region);
+ isc_region_consume(&region, 1);
+
+ ipseckey->algorithm = uint8_fromregion(&region);
+ isc_region_consume(&region, 1);
+
+ switch (ipseckey->gateway_type) {
+ case 0:
+ break;
+
+ case 1:
+ n = uint32_fromregion(&region);
+ ipseckey->in_addr.s_addr = htonl(n);
+ isc_region_consume(&region, 4);
+ break;
+
+ case 2:
+ memmove(ipseckey->in6_addr.s6_addr, region.base, 16);
+ isc_region_consume(&region, 16);
+ break;
+
+ case 3:
+ dns_name_init(&ipseckey->gateway, NULL);
+ dns_name_fromregion(&name, &region);
+ name_duporclone(&name, mctx, &ipseckey->gateway);
+ isc_region_consume(&region, name_length(&name));
+ break;
+ }
+
+ ipseckey->keylength = region.length;
+ if (ipseckey->keylength != 0U) {
+ ipseckey->key = mem_maybedup(mctx, region.base,
+ ipseckey->keylength);
+ if (ipseckey->key == NULL) {
+ if (ipseckey->gateway_type == 3) {
+ dns_name_free(&ipseckey->gateway,
+ ipseckey->mctx);
+ }
+ return (ISC_R_NOMEMORY);
+ }
+ } else {
+ ipseckey->key = NULL;
+ }
+
+ ipseckey->mctx = mctx;
+ return (ISC_R_SUCCESS);
+}
+
+static void
+freestruct_ipseckey(ARGS_FREESTRUCT) {
+ dns_rdata_ipseckey_t *ipseckey = source;
+
+ REQUIRE(ipseckey != NULL);
+ REQUIRE(ipseckey->common.rdtype == dns_rdatatype_ipseckey);
+
+ if (ipseckey->mctx == NULL) {
+ return;
+ }
+
+ if (ipseckey->gateway_type == 3) {
+ dns_name_free(&ipseckey->gateway, ipseckey->mctx);
+ }
+
+ if (ipseckey->key != NULL) {
+ isc_mem_free(ipseckey->mctx, ipseckey->key);
+ }
+
+ ipseckey->mctx = NULL;
+}
+
+static isc_result_t
+additionaldata_ipseckey(ARGS_ADDLDATA) {
+ REQUIRE(rdata->type == dns_rdatatype_ipseckey);
+
+ UNUSED(rdata);
+ UNUSED(owner);
+ UNUSED(add);
+ UNUSED(arg);
+
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+digest_ipseckey(ARGS_DIGEST) {
+ isc_region_t region;
+
+ REQUIRE(rdata->type == dns_rdatatype_ipseckey);
+
+ dns_rdata_toregion(rdata, &region);
+ return ((digest)(arg, &region));
+}
+
+static bool
+checkowner_ipseckey(ARGS_CHECKOWNER) {
+ REQUIRE(type == dns_rdatatype_ipseckey);
+
+ UNUSED(name);
+ UNUSED(type);
+ UNUSED(rdclass);
+ UNUSED(wildcard);
+
+ return (true);
+}
+
+static bool
+checknames_ipseckey(ARGS_CHECKNAMES) {
+ REQUIRE(rdata->type == dns_rdatatype_ipseckey);
+
+ UNUSED(rdata);
+ UNUSED(owner);
+ UNUSED(bad);
+
+ return (true);
+}
+
+static int
+casecompare_ipseckey(ARGS_COMPARE) {
+ isc_region_t region1;
+ isc_region_t region2;
+ dns_name_t name1;
+ dns_name_t name2;
+ int order;
+
+ REQUIRE(rdata1->type == rdata2->type);
+ REQUIRE(rdata1->rdclass == rdata2->rdclass);
+ REQUIRE(rdata1->type == dns_rdatatype_ipseckey);
+ REQUIRE(rdata1->length >= 3);
+ REQUIRE(rdata2->length >= 3);
+
+ dns_rdata_toregion(rdata1, &region1);
+ dns_rdata_toregion(rdata2, &region2);
+
+ if (memcmp(region1.base, region2.base, 3) != 0 || region1.base[1] != 3)
+ {
+ return (isc_region_compare(&region1, &region2));
+ }
+
+ dns_name_init(&name1, NULL);
+ dns_name_init(&name2, NULL);
+
+ isc_region_consume(&region1, 3);
+ isc_region_consume(&region2, 3);
+
+ dns_name_fromregion(&name1, &region1);
+ dns_name_fromregion(&name2, &region2);
+
+ order = dns_name_rdatacompare(&name1, &name2);
+ if (order != 0) {
+ return (order);
+ }
+
+ isc_region_consume(&region1, name_length(&name1));
+ isc_region_consume(&region2, name_length(&name2));
+
+ return (isc_region_compare(&region1, &region2));
+}
+
+#endif /* RDATA_GENERIC_IPSECKEY_45_C */
diff --git a/lib/dns/rdata/generic/ipseckey_45.h b/lib/dns/rdata/generic/ipseckey_45.h
new file mode 100644
index 0000000..829e9b4
--- /dev/null
+++ b/lib/dns/rdata/generic/ipseckey_45.h
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#pragma once
+
+typedef struct dns_rdata_ipseckey {
+ dns_rdatacommon_t common;
+ isc_mem_t *mctx;
+ uint8_t precedence;
+ uint8_t gateway_type;
+ uint8_t algorithm;
+ struct in_addr in_addr; /* gateway type 1 */
+ struct in6_addr in6_addr; /* gateway type 2 */
+ dns_name_t gateway; /* gateway type 3 */
+ unsigned char *key;
+ uint16_t keylength;
+} dns_rdata_ipseckey_t;
diff --git a/lib/dns/rdata/generic/isdn_20.c b/lib/dns/rdata/generic/isdn_20.c
new file mode 100644
index 0000000..8218d06
--- /dev/null
+++ b/lib/dns/rdata/generic/isdn_20.c
@@ -0,0 +1,248 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/* RFC1183 */
+
+#ifndef RDATA_GENERIC_ISDN_20_C
+#define RDATA_GENERIC_ISDN_20_C
+
+#define RRTYPE_ISDN_ATTRIBUTES (0)
+
+static isc_result_t
+fromtext_isdn(ARGS_FROMTEXT) {
+ isc_token_t token;
+
+ REQUIRE(type == dns_rdatatype_isdn);
+
+ UNUSED(type);
+ UNUSED(rdclass);
+ UNUSED(origin);
+ UNUSED(options);
+ UNUSED(callbacks);
+
+ /* ISDN-address */
+ RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_qstring,
+ false));
+ RETTOK(txt_fromtext(&token.value.as_textregion, target));
+
+ /* sa: optional */
+ RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_qstring,
+ true));
+ if (token.type != isc_tokentype_string &&
+ token.type != isc_tokentype_qstring)
+ {
+ isc_lex_ungettoken(lexer, &token);
+ return (ISC_R_SUCCESS);
+ }
+ RETTOK(txt_fromtext(&token.value.as_textregion, target));
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+totext_isdn(ARGS_TOTEXT) {
+ isc_region_t region;
+
+ REQUIRE(rdata->type == dns_rdatatype_isdn);
+ REQUIRE(rdata->length != 0);
+
+ UNUSED(tctx);
+
+ dns_rdata_toregion(rdata, &region);
+ RETERR(txt_totext(&region, true, target));
+ if (region.length == 0) {
+ return (ISC_R_SUCCESS);
+ }
+ RETERR(str_totext(" ", target));
+ return (txt_totext(&region, true, target));
+}
+
+static isc_result_t
+fromwire_isdn(ARGS_FROMWIRE) {
+ REQUIRE(type == dns_rdatatype_isdn);
+
+ UNUSED(type);
+ UNUSED(dctx);
+ UNUSED(rdclass);
+ UNUSED(options);
+
+ RETERR(txt_fromwire(source, target));
+ if (buffer_empty(source)) {
+ return (ISC_R_SUCCESS);
+ }
+ return (txt_fromwire(source, target));
+}
+
+static isc_result_t
+towire_isdn(ARGS_TOWIRE) {
+ UNUSED(cctx);
+
+ REQUIRE(rdata->type == dns_rdatatype_isdn);
+ REQUIRE(rdata->length != 0);
+
+ return (mem_tobuffer(target, rdata->data, rdata->length));
+}
+
+static int
+compare_isdn(ARGS_COMPARE) {
+ isc_region_t r1;
+ isc_region_t r2;
+
+ REQUIRE(rdata1->type == rdata2->type);
+ REQUIRE(rdata1->rdclass == rdata2->rdclass);
+ REQUIRE(rdata1->type == dns_rdatatype_isdn);
+ REQUIRE(rdata1->length != 0);
+ REQUIRE(rdata2->length != 0);
+
+ dns_rdata_toregion(rdata1, &r1);
+ dns_rdata_toregion(rdata2, &r2);
+ return (isc_region_compare(&r1, &r2));
+}
+
+static isc_result_t
+fromstruct_isdn(ARGS_FROMSTRUCT) {
+ dns_rdata_isdn_t *isdn = source;
+
+ REQUIRE(type == dns_rdatatype_isdn);
+ REQUIRE(isdn != NULL);
+ REQUIRE(isdn->common.rdtype == type);
+ REQUIRE(isdn->common.rdclass == rdclass);
+
+ UNUSED(type);
+ UNUSED(rdclass);
+
+ RETERR(uint8_tobuffer(isdn->isdn_len, target));
+ RETERR(mem_tobuffer(target, isdn->isdn, isdn->isdn_len));
+ if (isdn->subaddress == NULL) {
+ return (ISC_R_SUCCESS);
+ }
+ RETERR(uint8_tobuffer(isdn->subaddress_len, target));
+ return (mem_tobuffer(target, isdn->subaddress, isdn->subaddress_len));
+}
+
+static isc_result_t
+tostruct_isdn(ARGS_TOSTRUCT) {
+ dns_rdata_isdn_t *isdn = target;
+ isc_region_t r;
+
+ REQUIRE(rdata->type == dns_rdatatype_isdn);
+ REQUIRE(isdn != NULL);
+ REQUIRE(rdata->length != 0);
+
+ isdn->common.rdclass = rdata->rdclass;
+ isdn->common.rdtype = rdata->type;
+ ISC_LINK_INIT(&isdn->common, link);
+
+ dns_rdata_toregion(rdata, &r);
+
+ isdn->isdn_len = uint8_fromregion(&r);
+ isc_region_consume(&r, 1);
+ isdn->isdn = mem_maybedup(mctx, r.base, isdn->isdn_len);
+ if (isdn->isdn == NULL) {
+ return (ISC_R_NOMEMORY);
+ }
+ isc_region_consume(&r, isdn->isdn_len);
+
+ if (r.length == 0) {
+ isdn->subaddress_len = 0;
+ isdn->subaddress = NULL;
+ } else {
+ isdn->subaddress_len = uint8_fromregion(&r);
+ isc_region_consume(&r, 1);
+ isdn->subaddress = mem_maybedup(mctx, r.base,
+ isdn->subaddress_len);
+ if (isdn->subaddress == NULL) {
+ goto cleanup;
+ }
+ }
+
+ isdn->mctx = mctx;
+ return (ISC_R_SUCCESS);
+
+cleanup:
+ if (mctx != NULL && isdn->isdn != NULL) {
+ isc_mem_free(mctx, isdn->isdn);
+ }
+ return (ISC_R_NOMEMORY);
+}
+
+static void
+freestruct_isdn(ARGS_FREESTRUCT) {
+ dns_rdata_isdn_t *isdn = source;
+
+ REQUIRE(isdn != NULL);
+
+ if (isdn->mctx == NULL) {
+ return;
+ }
+
+ if (isdn->isdn != NULL) {
+ isc_mem_free(isdn->mctx, isdn->isdn);
+ }
+ if (isdn->subaddress != NULL) {
+ isc_mem_free(isdn->mctx, isdn->subaddress);
+ }
+ isdn->mctx = NULL;
+}
+
+static isc_result_t
+additionaldata_isdn(ARGS_ADDLDATA) {
+ REQUIRE(rdata->type == dns_rdatatype_isdn);
+
+ UNUSED(rdata);
+ UNUSED(owner);
+ UNUSED(add);
+ UNUSED(arg);
+
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+digest_isdn(ARGS_DIGEST) {
+ isc_region_t r;
+
+ REQUIRE(rdata->type == dns_rdatatype_isdn);
+
+ dns_rdata_toregion(rdata, &r);
+
+ return ((digest)(arg, &r));
+}
+
+static bool
+checkowner_isdn(ARGS_CHECKOWNER) {
+ REQUIRE(type == dns_rdatatype_isdn);
+
+ UNUSED(name);
+ UNUSED(type);
+ UNUSED(rdclass);
+ UNUSED(wildcard);
+
+ return (true);
+}
+
+static bool
+checknames_isdn(ARGS_CHECKNAMES) {
+ REQUIRE(rdata->type == dns_rdatatype_isdn);
+
+ UNUSED(rdata);
+ UNUSED(owner);
+ UNUSED(bad);
+
+ return (true);
+}
+
+static int
+casecompare_isdn(ARGS_COMPARE) {
+ return (compare_isdn(rdata1, rdata2));
+}
+
+#endif /* RDATA_GENERIC_ISDN_20_C */
diff --git a/lib/dns/rdata/generic/isdn_20.h b/lib/dns/rdata/generic/isdn_20.h
new file mode 100644
index 0000000..d18aa63
--- /dev/null
+++ b/lib/dns/rdata/generic/isdn_20.h
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#pragma once
+
+/*!
+ * \brief Per RFC1183 */
+
+typedef struct dns_rdata_isdn {
+ dns_rdatacommon_t common;
+ isc_mem_t *mctx;
+ char *isdn;
+ char *subaddress;
+ uint8_t isdn_len;
+ uint8_t subaddress_len;
+} dns_rdata_isdn_t;
diff --git a/lib/dns/rdata/generic/key_25.c b/lib/dns/rdata/generic/key_25.c
new file mode 100644
index 0000000..32c2db3
--- /dev/null
+++ b/lib/dns/rdata/generic/key_25.c
@@ -0,0 +1,469 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/* RFC2535 */
+
+#ifndef RDATA_GENERIC_KEY_25_C
+#define RDATA_GENERIC_KEY_25_C
+
+#include <dst/dst.h>
+
+#define RRTYPE_KEY_ATTRIBUTES \
+ (DNS_RDATATYPEATTR_ATCNAME | DNS_RDATATYPEATTR_ZONECUTAUTH)
+
+/*
+ * RFC 2535 section 3.1.2 says that if bits 0-1 of the Flags field are
+ * both set, it means there is no key information and the RR stops after
+ * the algorithm octet. However, this only applies to KEY records, as
+ * indicated by the specifications of the RR types based on KEY:
+ *
+ * CDNSKEY - RFC 7344
+ * DNSKEY - RFC 4034
+ * RKEY - draft-reid-dnsext-rkey-00
+ */
+static bool
+generic_key_nokey(dns_rdatatype_t type, unsigned int flags) {
+ switch (type) {
+ case dns_rdatatype_cdnskey:
+ case dns_rdatatype_dnskey:
+ case dns_rdatatype_rkey:
+ return (false);
+ case dns_rdatatype_key:
+ default:
+ return ((flags & DNS_KEYFLAG_TYPEMASK) == DNS_KEYTYPE_NOKEY);
+ }
+}
+
+static isc_result_t
+generic_fromtext_key(ARGS_FROMTEXT) {
+ isc_token_t token;
+ dns_secalg_t alg;
+ dns_secproto_t proto;
+ dns_keyflags_t flags;
+
+ UNUSED(rdclass);
+ UNUSED(origin);
+ UNUSED(options);
+ UNUSED(callbacks);
+
+ /* flags */
+ RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_string,
+ false));
+ RETTOK(dns_keyflags_fromtext(&flags, &token.value.as_textregion));
+ if (type == dns_rdatatype_rkey && flags != 0U) {
+ RETTOK(DNS_R_FORMERR);
+ }
+ RETERR(uint16_tobuffer(flags, target));
+
+ /* protocol */
+ RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_string,
+ false));
+ RETTOK(dns_secproto_fromtext(&proto, &token.value.as_textregion));
+ RETERR(mem_tobuffer(target, &proto, 1));
+
+ /* algorithm */
+ RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_string,
+ false));
+ RETTOK(dns_secalg_fromtext(&alg, &token.value.as_textregion));
+ RETERR(mem_tobuffer(target, &alg, 1));
+
+ /* No Key? */
+ if (generic_key_nokey(type, flags)) {
+ return (ISC_R_SUCCESS);
+ }
+
+ return (isc_base64_tobuffer(lexer, target, -2));
+}
+
+static isc_result_t
+generic_totext_key(ARGS_TOTEXT) {
+ isc_region_t sr;
+ char buf[sizeof("[key id = 64000]")];
+ unsigned int flags;
+ unsigned char algorithm;
+ char algbuf[DNS_NAME_FORMATSIZE];
+ const char *keyinfo;
+ isc_region_t tmpr;
+
+ REQUIRE(rdata->length != 0);
+
+ dns_rdata_toregion(rdata, &sr);
+
+ /* flags */
+ flags = uint16_fromregion(&sr);
+ isc_region_consume(&sr, 2);
+ snprintf(buf, sizeof(buf), "%u", flags);
+ RETERR(str_totext(buf, target));
+ RETERR(str_totext(" ", target));
+ if ((flags & DNS_KEYFLAG_KSK) != 0) {
+ if (flags & DNS_KEYFLAG_REVOKE) {
+ keyinfo = "revoked KSK";
+ } else {
+ keyinfo = "KSK";
+ }
+ } else {
+ keyinfo = "ZSK";
+ }
+
+ /* protocol */
+ snprintf(buf, sizeof(buf), "%u", sr.base[0]);
+ isc_region_consume(&sr, 1);
+ RETERR(str_totext(buf, target));
+ RETERR(str_totext(" ", target));
+
+ /* algorithm */
+ algorithm = sr.base[0];
+ snprintf(buf, sizeof(buf), "%u", algorithm);
+ isc_region_consume(&sr, 1);
+ RETERR(str_totext(buf, target));
+
+ /* No Key? */
+ if (generic_key_nokey(rdata->type, flags)) {
+ return (ISC_R_SUCCESS);
+ }
+
+ if ((tctx->flags & DNS_STYLEFLAG_RRCOMMENT) != 0 &&
+ algorithm == DNS_KEYALG_PRIVATEDNS)
+ {
+ dns_name_t name;
+ dns_name_init(&name, NULL);
+ dns_name_fromregion(&name, &sr);
+ dns_name_format(&name, algbuf, sizeof(algbuf));
+ } else {
+ dns_secalg_format((dns_secalg_t)algorithm, algbuf,
+ sizeof(algbuf));
+ }
+
+ /* key */
+ if ((tctx->flags & DNS_STYLEFLAG_MULTILINE) != 0) {
+ RETERR(str_totext(" (", target));
+ }
+ RETERR(str_totext(tctx->linebreak, target));
+
+ if ((tctx->flags & DNS_STYLEFLAG_NOCRYPTO) == 0) {
+ if (tctx->width == 0) { /* No splitting */
+ RETERR(isc_base64_totext(&sr, 60, "", target));
+ } else {
+ RETERR(isc_base64_totext(&sr, tctx->width - 2,
+ tctx->linebreak, target));
+ }
+ } else {
+ dns_rdata_toregion(rdata, &tmpr);
+ snprintf(buf, sizeof(buf), "[key id = %u]",
+ dst_region_computeid(&tmpr));
+ RETERR(str_totext(buf, target));
+ }
+
+ if ((tctx->flags & DNS_STYLEFLAG_RRCOMMENT) != 0) {
+ RETERR(str_totext(tctx->linebreak, target));
+ } else if ((tctx->flags & DNS_STYLEFLAG_MULTILINE) != 0) {
+ RETERR(str_totext(" ", target));
+ }
+
+ if ((tctx->flags & DNS_STYLEFLAG_MULTILINE) != 0) {
+ RETERR(str_totext(")", target));
+ }
+
+ if ((tctx->flags & DNS_STYLEFLAG_RRCOMMENT) != 0) {
+ if (rdata->type == dns_rdatatype_dnskey ||
+ rdata->type == dns_rdatatype_cdnskey)
+ {
+ RETERR(str_totext(" ; ", target));
+ RETERR(str_totext(keyinfo, target));
+ }
+ RETERR(str_totext("; alg = ", target));
+ RETERR(str_totext(algbuf, target));
+ RETERR(str_totext(" ; key id = ", target));
+ dns_rdata_toregion(rdata, &tmpr);
+ snprintf(buf, sizeof(buf), "%u", dst_region_computeid(&tmpr));
+ RETERR(str_totext(buf, target));
+ }
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+generic_fromwire_key(ARGS_FROMWIRE) {
+ unsigned char algorithm;
+ uint16_t flags;
+ isc_region_t sr;
+
+ UNUSED(rdclass);
+ UNUSED(dctx);
+ UNUSED(options);
+
+ isc_buffer_activeregion(source, &sr);
+ if (sr.length < 4) {
+ return (ISC_R_UNEXPECTEDEND);
+ }
+ flags = (sr.base[0] << 8) | sr.base[1];
+
+ if (type == dns_rdatatype_rkey && flags != 0U) {
+ return (DNS_R_FORMERR);
+ }
+
+ algorithm = sr.base[3];
+ RETERR(mem_tobuffer(target, sr.base, 4));
+ isc_region_consume(&sr, 4);
+ isc_buffer_forward(source, 4);
+
+ if (generic_key_nokey(type, flags)) {
+ return (ISC_R_SUCCESS);
+ }
+ if (sr.length == 0) {
+ return (ISC_R_UNEXPECTEDEND);
+ }
+
+ if (algorithm == DNS_KEYALG_PRIVATEDNS) {
+ dns_name_t name;
+ dns_decompress_setmethods(dctx, DNS_COMPRESS_NONE);
+ dns_name_init(&name, NULL);
+ RETERR(dns_name_fromwire(&name, source, dctx, options, target));
+ }
+
+ isc_buffer_activeregion(source, &sr);
+ isc_buffer_forward(source, sr.length);
+ return (mem_tobuffer(target, sr.base, sr.length));
+}
+
+static isc_result_t
+fromtext_key(ARGS_FROMTEXT) {
+ REQUIRE(type == dns_rdatatype_key);
+
+ return (generic_fromtext_key(CALL_FROMTEXT));
+}
+
+static isc_result_t
+totext_key(ARGS_TOTEXT) {
+ REQUIRE(rdata != NULL);
+ REQUIRE(rdata->type == dns_rdatatype_key);
+
+ return (generic_totext_key(CALL_TOTEXT));
+}
+
+static isc_result_t
+fromwire_key(ARGS_FROMWIRE) {
+ REQUIRE(type == dns_rdatatype_key);
+
+ return (generic_fromwire_key(CALL_FROMWIRE));
+}
+
+static isc_result_t
+towire_key(ARGS_TOWIRE) {
+ isc_region_t sr;
+
+ REQUIRE(rdata != NULL);
+ REQUIRE(rdata->type == dns_rdatatype_key);
+ REQUIRE(rdata->length != 0);
+
+ UNUSED(cctx);
+
+ dns_rdata_toregion(rdata, &sr);
+ return (mem_tobuffer(target, sr.base, sr.length));
+}
+
+static int
+compare_key(ARGS_COMPARE) {
+ isc_region_t r1;
+ isc_region_t r2;
+
+ REQUIRE(rdata1 != NULL);
+ REQUIRE(rdata2 != NULL);
+ REQUIRE(rdata1->type == rdata2->type);
+ REQUIRE(rdata1->rdclass == rdata2->rdclass);
+ REQUIRE(rdata1->type == dns_rdatatype_key);
+ REQUIRE(rdata1->length != 0);
+ REQUIRE(rdata2->length != 0);
+
+ dns_rdata_toregion(rdata1, &r1);
+ dns_rdata_toregion(rdata2, &r2);
+ return (isc_region_compare(&r1, &r2));
+}
+
+static isc_result_t
+generic_fromstruct_key(ARGS_FROMSTRUCT) {
+ dns_rdata_key_t *key = source;
+
+ REQUIRE(key != NULL);
+ REQUIRE(key->common.rdtype == type);
+ REQUIRE(key->common.rdclass == rdclass);
+
+ UNUSED(type);
+ UNUSED(rdclass);
+
+ if (type == dns_rdatatype_rkey) {
+ INSIST(key->flags == 0U);
+ }
+
+ /* Flags */
+ RETERR(uint16_tobuffer(key->flags, target));
+
+ /* Protocol */
+ RETERR(uint8_tobuffer(key->protocol, target));
+
+ /* Algorithm */
+ RETERR(uint8_tobuffer(key->algorithm, target));
+
+ /* Data */
+ return (mem_tobuffer(target, key->data, key->datalen));
+}
+
+static isc_result_t
+generic_tostruct_key(ARGS_TOSTRUCT) {
+ dns_rdata_key_t *key = target;
+ isc_region_t sr;
+
+ REQUIRE(key != NULL);
+ REQUIRE(rdata->length != 0);
+
+ REQUIRE(key != NULL);
+ REQUIRE(key->common.rdclass == rdata->rdclass);
+ REQUIRE(key->common.rdtype == rdata->type);
+ REQUIRE(!ISC_LINK_LINKED(&key->common, link));
+
+ dns_rdata_toregion(rdata, &sr);
+
+ /* Flags */
+ if (sr.length < 2) {
+ return (ISC_R_UNEXPECTEDEND);
+ }
+ key->flags = uint16_fromregion(&sr);
+ isc_region_consume(&sr, 2);
+
+ /* Protocol */
+ if (sr.length < 1) {
+ return (ISC_R_UNEXPECTEDEND);
+ }
+ key->protocol = uint8_fromregion(&sr);
+ isc_region_consume(&sr, 1);
+
+ /* Algorithm */
+ if (sr.length < 1) {
+ return (ISC_R_UNEXPECTEDEND);
+ }
+ key->algorithm = uint8_fromregion(&sr);
+ isc_region_consume(&sr, 1);
+
+ /* Data */
+ key->datalen = sr.length;
+ key->data = mem_maybedup(mctx, sr.base, key->datalen);
+ if (key->data == NULL) {
+ return (ISC_R_NOMEMORY);
+ }
+
+ key->mctx = mctx;
+ return (ISC_R_SUCCESS);
+}
+
+static void
+generic_freestruct_key(ARGS_FREESTRUCT) {
+ dns_rdata_key_t *key = (dns_rdata_key_t *)source;
+
+ REQUIRE(key != NULL);
+
+ if (key->mctx == NULL) {
+ return;
+ }
+
+ if (key->data != NULL) {
+ isc_mem_free(key->mctx, key->data);
+ }
+ key->mctx = NULL;
+}
+
+static isc_result_t
+fromstruct_key(ARGS_FROMSTRUCT) {
+ REQUIRE(type == dns_rdatatype_key);
+
+ return (generic_fromstruct_key(CALL_FROMSTRUCT));
+}
+
+static isc_result_t
+tostruct_key(ARGS_TOSTRUCT) {
+ dns_rdata_key_t *key = target;
+
+ REQUIRE(key != NULL);
+ REQUIRE(rdata != NULL);
+ REQUIRE(rdata->type == dns_rdatatype_key);
+
+ key->common.rdclass = rdata->rdclass;
+ key->common.rdtype = rdata->type;
+ ISC_LINK_INIT(&key->common, link);
+
+ return (generic_tostruct_key(CALL_TOSTRUCT));
+}
+
+static void
+freestruct_key(ARGS_FREESTRUCT) {
+ dns_rdata_key_t *key = (dns_rdata_key_t *)source;
+
+ REQUIRE(key != NULL);
+ REQUIRE(key->common.rdtype == dns_rdatatype_key);
+
+ generic_freestruct_key(source);
+}
+
+static isc_result_t
+additionaldata_key(ARGS_ADDLDATA) {
+ REQUIRE(rdata != NULL);
+ REQUIRE(rdata->type == dns_rdatatype_key);
+
+ UNUSED(rdata);
+ UNUSED(owner);
+ UNUSED(add);
+ UNUSED(arg);
+
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+digest_key(ARGS_DIGEST) {
+ isc_region_t r;
+
+ REQUIRE(rdata != NULL);
+ REQUIRE(rdata->type == dns_rdatatype_key);
+
+ dns_rdata_toregion(rdata, &r);
+
+ return ((digest)(arg, &r));
+}
+
+static bool
+checkowner_key(ARGS_CHECKOWNER) {
+ REQUIRE(type == dns_rdatatype_key);
+
+ UNUSED(name);
+ UNUSED(type);
+ UNUSED(rdclass);
+ UNUSED(wildcard);
+
+ return (true);
+}
+
+static bool
+checknames_key(ARGS_CHECKNAMES) {
+ REQUIRE(rdata != NULL);
+ REQUIRE(rdata->type == dns_rdatatype_key);
+
+ UNUSED(rdata);
+ UNUSED(owner);
+ UNUSED(bad);
+
+ return (true);
+}
+
+static int
+casecompare_key(ARGS_COMPARE) {
+ return (compare_key(rdata1, rdata2));
+}
+
+#endif /* RDATA_GENERIC_KEY_25_C */
diff --git a/lib/dns/rdata/generic/key_25.h b/lib/dns/rdata/generic/key_25.h
new file mode 100644
index 0000000..4702434
--- /dev/null
+++ b/lib/dns/rdata/generic/key_25.h
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#pragma once
+
+/*!
+ * \brief Per RFC2535 */
+
+typedef struct dns_rdata_key {
+ dns_rdatacommon_t common;
+ isc_mem_t *mctx;
+ uint16_t flags;
+ dns_secproto_t protocol;
+ dns_secalg_t algorithm;
+ uint16_t datalen;
+ unsigned char *data;
+} dns_rdata_key_t;
diff --git a/lib/dns/rdata/generic/keydata_65533.c b/lib/dns/rdata/generic/keydata_65533.c
new file mode 100644
index 0000000..175afad
--- /dev/null
+++ b/lib/dns/rdata/generic/keydata_65533.c
@@ -0,0 +1,463 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#ifndef GENERIC_KEYDATA_65533_C
+#define GENERIC_KEYDATA_65533_C 1
+
+#include <isc/stdtime.h>
+#include <isc/time.h>
+
+#include <dst/dst.h>
+
+#define RRTYPE_KEYDATA_ATTRIBUTES (0)
+
+static isc_result_t
+fromtext_keydata(ARGS_FROMTEXT) {
+ isc_token_t token;
+ dns_secalg_t alg;
+ dns_secproto_t proto;
+ dns_keyflags_t flags;
+ uint32_t refresh, addhd, removehd;
+
+ REQUIRE(type == dns_rdatatype_keydata);
+
+ UNUSED(type);
+ UNUSED(rdclass);
+ UNUSED(origin);
+ UNUSED(options);
+ UNUSED(callbacks);
+
+ /* refresh timer */
+ RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_string,
+ false));
+ RETTOK(dns_time32_fromtext(DNS_AS_STR(token), &refresh));
+ RETERR(uint32_tobuffer(refresh, target));
+
+ /* add hold-down */
+ RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_string,
+ false));
+ RETTOK(dns_time32_fromtext(DNS_AS_STR(token), &addhd));
+ RETERR(uint32_tobuffer(addhd, target));
+
+ /* remove hold-down */
+ RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_string,
+ false));
+ RETTOK(dns_time32_fromtext(DNS_AS_STR(token), &removehd));
+ RETERR(uint32_tobuffer(removehd, target));
+
+ /* flags */
+ RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_string,
+ false));
+ RETTOK(dns_keyflags_fromtext(&flags, &token.value.as_textregion));
+ RETERR(uint16_tobuffer(flags, target));
+
+ /* protocol */
+ RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_string,
+ false));
+ RETTOK(dns_secproto_fromtext(&proto, &token.value.as_textregion));
+ RETERR(mem_tobuffer(target, &proto, 1));
+
+ /* algorithm */
+ RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_string,
+ false));
+ RETTOK(dns_secalg_fromtext(&alg, &token.value.as_textregion));
+ RETERR(mem_tobuffer(target, &alg, 1));
+
+ /* Do we have a placeholder KEYDATA record? */
+ if (flags == 0 && proto == 0 && alg == 0) {
+ return (ISC_R_SUCCESS);
+ }
+
+ /* No Key? */
+ if ((flags & 0xc000) == 0xc000) {
+ return (ISC_R_SUCCESS);
+ }
+
+ return (isc_base64_tobuffer(lexer, target, -2));
+}
+
+static isc_result_t
+totext_keydata(ARGS_TOTEXT) {
+ isc_region_t sr;
+ char buf[sizeof("64000")];
+ unsigned int flags;
+ unsigned char proto, algorithm;
+ unsigned long refresh, add, deltime;
+ char algbuf[DNS_NAME_FORMATSIZE];
+ const char *keyinfo;
+
+ REQUIRE(rdata->type == dns_rdatatype_keydata);
+
+ if ((tctx->flags & DNS_STYLEFLAG_KEYDATA) == 0 || rdata->length < 16) {
+ return (unknown_totext(rdata, tctx, target));
+ }
+
+ dns_rdata_toregion(rdata, &sr);
+
+ /* refresh timer */
+ refresh = uint32_fromregion(&sr);
+ isc_region_consume(&sr, 4);
+ RETERR(dns_time32_totext(refresh, target));
+ RETERR(str_totext(" ", target));
+
+ /* add hold-down */
+ add = uint32_fromregion(&sr);
+ isc_region_consume(&sr, 4);
+ RETERR(dns_time32_totext(add, target));
+ RETERR(str_totext(" ", target));
+
+ /* remove hold-down */
+ deltime = uint32_fromregion(&sr);
+ isc_region_consume(&sr, 4);
+ RETERR(dns_time32_totext(deltime, target));
+ RETERR(str_totext(" ", target));
+
+ /* flags */
+ flags = uint16_fromregion(&sr);
+ isc_region_consume(&sr, 2);
+ snprintf(buf, sizeof(buf), "%u", flags);
+ RETERR(str_totext(buf, target));
+ RETERR(str_totext(" ", target));
+ if ((flags & DNS_KEYFLAG_KSK) != 0) {
+ if ((flags & DNS_KEYFLAG_REVOKE) != 0) {
+ keyinfo = "revoked KSK";
+ } else {
+ keyinfo = "KSK";
+ }
+ } else {
+ keyinfo = "ZSK";
+ }
+
+ /* protocol */
+ proto = sr.base[0];
+ snprintf(buf, sizeof(buf), "%u", proto);
+ isc_region_consume(&sr, 1);
+ RETERR(str_totext(buf, target));
+ RETERR(str_totext(" ", target));
+
+ /* algorithm */
+ algorithm = sr.base[0];
+ snprintf(buf, sizeof(buf), "%u", algorithm);
+ isc_region_consume(&sr, 1);
+ RETERR(str_totext(buf, target));
+
+ /* Do we have a placeholder KEYDATA record? */
+ if (flags == 0 && proto == 0 && algorithm == 0) {
+ if ((tctx->flags & DNS_STYLEFLAG_RRCOMMENT) != 0) {
+ RETERR(str_totext(" ; placeholder", target));
+ }
+ return (ISC_R_SUCCESS);
+ }
+
+ /* No Key? */
+ if ((flags & 0xc000) == 0xc000) {
+ return (ISC_R_SUCCESS);
+ }
+
+ /* key */
+ if ((tctx->flags & DNS_STYLEFLAG_MULTILINE) != 0) {
+ RETERR(str_totext(" (", target));
+ }
+ RETERR(str_totext(tctx->linebreak, target));
+ if (tctx->width == 0) { /* No splitting */
+ RETERR(isc_base64_totext(&sr, 60, "", target));
+ } else {
+ RETERR(isc_base64_totext(&sr, tctx->width - 2, tctx->linebreak,
+ target));
+ }
+
+ if ((tctx->flags & DNS_STYLEFLAG_RRCOMMENT) != 0) {
+ RETERR(str_totext(tctx->linebreak, target));
+ } else if ((tctx->flags & DNS_STYLEFLAG_MULTILINE) != 0) {
+ RETERR(str_totext(" ", target));
+ }
+
+ if ((tctx->flags & DNS_STYLEFLAG_MULTILINE) != 0) {
+ RETERR(str_totext(")", target));
+ }
+
+ if ((tctx->flags & DNS_STYLEFLAG_RRCOMMENT) != 0) {
+ isc_region_t tmpr;
+ char rbuf[ISC_FORMATHTTPTIMESTAMP_SIZE];
+ char abuf[ISC_FORMATHTTPTIMESTAMP_SIZE];
+ char dbuf[ISC_FORMATHTTPTIMESTAMP_SIZE];
+ isc_time_t t;
+
+ RETERR(str_totext(" ; ", target));
+ RETERR(str_totext(keyinfo, target));
+ dns_secalg_format((dns_secalg_t)algorithm, algbuf,
+ sizeof(algbuf));
+ RETERR(str_totext("; alg = ", target));
+ RETERR(str_totext(algbuf, target));
+ RETERR(str_totext("; key id = ", target));
+ dns_rdata_toregion(rdata, &tmpr);
+ /* Skip over refresh, addhd, and removehd */
+ isc_region_consume(&tmpr, 12);
+ snprintf(buf, sizeof(buf), "%u", dst_region_computeid(&tmpr));
+ RETERR(str_totext(buf, target));
+
+ if ((tctx->flags & DNS_STYLEFLAG_MULTILINE) != 0) {
+ isc_stdtime_t now;
+
+ isc_stdtime_get(&now);
+
+ RETERR(str_totext(tctx->linebreak, target));
+ RETERR(str_totext("; next refresh: ", target));
+ isc_time_set(&t, refresh, 0);
+ isc_time_formathttptimestamp(&t, rbuf, sizeof(rbuf));
+ RETERR(str_totext(rbuf, target));
+
+ if (add == 0U) {
+ RETERR(str_totext(tctx->linebreak, target));
+ RETERR(str_totext("; no trust", target));
+ } else {
+ RETERR(str_totext(tctx->linebreak, target));
+ if (add < now) {
+ RETERR(str_totext("; trusted since: ",
+ target));
+ } else {
+ RETERR(str_totext("; trust pending: ",
+ target));
+ }
+ isc_time_set(&t, add, 0);
+ isc_time_formathttptimestamp(&t, abuf,
+ sizeof(abuf));
+ RETERR(str_totext(abuf, target));
+ }
+
+ if (deltime != 0U) {
+ RETERR(str_totext(tctx->linebreak, target));
+ RETERR(str_totext("; removal pending: ",
+ target));
+ isc_time_set(&t, deltime, 0);
+ isc_time_formathttptimestamp(&t, dbuf,
+ sizeof(dbuf));
+ RETERR(str_totext(dbuf, target));
+ }
+ }
+ }
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+fromwire_keydata(ARGS_FROMWIRE) {
+ isc_region_t sr;
+
+ REQUIRE(type == dns_rdatatype_keydata);
+
+ UNUSED(type);
+ UNUSED(rdclass);
+ UNUSED(dctx);
+ UNUSED(options);
+
+ isc_buffer_activeregion(source, &sr);
+ isc_buffer_forward(source, sr.length);
+ return (mem_tobuffer(target, sr.base, sr.length));
+}
+
+static isc_result_t
+towire_keydata(ARGS_TOWIRE) {
+ isc_region_t sr;
+
+ REQUIRE(rdata->type == dns_rdatatype_keydata);
+
+ UNUSED(cctx);
+
+ dns_rdata_toregion(rdata, &sr);
+ return (mem_tobuffer(target, sr.base, sr.length));
+}
+
+static int
+compare_keydata(ARGS_COMPARE) {
+ isc_region_t r1;
+ isc_region_t r2;
+
+ REQUIRE(rdata1->type == rdata2->type);
+ REQUIRE(rdata1->rdclass == rdata2->rdclass);
+ REQUIRE(rdata1->type == dns_rdatatype_keydata);
+
+ dns_rdata_toregion(rdata1, &r1);
+ dns_rdata_toregion(rdata2, &r2);
+ return (isc_region_compare(&r1, &r2));
+}
+
+static isc_result_t
+fromstruct_keydata(ARGS_FROMSTRUCT) {
+ dns_rdata_keydata_t *keydata = source;
+
+ REQUIRE(type == dns_rdatatype_keydata);
+ REQUIRE(keydata != NULL);
+ REQUIRE(keydata->common.rdtype == type);
+ REQUIRE(keydata->common.rdclass == rdclass);
+
+ UNUSED(type);
+ UNUSED(rdclass);
+
+ /* Refresh timer */
+ RETERR(uint32_tobuffer(keydata->refresh, target));
+
+ /* Add hold-down */
+ RETERR(uint32_tobuffer(keydata->addhd, target));
+
+ /* Remove hold-down */
+ RETERR(uint32_tobuffer(keydata->removehd, target));
+
+ /* Flags */
+ RETERR(uint16_tobuffer(keydata->flags, target));
+
+ /* Protocol */
+ RETERR(uint8_tobuffer(keydata->protocol, target));
+
+ /* Algorithm */
+ RETERR(uint8_tobuffer(keydata->algorithm, target));
+
+ /* Data */
+ return (mem_tobuffer(target, keydata->data, keydata->datalen));
+}
+
+static isc_result_t
+tostruct_keydata(ARGS_TOSTRUCT) {
+ dns_rdata_keydata_t *keydata = target;
+ isc_region_t sr;
+
+ REQUIRE(rdata->type == dns_rdatatype_keydata);
+ REQUIRE(keydata != NULL);
+
+ keydata->common.rdclass = rdata->rdclass;
+ keydata->common.rdtype = rdata->type;
+ ISC_LINK_INIT(&keydata->common, link);
+
+ dns_rdata_toregion(rdata, &sr);
+
+ /* Refresh timer */
+ if (sr.length < 4) {
+ return (ISC_R_UNEXPECTEDEND);
+ }
+ keydata->refresh = uint32_fromregion(&sr);
+ isc_region_consume(&sr, 4);
+
+ /* Add hold-down */
+ if (sr.length < 4) {
+ return (ISC_R_UNEXPECTEDEND);
+ }
+ keydata->addhd = uint32_fromregion(&sr);
+ isc_region_consume(&sr, 4);
+
+ /* Remove hold-down */
+ if (sr.length < 4) {
+ return (ISC_R_UNEXPECTEDEND);
+ }
+ keydata->removehd = uint32_fromregion(&sr);
+ isc_region_consume(&sr, 4);
+
+ /* Flags */
+ if (sr.length < 2) {
+ return (ISC_R_UNEXPECTEDEND);
+ }
+ keydata->flags = uint16_fromregion(&sr);
+ isc_region_consume(&sr, 2);
+
+ /* Protocol */
+ if (sr.length < 1) {
+ return (ISC_R_UNEXPECTEDEND);
+ }
+ keydata->protocol = uint8_fromregion(&sr);
+ isc_region_consume(&sr, 1);
+
+ /* Algorithm */
+ if (sr.length < 1) {
+ return (ISC_R_UNEXPECTEDEND);
+ }
+ keydata->algorithm = uint8_fromregion(&sr);
+ isc_region_consume(&sr, 1);
+
+ /* Data */
+ keydata->datalen = sr.length;
+ keydata->data = mem_maybedup(mctx, sr.base, keydata->datalen);
+ if (keydata->data == NULL) {
+ return (ISC_R_NOMEMORY);
+ }
+
+ keydata->mctx = mctx;
+ return (ISC_R_SUCCESS);
+}
+
+static void
+freestruct_keydata(ARGS_FREESTRUCT) {
+ dns_rdata_keydata_t *keydata = (dns_rdata_keydata_t *)source;
+
+ REQUIRE(keydata != NULL);
+ REQUIRE(keydata->common.rdtype == dns_rdatatype_keydata);
+
+ if (keydata->mctx == NULL) {
+ return;
+ }
+
+ if (keydata->data != NULL) {
+ isc_mem_free(keydata->mctx, keydata->data);
+ }
+ keydata->mctx = NULL;
+}
+
+static isc_result_t
+additionaldata_keydata(ARGS_ADDLDATA) {
+ REQUIRE(rdata->type == dns_rdatatype_keydata);
+
+ UNUSED(rdata);
+ UNUSED(owner);
+ UNUSED(add);
+ UNUSED(arg);
+
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+digest_keydata(ARGS_DIGEST) {
+ isc_region_t r;
+
+ REQUIRE(rdata->type == dns_rdatatype_keydata);
+
+ dns_rdata_toregion(rdata, &r);
+
+ return ((digest)(arg, &r));
+}
+
+static bool
+checkowner_keydata(ARGS_CHECKOWNER) {
+ REQUIRE(type == dns_rdatatype_keydata);
+
+ UNUSED(name);
+ UNUSED(type);
+ UNUSED(rdclass);
+ UNUSED(wildcard);
+
+ return (true);
+}
+
+static bool
+checknames_keydata(ARGS_CHECKNAMES) {
+ REQUIRE(rdata->type == dns_rdatatype_keydata);
+
+ UNUSED(rdata);
+ UNUSED(owner);
+ UNUSED(bad);
+
+ return (true);
+}
+
+static int
+casecompare_keydata(ARGS_COMPARE) {
+ return (compare_keydata(rdata1, rdata2));
+}
+
+#endif /* GENERIC_KEYDATA_65533_C */
diff --git a/lib/dns/rdata/generic/keydata_65533.h b/lib/dns/rdata/generic/keydata_65533.h
new file mode 100644
index 0000000..598b31b
--- /dev/null
+++ b/lib/dns/rdata/generic/keydata_65533.h
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#pragma once
+
+typedef struct dns_rdata_keydata {
+ dns_rdatacommon_t common;
+ isc_mem_t *mctx;
+ uint32_t refresh; /* Timer for refreshing data */
+ uint32_t addhd; /* Hold-down timer for adding */
+ uint32_t removehd; /* Hold-down timer for removing */
+ uint16_t flags; /* Copy of DNSKEY_48 */
+ dns_secproto_t protocol;
+ dns_secalg_t algorithm;
+ uint16_t datalen;
+ unsigned char *data;
+} dns_rdata_keydata_t;
diff --git a/lib/dns/rdata/generic/l32_105.c b/lib/dns/rdata/generic/l32_105.c
new file mode 100644
index 0000000..7ab6518
--- /dev/null
+++ b/lib/dns/rdata/generic/l32_105.c
@@ -0,0 +1,231 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#ifndef RDATA_GENERIC_L32_105_C
+#define RDATA_GENERIC_L32_105_C
+
+#include <string.h>
+
+#include <isc/net.h>
+
+#define RRTYPE_L32_ATTRIBUTES (0)
+
+static isc_result_t
+fromtext_l32(ARGS_FROMTEXT) {
+ isc_token_t token;
+ struct in_addr addr;
+ isc_region_t region;
+
+ REQUIRE(type == dns_rdatatype_l32);
+
+ UNUSED(type);
+ UNUSED(rdclass);
+ UNUSED(origin);
+ UNUSED(options);
+ UNUSED(callbacks);
+
+ RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_number,
+ false));
+ if (token.value.as_ulong > 0xffffU) {
+ RETTOK(ISC_R_RANGE);
+ }
+ RETERR(uint16_tobuffer(token.value.as_ulong, target));
+
+ RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_string,
+ false));
+
+ if (inet_pton(AF_INET, DNS_AS_STR(token), &addr) != 1) {
+ RETTOK(DNS_R_BADDOTTEDQUAD);
+ }
+ isc_buffer_availableregion(target, &region);
+ if (region.length < 4) {
+ return (ISC_R_NOSPACE);
+ }
+ memmove(region.base, &addr, 4);
+ isc_buffer_add(target, 4);
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+totext_l32(ARGS_TOTEXT) {
+ isc_region_t region;
+ char buf[sizeof("65000")];
+ unsigned short num;
+
+ REQUIRE(rdata->type == dns_rdatatype_l32);
+ REQUIRE(rdata->length == 6);
+
+ UNUSED(tctx);
+
+ dns_rdata_toregion(rdata, &region);
+ num = uint16_fromregion(&region);
+ isc_region_consume(&region, 2);
+ snprintf(buf, sizeof(buf), "%u", num);
+ RETERR(str_totext(buf, target));
+
+ RETERR(str_totext(" ", target));
+
+ return (inet_totext(AF_INET, tctx->flags, &region, target));
+}
+
+static isc_result_t
+fromwire_l32(ARGS_FROMWIRE) {
+ isc_region_t sregion;
+
+ REQUIRE(type == dns_rdatatype_l32);
+
+ UNUSED(type);
+ UNUSED(options);
+ UNUSED(rdclass);
+ UNUSED(dctx);
+
+ isc_buffer_activeregion(source, &sregion);
+ if (sregion.length != 6) {
+ return (DNS_R_FORMERR);
+ }
+ isc_buffer_forward(source, sregion.length);
+ return (mem_tobuffer(target, sregion.base, sregion.length));
+}
+
+static isc_result_t
+towire_l32(ARGS_TOWIRE) {
+ REQUIRE(rdata->type == dns_rdatatype_l32);
+ REQUIRE(rdata->length == 6);
+
+ UNUSED(cctx);
+
+ return (mem_tobuffer(target, rdata->data, rdata->length));
+}
+
+static int
+compare_l32(ARGS_COMPARE) {
+ isc_region_t region1;
+ isc_region_t region2;
+
+ REQUIRE(rdata1->type == rdata2->type);
+ REQUIRE(rdata1->rdclass == rdata2->rdclass);
+ REQUIRE(rdata1->type == dns_rdatatype_l32);
+ REQUIRE(rdata1->length == 6);
+ REQUIRE(rdata2->length == 6);
+
+ dns_rdata_toregion(rdata1, &region1);
+ dns_rdata_toregion(rdata2, &region2);
+ return (isc_region_compare(&region1, &region2));
+}
+
+static isc_result_t
+fromstruct_l32(ARGS_FROMSTRUCT) {
+ dns_rdata_l32_t *l32 = source;
+ uint32_t n;
+
+ REQUIRE(type == dns_rdatatype_l32);
+ REQUIRE(l32 != NULL);
+ REQUIRE(l32->common.rdtype == type);
+ REQUIRE(l32->common.rdclass == rdclass);
+
+ UNUSED(type);
+ UNUSED(rdclass);
+
+ RETERR(uint16_tobuffer(l32->pref, target));
+ n = ntohl(l32->l32.s_addr);
+ return (uint32_tobuffer(n, target));
+}
+
+static isc_result_t
+tostruct_l32(ARGS_TOSTRUCT) {
+ isc_region_t region;
+ dns_rdata_l32_t *l32 = target;
+ uint32_t n;
+
+ REQUIRE(rdata->type == dns_rdatatype_l32);
+ REQUIRE(l32 != NULL);
+ REQUIRE(rdata->length == 6);
+
+ UNUSED(mctx);
+
+ l32->common.rdclass = rdata->rdclass;
+ l32->common.rdtype = rdata->type;
+ ISC_LINK_INIT(&l32->common, link);
+
+ dns_rdata_toregion(rdata, &region);
+ l32->pref = uint16_fromregion(&region);
+ n = uint32_fromregion(&region);
+ l32->l32.s_addr = htonl(n);
+ return (ISC_R_SUCCESS);
+}
+
+static void
+freestruct_l32(ARGS_FREESTRUCT) {
+ dns_rdata_l32_t *l32 = source;
+
+ REQUIRE(l32 != NULL);
+ REQUIRE(l32->common.rdtype == dns_rdatatype_l32);
+
+ return;
+}
+
+static isc_result_t
+additionaldata_l32(ARGS_ADDLDATA) {
+ REQUIRE(rdata->type == dns_rdatatype_l32);
+ REQUIRE(rdata->length == 6);
+
+ UNUSED(rdata);
+ UNUSED(owner);
+ UNUSED(add);
+ UNUSED(arg);
+
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+digest_l32(ARGS_DIGEST) {
+ isc_region_t r;
+
+ REQUIRE(rdata->type == dns_rdatatype_l32);
+ REQUIRE(rdata->length == 6);
+
+ dns_rdata_toregion(rdata, &r);
+
+ return ((digest)(arg, &r));
+}
+
+static bool
+checkowner_l32(ARGS_CHECKOWNER) {
+ REQUIRE(type == dns_rdatatype_l32);
+
+ UNUSED(name);
+ UNUSED(type);
+ UNUSED(rdclass);
+ UNUSED(wildcard);
+
+ return (true);
+}
+
+static bool
+checknames_l32(ARGS_CHECKNAMES) {
+ REQUIRE(rdata->type == dns_rdatatype_l32);
+ REQUIRE(rdata->length == 6);
+
+ UNUSED(rdata);
+ UNUSED(owner);
+ UNUSED(bad);
+
+ return (true);
+}
+
+static int
+casecompare_l32(ARGS_COMPARE) {
+ return (compare_l32(rdata1, rdata2));
+}
+
+#endif /* RDATA_GENERIC_L32_105_C */
diff --git a/lib/dns/rdata/generic/l32_105.h b/lib/dns/rdata/generic/l32_105.h
new file mode 100644
index 0000000..58a76d2
--- /dev/null
+++ b/lib/dns/rdata/generic/l32_105.h
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/* */
+#pragma once
+
+typedef struct dns_rdata_l32 {
+ dns_rdatacommon_t common;
+ uint16_t pref;
+ struct in_addr l32;
+} dns_rdata_l32_t;
diff --git a/lib/dns/rdata/generic/l64_106.c b/lib/dns/rdata/generic/l64_106.c
new file mode 100644
index 0000000..ee28f2e
--- /dev/null
+++ b/lib/dns/rdata/generic/l64_106.c
@@ -0,0 +1,225 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#ifndef RDATA_GENERIC_L64_106_C
+#define RDATA_GENERIC_L64_106_C
+
+#include <string.h>
+
+#include <isc/net.h>
+
+#define RRTYPE_L64_ATTRIBUTES (0)
+
+static isc_result_t
+fromtext_l64(ARGS_FROMTEXT) {
+ isc_token_t token;
+ unsigned char locator[NS_LOCATORSZ];
+
+ REQUIRE(type == dns_rdatatype_l64);
+
+ UNUSED(type);
+ UNUSED(rdclass);
+ UNUSED(origin);
+ UNUSED(options);
+ UNUSED(callbacks);
+
+ RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_number,
+ false));
+ if (token.value.as_ulong > 0xffffU) {
+ RETTOK(ISC_R_RANGE);
+ }
+ RETERR(uint16_tobuffer(token.value.as_ulong, target));
+
+ RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_string,
+ false));
+
+ if (locator_pton(DNS_AS_STR(token), locator) != 1) {
+ RETTOK(DNS_R_SYNTAX);
+ }
+ return (mem_tobuffer(target, locator, NS_LOCATORSZ));
+}
+
+static isc_result_t
+totext_l64(ARGS_TOTEXT) {
+ isc_region_t region;
+ char buf[sizeof("xxxx:xxxx:xxxx:xxxx")];
+ unsigned short num;
+
+ REQUIRE(rdata->type == dns_rdatatype_l64);
+ REQUIRE(rdata->length == 10);
+
+ UNUSED(tctx);
+
+ dns_rdata_toregion(rdata, &region);
+ num = uint16_fromregion(&region);
+ isc_region_consume(&region, 2);
+ snprintf(buf, sizeof(buf), "%u", num);
+ RETERR(str_totext(buf, target));
+
+ RETERR(str_totext(" ", target));
+
+ snprintf(buf, sizeof(buf), "%x:%x:%x:%x",
+ region.base[0] << 8 | region.base[1],
+ region.base[2] << 8 | region.base[3],
+ region.base[4] << 8 | region.base[5],
+ region.base[6] << 8 | region.base[7]);
+ return (str_totext(buf, target));
+}
+
+static isc_result_t
+fromwire_l64(ARGS_FROMWIRE) {
+ isc_region_t sregion;
+
+ REQUIRE(type == dns_rdatatype_l64);
+
+ UNUSED(type);
+ UNUSED(options);
+ UNUSED(rdclass);
+ UNUSED(dctx);
+
+ isc_buffer_activeregion(source, &sregion);
+ if (sregion.length != 10) {
+ return (DNS_R_FORMERR);
+ }
+ isc_buffer_forward(source, sregion.length);
+ return (mem_tobuffer(target, sregion.base, sregion.length));
+}
+
+static isc_result_t
+towire_l64(ARGS_TOWIRE) {
+ REQUIRE(rdata->type == dns_rdatatype_l64);
+ REQUIRE(rdata->length == 10);
+
+ UNUSED(cctx);
+
+ return (mem_tobuffer(target, rdata->data, rdata->length));
+}
+
+static int
+compare_l64(ARGS_COMPARE) {
+ isc_region_t region1;
+ isc_region_t region2;
+
+ REQUIRE(rdata1->type == rdata2->type);
+ REQUIRE(rdata1->rdclass == rdata2->rdclass);
+ REQUIRE(rdata1->type == dns_rdatatype_l64);
+ REQUIRE(rdata1->length == 10);
+ REQUIRE(rdata2->length == 10);
+
+ dns_rdata_toregion(rdata1, &region1);
+ dns_rdata_toregion(rdata2, &region2);
+ return (isc_region_compare(&region1, &region2));
+}
+
+static isc_result_t
+fromstruct_l64(ARGS_FROMSTRUCT) {
+ dns_rdata_l64_t *l64 = source;
+
+ REQUIRE(type == dns_rdatatype_l64);
+ REQUIRE(l64 != NULL);
+ REQUIRE(l64->common.rdtype == type);
+ REQUIRE(l64->common.rdclass == rdclass);
+
+ UNUSED(type);
+ UNUSED(rdclass);
+
+ RETERR(uint16_tobuffer(l64->pref, target));
+ return (mem_tobuffer(target, l64->l64, sizeof(l64->l64)));
+}
+
+static isc_result_t
+tostruct_l64(ARGS_TOSTRUCT) {
+ isc_region_t region;
+ dns_rdata_l64_t *l64 = target;
+
+ REQUIRE(rdata->type == dns_rdatatype_l64);
+ REQUIRE(l64 != NULL);
+ REQUIRE(rdata->length == 10);
+
+ UNUSED(mctx);
+
+ l64->common.rdclass = rdata->rdclass;
+ l64->common.rdtype = rdata->type;
+ ISC_LINK_INIT(&l64->common, link);
+
+ dns_rdata_toregion(rdata, &region);
+ l64->pref = uint16_fromregion(&region);
+ memmove(l64->l64, region.base, region.length);
+ return (ISC_R_SUCCESS);
+}
+
+static void
+freestruct_l64(ARGS_FREESTRUCT) {
+ dns_rdata_l64_t *l64 = source;
+
+ REQUIRE(l64 != NULL);
+ REQUIRE(l64->common.rdtype == dns_rdatatype_l64);
+
+ return;
+}
+
+static isc_result_t
+additionaldata_l64(ARGS_ADDLDATA) {
+ REQUIRE(rdata->type == dns_rdatatype_l64);
+ REQUIRE(rdata->length == 10);
+
+ UNUSED(rdata);
+ UNUSED(owner);
+ UNUSED(add);
+ UNUSED(arg);
+
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+digest_l64(ARGS_DIGEST) {
+ isc_region_t r;
+
+ REQUIRE(rdata->type == dns_rdatatype_l64);
+ REQUIRE(rdata->length == 10);
+
+ dns_rdata_toregion(rdata, &r);
+
+ return ((digest)(arg, &r));
+}
+
+static bool
+checkowner_l64(ARGS_CHECKOWNER) {
+ REQUIRE(type == dns_rdatatype_l64);
+
+ UNUSED(name);
+ UNUSED(type);
+ UNUSED(rdclass);
+ UNUSED(wildcard);
+
+ return (true);
+}
+
+static bool
+checknames_l64(ARGS_CHECKNAMES) {
+ REQUIRE(rdata->type == dns_rdatatype_l64);
+ REQUIRE(rdata->length == 10);
+
+ UNUSED(rdata);
+ UNUSED(owner);
+ UNUSED(bad);
+
+ return (true);
+}
+
+static int
+casecompare_l64(ARGS_COMPARE) {
+ return (compare_l64(rdata1, rdata2));
+}
+
+#endif /* RDATA_GENERIC_L64_106_C */
diff --git a/lib/dns/rdata/generic/l64_106.h b/lib/dns/rdata/generic/l64_106.h
new file mode 100644
index 0000000..aa4d9b8
--- /dev/null
+++ b/lib/dns/rdata/generic/l64_106.h
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/* */
+#pragma once
+
+typedef struct dns_rdata_l64 {
+ dns_rdatacommon_t common;
+ uint16_t pref;
+ unsigned char l64[8];
+} dns_rdata_l64_t;
diff --git a/lib/dns/rdata/generic/loc_29.c b/lib/dns/rdata/generic/loc_29.c
new file mode 100644
index 0000000..c05e144
--- /dev/null
+++ b/lib/dns/rdata/generic/loc_29.c
@@ -0,0 +1,839 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/* RFC1876 */
+
+#ifndef RDATA_GENERIC_LOC_29_C
+#define RDATA_GENERIC_LOC_29_C
+
+#define RRTYPE_LOC_ATTRIBUTES (0)
+
+static isc_result_t
+loc_getdecimal(const char *str, unsigned long max, size_t precision, char units,
+ unsigned long *valuep) {
+ bool ok;
+ char *e;
+ size_t i;
+ long tmp;
+ unsigned long value;
+
+ value = strtoul(str, &e, 10);
+ if (*e != 0 && *e != '.' && *e != units) {
+ return (DNS_R_SYNTAX);
+ }
+ if (value > max) {
+ return (ISC_R_RANGE);
+ }
+ ok = e != str;
+ if (*e == '.') {
+ e++;
+ for (i = 0; i < precision; i++) {
+ if (*e == 0 || *e == units) {
+ break;
+ }
+ if ((tmp = decvalue(*e++)) < 0) {
+ return (DNS_R_SYNTAX);
+ }
+ ok = true;
+ value *= 10;
+ value += tmp;
+ }
+ for (; i < precision; i++) {
+ value *= 10;
+ }
+ } else {
+ for (i = 0; i < precision; i++) {
+ value *= 10;
+ }
+ }
+ if (*e != 0 && *e == units) {
+ e++;
+ }
+ if (!ok || *e != 0) {
+ return (DNS_R_SYNTAX);
+ }
+ *valuep = value;
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+loc_getprecision(const char *str, unsigned char *valuep) {
+ unsigned long poweroften[8] = { 1, 10, 100, 1000,
+ 10000, 100000, 1000000, 10000000 };
+ unsigned long m, cm;
+ bool ok;
+ char *e;
+ size_t i;
+ long tmp;
+ int man;
+ int exp;
+
+ m = strtoul(str, &e, 10);
+ if (*e != 0 && *e != '.' && *e != 'm') {
+ return (DNS_R_SYNTAX);
+ }
+ if (m > 90000000) {
+ return (ISC_R_RANGE);
+ }
+ cm = 0;
+ ok = e != str;
+ if (*e == '.') {
+ e++;
+ for (i = 0; i < 2; i++) {
+ if (*e == 0 || *e == 'm') {
+ break;
+ }
+ if ((tmp = decvalue(*e++)) < 0) {
+ return (DNS_R_SYNTAX);
+ }
+ ok = true;
+ cm *= 10;
+ cm += tmp;
+ }
+ for (; i < 2; i++) {
+ cm *= 10;
+ }
+ }
+ if (*e == 'm') {
+ e++;
+ }
+ if (!ok || *e != 0) {
+ return (DNS_R_SYNTAX);
+ }
+
+ /*
+ * We don't just multiply out as we will overflow.
+ */
+ if (m > 0) {
+ for (exp = 0; exp < 7; exp++) {
+ if (m < poweroften[exp + 1]) {
+ break;
+ }
+ }
+ man = m / poweroften[exp];
+ exp += 2;
+ } else if (cm >= 10) {
+ man = cm / 10;
+ exp = 1;
+ } else {
+ man = cm;
+ exp = 0;
+ }
+ *valuep = (man << 4) + exp;
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+get_degrees(isc_lex_t *lexer, isc_token_t *token, unsigned long *d) {
+ RETERR(isc_lex_getmastertoken(lexer, token, isc_tokentype_number,
+ false));
+ *d = token->value.as_ulong;
+
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+check_coordinate(unsigned long d, unsigned long m, unsigned long s,
+ unsigned long maxd) {
+ if (d > maxd || m > 59U) {
+ return (ISC_R_RANGE);
+ }
+ if (d == maxd && (m != 0 || s != 0)) {
+ return (ISC_R_RANGE);
+ }
+
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+get_minutes(isc_lex_t *lexer, isc_token_t *token, unsigned long *m) {
+ RETERR(isc_lex_getmastertoken(lexer, token, isc_tokentype_number,
+ false));
+
+ *m = token->value.as_ulong;
+
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+get_seconds(isc_lex_t *lexer, isc_token_t *token, unsigned long *s) {
+ RETERR(isc_lex_getmastertoken(lexer, token, isc_tokentype_string,
+ false));
+ RETERR(loc_getdecimal(DNS_AS_STR(*token), 59, 3, '\0', s));
+
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+get_direction(isc_lex_t *lexer, isc_token_t *token, const char *directions,
+ int *direction) {
+ RETERR(isc_lex_getmastertoken(lexer, token, isc_tokentype_string,
+ false));
+ if (DNS_AS_STR(*token)[0] == directions[1] &&
+ DNS_AS_STR(*token)[1] == 0)
+ {
+ *direction = DNS_AS_STR(*token)[0];
+ return (ISC_R_SUCCESS);
+ }
+
+ if (DNS_AS_STR(*token)[0] == directions[0] &&
+ DNS_AS_STR(*token)[1] == 0)
+ {
+ *direction = DNS_AS_STR(*token)[0];
+ return (ISC_R_SUCCESS);
+ }
+
+ *direction = 0;
+ isc_lex_ungettoken(lexer, token);
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+loc_getcoordinate(isc_lex_t *lexer, unsigned long *dp, unsigned long *mp,
+ unsigned long *sp, const char *directions, int *directionp,
+ unsigned long maxd) {
+ isc_result_t result = ISC_R_SUCCESS;
+ isc_token_t token;
+ unsigned long d, m, s;
+ int direction = 0;
+
+ m = 0;
+ s = 0;
+
+ /*
+ * Degrees.
+ */
+ RETERR(get_degrees(lexer, &token, &d));
+ RETTOK(check_coordinate(d, m, s, maxd));
+
+ /*
+ * Minutes.
+ */
+ RETERR(get_direction(lexer, &token, directions, &direction));
+ if (direction > 0) {
+ goto done;
+ }
+
+ RETERR(get_minutes(lexer, &token, &m));
+ RETTOK(check_coordinate(d, m, s, maxd));
+
+ /*
+ * Seconds.
+ */
+ RETERR(get_direction(lexer, &token, directions, &direction));
+ if (direction > 0) {
+ goto done;
+ }
+
+ result = get_seconds(lexer, &token, &s);
+ if (result == ISC_R_RANGE || result == DNS_R_SYNTAX) {
+ RETTOK(result);
+ }
+ RETERR(result);
+ RETTOK(check_coordinate(d, m, s, maxd));
+
+ /*
+ * Direction.
+ */
+ RETERR(get_direction(lexer, &token, directions, &direction));
+ if (direction == 0) {
+ RETERR(DNS_R_SYNTAX);
+ }
+done:
+
+ *directionp = direction;
+ *dp = d;
+ *mp = m;
+ *sp = s;
+
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+loc_getlatitude(isc_lex_t *lexer, unsigned long *latitude) {
+ unsigned long d1 = 0, m1 = 0, s1 = 0;
+ int direction = 0;
+
+ RETERR(loc_getcoordinate(lexer, &d1, &m1, &s1, "SN", &direction, 90U));
+
+ switch (direction) {
+ case 'N':
+ *latitude = 0x80000000 + (d1 * 3600 + m1 * 60) * 1000 + s1;
+ break;
+ case 'S':
+ *latitude = 0x80000000 - (d1 * 3600 + m1 * 60) * 1000 - s1;
+ break;
+ default:
+ UNREACHABLE();
+ }
+
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+loc_getlongitude(isc_lex_t *lexer, unsigned long *longitude) {
+ unsigned long d2 = 0, m2 = 0, s2 = 0;
+ int direction = 0;
+
+ RETERR(loc_getcoordinate(lexer, &d2, &m2, &s2, "WE", &direction, 180U));
+
+ switch (direction) {
+ case 'E':
+ *longitude = 0x80000000 + (d2 * 3600 + m2 * 60) * 1000 + s2;
+ break;
+ case 'W':
+ *longitude = 0x80000000 - (d2 * 3600 + m2 * 60) * 1000 - s2;
+ break;
+ default:
+ UNREACHABLE();
+ }
+
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+loc_getaltitude(isc_lex_t *lexer, unsigned long *altitude) {
+ isc_token_t token;
+ unsigned long cm;
+ const char *str;
+
+ RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_string,
+ false));
+ str = DNS_AS_STR(token);
+ if (DNS_AS_STR(token)[0] == '-') {
+ RETTOK(loc_getdecimal(str + 1, 100000, 2, 'm', &cm));
+ if (cm > 10000000UL) {
+ RETTOK(ISC_R_RANGE);
+ }
+ *altitude = 10000000 - cm;
+ } else {
+ RETTOK(loc_getdecimal(str, 42849672, 2, 'm', &cm));
+ if (cm > 4284967295UL) {
+ RETTOK(ISC_R_RANGE);
+ }
+ *altitude = 10000000 + cm;
+ }
+
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+loc_getoptionalprecision(isc_lex_t *lexer, unsigned char *valuep) {
+ isc_token_t token;
+
+ RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_string,
+ true));
+ if (token.type == isc_tokentype_eol || token.type == isc_tokentype_eof)
+ {
+ isc_lex_ungettoken(lexer, &token);
+ return (ISC_R_NOMORE);
+ }
+ RETTOK(loc_getprecision(DNS_AS_STR(token), valuep));
+
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+loc_getsize(isc_lex_t *lexer, unsigned char *sizep) {
+ return (loc_getoptionalprecision(lexer, sizep));
+}
+
+static isc_result_t
+loc_gethorizontalprecision(isc_lex_t *lexer, unsigned char *hpp) {
+ return (loc_getoptionalprecision(lexer, hpp));
+}
+
+static isc_result_t
+loc_getverticalprecision(isc_lex_t *lexer, unsigned char *vpp) {
+ return (loc_getoptionalprecision(lexer, vpp));
+}
+
+/* The LOC record is expressed in a master file in the following format:
+ *
+ * <owner> <TTL> <class> LOC ( d1 [m1 [s1]] {"N"|"S"} d2 [m2 [s2]]
+ * {"E"|"W"} alt["m"] [siz["m"] [hp["m"]
+ * [vp["m"]]]] )
+ *
+ * (The parentheses are used for multi-line data as specified in [RFC
+ * 1035] section 5.1.)
+ *
+ * where:
+ *
+ * d1: [0 .. 90] (degrees latitude)
+ * d2: [0 .. 180] (degrees longitude)
+ * m1, m2: [0 .. 59] (minutes latitude/longitude)
+ * s1, s2: [0 .. 59.999] (seconds latitude/longitude)
+ * alt: [-100000.00 .. 42849672.95] BY .01 (altitude in meters)
+ * siz, hp, vp: [0 .. 90000000.00] (size/precision in meters)
+ *
+ * If omitted, minutes and seconds default to zero, size defaults to 1m,
+ * horizontal precision defaults to 10000m, and vertical precision
+ * defaults to 10m. These defaults are chosen to represent typical
+ * ZIP/postal code area sizes, since it is often easy to find
+ * approximate geographical location by ZIP/postal code.
+ */
+static isc_result_t
+fromtext_loc(ARGS_FROMTEXT) {
+ isc_result_t result = ISC_R_SUCCESS;
+ unsigned long latitude = 0;
+ unsigned long longitude = 0;
+ unsigned long altitude = 0;
+ unsigned char size = 0x12; /* Default: 1.00m */
+ unsigned char hp = 0x16; /* Default: 10000.00 m */
+ unsigned char vp = 0x13; /* Default: 10.00 m */
+ unsigned char version = 0;
+
+ REQUIRE(type == dns_rdatatype_loc);
+
+ UNUSED(type);
+ UNUSED(rdclass);
+ UNUSED(origin);
+ UNUSED(options);
+ UNUSED(callbacks);
+
+ RETERR(loc_getlatitude(lexer, &latitude));
+ RETERR(loc_getlongitude(lexer, &longitude));
+ RETERR(loc_getaltitude(lexer, &altitude));
+ result = loc_getsize(lexer, &size);
+ if (result == ISC_R_NOMORE) {
+ result = ISC_R_SUCCESS;
+ goto encode;
+ }
+ RETERR(result);
+ result = loc_gethorizontalprecision(lexer, &hp);
+ if (result == ISC_R_NOMORE) {
+ result = ISC_R_SUCCESS;
+ goto encode;
+ }
+ RETERR(result);
+ result = loc_getverticalprecision(lexer, &vp);
+ if (result == ISC_R_NOMORE) {
+ result = ISC_R_SUCCESS;
+ goto encode;
+ }
+ RETERR(result);
+encode:
+ RETERR(mem_tobuffer(target, &version, 1));
+ RETERR(mem_tobuffer(target, &size, 1));
+ RETERR(mem_tobuffer(target, &hp, 1));
+ RETERR(mem_tobuffer(target, &vp, 1));
+
+ RETERR(uint32_tobuffer(latitude, target));
+ RETERR(uint32_tobuffer(longitude, target));
+ RETERR(uint32_tobuffer(altitude, target));
+
+ return (result);
+}
+
+static isc_result_t
+totext_loc(ARGS_TOTEXT) {
+ int d1, m1, s1, fs1;
+ int d2, m2, s2, fs2;
+ unsigned long latitude;
+ unsigned long longitude;
+ unsigned long altitude;
+ bool north;
+ bool east;
+ bool below;
+ isc_region_t sr;
+ char sbuf[sizeof("90000000m")];
+ char hbuf[sizeof("90000000m")];
+ char vbuf[sizeof("90000000m")];
+ /* "89 59 59.999 N 179 59 59.999 E " */
+ /* "-42849672.95m 90000000m 90000000m 90000000m"; */
+ char buf[8 * 6 + 12 * 1 + 2 * 10 + sizeof(sbuf) + sizeof(hbuf) +
+ sizeof(vbuf)];
+ unsigned char size, hp, vp;
+ unsigned long poweroften[8] = { 1, 10, 100, 1000,
+ 10000, 100000, 1000000, 10000000 };
+
+ UNUSED(tctx);
+
+ REQUIRE(rdata->type == dns_rdatatype_loc);
+ REQUIRE(rdata->length != 0);
+
+ dns_rdata_toregion(rdata, &sr);
+
+ if (sr.base[0] != 0) {
+ return (ISC_R_NOTIMPLEMENTED);
+ }
+
+ REQUIRE(rdata->length == 16);
+
+ size = sr.base[1];
+ INSIST((size & 0x0f) < 10 && (size >> 4) < 10);
+ if ((size & 0x0f) > 1) {
+ snprintf(sbuf, sizeof(sbuf), "%lum",
+ (size >> 4) * poweroften[(size & 0x0f) - 2]);
+ } else {
+ snprintf(sbuf, sizeof(sbuf), "0.%02lum",
+ (size >> 4) * poweroften[(size & 0x0f)]);
+ }
+ hp = sr.base[2];
+ INSIST((hp & 0x0f) < 10 && (hp >> 4) < 10);
+ if ((hp & 0x0f) > 1) {
+ snprintf(hbuf, sizeof(hbuf), "%lum",
+ (hp >> 4) * poweroften[(hp & 0x0f) - 2]);
+ } else {
+ snprintf(hbuf, sizeof(hbuf), "0.%02lum",
+ (hp >> 4) * poweroften[(hp & 0x0f)]);
+ }
+ vp = sr.base[3];
+ INSIST((vp & 0x0f) < 10 && (vp >> 4) < 10);
+ if ((vp & 0x0f) > 1) {
+ snprintf(vbuf, sizeof(vbuf), "%lum",
+ (vp >> 4) * poweroften[(vp & 0x0f) - 2]);
+ } else {
+ snprintf(vbuf, sizeof(vbuf), "0.%02lum",
+ (vp >> 4) * poweroften[(vp & 0x0f)]);
+ }
+ isc_region_consume(&sr, 4);
+
+ latitude = uint32_fromregion(&sr);
+ isc_region_consume(&sr, 4);
+ if (latitude >= 0x80000000) {
+ north = true;
+ latitude -= 0x80000000;
+ } else {
+ north = false;
+ latitude = 0x80000000 - latitude;
+ }
+ fs1 = (int)(latitude % 1000);
+ latitude /= 1000;
+ s1 = (int)(latitude % 60);
+ latitude /= 60;
+ m1 = (int)(latitude % 60);
+ latitude /= 60;
+ d1 = (int)latitude;
+ INSIST(latitude <= 90U);
+
+ longitude = uint32_fromregion(&sr);
+ isc_region_consume(&sr, 4);
+ if (longitude >= 0x80000000) {
+ east = true;
+ longitude -= 0x80000000;
+ } else {
+ east = false;
+ longitude = 0x80000000 - longitude;
+ }
+ fs2 = (int)(longitude % 1000);
+ longitude /= 1000;
+ s2 = (int)(longitude % 60);
+ longitude /= 60;
+ m2 = (int)(longitude % 60);
+ longitude /= 60;
+ d2 = (int)longitude;
+ INSIST(longitude <= 180U);
+
+ altitude = uint32_fromregion(&sr);
+ isc_region_consume(&sr, 4);
+ if (altitude < 10000000U) {
+ below = true;
+ altitude = 10000000 - altitude;
+ } else {
+ below = false;
+ altitude -= 10000000;
+ }
+
+ snprintf(buf, sizeof(buf),
+ "%d %d %d.%03d %s %d %d %d.%03d %s %s%lu.%02lum %s %s %s", d1,
+ m1, s1, fs1, north ? "N" : "S", d2, m2, s2, fs2,
+ east ? "E" : "W", below ? "-" : "", altitude / 100,
+ altitude % 100, sbuf, hbuf, vbuf);
+
+ return (str_totext(buf, target));
+}
+
+static isc_result_t
+fromwire_loc(ARGS_FROMWIRE) {
+ isc_region_t sr;
+ unsigned char c;
+ unsigned long latitude;
+ unsigned long longitude;
+
+ REQUIRE(type == dns_rdatatype_loc);
+
+ UNUSED(type);
+ UNUSED(rdclass);
+ UNUSED(dctx);
+ UNUSED(options);
+
+ isc_buffer_activeregion(source, &sr);
+ if (sr.length < 1) {
+ return (ISC_R_UNEXPECTEDEND);
+ }
+ if (sr.base[0] != 0) {
+ /* Treat as unknown. */
+ isc_buffer_forward(source, sr.length);
+ return (mem_tobuffer(target, sr.base, sr.length));
+ }
+ if (sr.length < 16) {
+ return (ISC_R_UNEXPECTEDEND);
+ }
+
+ /*
+ * Size.
+ */
+ c = sr.base[1];
+ if (c != 0) {
+ if ((c & 0xf) > 9 || ((c >> 4) & 0xf) > 9 ||
+ ((c >> 4) & 0xf) == 0)
+ {
+ return (ISC_R_RANGE);
+
+ /*
+ * Horizontal precision.
+ */
+ }
+ }
+
+ /*
+ * Horizontal precision.
+ */
+ c = sr.base[2];
+ if (c != 0) {
+ if ((c & 0xf) > 9 || ((c >> 4) & 0xf) > 9 ||
+ ((c >> 4) & 0xf) == 0)
+ {
+ return (ISC_R_RANGE);
+
+ /*
+ * Vertical precision.
+ */
+ }
+ }
+
+ /*
+ * Vertical precision.
+ */
+ c = sr.base[3];
+ if (c != 0) {
+ if ((c & 0xf) > 9 || ((c >> 4) & 0xf) > 9 ||
+ ((c >> 4) & 0xf) == 0)
+ {
+ return (ISC_R_RANGE);
+ }
+ }
+ isc_region_consume(&sr, 4);
+
+ /*
+ * Latitude.
+ */
+ latitude = uint32_fromregion(&sr);
+ if (latitude < (0x80000000UL - 90 * 3600000) ||
+ latitude > (0x80000000UL + 90 * 3600000))
+ {
+ return (ISC_R_RANGE);
+ }
+ isc_region_consume(&sr, 4);
+
+ /*
+ * Longitude.
+ */
+ longitude = uint32_fromregion(&sr);
+ if (longitude < (0x80000000UL - 180 * 3600000) ||
+ longitude > (0x80000000UL + 180 * 3600000))
+ {
+ return (ISC_R_RANGE);
+ }
+
+ /*
+ * Altitude.
+ * All values possible.
+ */
+
+ isc_buffer_activeregion(source, &sr);
+ isc_buffer_forward(source, 16);
+ return (mem_tobuffer(target, sr.base, 16));
+}
+
+static isc_result_t
+towire_loc(ARGS_TOWIRE) {
+ UNUSED(cctx);
+
+ REQUIRE(rdata->type == dns_rdatatype_loc);
+ REQUIRE(rdata->length != 0);
+
+ return (mem_tobuffer(target, rdata->data, rdata->length));
+}
+
+static int
+compare_loc(ARGS_COMPARE) {
+ isc_region_t r1;
+ isc_region_t r2;
+
+ REQUIRE(rdata1->type == rdata2->type);
+ REQUIRE(rdata1->rdclass == rdata2->rdclass);
+ REQUIRE(rdata1->type == dns_rdatatype_loc);
+ REQUIRE(rdata1->length != 0);
+ REQUIRE(rdata2->length != 0);
+
+ dns_rdata_toregion(rdata1, &r1);
+ dns_rdata_toregion(rdata2, &r2);
+ return (isc_region_compare(&r1, &r2));
+}
+
+static isc_result_t
+fromstruct_loc(ARGS_FROMSTRUCT) {
+ dns_rdata_loc_t *loc = source;
+ uint8_t c;
+
+ REQUIRE(type == dns_rdatatype_loc);
+ REQUIRE(loc != NULL);
+ REQUIRE(loc->common.rdtype == type);
+ REQUIRE(loc->common.rdclass == rdclass);
+
+ UNUSED(type);
+ UNUSED(rdclass);
+
+ if (loc->v.v0.version != 0) {
+ return (ISC_R_NOTIMPLEMENTED);
+ }
+ RETERR(uint8_tobuffer(loc->v.v0.version, target));
+
+ c = loc->v.v0.size;
+ if ((c & 0xf) > 9 || ((c >> 4) & 0xf) > 9 || ((c >> 4) & 0xf) == 0) {
+ return (ISC_R_RANGE);
+ }
+ RETERR(uint8_tobuffer(loc->v.v0.size, target));
+
+ c = loc->v.v0.horizontal;
+ if ((c & 0xf) > 9 || ((c >> 4) & 0xf) > 9 || ((c >> 4) & 0xf) == 0) {
+ return (ISC_R_RANGE);
+ }
+ RETERR(uint8_tobuffer(loc->v.v0.horizontal, target));
+
+ c = loc->v.v0.vertical;
+ if ((c & 0xf) > 9 || ((c >> 4) & 0xf) > 9 || ((c >> 4) & 0xf) == 0) {
+ return (ISC_R_RANGE);
+ }
+ RETERR(uint8_tobuffer(loc->v.v0.vertical, target));
+
+ if (loc->v.v0.latitude < (0x80000000UL - 90 * 3600000) ||
+ loc->v.v0.latitude > (0x80000000UL + 90 * 3600000))
+ {
+ return (ISC_R_RANGE);
+ }
+ RETERR(uint32_tobuffer(loc->v.v0.latitude, target));
+
+ if (loc->v.v0.longitude < (0x80000000UL - 180 * 3600000) ||
+ loc->v.v0.longitude > (0x80000000UL + 180 * 3600000))
+ {
+ return (ISC_R_RANGE);
+ }
+ RETERR(uint32_tobuffer(loc->v.v0.longitude, target));
+ return (uint32_tobuffer(loc->v.v0.altitude, target));
+}
+
+static isc_result_t
+tostruct_loc(ARGS_TOSTRUCT) {
+ dns_rdata_loc_t *loc = target;
+ isc_region_t r;
+ uint8_t version;
+
+ REQUIRE(rdata->type == dns_rdatatype_loc);
+ REQUIRE(loc != NULL);
+ REQUIRE(rdata->length != 0);
+
+ UNUSED(mctx);
+
+ dns_rdata_toregion(rdata, &r);
+ version = uint8_fromregion(&r);
+ if (version != 0) {
+ return (ISC_R_NOTIMPLEMENTED);
+ }
+
+ loc->common.rdclass = rdata->rdclass;
+ loc->common.rdtype = rdata->type;
+ ISC_LINK_INIT(&loc->common, link);
+
+ loc->v.v0.version = version;
+ isc_region_consume(&r, 1);
+ loc->v.v0.size = uint8_fromregion(&r);
+ isc_region_consume(&r, 1);
+ loc->v.v0.horizontal = uint8_fromregion(&r);
+ isc_region_consume(&r, 1);
+ loc->v.v0.vertical = uint8_fromregion(&r);
+ isc_region_consume(&r, 1);
+ loc->v.v0.latitude = uint32_fromregion(&r);
+ isc_region_consume(&r, 4);
+ loc->v.v0.longitude = uint32_fromregion(&r);
+ isc_region_consume(&r, 4);
+ loc->v.v0.altitude = uint32_fromregion(&r);
+ isc_region_consume(&r, 4);
+ return (ISC_R_SUCCESS);
+}
+
+static void
+freestruct_loc(ARGS_FREESTRUCT) {
+ dns_rdata_loc_t *loc = source;
+
+ REQUIRE(loc != NULL);
+ REQUIRE(loc->common.rdtype == dns_rdatatype_loc);
+
+ UNUSED(source);
+ UNUSED(loc);
+}
+
+static isc_result_t
+additionaldata_loc(ARGS_ADDLDATA) {
+ REQUIRE(rdata->type == dns_rdatatype_loc);
+
+ UNUSED(rdata);
+ UNUSED(owner);
+ UNUSED(add);
+ UNUSED(arg);
+
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+digest_loc(ARGS_DIGEST) {
+ isc_region_t r;
+
+ REQUIRE(rdata->type == dns_rdatatype_loc);
+
+ dns_rdata_toregion(rdata, &r);
+
+ return ((digest)(arg, &r));
+}
+
+static bool
+checkowner_loc(ARGS_CHECKOWNER) {
+ REQUIRE(type == dns_rdatatype_loc);
+
+ UNUSED(name);
+ UNUSED(type);
+ UNUSED(rdclass);
+ UNUSED(wildcard);
+
+ return (true);
+}
+
+static bool
+checknames_loc(ARGS_CHECKNAMES) {
+ REQUIRE(rdata->type == dns_rdatatype_loc);
+
+ UNUSED(rdata);
+ UNUSED(owner);
+ UNUSED(bad);
+
+ return (true);
+}
+
+static int
+casecompare_loc(ARGS_COMPARE) {
+ return (compare_loc(rdata1, rdata2));
+}
+
+#endif /* RDATA_GENERIC_LOC_29_C */
diff --git a/lib/dns/rdata/generic/loc_29.h b/lib/dns/rdata/generic/loc_29.h
new file mode 100644
index 0000000..635563a
--- /dev/null
+++ b/lib/dns/rdata/generic/loc_29.h
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#pragma once
+
+/*!
+ * \brief Per RFC1876 */
+
+typedef struct dns_rdata_loc_0 {
+ uint8_t version; /* must be first and zero */
+ uint8_t size;
+ uint8_t horizontal;
+ uint8_t vertical;
+ uint32_t latitude;
+ uint32_t longitude;
+ uint32_t altitude;
+} dns_rdata_loc_0_t;
+
+typedef struct dns_rdata_loc {
+ dns_rdatacommon_t common;
+ union {
+ dns_rdata_loc_0_t v0;
+ } v;
+} dns_rdata_loc_t;
diff --git a/lib/dns/rdata/generic/lp_107.c b/lib/dns/rdata/generic/lp_107.c
new file mode 100644
index 0000000..c4342c0
--- /dev/null
+++ b/lib/dns/rdata/generic/lp_107.c
@@ -0,0 +1,278 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#ifndef RDATA_GENERIC_LP_107_C
+#define RDATA_GENERIC_LP_107_C
+
+#include <string.h>
+
+#include <isc/net.h>
+
+#define RRTYPE_LP_ATTRIBUTES (0)
+
+static isc_result_t
+fromtext_lp(ARGS_FROMTEXT) {
+ isc_token_t token;
+ dns_name_t name;
+ isc_buffer_t buffer;
+
+ REQUIRE(type == dns_rdatatype_lp);
+
+ UNUSED(type);
+ UNUSED(rdclass);
+ UNUSED(callbacks);
+
+ RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_number,
+ false));
+ if (token.value.as_ulong > 0xffffU) {
+ RETTOK(ISC_R_RANGE);
+ }
+ RETERR(uint16_tobuffer(token.value.as_ulong, target));
+
+ RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_string,
+ false));
+
+ dns_name_init(&name, NULL);
+ buffer_fromregion(&buffer, &token.value.as_region);
+ if (origin == NULL) {
+ origin = dns_rootname;
+ }
+ return (dns_name_fromtext(&name, &buffer, origin, options, target));
+}
+
+static isc_result_t
+totext_lp(ARGS_TOTEXT) {
+ isc_region_t region;
+ dns_name_t name;
+ dns_name_t prefix;
+ bool sub;
+ char buf[sizeof("64000")];
+ unsigned short num;
+
+ REQUIRE(rdata->type == dns_rdatatype_lp);
+ REQUIRE(rdata->length != 0);
+
+ dns_name_init(&name, NULL);
+ dns_name_init(&prefix, NULL);
+
+ dns_rdata_toregion(rdata, &region);
+ num = uint16_fromregion(&region);
+ isc_region_consume(&region, 2);
+ snprintf(buf, sizeof(buf), "%u", num);
+ RETERR(str_totext(buf, target));
+
+ RETERR(str_totext(" ", target));
+
+ dns_name_fromregion(&name, &region);
+ sub = name_prefix(&name, tctx->origin, &prefix);
+ return (dns_name_totext(&prefix, sub, target));
+}
+
+static isc_result_t
+fromwire_lp(ARGS_FROMWIRE) {
+ dns_name_t name;
+ isc_region_t sregion;
+
+ REQUIRE(type == dns_rdatatype_lp);
+
+ UNUSED(type);
+ UNUSED(rdclass);
+
+ dns_decompress_setmethods(dctx, DNS_COMPRESS_GLOBAL14);
+
+ dns_name_init(&name, NULL);
+
+ isc_buffer_activeregion(source, &sregion);
+ if (sregion.length < 2) {
+ return (ISC_R_UNEXPECTEDEND);
+ }
+ RETERR(mem_tobuffer(target, sregion.base, 2));
+ isc_buffer_forward(source, 2);
+ return (dns_name_fromwire(&name, source, dctx, options, target));
+}
+
+static isc_result_t
+towire_lp(ARGS_TOWIRE) {
+ REQUIRE(rdata->type == dns_rdatatype_lp);
+ REQUIRE(rdata->length != 0);
+
+ UNUSED(cctx);
+
+ return (mem_tobuffer(target, rdata->data, rdata->length));
+}
+
+static int
+compare_lp(ARGS_COMPARE) {
+ isc_region_t region1;
+ isc_region_t region2;
+
+ REQUIRE(rdata1->type == rdata2->type);
+ REQUIRE(rdata1->rdclass == rdata2->rdclass);
+ REQUIRE(rdata1->type == dns_rdatatype_lp);
+ REQUIRE(rdata1->length != 0);
+ REQUIRE(rdata2->length != 0);
+
+ dns_rdata_toregion(rdata1, &region1);
+ dns_rdata_toregion(rdata2, &region2);
+
+ return (isc_region_compare(&region1, &region2));
+}
+
+static isc_result_t
+fromstruct_lp(ARGS_FROMSTRUCT) {
+ dns_rdata_lp_t *lp = source;
+ isc_region_t region;
+
+ REQUIRE(type == dns_rdatatype_lp);
+ REQUIRE(lp != NULL);
+ REQUIRE(lp->common.rdtype == type);
+ REQUIRE(lp->common.rdclass == rdclass);
+
+ UNUSED(type);
+ UNUSED(rdclass);
+
+ RETERR(uint16_tobuffer(lp->pref, target));
+ dns_name_toregion(&lp->lp, &region);
+ return (isc_buffer_copyregion(target, &region));
+}
+
+static isc_result_t
+tostruct_lp(ARGS_TOSTRUCT) {
+ isc_region_t region;
+ dns_rdata_lp_t *lp = target;
+ dns_name_t name;
+
+ REQUIRE(rdata->type == dns_rdatatype_lp);
+ REQUIRE(lp != NULL);
+ REQUIRE(rdata->length != 0);
+
+ lp->common.rdclass = rdata->rdclass;
+ lp->common.rdtype = rdata->type;
+ ISC_LINK_INIT(&lp->common, link);
+
+ dns_name_init(&name, NULL);
+ dns_rdata_toregion(rdata, &region);
+ lp->pref = uint16_fromregion(&region);
+ isc_region_consume(&region, 2);
+ dns_name_fromregion(&name, &region);
+ dns_name_init(&lp->lp, NULL);
+ name_duporclone(&name, mctx, &lp->lp);
+ lp->mctx = mctx;
+ return (ISC_R_SUCCESS);
+}
+
+static void
+freestruct_lp(ARGS_FREESTRUCT) {
+ dns_rdata_lp_t *lp = source;
+
+ REQUIRE(lp != NULL);
+ REQUIRE(lp->common.rdtype == dns_rdatatype_lp);
+
+ if (lp->mctx == NULL) {
+ return;
+ }
+
+ dns_name_free(&lp->lp, lp->mctx);
+ lp->mctx = NULL;
+}
+
+static isc_result_t
+additionaldata_lp(ARGS_ADDLDATA) {
+ dns_name_t name;
+ dns_offsets_t offsets;
+ isc_region_t region;
+ isc_result_t result;
+
+ REQUIRE(rdata->type == dns_rdatatype_lp);
+
+ UNUSED(owner);
+
+ dns_name_init(&name, offsets);
+ dns_rdata_toregion(rdata, &region);
+ isc_region_consume(&region, 2);
+ dns_name_fromregion(&name, &region);
+
+ result = (add)(arg, &name, dns_rdatatype_l32, NULL);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+ return ((add)(arg, &name, dns_rdatatype_l64, NULL));
+}
+
+static isc_result_t
+digest_lp(ARGS_DIGEST) {
+ isc_region_t region;
+
+ REQUIRE(rdata->type == dns_rdatatype_lp);
+
+ dns_rdata_toregion(rdata, &region);
+ return ((digest)(arg, &region));
+}
+
+static bool
+checkowner_lp(ARGS_CHECKOWNER) {
+ REQUIRE(type == dns_rdatatype_lp);
+
+ UNUSED(type);
+ UNUSED(rdclass);
+ UNUSED(name);
+ UNUSED(wildcard);
+
+ return (true);
+}
+
+static bool
+checknames_lp(ARGS_CHECKNAMES) {
+ REQUIRE(rdata->type == dns_rdatatype_lp);
+
+ UNUSED(bad);
+ UNUSED(owner);
+
+ return (true);
+}
+
+static int
+casecompare_lp(ARGS_COMPARE) {
+ dns_name_t name1;
+ dns_name_t name2;
+ isc_region_t region1;
+ isc_region_t region2;
+ int order;
+
+ REQUIRE(rdata1->type == rdata2->type);
+ REQUIRE(rdata1->rdclass == rdata2->rdclass);
+ REQUIRE(rdata1->type == dns_rdatatype_lp);
+ REQUIRE(rdata1->length != 0);
+ REQUIRE(rdata2->length != 0);
+
+ order = memcmp(rdata1->data, rdata2->data, 2);
+ if (order != 0) {
+ return (order < 0 ? -1 : 1);
+ }
+
+ dns_name_init(&name1, NULL);
+ dns_name_init(&name2, NULL);
+
+ dns_rdata_toregion(rdata1, &region1);
+ dns_rdata_toregion(rdata2, &region2);
+
+ isc_region_consume(&region1, 2);
+ isc_region_consume(&region2, 2);
+
+ dns_name_fromregion(&name1, &region1);
+ dns_name_fromregion(&name2, &region2);
+
+ return (dns_name_rdatacompare(&name1, &name2));
+}
+
+#endif /* RDATA_GENERIC_LP_107_C */
diff --git a/lib/dns/rdata/generic/lp_107.h b/lib/dns/rdata/generic/lp_107.h
new file mode 100644
index 0000000..09031c4
--- /dev/null
+++ b/lib/dns/rdata/generic/lp_107.h
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/* */
+#pragma once
+
+typedef struct dns_rdata_lp {
+ dns_rdatacommon_t common;
+ isc_mem_t *mctx;
+ uint16_t pref;
+ dns_name_t lp;
+} dns_rdata_lp_t;
diff --git a/lib/dns/rdata/generic/mb_7.c b/lib/dns/rdata/generic/mb_7.c
new file mode 100644
index 0000000..23e9e09
--- /dev/null
+++ b/lib/dns/rdata/generic/mb_7.c
@@ -0,0 +1,234 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#ifndef RDATA_GENERIC_MB_7_C
+#define RDATA_GENERIC_MB_7_C
+
+#define RRTYPE_MB_ATTRIBUTES (0)
+
+static isc_result_t
+fromtext_mb(ARGS_FROMTEXT) {
+ isc_token_t token;
+ dns_name_t name;
+ isc_buffer_t buffer;
+
+ REQUIRE(type == dns_rdatatype_mb);
+
+ UNUSED(type);
+ UNUSED(rdclass);
+ UNUSED(callbacks);
+
+ RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_string,
+ false));
+
+ dns_name_init(&name, NULL);
+ buffer_fromregion(&buffer, &token.value.as_region);
+ if (origin == NULL) {
+ origin = dns_rootname;
+ }
+ RETTOK(dns_name_fromtext(&name, &buffer, origin, options, target));
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+totext_mb(ARGS_TOTEXT) {
+ isc_region_t region;
+ dns_name_t name;
+ dns_name_t prefix;
+ bool sub;
+
+ REQUIRE(rdata->type == dns_rdatatype_mb);
+ REQUIRE(rdata->length != 0);
+
+ dns_name_init(&name, NULL);
+ dns_name_init(&prefix, NULL);
+
+ dns_rdata_toregion(rdata, &region);
+ dns_name_fromregion(&name, &region);
+
+ sub = name_prefix(&name, tctx->origin, &prefix);
+
+ return (dns_name_totext(&prefix, sub, target));
+}
+
+static isc_result_t
+fromwire_mb(ARGS_FROMWIRE) {
+ dns_name_t name;
+
+ REQUIRE(type == dns_rdatatype_mb);
+
+ UNUSED(type);
+ UNUSED(rdclass);
+
+ dns_decompress_setmethods(dctx, DNS_COMPRESS_GLOBAL14);
+
+ dns_name_init(&name, NULL);
+ return (dns_name_fromwire(&name, source, dctx, options, target));
+}
+
+static isc_result_t
+towire_mb(ARGS_TOWIRE) {
+ dns_name_t name;
+ dns_offsets_t offsets;
+ isc_region_t region;
+
+ REQUIRE(rdata->type == dns_rdatatype_mb);
+ REQUIRE(rdata->length != 0);
+
+ dns_compress_setmethods(cctx, DNS_COMPRESS_GLOBAL14);
+
+ dns_name_init(&name, offsets);
+ dns_rdata_toregion(rdata, &region);
+ dns_name_fromregion(&name, &region);
+
+ return (dns_name_towire(&name, cctx, target));
+}
+
+static int
+compare_mb(ARGS_COMPARE) {
+ dns_name_t name1;
+ dns_name_t name2;
+ isc_region_t region1;
+ isc_region_t region2;
+
+ REQUIRE(rdata1->type == rdata2->type);
+ REQUIRE(rdata1->rdclass == rdata2->rdclass);
+ REQUIRE(rdata1->type == dns_rdatatype_mb);
+ REQUIRE(rdata1->length != 0);
+ REQUIRE(rdata2->length != 0);
+
+ dns_name_init(&name1, NULL);
+ dns_name_init(&name2, NULL);
+
+ dns_rdata_toregion(rdata1, &region1);
+ dns_rdata_toregion(rdata2, &region2);
+
+ dns_name_fromregion(&name1, &region1);
+ dns_name_fromregion(&name2, &region2);
+
+ return (dns_name_rdatacompare(&name1, &name2));
+}
+
+static isc_result_t
+fromstruct_mb(ARGS_FROMSTRUCT) {
+ dns_rdata_mb_t *mb = source;
+ isc_region_t region;
+
+ REQUIRE(type == dns_rdatatype_mb);
+ REQUIRE(mb != NULL);
+ REQUIRE(mb->common.rdtype == type);
+ REQUIRE(mb->common.rdclass == rdclass);
+
+ UNUSED(type);
+ UNUSED(rdclass);
+
+ dns_name_toregion(&mb->mb, &region);
+ return (isc_buffer_copyregion(target, &region));
+}
+
+static isc_result_t
+tostruct_mb(ARGS_TOSTRUCT) {
+ isc_region_t region;
+ dns_rdata_mb_t *mb = target;
+ dns_name_t name;
+
+ REQUIRE(rdata->type == dns_rdatatype_mb);
+ REQUIRE(mb != NULL);
+ REQUIRE(rdata->length != 0);
+
+ mb->common.rdclass = rdata->rdclass;
+ mb->common.rdtype = rdata->type;
+ ISC_LINK_INIT(&mb->common, link);
+
+ dns_name_init(&name, NULL);
+ dns_rdata_toregion(rdata, &region);
+ dns_name_fromregion(&name, &region);
+ dns_name_init(&mb->mb, NULL);
+ name_duporclone(&name, mctx, &mb->mb);
+ mb->mctx = mctx;
+ return (ISC_R_SUCCESS);
+}
+
+static void
+freestruct_mb(ARGS_FREESTRUCT) {
+ dns_rdata_mb_t *mb = source;
+
+ REQUIRE(mb != NULL);
+
+ if (mb->mctx == NULL) {
+ return;
+ }
+
+ dns_name_free(&mb->mb, mb->mctx);
+ mb->mctx = NULL;
+}
+
+static isc_result_t
+additionaldata_mb(ARGS_ADDLDATA) {
+ dns_name_t name;
+ dns_offsets_t offsets;
+ isc_region_t region;
+
+ REQUIRE(rdata->type == dns_rdatatype_mb);
+
+ UNUSED(owner);
+
+ dns_name_init(&name, offsets);
+ dns_rdata_toregion(rdata, &region);
+ dns_name_fromregion(&name, &region);
+
+ return ((add)(arg, &name, dns_rdatatype_a, NULL));
+}
+
+static isc_result_t
+digest_mb(ARGS_DIGEST) {
+ isc_region_t r;
+ dns_name_t name;
+
+ REQUIRE(rdata->type == dns_rdatatype_mb);
+
+ dns_rdata_toregion(rdata, &r);
+ dns_name_init(&name, NULL);
+ dns_name_fromregion(&name, &r);
+
+ return (dns_name_digest(&name, digest, arg));
+}
+
+static bool
+checkowner_mb(ARGS_CHECKOWNER) {
+ REQUIRE(type == dns_rdatatype_mb);
+
+ UNUSED(type);
+ UNUSED(rdclass);
+ UNUSED(wildcard);
+
+ return (dns_name_ismailbox(name));
+}
+
+static bool
+checknames_mb(ARGS_CHECKNAMES) {
+ REQUIRE(rdata->type == dns_rdatatype_mb);
+
+ UNUSED(rdata);
+ UNUSED(owner);
+ UNUSED(bad);
+
+ return (true);
+}
+
+static int
+casecompare_mb(ARGS_COMPARE) {
+ return (compare_mb(rdata1, rdata2));
+}
+
+#endif /* RDATA_GENERIC_MB_7_C */
diff --git a/lib/dns/rdata/generic/mb_7.h b/lib/dns/rdata/generic/mb_7.h
new file mode 100644
index 0000000..187e657
--- /dev/null
+++ b/lib/dns/rdata/generic/mb_7.h
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/* */
+#pragma once
+
+typedef struct dns_rdata_mb {
+ dns_rdatacommon_t common;
+ isc_mem_t *mctx;
+ dns_name_t mb;
+} dns_rdata_mb_t;
diff --git a/lib/dns/rdata/generic/md_3.c b/lib/dns/rdata/generic/md_3.c
new file mode 100644
index 0000000..38d48d3
--- /dev/null
+++ b/lib/dns/rdata/generic/md_3.c
@@ -0,0 +1,236 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#ifndef RDATA_GENERIC_MD_3_C
+#define RDATA_GENERIC_MD_3_C
+
+#define RRTYPE_MD_ATTRIBUTES (0)
+
+static isc_result_t
+fromtext_md(ARGS_FROMTEXT) {
+ isc_token_t token;
+ dns_name_t name;
+ isc_buffer_t buffer;
+
+ REQUIRE(type == dns_rdatatype_md);
+
+ UNUSED(type);
+ UNUSED(rdclass);
+ UNUSED(callbacks);
+
+ RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_string,
+ false));
+
+ dns_name_init(&name, NULL);
+ buffer_fromregion(&buffer, &token.value.as_region);
+ if (origin == NULL) {
+ origin = dns_rootname;
+ }
+ RETTOK(dns_name_fromtext(&name, &buffer, origin, options, target));
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+totext_md(ARGS_TOTEXT) {
+ isc_region_t region;
+ dns_name_t name;
+ dns_name_t prefix;
+ bool sub;
+
+ REQUIRE(rdata->type == dns_rdatatype_md);
+ REQUIRE(rdata->length != 0);
+
+ dns_name_init(&name, NULL);
+ dns_name_init(&prefix, NULL);
+
+ dns_rdata_toregion(rdata, &region);
+ dns_name_fromregion(&name, &region);
+
+ sub = name_prefix(&name, tctx->origin, &prefix);
+
+ return (dns_name_totext(&prefix, sub, target));
+}
+
+static isc_result_t
+fromwire_md(ARGS_FROMWIRE) {
+ dns_name_t name;
+
+ REQUIRE(type == dns_rdatatype_md);
+
+ UNUSED(type);
+ UNUSED(rdclass);
+
+ dns_decompress_setmethods(dctx, DNS_COMPRESS_GLOBAL14);
+
+ dns_name_init(&name, NULL);
+ return (dns_name_fromwire(&name, source, dctx, options, target));
+}
+
+static isc_result_t
+towire_md(ARGS_TOWIRE) {
+ dns_name_t name;
+ dns_offsets_t offsets;
+ isc_region_t region;
+
+ REQUIRE(rdata->type == dns_rdatatype_md);
+ REQUIRE(rdata->length != 0);
+
+ dns_compress_setmethods(cctx, DNS_COMPRESS_GLOBAL14);
+
+ dns_name_init(&name, offsets);
+ dns_rdata_toregion(rdata, &region);
+ dns_name_fromregion(&name, &region);
+
+ return (dns_name_towire(&name, cctx, target));
+}
+
+static int
+compare_md(ARGS_COMPARE) {
+ dns_name_t name1;
+ dns_name_t name2;
+ isc_region_t region1;
+ isc_region_t region2;
+
+ REQUIRE(rdata1->type == rdata2->type);
+ REQUIRE(rdata1->rdclass == rdata2->rdclass);
+ REQUIRE(rdata1->type == dns_rdatatype_md);
+ REQUIRE(rdata1->length != 0);
+ REQUIRE(rdata2->length != 0);
+
+ dns_name_init(&name1, NULL);
+ dns_name_init(&name2, NULL);
+
+ dns_rdata_toregion(rdata1, &region1);
+ dns_rdata_toregion(rdata2, &region2);
+
+ dns_name_fromregion(&name1, &region1);
+ dns_name_fromregion(&name2, &region2);
+
+ return (dns_name_rdatacompare(&name1, &name2));
+}
+
+static isc_result_t
+fromstruct_md(ARGS_FROMSTRUCT) {
+ dns_rdata_md_t *md = source;
+ isc_region_t region;
+
+ REQUIRE(type == dns_rdatatype_md);
+ REQUIRE(md != NULL);
+ REQUIRE(md->common.rdtype == type);
+ REQUIRE(md->common.rdclass == rdclass);
+
+ UNUSED(type);
+ UNUSED(rdclass);
+
+ dns_name_toregion(&md->md, &region);
+ return (isc_buffer_copyregion(target, &region));
+}
+
+static isc_result_t
+tostruct_md(ARGS_TOSTRUCT) {
+ dns_rdata_md_t *md = target;
+ isc_region_t r;
+ dns_name_t name;
+
+ REQUIRE(rdata->type == dns_rdatatype_md);
+ REQUIRE(md != NULL);
+ REQUIRE(rdata->length != 0);
+
+ md->common.rdclass = rdata->rdclass;
+ md->common.rdtype = rdata->type;
+ ISC_LINK_INIT(&md->common, link);
+
+ dns_name_init(&name, NULL);
+ dns_rdata_toregion(rdata, &r);
+ dns_name_fromregion(&name, &r);
+ dns_name_init(&md->md, NULL);
+ name_duporclone(&name, mctx, &md->md);
+ md->mctx = mctx;
+ return (ISC_R_SUCCESS);
+}
+
+static void
+freestruct_md(ARGS_FREESTRUCT) {
+ dns_rdata_md_t *md = source;
+
+ REQUIRE(md != NULL);
+ REQUIRE(md->common.rdtype == dns_rdatatype_md);
+
+ if (md->mctx == NULL) {
+ return;
+ }
+
+ dns_name_free(&md->md, md->mctx);
+ md->mctx = NULL;
+}
+
+static isc_result_t
+additionaldata_md(ARGS_ADDLDATA) {
+ dns_name_t name;
+ dns_offsets_t offsets;
+ isc_region_t region;
+
+ REQUIRE(rdata->type == dns_rdatatype_md);
+
+ UNUSED(owner);
+
+ dns_name_init(&name, offsets);
+ dns_rdata_toregion(rdata, &region);
+ dns_name_fromregion(&name, &region);
+
+ return ((add)(arg, &name, dns_rdatatype_a, NULL));
+}
+
+static isc_result_t
+digest_md(ARGS_DIGEST) {
+ isc_region_t r;
+ dns_name_t name;
+
+ REQUIRE(rdata->type == dns_rdatatype_md);
+
+ dns_rdata_toregion(rdata, &r);
+ dns_name_init(&name, NULL);
+ dns_name_fromregion(&name, &r);
+
+ return (dns_name_digest(&name, digest, arg));
+}
+
+static bool
+checkowner_md(ARGS_CHECKOWNER) {
+ REQUIRE(type == dns_rdatatype_md);
+
+ UNUSED(name);
+ UNUSED(type);
+ UNUSED(rdclass);
+ UNUSED(wildcard);
+
+ return (true);
+}
+
+static bool
+checknames_md(ARGS_CHECKNAMES) {
+ REQUIRE(rdata->type == dns_rdatatype_md);
+
+ UNUSED(rdata);
+ UNUSED(owner);
+ UNUSED(bad);
+
+ return (true);
+}
+
+static int
+casecompare_md(ARGS_COMPARE) {
+ return (compare_md(rdata1, rdata2));
+}
+
+#endif /* RDATA_GENERIC_MD_3_C */
diff --git a/lib/dns/rdata/generic/md_3.h b/lib/dns/rdata/generic/md_3.h
new file mode 100644
index 0000000..530e2cb
--- /dev/null
+++ b/lib/dns/rdata/generic/md_3.h
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/* */
+#pragma once
+
+typedef struct dns_rdata_md {
+ dns_rdatacommon_t common;
+ isc_mem_t *mctx;
+ dns_name_t md;
+} dns_rdata_md_t;
diff --git a/lib/dns/rdata/generic/mf_4.c b/lib/dns/rdata/generic/mf_4.c
new file mode 100644
index 0000000..3590767
--- /dev/null
+++ b/lib/dns/rdata/generic/mf_4.c
@@ -0,0 +1,235 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#ifndef RDATA_GENERIC_MF_4_C
+#define RDATA_GENERIC_MF_4_C
+
+#define RRTYPE_MF_ATTRIBUTES (0)
+
+static isc_result_t
+fromtext_mf(ARGS_FROMTEXT) {
+ isc_token_t token;
+ dns_name_t name;
+ isc_buffer_t buffer;
+
+ REQUIRE(type == dns_rdatatype_mf);
+
+ UNUSED(type);
+ UNUSED(rdclass);
+ UNUSED(callbacks);
+
+ RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_string,
+ false));
+
+ dns_name_init(&name, NULL);
+ buffer_fromregion(&buffer, &token.value.as_region);
+ if (origin == NULL) {
+ origin = dns_rootname;
+ }
+ RETTOK(dns_name_fromtext(&name, &buffer, origin, options, target));
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+totext_mf(ARGS_TOTEXT) {
+ isc_region_t region;
+ dns_name_t name;
+ dns_name_t prefix;
+ bool sub;
+
+ REQUIRE(rdata->type == dns_rdatatype_mf);
+ REQUIRE(rdata->length != 0);
+
+ dns_name_init(&name, NULL);
+ dns_name_init(&prefix, NULL);
+
+ dns_rdata_toregion(rdata, &region);
+ dns_name_fromregion(&name, &region);
+
+ sub = name_prefix(&name, tctx->origin, &prefix);
+
+ return (dns_name_totext(&prefix, sub, target));
+}
+
+static isc_result_t
+fromwire_mf(ARGS_FROMWIRE) {
+ dns_name_t name;
+
+ REQUIRE(type == dns_rdatatype_mf);
+
+ UNUSED(type);
+ UNUSED(rdclass);
+
+ dns_decompress_setmethods(dctx, DNS_COMPRESS_GLOBAL14);
+
+ dns_name_init(&name, NULL);
+ return (dns_name_fromwire(&name, source, dctx, options, target));
+}
+
+static isc_result_t
+towire_mf(ARGS_TOWIRE) {
+ dns_name_t name;
+ dns_offsets_t offsets;
+ isc_region_t region;
+
+ REQUIRE(rdata->type == dns_rdatatype_mf);
+ REQUIRE(rdata->length != 0);
+
+ dns_compress_setmethods(cctx, DNS_COMPRESS_GLOBAL14);
+
+ dns_name_init(&name, offsets);
+ dns_rdata_toregion(rdata, &region);
+ dns_name_fromregion(&name, &region);
+
+ return (dns_name_towire(&name, cctx, target));
+}
+
+static int
+compare_mf(ARGS_COMPARE) {
+ dns_name_t name1;
+ dns_name_t name2;
+ isc_region_t region1;
+ isc_region_t region2;
+
+ REQUIRE(rdata1->type == rdata2->type);
+ REQUIRE(rdata1->rdclass == rdata2->rdclass);
+ REQUIRE(rdata1->type == dns_rdatatype_mf);
+ REQUIRE(rdata1->length != 0);
+ REQUIRE(rdata2->length != 0);
+
+ dns_name_init(&name1, NULL);
+ dns_name_init(&name2, NULL);
+
+ dns_rdata_toregion(rdata1, &region1);
+ dns_rdata_toregion(rdata2, &region2);
+
+ dns_name_fromregion(&name1, &region1);
+ dns_name_fromregion(&name2, &region2);
+
+ return (dns_name_rdatacompare(&name1, &name2));
+}
+
+static isc_result_t
+fromstruct_mf(ARGS_FROMSTRUCT) {
+ dns_rdata_mf_t *mf = source;
+ isc_region_t region;
+
+ REQUIRE(type == dns_rdatatype_mf);
+ REQUIRE(mf != NULL);
+ REQUIRE(mf->common.rdtype == type);
+ REQUIRE(mf->common.rdclass == rdclass);
+
+ UNUSED(type);
+ UNUSED(rdclass);
+
+ dns_name_toregion(&mf->mf, &region);
+ return (isc_buffer_copyregion(target, &region));
+}
+
+static isc_result_t
+tostruct_mf(ARGS_TOSTRUCT) {
+ dns_rdata_mf_t *mf = target;
+ isc_region_t r;
+ dns_name_t name;
+
+ REQUIRE(rdata->type == dns_rdatatype_mf);
+ REQUIRE(mf != NULL);
+ REQUIRE(rdata->length != 0);
+
+ mf->common.rdclass = rdata->rdclass;
+ mf->common.rdtype = rdata->type;
+ ISC_LINK_INIT(&mf->common, link);
+
+ dns_name_init(&name, NULL);
+ dns_rdata_toregion(rdata, &r);
+ dns_name_fromregion(&name, &r);
+ dns_name_init(&mf->mf, NULL);
+ name_duporclone(&name, mctx, &mf->mf);
+ mf->mctx = mctx;
+ return (ISC_R_SUCCESS);
+}
+
+static void
+freestruct_mf(ARGS_FREESTRUCT) {
+ dns_rdata_mf_t *mf = source;
+
+ REQUIRE(mf != NULL);
+ REQUIRE(mf->common.rdtype == dns_rdatatype_mf);
+
+ if (mf->mctx == NULL) {
+ return;
+ }
+ dns_name_free(&mf->mf, mf->mctx);
+ mf->mctx = NULL;
+}
+
+static isc_result_t
+additionaldata_mf(ARGS_ADDLDATA) {
+ dns_name_t name;
+ dns_offsets_t offsets;
+ isc_region_t region;
+
+ REQUIRE(rdata->type == dns_rdatatype_mf);
+
+ UNUSED(owner);
+
+ dns_name_init(&name, offsets);
+ dns_rdata_toregion(rdata, &region);
+ dns_name_fromregion(&name, &region);
+
+ return ((add)(arg, &name, dns_rdatatype_a, NULL));
+}
+
+static isc_result_t
+digest_mf(ARGS_DIGEST) {
+ isc_region_t r;
+ dns_name_t name;
+
+ REQUIRE(rdata->type == dns_rdatatype_mf);
+
+ dns_rdata_toregion(rdata, &r);
+ dns_name_init(&name, NULL);
+ dns_name_fromregion(&name, &r);
+
+ return (dns_name_digest(&name, digest, arg));
+}
+
+static bool
+checkowner_mf(ARGS_CHECKOWNER) {
+ REQUIRE(type == dns_rdatatype_mf);
+
+ UNUSED(name);
+ UNUSED(type);
+ UNUSED(rdclass);
+ UNUSED(wildcard);
+
+ return (true);
+}
+
+static bool
+checknames_mf(ARGS_CHECKNAMES) {
+ REQUIRE(rdata->type == dns_rdatatype_mf);
+
+ UNUSED(rdata);
+ UNUSED(owner);
+ UNUSED(bad);
+
+ return (true);
+}
+
+static int
+casecompare_mf(ARGS_COMPARE) {
+ return (compare_mf(rdata1, rdata2));
+}
+
+#endif /* RDATA_GENERIC_MF_4_C */
diff --git a/lib/dns/rdata/generic/mf_4.h b/lib/dns/rdata/generic/mf_4.h
new file mode 100644
index 0000000..eb1559a
--- /dev/null
+++ b/lib/dns/rdata/generic/mf_4.h
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/* */
+#pragma once
+
+typedef struct dns_rdata_mf {
+ dns_rdatacommon_t common;
+ isc_mem_t *mctx;
+ dns_name_t mf;
+} dns_rdata_mf_t;
diff --git a/lib/dns/rdata/generic/mg_8.c b/lib/dns/rdata/generic/mg_8.c
new file mode 100644
index 0000000..201a295
--- /dev/null
+++ b/lib/dns/rdata/generic/mg_8.c
@@ -0,0 +1,229 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#ifndef RDATA_GENERIC_MG_8_C
+#define RDATA_GENERIC_MG_8_C
+
+#define RRTYPE_MG_ATTRIBUTES (0)
+
+static isc_result_t
+fromtext_mg(ARGS_FROMTEXT) {
+ isc_token_t token;
+ dns_name_t name;
+ isc_buffer_t buffer;
+
+ REQUIRE(type == dns_rdatatype_mg);
+
+ UNUSED(type);
+ UNUSED(rdclass);
+ UNUSED(callbacks);
+
+ RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_string,
+ false));
+
+ dns_name_init(&name, NULL);
+ buffer_fromregion(&buffer, &token.value.as_region);
+ if (origin == NULL) {
+ origin = dns_rootname;
+ }
+ RETTOK(dns_name_fromtext(&name, &buffer, origin, options, target));
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+totext_mg(ARGS_TOTEXT) {
+ isc_region_t region;
+ dns_name_t name;
+ dns_name_t prefix;
+ bool sub;
+
+ REQUIRE(rdata->type == dns_rdatatype_mg);
+ REQUIRE(rdata->length != 0);
+
+ dns_name_init(&name, NULL);
+ dns_name_init(&prefix, NULL);
+
+ dns_rdata_toregion(rdata, &region);
+ dns_name_fromregion(&name, &region);
+
+ sub = name_prefix(&name, tctx->origin, &prefix);
+
+ return (dns_name_totext(&prefix, sub, target));
+}
+
+static isc_result_t
+fromwire_mg(ARGS_FROMWIRE) {
+ dns_name_t name;
+
+ REQUIRE(type == dns_rdatatype_mg);
+
+ UNUSED(type);
+ UNUSED(rdclass);
+
+ dns_decompress_setmethods(dctx, DNS_COMPRESS_GLOBAL14);
+
+ dns_name_init(&name, NULL);
+ return (dns_name_fromwire(&name, source, dctx, options, target));
+}
+
+static isc_result_t
+towire_mg(ARGS_TOWIRE) {
+ dns_name_t name;
+ dns_offsets_t offsets;
+ isc_region_t region;
+
+ REQUIRE(rdata->type == dns_rdatatype_mg);
+ REQUIRE(rdata->length != 0);
+
+ dns_compress_setmethods(cctx, DNS_COMPRESS_GLOBAL14);
+
+ dns_name_init(&name, offsets);
+ dns_rdata_toregion(rdata, &region);
+ dns_name_fromregion(&name, &region);
+
+ return (dns_name_towire(&name, cctx, target));
+}
+
+static int
+compare_mg(ARGS_COMPARE) {
+ dns_name_t name1;
+ dns_name_t name2;
+ isc_region_t region1;
+ isc_region_t region2;
+
+ REQUIRE(rdata1->type == rdata2->type);
+ REQUIRE(rdata1->rdclass == rdata2->rdclass);
+ REQUIRE(rdata1->type == dns_rdatatype_mg);
+ REQUIRE(rdata1->length != 0);
+ REQUIRE(rdata2->length != 0);
+
+ dns_name_init(&name1, NULL);
+ dns_name_init(&name2, NULL);
+
+ dns_rdata_toregion(rdata1, &region1);
+ dns_rdata_toregion(rdata2, &region2);
+
+ dns_name_fromregion(&name1, &region1);
+ dns_name_fromregion(&name2, &region2);
+
+ return (dns_name_rdatacompare(&name1, &name2));
+}
+
+static isc_result_t
+fromstruct_mg(ARGS_FROMSTRUCT) {
+ dns_rdata_mg_t *mg = source;
+ isc_region_t region;
+
+ REQUIRE(type == dns_rdatatype_mg);
+ REQUIRE(mg != NULL);
+ REQUIRE(mg->common.rdtype == type);
+ REQUIRE(mg->common.rdclass == rdclass);
+
+ UNUSED(type);
+ UNUSED(rdclass);
+
+ dns_name_toregion(&mg->mg, &region);
+ return (isc_buffer_copyregion(target, &region));
+}
+
+static isc_result_t
+tostruct_mg(ARGS_TOSTRUCT) {
+ isc_region_t region;
+ dns_rdata_mg_t *mg = target;
+ dns_name_t name;
+
+ REQUIRE(rdata->type == dns_rdatatype_mg);
+ REQUIRE(mg != NULL);
+ REQUIRE(rdata->length != 0);
+
+ mg->common.rdclass = rdata->rdclass;
+ mg->common.rdtype = rdata->type;
+ ISC_LINK_INIT(&mg->common, link);
+
+ dns_name_init(&name, NULL);
+ dns_rdata_toregion(rdata, &region);
+ dns_name_fromregion(&name, &region);
+ dns_name_init(&mg->mg, NULL);
+ name_duporclone(&name, mctx, &mg->mg);
+ mg->mctx = mctx;
+ return (ISC_R_SUCCESS);
+}
+
+static void
+freestruct_mg(ARGS_FREESTRUCT) {
+ dns_rdata_mg_t *mg = source;
+
+ REQUIRE(mg != NULL);
+ REQUIRE(mg->common.rdtype == dns_rdatatype_mg);
+
+ if (mg->mctx == NULL) {
+ return;
+ }
+ dns_name_free(&mg->mg, mg->mctx);
+ mg->mctx = NULL;
+}
+
+static isc_result_t
+additionaldata_mg(ARGS_ADDLDATA) {
+ REQUIRE(rdata->type == dns_rdatatype_mg);
+
+ UNUSED(add);
+ UNUSED(arg);
+ UNUSED(rdata);
+ UNUSED(owner);
+
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+digest_mg(ARGS_DIGEST) {
+ isc_region_t r;
+ dns_name_t name;
+
+ REQUIRE(rdata->type == dns_rdatatype_mg);
+
+ dns_rdata_toregion(rdata, &r);
+ dns_name_init(&name, NULL);
+ dns_name_fromregion(&name, &r);
+
+ return (dns_name_digest(&name, digest, arg));
+}
+
+static bool
+checkowner_mg(ARGS_CHECKOWNER) {
+ REQUIRE(type == dns_rdatatype_mg);
+
+ UNUSED(type);
+ UNUSED(rdclass);
+ UNUSED(wildcard);
+
+ return (dns_name_ismailbox(name));
+}
+
+static bool
+checknames_mg(ARGS_CHECKNAMES) {
+ REQUIRE(rdata->type == dns_rdatatype_mg);
+
+ UNUSED(rdata);
+ UNUSED(owner);
+ UNUSED(bad);
+
+ return (true);
+}
+
+static int
+casecompare_mg(ARGS_COMPARE) {
+ return (compare_mg(rdata1, rdata2));
+}
+
+#endif /* RDATA_GENERIC_MG_8_C */
diff --git a/lib/dns/rdata/generic/mg_8.h b/lib/dns/rdata/generic/mg_8.h
new file mode 100644
index 0000000..09fd206
--- /dev/null
+++ b/lib/dns/rdata/generic/mg_8.h
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/* */
+#pragma once
+
+typedef struct dns_rdata_mg {
+ dns_rdatacommon_t common;
+ isc_mem_t *mctx;
+ dns_name_t mg;
+} dns_rdata_mg_t;
diff --git a/lib/dns/rdata/generic/minfo_14.c b/lib/dns/rdata/generic/minfo_14.c
new file mode 100644
index 0000000..38ff8bd
--- /dev/null
+++ b/lib/dns/rdata/generic/minfo_14.c
@@ -0,0 +1,323 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#ifndef RDATA_GENERIC_MINFO_14_C
+#define RDATA_GENERIC_MINFO_14_C
+
+#define RRTYPE_MINFO_ATTRIBUTES (0)
+
+static isc_result_t
+fromtext_minfo(ARGS_FROMTEXT) {
+ isc_token_t token;
+ dns_name_t name;
+ isc_buffer_t buffer;
+ int i;
+ bool ok;
+
+ REQUIRE(type == dns_rdatatype_minfo);
+
+ UNUSED(type);
+ UNUSED(rdclass);
+ UNUSED(callbacks);
+
+ if (origin == NULL) {
+ origin = dns_rootname;
+ }
+
+ for (i = 0; i < 2; i++) {
+ RETERR(isc_lex_getmastertoken(lexer, &token,
+ isc_tokentype_string, false));
+ dns_name_init(&name, NULL);
+ buffer_fromregion(&buffer, &token.value.as_region);
+ RETTOK(dns_name_fromtext(&name, &buffer, origin, options,
+ target));
+ ok = true;
+ if ((options & DNS_RDATA_CHECKNAMES) != 0) {
+ ok = dns_name_ismailbox(&name);
+ }
+ if (!ok && (options & DNS_RDATA_CHECKNAMESFAIL) != 0) {
+ RETTOK(DNS_R_BADNAME);
+ }
+ if (!ok && callbacks != NULL) {
+ warn_badname(&name, lexer, callbacks);
+ }
+ }
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+totext_minfo(ARGS_TOTEXT) {
+ isc_region_t region;
+ dns_name_t rmail;
+ dns_name_t email;
+ dns_name_t prefix;
+ bool sub;
+
+ REQUIRE(rdata->type == dns_rdatatype_minfo);
+ REQUIRE(rdata->length != 0);
+
+ dns_name_init(&rmail, NULL);
+ dns_name_init(&email, NULL);
+ dns_name_init(&prefix, NULL);
+
+ dns_rdata_toregion(rdata, &region);
+
+ dns_name_fromregion(&rmail, &region);
+ isc_region_consume(&region, rmail.length);
+
+ dns_name_fromregion(&email, &region);
+ isc_region_consume(&region, email.length);
+
+ sub = name_prefix(&rmail, tctx->origin, &prefix);
+
+ RETERR(dns_name_totext(&prefix, sub, target));
+
+ RETERR(str_totext(" ", target));
+
+ sub = name_prefix(&email, tctx->origin, &prefix);
+ return (dns_name_totext(&prefix, sub, target));
+}
+
+static isc_result_t
+fromwire_minfo(ARGS_FROMWIRE) {
+ dns_name_t rmail;
+ dns_name_t email;
+
+ REQUIRE(type == dns_rdatatype_minfo);
+
+ UNUSED(type);
+ UNUSED(rdclass);
+
+ dns_decompress_setmethods(dctx, DNS_COMPRESS_GLOBAL14);
+
+ dns_name_init(&rmail, NULL);
+ dns_name_init(&email, NULL);
+
+ RETERR(dns_name_fromwire(&rmail, source, dctx, options, target));
+ return (dns_name_fromwire(&email, source, dctx, options, target));
+}
+
+static isc_result_t
+towire_minfo(ARGS_TOWIRE) {
+ isc_region_t region;
+ dns_name_t rmail;
+ dns_name_t email;
+ dns_offsets_t roffsets;
+ dns_offsets_t eoffsets;
+
+ REQUIRE(rdata->type == dns_rdatatype_minfo);
+ REQUIRE(rdata->length != 0);
+
+ dns_compress_setmethods(cctx, DNS_COMPRESS_GLOBAL14);
+
+ dns_name_init(&rmail, roffsets);
+ dns_name_init(&email, eoffsets);
+
+ dns_rdata_toregion(rdata, &region);
+
+ dns_name_fromregion(&rmail, &region);
+ isc_region_consume(&region, name_length(&rmail));
+
+ RETERR(dns_name_towire(&rmail, cctx, target));
+
+ dns_name_fromregion(&rmail, &region);
+ isc_region_consume(&region, rmail.length);
+
+ return (dns_name_towire(&rmail, cctx, target));
+}
+
+static int
+compare_minfo(ARGS_COMPARE) {
+ isc_region_t region1;
+ isc_region_t region2;
+ dns_name_t name1;
+ dns_name_t name2;
+ int order;
+
+ REQUIRE(rdata1->type == rdata2->type);
+ REQUIRE(rdata1->rdclass == rdata2->rdclass);
+ REQUIRE(rdata1->type == dns_rdatatype_minfo);
+ REQUIRE(rdata1->length != 0);
+ REQUIRE(rdata2->length != 0);
+
+ dns_name_init(&name1, NULL);
+ dns_name_init(&name2, NULL);
+
+ dns_rdata_toregion(rdata1, &region1);
+ dns_rdata_toregion(rdata2, &region2);
+
+ dns_name_fromregion(&name1, &region1);
+ dns_name_fromregion(&name2, &region2);
+
+ order = dns_name_rdatacompare(&name1, &name2);
+ if (order != 0) {
+ return (order);
+ }
+
+ isc_region_consume(&region1, name_length(&name1));
+ isc_region_consume(&region2, name_length(&name2));
+
+ dns_name_init(&name1, NULL);
+ dns_name_init(&name2, NULL);
+
+ dns_name_fromregion(&name1, &region1);
+ dns_name_fromregion(&name2, &region2);
+
+ order = dns_name_rdatacompare(&name1, &name2);
+ return (order);
+}
+
+static isc_result_t
+fromstruct_minfo(ARGS_FROMSTRUCT) {
+ dns_rdata_minfo_t *minfo = source;
+ isc_region_t region;
+
+ REQUIRE(type == dns_rdatatype_minfo);
+ REQUIRE(minfo != NULL);
+ REQUIRE(minfo->common.rdtype == type);
+ REQUIRE(minfo->common.rdclass == rdclass);
+
+ UNUSED(type);
+ UNUSED(rdclass);
+
+ dns_name_toregion(&minfo->rmailbox, &region);
+ RETERR(isc_buffer_copyregion(target, &region));
+ dns_name_toregion(&minfo->emailbox, &region);
+ return (isc_buffer_copyregion(target, &region));
+}
+
+static isc_result_t
+tostruct_minfo(ARGS_TOSTRUCT) {
+ dns_rdata_minfo_t *minfo = target;
+ isc_region_t region;
+ dns_name_t name;
+
+ REQUIRE(rdata->type == dns_rdatatype_minfo);
+ REQUIRE(minfo != NULL);
+ REQUIRE(rdata->length != 0);
+
+ minfo->common.rdclass = rdata->rdclass;
+ minfo->common.rdtype = rdata->type;
+ ISC_LINK_INIT(&minfo->common, link);
+
+ dns_name_init(&name, NULL);
+ dns_rdata_toregion(rdata, &region);
+ dns_name_fromregion(&name, &region);
+ dns_name_init(&minfo->rmailbox, NULL);
+ name_duporclone(&name, mctx, &minfo->rmailbox);
+ isc_region_consume(&region, name_length(&name));
+
+ dns_name_fromregion(&name, &region);
+ dns_name_init(&minfo->emailbox, NULL);
+ name_duporclone(&name, mctx, &minfo->emailbox);
+ minfo->mctx = mctx;
+ return (ISC_R_SUCCESS);
+}
+
+static void
+freestruct_minfo(ARGS_FREESTRUCT) {
+ dns_rdata_minfo_t *minfo = source;
+
+ REQUIRE(minfo != NULL);
+ REQUIRE(minfo->common.rdtype == dns_rdatatype_minfo);
+
+ if (minfo->mctx == NULL) {
+ return;
+ }
+
+ dns_name_free(&minfo->rmailbox, minfo->mctx);
+ dns_name_free(&minfo->emailbox, minfo->mctx);
+ minfo->mctx = NULL;
+}
+
+static isc_result_t
+additionaldata_minfo(ARGS_ADDLDATA) {
+ REQUIRE(rdata->type == dns_rdatatype_minfo);
+
+ UNUSED(rdata);
+ UNUSED(owner);
+ UNUSED(add);
+ UNUSED(arg);
+
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+digest_minfo(ARGS_DIGEST) {
+ isc_region_t r;
+ dns_name_t name;
+ isc_result_t result;
+
+ REQUIRE(rdata->type == dns_rdatatype_minfo);
+
+ dns_rdata_toregion(rdata, &r);
+ dns_name_init(&name, NULL);
+ dns_name_fromregion(&name, &r);
+ result = dns_name_digest(&name, digest, arg);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+ isc_region_consume(&r, name_length(&name));
+ dns_name_init(&name, NULL);
+ dns_name_fromregion(&name, &r);
+
+ return (dns_name_digest(&name, digest, arg));
+}
+
+static bool
+checkowner_minfo(ARGS_CHECKOWNER) {
+ REQUIRE(type == dns_rdatatype_minfo);
+
+ UNUSED(name);
+ UNUSED(type);
+ UNUSED(rdclass);
+ UNUSED(wildcard);
+
+ return (true);
+}
+
+static bool
+checknames_minfo(ARGS_CHECKNAMES) {
+ isc_region_t region;
+ dns_name_t name;
+
+ REQUIRE(rdata->type == dns_rdatatype_minfo);
+
+ UNUSED(owner);
+
+ dns_rdata_toregion(rdata, &region);
+ dns_name_init(&name, NULL);
+ dns_name_fromregion(&name, &region);
+ if (!dns_name_ismailbox(&name)) {
+ if (bad != NULL) {
+ dns_name_clone(&name, bad);
+ }
+ return (false);
+ }
+ isc_region_consume(&region, name_length(&name));
+ dns_name_fromregion(&name, &region);
+ if (!dns_name_ismailbox(&name)) {
+ if (bad != NULL) {
+ dns_name_clone(&name, bad);
+ }
+ return (false);
+ }
+ return (true);
+}
+
+static int
+casecompare_minfo(ARGS_COMPARE) {
+ return (compare_minfo(rdata1, rdata2));
+}
+
+#endif /* RDATA_GENERIC_MINFO_14_C */
diff --git a/lib/dns/rdata/generic/minfo_14.h b/lib/dns/rdata/generic/minfo_14.h
new file mode 100644
index 0000000..f2a530a
--- /dev/null
+++ b/lib/dns/rdata/generic/minfo_14.h
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/* */
+#pragma once
+
+typedef struct dns_rdata_minfo {
+ dns_rdatacommon_t common;
+ isc_mem_t *mctx;
+ dns_name_t rmailbox;
+ dns_name_t emailbox;
+} dns_rdata_minfo_t;
diff --git a/lib/dns/rdata/generic/mr_9.c b/lib/dns/rdata/generic/mr_9.c
new file mode 100644
index 0000000..4ea279b
--- /dev/null
+++ b/lib/dns/rdata/generic/mr_9.c
@@ -0,0 +1,230 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#ifndef RDATA_GENERIC_MR_9_C
+#define RDATA_GENERIC_MR_9_C
+
+#define RRTYPE_MR_ATTRIBUTES (0)
+
+static isc_result_t
+fromtext_mr(ARGS_FROMTEXT) {
+ isc_token_t token;
+ dns_name_t name;
+ isc_buffer_t buffer;
+
+ REQUIRE(type == dns_rdatatype_mr);
+
+ UNUSED(type);
+ UNUSED(rdclass);
+ UNUSED(callbacks);
+
+ RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_string,
+ false));
+
+ dns_name_init(&name, NULL);
+ buffer_fromregion(&buffer, &token.value.as_region);
+ if (origin == NULL) {
+ origin = dns_rootname;
+ }
+ RETTOK(dns_name_fromtext(&name, &buffer, origin, options, target));
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+totext_mr(ARGS_TOTEXT) {
+ isc_region_t region;
+ dns_name_t name;
+ dns_name_t prefix;
+ bool sub;
+
+ REQUIRE(rdata->type == dns_rdatatype_mr);
+ REQUIRE(rdata->length != 0);
+
+ dns_name_init(&name, NULL);
+ dns_name_init(&prefix, NULL);
+
+ dns_rdata_toregion(rdata, &region);
+ dns_name_fromregion(&name, &region);
+
+ sub = name_prefix(&name, tctx->origin, &prefix);
+
+ return (dns_name_totext(&prefix, sub, target));
+}
+
+static isc_result_t
+fromwire_mr(ARGS_FROMWIRE) {
+ dns_name_t name;
+
+ REQUIRE(type == dns_rdatatype_mr);
+
+ UNUSED(type);
+ UNUSED(rdclass);
+
+ dns_decompress_setmethods(dctx, DNS_COMPRESS_GLOBAL14);
+
+ dns_name_init(&name, NULL);
+ return (dns_name_fromwire(&name, source, dctx, options, target));
+}
+
+static isc_result_t
+towire_mr(ARGS_TOWIRE) {
+ dns_name_t name;
+ dns_offsets_t offsets;
+ isc_region_t region;
+
+ REQUIRE(rdata->type == dns_rdatatype_mr);
+ REQUIRE(rdata->length != 0);
+
+ dns_compress_setmethods(cctx, DNS_COMPRESS_GLOBAL14);
+
+ dns_name_init(&name, offsets);
+ dns_rdata_toregion(rdata, &region);
+ dns_name_fromregion(&name, &region);
+
+ return (dns_name_towire(&name, cctx, target));
+}
+
+static int
+compare_mr(ARGS_COMPARE) {
+ dns_name_t name1;
+ dns_name_t name2;
+ isc_region_t region1;
+ isc_region_t region2;
+
+ REQUIRE(rdata1->type == rdata2->type);
+ REQUIRE(rdata1->rdclass == rdata2->rdclass);
+ REQUIRE(rdata1->type == dns_rdatatype_mr);
+ REQUIRE(rdata1->length != 0);
+ REQUIRE(rdata2->length != 0);
+
+ dns_name_init(&name1, NULL);
+ dns_name_init(&name2, NULL);
+
+ dns_rdata_toregion(rdata1, &region1);
+ dns_rdata_toregion(rdata2, &region2);
+
+ dns_name_fromregion(&name1, &region1);
+ dns_name_fromregion(&name2, &region2);
+
+ return (dns_name_rdatacompare(&name1, &name2));
+}
+
+static isc_result_t
+fromstruct_mr(ARGS_FROMSTRUCT) {
+ dns_rdata_mr_t *mr = source;
+ isc_region_t region;
+
+ REQUIRE(type == dns_rdatatype_mr);
+ REQUIRE(mr != NULL);
+ REQUIRE(mr->common.rdtype == type);
+ REQUIRE(mr->common.rdclass == rdclass);
+
+ UNUSED(type);
+ UNUSED(rdclass);
+
+ dns_name_toregion(&mr->mr, &region);
+ return (isc_buffer_copyregion(target, &region));
+}
+
+static isc_result_t
+tostruct_mr(ARGS_TOSTRUCT) {
+ isc_region_t region;
+ dns_rdata_mr_t *mr = target;
+ dns_name_t name;
+
+ REQUIRE(rdata->type == dns_rdatatype_mr);
+ REQUIRE(mr != NULL);
+ REQUIRE(rdata->length != 0);
+
+ mr->common.rdclass = rdata->rdclass;
+ mr->common.rdtype = rdata->type;
+ ISC_LINK_INIT(&mr->common, link);
+
+ dns_name_init(&name, NULL);
+ dns_rdata_toregion(rdata, &region);
+ dns_name_fromregion(&name, &region);
+ dns_name_init(&mr->mr, NULL);
+ name_duporclone(&name, mctx, &mr->mr);
+ mr->mctx = mctx;
+ return (ISC_R_SUCCESS);
+}
+
+static void
+freestruct_mr(ARGS_FREESTRUCT) {
+ dns_rdata_mr_t *mr = source;
+
+ REQUIRE(mr != NULL);
+ REQUIRE(mr->common.rdtype == dns_rdatatype_mr);
+
+ if (mr->mctx == NULL) {
+ return;
+ }
+ dns_name_free(&mr->mr, mr->mctx);
+ mr->mctx = NULL;
+}
+
+static isc_result_t
+additionaldata_mr(ARGS_ADDLDATA) {
+ REQUIRE(rdata->type == dns_rdatatype_mr);
+
+ UNUSED(rdata);
+ UNUSED(owner);
+ UNUSED(add);
+ UNUSED(arg);
+
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+digest_mr(ARGS_DIGEST) {
+ isc_region_t r;
+ dns_name_t name;
+
+ REQUIRE(rdata->type == dns_rdatatype_mr);
+
+ dns_rdata_toregion(rdata, &r);
+ dns_name_init(&name, NULL);
+ dns_name_fromregion(&name, &r);
+
+ return (dns_name_digest(&name, digest, arg));
+}
+
+static bool
+checkowner_mr(ARGS_CHECKOWNER) {
+ REQUIRE(type == dns_rdatatype_mr);
+
+ UNUSED(name);
+ UNUSED(type);
+ UNUSED(rdclass);
+ UNUSED(wildcard);
+
+ return (true);
+}
+
+static bool
+checknames_mr(ARGS_CHECKNAMES) {
+ REQUIRE(rdata->type == dns_rdatatype_mr);
+
+ UNUSED(rdata);
+ UNUSED(owner);
+ UNUSED(bad);
+
+ return (true);
+}
+
+static int
+casecompare_mr(ARGS_COMPARE) {
+ return (compare_mr(rdata1, rdata2));
+}
+
+#endif /* RDATA_GENERIC_MR_9_C */
diff --git a/lib/dns/rdata/generic/mr_9.h b/lib/dns/rdata/generic/mr_9.h
new file mode 100644
index 0000000..e9a876e
--- /dev/null
+++ b/lib/dns/rdata/generic/mr_9.h
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/* */
+#pragma once
+
+typedef struct dns_rdata_mr {
+ dns_rdatacommon_t common;
+ isc_mem_t *mctx;
+ dns_name_t mr;
+} dns_rdata_mr_t;
diff --git a/lib/dns/rdata/generic/mx_15.c b/lib/dns/rdata/generic/mx_15.c
new file mode 100644
index 0000000..9356f0d
--- /dev/null
+++ b/lib/dns/rdata/generic/mx_15.c
@@ -0,0 +1,359 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#ifndef RDATA_GENERIC_MX_15_C
+#define RDATA_GENERIC_MX_15_C
+
+#include <string.h>
+
+#include <isc/net.h>
+
+#include <dns/fixedname.h>
+
+#define RRTYPE_MX_ATTRIBUTES (0)
+
+static bool
+check_mx(isc_token_t *token) {
+ char tmp[sizeof("xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:123.123.123.123.")];
+ struct in_addr addr;
+ struct in6_addr addr6;
+
+ if (strlcpy(tmp, DNS_AS_STR(*token), sizeof(tmp)) >= sizeof(tmp)) {
+ return (true);
+ }
+
+ if (tmp[strlen(tmp) - 1] == '.') {
+ tmp[strlen(tmp) - 1] = '\0';
+ }
+ if (inet_pton(AF_INET, tmp, &addr) == 1 ||
+ inet_pton(AF_INET6, tmp, &addr6) == 1)
+ {
+ return (false);
+ }
+
+ return (true);
+}
+
+static isc_result_t
+fromtext_mx(ARGS_FROMTEXT) {
+ isc_token_t token;
+ dns_name_t name;
+ isc_buffer_t buffer;
+ bool ok;
+
+ REQUIRE(type == dns_rdatatype_mx);
+
+ UNUSED(type);
+ UNUSED(rdclass);
+
+ RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_number,
+ false));
+ if (token.value.as_ulong > 0xffffU) {
+ RETTOK(ISC_R_RANGE);
+ }
+ RETERR(uint16_tobuffer(token.value.as_ulong, target));
+
+ RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_string,
+ false));
+
+ ok = true;
+ if ((options & DNS_RDATA_CHECKMX) != 0) {
+ ok = check_mx(&token);
+ }
+ if (!ok && (options & DNS_RDATA_CHECKMXFAIL) != 0) {
+ RETTOK(DNS_R_MXISADDRESS);
+ }
+ if (!ok && callbacks != NULL) {
+ warn_badmx(&token, lexer, callbacks);
+ }
+
+ dns_name_init(&name, NULL);
+ buffer_fromregion(&buffer, &token.value.as_region);
+ if (origin == NULL) {
+ origin = dns_rootname;
+ }
+ RETTOK(dns_name_fromtext(&name, &buffer, origin, options, target));
+ ok = true;
+ if ((options & DNS_RDATA_CHECKNAMES) != 0) {
+ ok = dns_name_ishostname(&name, false);
+ }
+ if (!ok && (options & DNS_RDATA_CHECKNAMESFAIL) != 0) {
+ RETTOK(DNS_R_BADNAME);
+ }
+ if (!ok && callbacks != NULL) {
+ warn_badname(&name, lexer, callbacks);
+ }
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+totext_mx(ARGS_TOTEXT) {
+ isc_region_t region;
+ dns_name_t name;
+ dns_name_t prefix;
+ bool sub;
+ char buf[sizeof("64000")];
+ unsigned short num;
+
+ REQUIRE(rdata->type == dns_rdatatype_mx);
+ REQUIRE(rdata->length != 0);
+
+ dns_name_init(&name, NULL);
+ dns_name_init(&prefix, NULL);
+
+ dns_rdata_toregion(rdata, &region);
+ num = uint16_fromregion(&region);
+ isc_region_consume(&region, 2);
+ snprintf(buf, sizeof(buf), "%u", num);
+ RETERR(str_totext(buf, target));
+
+ RETERR(str_totext(" ", target));
+
+ dns_name_fromregion(&name, &region);
+ sub = name_prefix(&name, tctx->origin, &prefix);
+ return (dns_name_totext(&prefix, sub, target));
+}
+
+static isc_result_t
+fromwire_mx(ARGS_FROMWIRE) {
+ dns_name_t name;
+ isc_region_t sregion;
+
+ REQUIRE(type == dns_rdatatype_mx);
+
+ UNUSED(type);
+ UNUSED(rdclass);
+
+ dns_decompress_setmethods(dctx, DNS_COMPRESS_GLOBAL14);
+
+ dns_name_init(&name, NULL);
+
+ isc_buffer_activeregion(source, &sregion);
+ if (sregion.length < 2) {
+ return (ISC_R_UNEXPECTEDEND);
+ }
+ RETERR(mem_tobuffer(target, sregion.base, 2));
+ isc_buffer_forward(source, 2);
+ return (dns_name_fromwire(&name, source, dctx, options, target));
+}
+
+static isc_result_t
+towire_mx(ARGS_TOWIRE) {
+ dns_name_t name;
+ dns_offsets_t offsets;
+ isc_region_t region;
+
+ REQUIRE(rdata->type == dns_rdatatype_mx);
+ REQUIRE(rdata->length != 0);
+
+ dns_compress_setmethods(cctx, DNS_COMPRESS_GLOBAL14);
+
+ dns_rdata_toregion(rdata, &region);
+ RETERR(mem_tobuffer(target, region.base, 2));
+ isc_region_consume(&region, 2);
+
+ dns_name_init(&name, offsets);
+ dns_name_fromregion(&name, &region);
+
+ return (dns_name_towire(&name, cctx, target));
+}
+
+static int
+compare_mx(ARGS_COMPARE) {
+ dns_name_t name1;
+ dns_name_t name2;
+ isc_region_t region1;
+ isc_region_t region2;
+ int order;
+
+ REQUIRE(rdata1->type == rdata2->type);
+ REQUIRE(rdata1->rdclass == rdata2->rdclass);
+ REQUIRE(rdata1->type == dns_rdatatype_mx);
+ REQUIRE(rdata1->length != 0);
+ REQUIRE(rdata2->length != 0);
+
+ order = memcmp(rdata1->data, rdata2->data, 2);
+ if (order != 0) {
+ return (order < 0 ? -1 : 1);
+ }
+
+ dns_name_init(&name1, NULL);
+ dns_name_init(&name2, NULL);
+
+ dns_rdata_toregion(rdata1, &region1);
+ dns_rdata_toregion(rdata2, &region2);
+
+ isc_region_consume(&region1, 2);
+ isc_region_consume(&region2, 2);
+
+ dns_name_fromregion(&name1, &region1);
+ dns_name_fromregion(&name2, &region2);
+
+ return (dns_name_rdatacompare(&name1, &name2));
+}
+
+static isc_result_t
+fromstruct_mx(ARGS_FROMSTRUCT) {
+ dns_rdata_mx_t *mx = source;
+ isc_region_t region;
+
+ REQUIRE(type == dns_rdatatype_mx);
+ REQUIRE(mx != NULL);
+ REQUIRE(mx->common.rdtype == type);
+ REQUIRE(mx->common.rdclass == rdclass);
+
+ UNUSED(type);
+ UNUSED(rdclass);
+
+ RETERR(uint16_tobuffer(mx->pref, target));
+ dns_name_toregion(&mx->mx, &region);
+ return (isc_buffer_copyregion(target, &region));
+}
+
+static isc_result_t
+tostruct_mx(ARGS_TOSTRUCT) {
+ isc_region_t region;
+ dns_rdata_mx_t *mx = target;
+ dns_name_t name;
+
+ REQUIRE(rdata->type == dns_rdatatype_mx);
+ REQUIRE(mx != NULL);
+ REQUIRE(rdata->length != 0);
+
+ mx->common.rdclass = rdata->rdclass;
+ mx->common.rdtype = rdata->type;
+ ISC_LINK_INIT(&mx->common, link);
+
+ dns_name_init(&name, NULL);
+ dns_rdata_toregion(rdata, &region);
+ mx->pref = uint16_fromregion(&region);
+ isc_region_consume(&region, 2);
+ dns_name_fromregion(&name, &region);
+ dns_name_init(&mx->mx, NULL);
+ name_duporclone(&name, mctx, &mx->mx);
+ mx->mctx = mctx;
+ return (ISC_R_SUCCESS);
+}
+
+static void
+freestruct_mx(ARGS_FREESTRUCT) {
+ dns_rdata_mx_t *mx = source;
+
+ REQUIRE(mx != NULL);
+ REQUIRE(mx->common.rdtype == dns_rdatatype_mx);
+
+ if (mx->mctx == NULL) {
+ return;
+ }
+
+ dns_name_free(&mx->mx, mx->mctx);
+ mx->mctx = NULL;
+}
+
+static unsigned char port25_offset[] = { 0, 3 };
+static unsigned char port25_ndata[] = "\003_25\004_tcp";
+static dns_name_t port25 = DNS_NAME_INITNONABSOLUTE(port25_ndata,
+ port25_offset);
+
+static isc_result_t
+additionaldata_mx(ARGS_ADDLDATA) {
+ isc_result_t result;
+ dns_fixedname_t fixed;
+ dns_name_t name;
+ dns_offsets_t offsets;
+ isc_region_t region;
+
+ REQUIRE(rdata->type == dns_rdatatype_mx);
+
+ UNUSED(owner);
+
+ dns_name_init(&name, offsets);
+ dns_rdata_toregion(rdata, &region);
+ isc_region_consume(&region, 2);
+ dns_name_fromregion(&name, &region);
+
+ if (dns_name_equal(&name, dns_rootname)) {
+ return (ISC_R_SUCCESS);
+ }
+
+ result = (add)(arg, &name, dns_rdatatype_a, NULL);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+
+ dns_fixedname_init(&fixed);
+ result = dns_name_concatenate(&port25, &name,
+ dns_fixedname_name(&fixed), NULL);
+ if (result != ISC_R_SUCCESS) {
+ return (ISC_R_SUCCESS);
+ }
+
+ return ((add)(arg, dns_fixedname_name(&fixed), dns_rdatatype_tlsa,
+ NULL));
+}
+
+static isc_result_t
+digest_mx(ARGS_DIGEST) {
+ isc_region_t r1, r2;
+ dns_name_t name;
+
+ REQUIRE(rdata->type == dns_rdatatype_mx);
+
+ dns_rdata_toregion(rdata, &r1);
+ r2 = r1;
+ isc_region_consume(&r2, 2);
+ r1.length = 2;
+ RETERR((digest)(arg, &r1));
+ dns_name_init(&name, NULL);
+ dns_name_fromregion(&name, &r2);
+ return (dns_name_digest(&name, digest, arg));
+}
+
+static bool
+checkowner_mx(ARGS_CHECKOWNER) {
+ REQUIRE(type == dns_rdatatype_mx);
+
+ UNUSED(type);
+ UNUSED(rdclass);
+
+ return (dns_name_ishostname(name, wildcard));
+}
+
+static bool
+checknames_mx(ARGS_CHECKNAMES) {
+ isc_region_t region;
+ dns_name_t name;
+
+ REQUIRE(rdata->type == dns_rdatatype_mx);
+
+ UNUSED(owner);
+
+ dns_rdata_toregion(rdata, &region);
+ isc_region_consume(&region, 2);
+ dns_name_init(&name, NULL);
+ dns_name_fromregion(&name, &region);
+ if (!dns_name_ishostname(&name, false)) {
+ if (bad != NULL) {
+ dns_name_clone(&name, bad);
+ }
+ return (false);
+ }
+ return (true);
+}
+
+static int
+casecompare_mx(ARGS_COMPARE) {
+ return (compare_mx(rdata1, rdata2));
+}
+
+#endif /* RDATA_GENERIC_MX_15_C */
diff --git a/lib/dns/rdata/generic/mx_15.h b/lib/dns/rdata/generic/mx_15.h
new file mode 100644
index 0000000..f651591
--- /dev/null
+++ b/lib/dns/rdata/generic/mx_15.h
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/* */
+#pragma once
+
+typedef struct dns_rdata_mx {
+ dns_rdatacommon_t common;
+ isc_mem_t *mctx;
+ uint16_t pref;
+ dns_name_t mx;
+} dns_rdata_mx_t;
diff --git a/lib/dns/rdata/generic/naptr_35.c b/lib/dns/rdata/generic/naptr_35.c
new file mode 100644
index 0000000..3db485a
--- /dev/null
+++ b/lib/dns/rdata/generic/naptr_35.c
@@ -0,0 +1,738 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/* RFC2915 */
+
+#ifndef RDATA_GENERIC_NAPTR_35_C
+#define RDATA_GENERIC_NAPTR_35_C
+
+#define RRTYPE_NAPTR_ATTRIBUTES (0)
+
+#include <isc/regex.h>
+
+/*
+ * Check the wire format of the Regexp field.
+ * Don't allow embedded NUL's.
+ */
+static isc_result_t
+txt_valid_regex(const unsigned char *txt) {
+ unsigned int nsub = 0;
+ char regex[256];
+ char *cp;
+ bool flags = false;
+ bool replace = false;
+ unsigned char c;
+ unsigned char delim;
+ unsigned int len;
+ int n;
+
+ len = *txt++;
+ if (len == 0U) {
+ return (ISC_R_SUCCESS);
+ }
+
+ delim = *txt++;
+ len--;
+
+ /*
+ * Digits, backslash and flags can't be delimiters.
+ */
+ switch (delim) {
+ case '0':
+ case '1':
+ case '2':
+ case '3':
+ case '4':
+ case '5':
+ case '6':
+ case '7':
+ case '8':
+ case '9':
+ case '\\':
+ case 'i':
+ case 0:
+ return (DNS_R_SYNTAX);
+ }
+
+ cp = regex;
+ while (len-- > 0) {
+ c = *txt++;
+ if (c == 0) {
+ return (DNS_R_SYNTAX);
+ }
+ if (c == delim && !replace) {
+ replace = true;
+ continue;
+ } else if (c == delim && !flags) {
+ flags = true;
+ continue;
+ } else if (c == delim) {
+ return (DNS_R_SYNTAX);
+ }
+ /*
+ * Flags are not escaped.
+ */
+ if (flags) {
+ switch (c) {
+ case 'i':
+ continue;
+ default:
+ return (DNS_R_SYNTAX);
+ }
+ }
+ if (!replace) {
+ *cp++ = c;
+ }
+ if (c == '\\') {
+ if (len == 0) {
+ return (DNS_R_SYNTAX);
+ }
+ c = *txt++;
+ if (c == 0) {
+ return (DNS_R_SYNTAX);
+ }
+ len--;
+ if (replace) {
+ switch (c) {
+ case '0':
+ return (DNS_R_SYNTAX);
+ case '1':
+ if (nsub < 1) {
+ nsub = 1;
+ }
+ break;
+ case '2':
+ if (nsub < 2) {
+ nsub = 2;
+ }
+ break;
+ case '3':
+ if (nsub < 3) {
+ nsub = 3;
+ }
+ break;
+ case '4':
+ if (nsub < 4) {
+ nsub = 4;
+ }
+ break;
+ case '5':
+ if (nsub < 5) {
+ nsub = 5;
+ }
+ break;
+ case '6':
+ if (nsub < 6) {
+ nsub = 6;
+ }
+ break;
+ case '7':
+ if (nsub < 7) {
+ nsub = 7;
+ }
+ break;
+ case '8':
+ if (nsub < 8) {
+ nsub = 8;
+ }
+ break;
+ case '9':
+ if (nsub < 9) {
+ nsub = 9;
+ }
+ break;
+ }
+ }
+ if (!replace) {
+ *cp++ = c;
+ }
+ }
+ }
+ if (!flags) {
+ return (DNS_R_SYNTAX);
+ }
+ *cp = '\0';
+ n = isc_regex_validate(regex);
+ if (n < 0 || nsub > (unsigned int)n) {
+ return (DNS_R_SYNTAX);
+ }
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+fromtext_naptr(ARGS_FROMTEXT) {
+ isc_token_t token;
+ dns_name_t name;
+ isc_buffer_t buffer;
+ unsigned char *regex;
+
+ REQUIRE(type == dns_rdatatype_naptr);
+
+ UNUSED(type);
+ UNUSED(rdclass);
+ UNUSED(callbacks);
+
+ /*
+ * Order.
+ */
+ RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_number,
+ false));
+ if (token.value.as_ulong > 0xffffU) {
+ RETTOK(ISC_R_RANGE);
+ }
+ RETERR(uint16_tobuffer(token.value.as_ulong, target));
+
+ /*
+ * Preference.
+ */
+ RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_number,
+ false));
+ if (token.value.as_ulong > 0xffffU) {
+ RETTOK(ISC_R_RANGE);
+ }
+ RETERR(uint16_tobuffer(token.value.as_ulong, target));
+
+ /*
+ * Flags.
+ */
+ RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_qstring,
+ false));
+ RETTOK(txt_fromtext(&token.value.as_textregion, target));
+
+ /*
+ * Service.
+ */
+ RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_qstring,
+ false));
+ RETTOK(txt_fromtext(&token.value.as_textregion, target));
+
+ /*
+ * Regexp.
+ */
+ regex = isc_buffer_used(target);
+ RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_qstring,
+ false));
+ RETTOK(txt_fromtext(&token.value.as_textregion, target));
+ RETTOK(txt_valid_regex(regex));
+
+ /*
+ * Replacement.
+ */
+ RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_string,
+ false));
+ dns_name_init(&name, NULL);
+ buffer_fromregion(&buffer, &token.value.as_region);
+ if (origin == NULL) {
+ origin = dns_rootname;
+ }
+ RETTOK(dns_name_fromtext(&name, &buffer, origin, options, target));
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+totext_naptr(ARGS_TOTEXT) {
+ isc_region_t region;
+ dns_name_t name;
+ dns_name_t prefix;
+ bool sub;
+ char buf[sizeof("64000")];
+ unsigned short num;
+
+ REQUIRE(rdata->type == dns_rdatatype_naptr);
+ REQUIRE(rdata->length != 0);
+
+ dns_name_init(&name, NULL);
+ dns_name_init(&prefix, NULL);
+
+ dns_rdata_toregion(rdata, &region);
+
+ /*
+ * Order.
+ */
+ num = uint16_fromregion(&region);
+ isc_region_consume(&region, 2);
+ snprintf(buf, sizeof(buf), "%u", num);
+ RETERR(str_totext(buf, target));
+ RETERR(str_totext(" ", target));
+
+ /*
+ * Preference.
+ */
+ num = uint16_fromregion(&region);
+ isc_region_consume(&region, 2);
+ snprintf(buf, sizeof(buf), "%u", num);
+ RETERR(str_totext(buf, target));
+ RETERR(str_totext(" ", target));
+
+ /*
+ * Flags.
+ */
+ RETERR(txt_totext(&region, true, target));
+ RETERR(str_totext(" ", target));
+
+ /*
+ * Service.
+ */
+ RETERR(txt_totext(&region, true, target));
+ RETERR(str_totext(" ", target));
+
+ /*
+ * Regexp.
+ */
+ RETERR(txt_totext(&region, true, target));
+ RETERR(str_totext(" ", target));
+
+ /*
+ * Replacement.
+ */
+ dns_name_fromregion(&name, &region);
+ sub = name_prefix(&name, tctx->origin, &prefix);
+ return (dns_name_totext(&prefix, sub, target));
+}
+
+static isc_result_t
+fromwire_naptr(ARGS_FROMWIRE) {
+ dns_name_t name;
+ isc_region_t sr;
+ unsigned char *regex;
+
+ REQUIRE(type == dns_rdatatype_naptr);
+
+ UNUSED(type);
+ UNUSED(rdclass);
+
+ dns_decompress_setmethods(dctx, DNS_COMPRESS_NONE);
+
+ dns_name_init(&name, NULL);
+
+ /*
+ * Order, preference.
+ */
+ isc_buffer_activeregion(source, &sr);
+ if (sr.length < 4) {
+ return (ISC_R_UNEXPECTEDEND);
+ }
+ RETERR(mem_tobuffer(target, sr.base, 4));
+ isc_buffer_forward(source, 4);
+
+ /*
+ * Flags.
+ */
+ RETERR(txt_fromwire(source, target));
+
+ /*
+ * Service.
+ */
+ RETERR(txt_fromwire(source, target));
+
+ /*
+ * Regexp.
+ */
+ regex = isc_buffer_used(target);
+ RETERR(txt_fromwire(source, target));
+ RETERR(txt_valid_regex(regex));
+
+ /*
+ * Replacement.
+ */
+ return (dns_name_fromwire(&name, source, dctx, options, target));
+}
+
+static isc_result_t
+towire_naptr(ARGS_TOWIRE) {
+ dns_name_t name;
+ dns_offsets_t offsets;
+ isc_region_t sr;
+
+ REQUIRE(rdata->type == dns_rdatatype_naptr);
+ REQUIRE(rdata->length != 0);
+
+ dns_compress_setmethods(cctx, DNS_COMPRESS_NONE);
+ /*
+ * Order, preference.
+ */
+ dns_rdata_toregion(rdata, &sr);
+ RETERR(mem_tobuffer(target, sr.base, 4));
+ isc_region_consume(&sr, 4);
+
+ /*
+ * Flags.
+ */
+ RETERR(mem_tobuffer(target, sr.base, sr.base[0] + 1));
+ isc_region_consume(&sr, sr.base[0] + 1);
+
+ /*
+ * Service.
+ */
+ RETERR(mem_tobuffer(target, sr.base, sr.base[0] + 1));
+ isc_region_consume(&sr, sr.base[0] + 1);
+
+ /*
+ * Regexp.
+ */
+ RETERR(mem_tobuffer(target, sr.base, sr.base[0] + 1));
+ isc_region_consume(&sr, sr.base[0] + 1);
+
+ /*
+ * Replacement.
+ */
+ dns_name_init(&name, offsets);
+ dns_name_fromregion(&name, &sr);
+ return (dns_name_towire(&name, cctx, target));
+}
+
+static int
+compare_naptr(ARGS_COMPARE) {
+ dns_name_t name1;
+ dns_name_t name2;
+ isc_region_t region1;
+ isc_region_t region2;
+ int order, len;
+
+ REQUIRE(rdata1->type == rdata2->type);
+ REQUIRE(rdata1->rdclass == rdata2->rdclass);
+ REQUIRE(rdata1->type == dns_rdatatype_naptr);
+ REQUIRE(rdata1->length != 0);
+ REQUIRE(rdata2->length != 0);
+
+ dns_rdata_toregion(rdata1, &region1);
+ dns_rdata_toregion(rdata2, &region2);
+
+ /*
+ * Order, preference.
+ */
+ order = memcmp(region1.base, region2.base, 4);
+ if (order != 0) {
+ return (order < 0 ? -1 : 1);
+ }
+ isc_region_consume(&region1, 4);
+ isc_region_consume(&region2, 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(&region1, region1.base[0] + 1);
+ isc_region_consume(&region2, 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(&region1, region1.base[0] + 1);
+ isc_region_consume(&region2, 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(&region1, region1.base[0] + 1);
+ isc_region_consume(&region2, region2.base[0] + 1);
+
+ /*
+ * Replacement.
+ */
+ dns_name_init(&name1, NULL);
+ dns_name_init(&name2, NULL);
+
+ dns_name_fromregion(&name1, &region1);
+ dns_name_fromregion(&name2, &region2);
+
+ return (dns_name_rdatacompare(&name1, &name2));
+}
+
+static isc_result_t
+fromstruct_naptr(ARGS_FROMSTRUCT) {
+ dns_rdata_naptr_t *naptr = source;
+ isc_region_t region;
+
+ REQUIRE(type == dns_rdatatype_naptr);
+ REQUIRE(naptr != NULL);
+ REQUIRE(naptr->common.rdtype == type);
+ REQUIRE(naptr->common.rdclass == rdclass);
+ REQUIRE(naptr->flags != NULL || naptr->flags_len == 0);
+ REQUIRE(naptr->service != NULL || naptr->service_len == 0);
+ REQUIRE(naptr->regexp != NULL || naptr->regexp_len == 0);
+
+ UNUSED(type);
+ UNUSED(rdclass);
+
+ RETERR(uint16_tobuffer(naptr->order, target));
+ RETERR(uint16_tobuffer(naptr->preference, target));
+ RETERR(uint8_tobuffer(naptr->flags_len, target));
+ RETERR(mem_tobuffer(target, naptr->flags, naptr->flags_len));
+ RETERR(uint8_tobuffer(naptr->service_len, target));
+ RETERR(mem_tobuffer(target, naptr->service, naptr->service_len));
+ RETERR(uint8_tobuffer(naptr->regexp_len, target));
+ RETERR(mem_tobuffer(target, naptr->regexp, naptr->regexp_len));
+ dns_name_toregion(&naptr->replacement, &region);
+ return (isc_buffer_copyregion(target, &region));
+}
+
+static isc_result_t
+tostruct_naptr(ARGS_TOSTRUCT) {
+ dns_rdata_naptr_t *naptr = target;
+ isc_region_t r;
+ dns_name_t name;
+
+ REQUIRE(rdata->type == dns_rdatatype_naptr);
+ REQUIRE(naptr != NULL);
+ REQUIRE(rdata->length != 0);
+
+ naptr->common.rdclass = rdata->rdclass;
+ naptr->common.rdtype = rdata->type;
+ ISC_LINK_INIT(&naptr->common, link);
+
+ naptr->flags = NULL;
+ naptr->service = NULL;
+ naptr->regexp = NULL;
+
+ dns_rdata_toregion(rdata, &r);
+
+ naptr->order = uint16_fromregion(&r);
+ isc_region_consume(&r, 2);
+
+ naptr->preference = uint16_fromregion(&r);
+ isc_region_consume(&r, 2);
+
+ naptr->flags_len = uint8_fromregion(&r);
+ isc_region_consume(&r, 1);
+ INSIST(naptr->flags_len <= r.length);
+ naptr->flags = mem_maybedup(mctx, r.base, naptr->flags_len);
+ if (naptr->flags == NULL) {
+ goto cleanup;
+ }
+ isc_region_consume(&r, naptr->flags_len);
+
+ naptr->service_len = uint8_fromregion(&r);
+ isc_region_consume(&r, 1);
+ INSIST(naptr->service_len <= r.length);
+ naptr->service = mem_maybedup(mctx, r.base, naptr->service_len);
+ if (naptr->service == NULL) {
+ goto cleanup;
+ }
+ isc_region_consume(&r, naptr->service_len);
+
+ naptr->regexp_len = uint8_fromregion(&r);
+ isc_region_consume(&r, 1);
+ INSIST(naptr->regexp_len <= r.length);
+ naptr->regexp = mem_maybedup(mctx, r.base, naptr->regexp_len);
+ if (naptr->regexp == NULL) {
+ goto cleanup;
+ }
+ isc_region_consume(&r, naptr->regexp_len);
+
+ dns_name_init(&name, NULL);
+ dns_name_fromregion(&name, &r);
+ dns_name_init(&naptr->replacement, NULL);
+ name_duporclone(&name, mctx, &naptr->replacement);
+ naptr->mctx = mctx;
+ return (ISC_R_SUCCESS);
+
+cleanup:
+ if (mctx != NULL && naptr->flags != NULL) {
+ isc_mem_free(mctx, naptr->flags);
+ }
+ if (mctx != NULL && naptr->service != NULL) {
+ isc_mem_free(mctx, naptr->service);
+ }
+ if (mctx != NULL && naptr->regexp != NULL) {
+ isc_mem_free(mctx, naptr->regexp);
+ }
+ return (ISC_R_NOMEMORY);
+}
+
+static void
+freestruct_naptr(ARGS_FREESTRUCT) {
+ dns_rdata_naptr_t *naptr = source;
+
+ REQUIRE(naptr != NULL);
+ REQUIRE(naptr->common.rdtype == dns_rdatatype_naptr);
+
+ if (naptr->mctx == NULL) {
+ return;
+ }
+
+ if (naptr->flags != NULL) {
+ isc_mem_free(naptr->mctx, naptr->flags);
+ }
+ if (naptr->service != NULL) {
+ isc_mem_free(naptr->mctx, naptr->service);
+ }
+ if (naptr->regexp != NULL) {
+ isc_mem_free(naptr->mctx, naptr->regexp);
+ }
+ dns_name_free(&naptr->replacement, naptr->mctx);
+ naptr->mctx = NULL;
+}
+
+static isc_result_t
+additionaldata_naptr(ARGS_ADDLDATA) {
+ dns_name_t name;
+ dns_offsets_t offsets;
+ isc_region_t sr;
+ dns_rdatatype_t atype;
+ unsigned int i, flagslen;
+ char *cp;
+
+ REQUIRE(rdata->type == dns_rdatatype_naptr);
+
+ UNUSED(owner);
+
+ /*
+ * Order, preference.
+ */
+ dns_rdata_toregion(rdata, &sr);
+ isc_region_consume(&sr, 4);
+
+ /*
+ * Flags.
+ */
+ atype = 0;
+ flagslen = sr.base[0];
+ cp = (char *)&sr.base[1];
+ for (i = 0; i < flagslen; i++, cp++) {
+ if (*cp == 'S' || *cp == 's') {
+ atype = dns_rdatatype_srv;
+ break;
+ }
+ if (*cp == 'A' || *cp == 'a') {
+ atype = dns_rdatatype_a;
+ break;
+ }
+ }
+ isc_region_consume(&sr, flagslen + 1);
+
+ /*
+ * Service.
+ */
+ isc_region_consume(&sr, sr.base[0] + 1);
+
+ /*
+ * Regexp.
+ */
+ isc_region_consume(&sr, sr.base[0] + 1);
+
+ /*
+ * Replacement.
+ */
+ dns_name_init(&name, offsets);
+ dns_name_fromregion(&name, &sr);
+
+ if (atype != 0) {
+ return ((add)(arg, &name, atype, NULL));
+ }
+
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+digest_naptr(ARGS_DIGEST) {
+ isc_region_t r1, r2;
+ unsigned int length, n;
+ isc_result_t result;
+ dns_name_t name;
+
+ REQUIRE(rdata->type == dns_rdatatype_naptr);
+
+ dns_rdata_toregion(rdata, &r1);
+ r2 = r1;
+ length = 0;
+
+ /*
+ * Order, preference.
+ */
+ length += 4;
+ isc_region_consume(&r2, 4);
+
+ /*
+ * Flags.
+ */
+ n = r2.base[0] + 1;
+ length += n;
+ isc_region_consume(&r2, n);
+
+ /*
+ * Service.
+ */
+ n = r2.base[0] + 1;
+ length += n;
+ isc_region_consume(&r2, n);
+
+ /*
+ * Regexp.
+ */
+ n = r2.base[0] + 1;
+ length += n;
+ isc_region_consume(&r2, n);
+
+ /*
+ * Digest the RR up to the replacement name.
+ */
+ r1.length = length;
+ result = (digest)(arg, &r1);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+
+ /*
+ * Replacement.
+ */
+
+ dns_name_init(&name, NULL);
+ dns_name_fromregion(&name, &r2);
+
+ return (dns_name_digest(&name, digest, arg));
+}
+
+static bool
+checkowner_naptr(ARGS_CHECKOWNER) {
+ REQUIRE(type == dns_rdatatype_naptr);
+
+ UNUSED(name);
+ UNUSED(type);
+ UNUSED(rdclass);
+ UNUSED(wildcard);
+
+ return (true);
+}
+
+static bool
+checknames_naptr(ARGS_CHECKNAMES) {
+ REQUIRE(rdata->type == dns_rdatatype_naptr);
+
+ UNUSED(rdata);
+ UNUSED(owner);
+ UNUSED(bad);
+
+ return (true);
+}
+
+static int
+casecompare_naptr(ARGS_COMPARE) {
+ return (compare_naptr(rdata1, rdata2));
+}
+
+#endif /* RDATA_GENERIC_NAPTR_35_C */
diff --git a/lib/dns/rdata/generic/naptr_35.h b/lib/dns/rdata/generic/naptr_35.h
new file mode 100644
index 0000000..f414ddd
--- /dev/null
+++ b/lib/dns/rdata/generic/naptr_35.h
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#pragma once
+
+/*!
+ * \brief Per RFC2915 */
+
+typedef struct dns_rdata_naptr {
+ dns_rdatacommon_t common;
+ isc_mem_t *mctx;
+ uint16_t order;
+ uint16_t preference;
+ char *flags;
+ uint8_t flags_len;
+ char *service;
+ uint8_t service_len;
+ char *regexp;
+ uint8_t regexp_len;
+ dns_name_t replacement;
+} dns_rdata_naptr_t;
diff --git a/lib/dns/rdata/generic/nid_104.c b/lib/dns/rdata/generic/nid_104.c
new file mode 100644
index 0000000..8a95813
--- /dev/null
+++ b/lib/dns/rdata/generic/nid_104.c
@@ -0,0 +1,225 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#ifndef RDATA_GENERIC_NID_104_C
+#define RDATA_GENERIC_NID_104_C
+
+#include <string.h>
+
+#include <isc/net.h>
+
+#define RRTYPE_NID_ATTRIBUTES (0)
+
+static isc_result_t
+fromtext_nid(ARGS_FROMTEXT) {
+ isc_token_t token;
+ unsigned char locator[NS_LOCATORSZ];
+
+ REQUIRE(type == dns_rdatatype_nid);
+
+ UNUSED(type);
+ UNUSED(rdclass);
+ UNUSED(origin);
+ UNUSED(options);
+ UNUSED(callbacks);
+
+ RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_number,
+ false));
+ if (token.value.as_ulong > 0xffffU) {
+ RETTOK(ISC_R_RANGE);
+ }
+ RETERR(uint16_tobuffer(token.value.as_ulong, target));
+
+ RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_string,
+ false));
+
+ if (locator_pton(DNS_AS_STR(token), locator) != 1) {
+ RETTOK(DNS_R_SYNTAX);
+ }
+ return (mem_tobuffer(target, locator, NS_LOCATORSZ));
+}
+
+static isc_result_t
+totext_nid(ARGS_TOTEXT) {
+ isc_region_t region;
+ char buf[sizeof("xxxx:xxxx:xxxx:xxxx")];
+ unsigned short num;
+
+ REQUIRE(rdata->type == dns_rdatatype_nid);
+ REQUIRE(rdata->length != 0);
+
+ UNUSED(tctx);
+
+ dns_rdata_toregion(rdata, &region);
+ num = uint16_fromregion(&region);
+ isc_region_consume(&region, 2);
+ snprintf(buf, sizeof(buf), "%u", num);
+ RETERR(str_totext(buf, target));
+
+ RETERR(str_totext(" ", target));
+
+ snprintf(buf, sizeof(buf), "%x:%x:%x:%x",
+ region.base[0] << 8 | region.base[1],
+ region.base[2] << 8 | region.base[3],
+ region.base[4] << 8 | region.base[5],
+ region.base[6] << 8 | region.base[7]);
+ return (str_totext(buf, target));
+}
+
+static isc_result_t
+fromwire_nid(ARGS_FROMWIRE) {
+ isc_region_t sregion;
+
+ REQUIRE(type == dns_rdatatype_nid);
+
+ UNUSED(type);
+ UNUSED(options);
+ UNUSED(rdclass);
+ UNUSED(dctx);
+
+ isc_buffer_activeregion(source, &sregion);
+ if (sregion.length != 10) {
+ return (DNS_R_FORMERR);
+ }
+ isc_buffer_forward(source, sregion.length);
+ return (mem_tobuffer(target, sregion.base, sregion.length));
+}
+
+static isc_result_t
+towire_nid(ARGS_TOWIRE) {
+ REQUIRE(rdata->type == dns_rdatatype_nid);
+ REQUIRE(rdata->length == 10);
+
+ UNUSED(cctx);
+
+ return (mem_tobuffer(target, rdata->data, rdata->length));
+}
+
+static int
+compare_nid(ARGS_COMPARE) {
+ isc_region_t region1;
+ isc_region_t region2;
+
+ REQUIRE(rdata1->type == rdata2->type);
+ REQUIRE(rdata1->rdclass == rdata2->rdclass);
+ REQUIRE(rdata1->type == dns_rdatatype_nid);
+ REQUIRE(rdata1->length == 10);
+ REQUIRE(rdata2->length == 10);
+
+ dns_rdata_toregion(rdata1, &region1);
+ dns_rdata_toregion(rdata2, &region2);
+ return (isc_region_compare(&region1, &region2));
+}
+
+static isc_result_t
+fromstruct_nid(ARGS_FROMSTRUCT) {
+ dns_rdata_nid_t *nid = source;
+
+ REQUIRE(type == dns_rdatatype_nid);
+ REQUIRE(nid != NULL);
+ REQUIRE(nid->common.rdtype == type);
+ REQUIRE(nid->common.rdclass == rdclass);
+
+ UNUSED(type);
+ UNUSED(rdclass);
+
+ RETERR(uint16_tobuffer(nid->pref, target));
+ return (mem_tobuffer(target, nid->nid, sizeof(nid->nid)));
+}
+
+static isc_result_t
+tostruct_nid(ARGS_TOSTRUCT) {
+ isc_region_t region;
+ dns_rdata_nid_t *nid = target;
+
+ REQUIRE(rdata->type == dns_rdatatype_nid);
+ REQUIRE(nid != NULL);
+ REQUIRE(rdata->length == 10);
+
+ UNUSED(mctx);
+
+ nid->common.rdclass = rdata->rdclass;
+ nid->common.rdtype = rdata->type;
+ ISC_LINK_INIT(&nid->common, link);
+
+ dns_rdata_toregion(rdata, &region);
+ nid->pref = uint16_fromregion(&region);
+ memmove(nid->nid, region.base, region.length);
+ return (ISC_R_SUCCESS);
+}
+
+static void
+freestruct_nid(ARGS_FREESTRUCT) {
+ dns_rdata_nid_t *nid = source;
+
+ REQUIRE(nid != NULL);
+ REQUIRE(nid->common.rdtype == dns_rdatatype_nid);
+
+ return;
+}
+
+static isc_result_t
+additionaldata_nid(ARGS_ADDLDATA) {
+ REQUIRE(rdata->type == dns_rdatatype_nid);
+ REQUIRE(rdata->length == 10);
+
+ UNUSED(rdata);
+ UNUSED(owner);
+ UNUSED(add);
+ UNUSED(arg);
+
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+digest_nid(ARGS_DIGEST) {
+ isc_region_t r;
+
+ REQUIRE(rdata->type == dns_rdatatype_nid);
+ REQUIRE(rdata->length == 10);
+
+ dns_rdata_toregion(rdata, &r);
+
+ return ((digest)(arg, &r));
+}
+
+static bool
+checkowner_nid(ARGS_CHECKOWNER) {
+ REQUIRE(type == dns_rdatatype_nid);
+
+ UNUSED(name);
+ UNUSED(type);
+ UNUSED(rdclass);
+ UNUSED(wildcard);
+
+ return (true);
+}
+
+static bool
+checknames_nid(ARGS_CHECKNAMES) {
+ REQUIRE(rdata->type == dns_rdatatype_nid);
+ REQUIRE(rdata->length == 10);
+
+ UNUSED(rdata);
+ UNUSED(owner);
+ UNUSED(bad);
+
+ return (true);
+}
+
+static int
+casecompare_nid(ARGS_COMPARE) {
+ return (compare_nid(rdata1, rdata2));
+}
+
+#endif /* RDATA_GENERIC_NID_104_C */
diff --git a/lib/dns/rdata/generic/nid_104.h b/lib/dns/rdata/generic/nid_104.h
new file mode 100644
index 0000000..194cefa
--- /dev/null
+++ b/lib/dns/rdata/generic/nid_104.h
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/* */
+#pragma once
+
+typedef struct dns_rdata_nid {
+ dns_rdatacommon_t common;
+ uint16_t pref;
+ unsigned char nid[8];
+} dns_rdata_nid_t;
diff --git a/lib/dns/rdata/generic/ninfo_56.c b/lib/dns/rdata/generic/ninfo_56.c
new file mode 100644
index 0000000..8e0fae1
--- /dev/null
+++ b/lib/dns/rdata/generic/ninfo_56.c
@@ -0,0 +1,170 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#ifndef RDATA_GENERIC_NINFO_56_C
+#define RDATA_GENERIC_NINFO_56_C
+
+#define RRTYPE_NINFO_ATTRIBUTES (0)
+
+static isc_result_t
+fromtext_ninfo(ARGS_FROMTEXT) {
+ REQUIRE(type == dns_rdatatype_ninfo);
+
+ return (generic_fromtext_txt(CALL_FROMTEXT));
+}
+
+static isc_result_t
+totext_ninfo(ARGS_TOTEXT) {
+ REQUIRE(rdata != NULL);
+ REQUIRE(rdata->type == dns_rdatatype_ninfo);
+
+ return (generic_totext_txt(CALL_TOTEXT));
+}
+
+static isc_result_t
+fromwire_ninfo(ARGS_FROMWIRE) {
+ REQUIRE(type == dns_rdatatype_ninfo);
+
+ return (generic_fromwire_txt(CALL_FROMWIRE));
+}
+
+static isc_result_t
+towire_ninfo(ARGS_TOWIRE) {
+ REQUIRE(rdata->type == dns_rdatatype_ninfo);
+
+ UNUSED(cctx);
+
+ return (mem_tobuffer(target, rdata->data, rdata->length));
+}
+
+static int
+compare_ninfo(ARGS_COMPARE) {
+ isc_region_t r1;
+ isc_region_t r2;
+
+ REQUIRE(rdata1->type == rdata2->type);
+ REQUIRE(rdata1->rdclass == rdata2->rdclass);
+ REQUIRE(rdata1->type == dns_rdatatype_ninfo);
+
+ dns_rdata_toregion(rdata1, &r1);
+ dns_rdata_toregion(rdata2, &r2);
+ return (isc_region_compare(&r1, &r2));
+}
+
+static isc_result_t
+fromstruct_ninfo(ARGS_FROMSTRUCT) {
+ REQUIRE(type == dns_rdatatype_ninfo);
+
+ return (generic_fromstruct_txt(CALL_FROMSTRUCT));
+}
+
+static isc_result_t
+tostruct_ninfo(ARGS_TOSTRUCT) {
+ dns_rdata_ninfo_t *ninfo = target;
+
+ REQUIRE(rdata->type == dns_rdatatype_ninfo);
+ REQUIRE(ninfo != NULL);
+
+ ninfo->common.rdclass = rdata->rdclass;
+ ninfo->common.rdtype = rdata->type;
+ ISC_LINK_INIT(&ninfo->common, link);
+
+ return (generic_tostruct_txt(CALL_TOSTRUCT));
+}
+
+static void
+freestruct_ninfo(ARGS_FREESTRUCT) {
+ dns_rdata_ninfo_t *ninfo = source;
+
+ REQUIRE(ninfo != NULL);
+ REQUIRE(ninfo->common.rdtype == dns_rdatatype_ninfo);
+
+ generic_freestruct_txt(source);
+}
+
+static isc_result_t
+additionaldata_ninfo(ARGS_ADDLDATA) {
+ REQUIRE(rdata->type == dns_rdatatype_ninfo);
+
+ UNUSED(rdata);
+ UNUSED(owner);
+ UNUSED(add);
+ UNUSED(arg);
+
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+digest_ninfo(ARGS_DIGEST) {
+ isc_region_t r;
+
+ REQUIRE(rdata->type == dns_rdatatype_ninfo);
+
+ dns_rdata_toregion(rdata, &r);
+
+ return ((digest)(arg, &r));
+}
+
+static bool
+checkowner_ninfo(ARGS_CHECKOWNER) {
+ REQUIRE(type == dns_rdatatype_ninfo);
+
+ UNUSED(name);
+ UNUSED(type);
+ UNUSED(rdclass);
+ UNUSED(wildcard);
+
+ return (true);
+}
+
+static bool
+checknames_ninfo(ARGS_CHECKNAMES) {
+ REQUIRE(rdata->type == dns_rdatatype_ninfo);
+
+ UNUSED(rdata);
+ UNUSED(owner);
+ UNUSED(bad);
+
+ return (true);
+}
+
+static int
+casecompare_ninfo(ARGS_COMPARE) {
+ return (compare_ninfo(rdata1, rdata2));
+}
+
+isc_result_t
+dns_rdata_ninfo_first(dns_rdata_ninfo_t *ninfo) {
+ REQUIRE(ninfo != NULL);
+ REQUIRE(ninfo->common.rdtype == dns_rdatatype_ninfo);
+
+ return (generic_txt_first(ninfo));
+}
+
+isc_result_t
+dns_rdata_ninfo_next(dns_rdata_ninfo_t *ninfo) {
+ REQUIRE(ninfo != NULL);
+ REQUIRE(ninfo->common.rdtype == dns_rdatatype_ninfo);
+
+ return (generic_txt_next(ninfo));
+}
+
+isc_result_t
+dns_rdata_ninfo_current(dns_rdata_ninfo_t *ninfo,
+ dns_rdata_ninfo_string_t *string) {
+ REQUIRE(ninfo != NULL);
+ REQUIRE(ninfo->common.rdtype == dns_rdatatype_ninfo);
+
+ return (generic_txt_current(ninfo, string));
+}
+#endif /* RDATA_GENERIC_NINFO_56_C */
diff --git a/lib/dns/rdata/generic/ninfo_56.h b/lib/dns/rdata/generic/ninfo_56.h
new file mode 100644
index 0000000..66cf948
--- /dev/null
+++ b/lib/dns/rdata/generic/ninfo_56.h
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/* */
+#pragma once
+
+typedef struct dns_rdata_txt_string dns_rdata_ninfo_string_t;
+
+typedef struct dns_rdata_txt dns_rdata_ninfo_t;
+
+/*
+ * ISC_LANG_BEGINDECLS and ISC_LANG_ENDDECLS are already done
+ * via rdatastructpre.h and rdatastructsuf.h.
+ */
+
+isc_result_t
+dns_rdata_ninfo_first(dns_rdata_ninfo_t *);
+
+isc_result_t
+dns_rdata_ninfo_next(dns_rdata_ninfo_t *);
+
+isc_result_t
+dns_rdata_ninfo_current(dns_rdata_ninfo_t *, dns_rdata_ninfo_string_t *);
diff --git a/lib/dns/rdata/generic/ns_2.c b/lib/dns/rdata/generic/ns_2.c
new file mode 100644
index 0000000..9fa8dba
--- /dev/null
+++ b/lib/dns/rdata/generic/ns_2.c
@@ -0,0 +1,256 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#ifndef RDATA_GENERIC_NS_2_C
+#define RDATA_GENERIC_NS_2_C
+
+#define RRTYPE_NS_ATTRIBUTES (DNS_RDATATYPEATTR_ZONECUTAUTH)
+
+static isc_result_t
+fromtext_ns(ARGS_FROMTEXT) {
+ isc_token_t token;
+ dns_name_t name;
+ isc_buffer_t buffer;
+ bool ok;
+
+ REQUIRE(type == dns_rdatatype_ns);
+
+ UNUSED(type);
+ UNUSED(rdclass);
+ UNUSED(callbacks);
+
+ RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_string,
+ false));
+
+ dns_name_init(&name, NULL);
+ buffer_fromregion(&buffer, &token.value.as_region);
+ if (origin == NULL) {
+ origin = dns_rootname;
+ }
+ RETTOK(dns_name_fromtext(&name, &buffer, origin, options, target));
+ ok = true;
+ if ((options & DNS_RDATA_CHECKNAMES) != 0) {
+ ok = dns_name_ishostname(&name, false);
+ }
+ if (!ok && (options & DNS_RDATA_CHECKNAMESFAIL) != 0) {
+ RETTOK(DNS_R_BADNAME);
+ }
+ if (!ok && callbacks != NULL) {
+ warn_badname(&name, lexer, callbacks);
+ }
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+totext_ns(ARGS_TOTEXT) {
+ isc_region_t region;
+ dns_name_t name;
+ dns_name_t prefix;
+ bool sub;
+
+ REQUIRE(rdata->type == dns_rdatatype_ns);
+ REQUIRE(rdata->length != 0);
+
+ dns_name_init(&name, NULL);
+ dns_name_init(&prefix, NULL);
+
+ dns_rdata_toregion(rdata, &region);
+ dns_name_fromregion(&name, &region);
+
+ sub = name_prefix(&name, tctx->origin, &prefix);
+
+ return (dns_name_totext(&prefix, sub, target));
+}
+
+static isc_result_t
+fromwire_ns(ARGS_FROMWIRE) {
+ dns_name_t name;
+
+ REQUIRE(type == dns_rdatatype_ns);
+
+ UNUSED(type);
+ UNUSED(rdclass);
+
+ dns_decompress_setmethods(dctx, DNS_COMPRESS_GLOBAL14);
+
+ dns_name_init(&name, NULL);
+ return (dns_name_fromwire(&name, source, dctx, options, target));
+}
+
+static isc_result_t
+towire_ns(ARGS_TOWIRE) {
+ dns_name_t name;
+ dns_offsets_t offsets;
+ isc_region_t region;
+
+ REQUIRE(rdata->type == dns_rdatatype_ns);
+ REQUIRE(rdata->length != 0);
+
+ dns_compress_setmethods(cctx, DNS_COMPRESS_GLOBAL14);
+
+ dns_name_init(&name, offsets);
+ dns_rdata_toregion(rdata, &region);
+ dns_name_fromregion(&name, &region);
+
+ return (dns_name_towire(&name, cctx, target));
+}
+
+static int
+compare_ns(ARGS_COMPARE) {
+ dns_name_t name1;
+ dns_name_t name2;
+ isc_region_t region1;
+ isc_region_t region2;
+
+ REQUIRE(rdata1->type == rdata2->type);
+ REQUIRE(rdata1->rdclass == rdata2->rdclass);
+ REQUIRE(rdata1->type == dns_rdatatype_ns);
+ REQUIRE(rdata1->length != 0);
+ REQUIRE(rdata2->length != 0);
+
+ dns_name_init(&name1, NULL);
+ dns_name_init(&name2, NULL);
+
+ dns_rdata_toregion(rdata1, &region1);
+ dns_rdata_toregion(rdata2, &region2);
+
+ dns_name_fromregion(&name1, &region1);
+ dns_name_fromregion(&name2, &region2);
+
+ return (dns_name_rdatacompare(&name1, &name2));
+}
+
+static isc_result_t
+fromstruct_ns(ARGS_FROMSTRUCT) {
+ dns_rdata_ns_t *ns = source;
+ isc_region_t region;
+
+ REQUIRE(type == dns_rdatatype_ns);
+ REQUIRE(ns != NULL);
+ REQUIRE(ns->common.rdtype == type);
+ REQUIRE(ns->common.rdclass == rdclass);
+
+ UNUSED(type);
+ UNUSED(rdclass);
+
+ dns_name_toregion(&ns->name, &region);
+ return (isc_buffer_copyregion(target, &region));
+}
+
+static isc_result_t
+tostruct_ns(ARGS_TOSTRUCT) {
+ isc_region_t region;
+ dns_rdata_ns_t *ns = target;
+ dns_name_t name;
+
+ REQUIRE(rdata->type == dns_rdatatype_ns);
+ REQUIRE(ns != NULL);
+ REQUIRE(rdata->length != 0);
+
+ ns->common.rdclass = rdata->rdclass;
+ ns->common.rdtype = rdata->type;
+ ISC_LINK_INIT(&ns->common, link);
+
+ dns_name_init(&name, NULL);
+ dns_rdata_toregion(rdata, &region);
+ dns_name_fromregion(&name, &region);
+ dns_name_init(&ns->name, NULL);
+ name_duporclone(&name, mctx, &ns->name);
+ ns->mctx = mctx;
+ return (ISC_R_SUCCESS);
+}
+
+static void
+freestruct_ns(ARGS_FREESTRUCT) {
+ dns_rdata_ns_t *ns = source;
+
+ REQUIRE(ns != NULL);
+
+ if (ns->mctx == NULL) {
+ return;
+ }
+
+ dns_name_free(&ns->name, ns->mctx);
+ ns->mctx = NULL;
+}
+
+static isc_result_t
+additionaldata_ns(ARGS_ADDLDATA) {
+ dns_name_t name;
+ dns_offsets_t offsets;
+ isc_region_t region;
+
+ REQUIRE(rdata->type == dns_rdatatype_ns);
+
+ UNUSED(owner);
+
+ dns_name_init(&name, offsets);
+ dns_rdata_toregion(rdata, &region);
+ dns_name_fromregion(&name, &region);
+
+ return ((add)(arg, &name, dns_rdatatype_a, NULL));
+}
+
+static isc_result_t
+digest_ns(ARGS_DIGEST) {
+ isc_region_t r;
+ dns_name_t name;
+
+ REQUIRE(rdata->type == dns_rdatatype_ns);
+
+ dns_rdata_toregion(rdata, &r);
+ dns_name_init(&name, NULL);
+ dns_name_fromregion(&name, &r);
+
+ return (dns_name_digest(&name, digest, arg));
+}
+
+static bool
+checkowner_ns(ARGS_CHECKOWNER) {
+ REQUIRE(type == dns_rdatatype_ns);
+
+ UNUSED(name);
+ UNUSED(type);
+ UNUSED(rdclass);
+ UNUSED(wildcard);
+
+ return (true);
+}
+
+static bool
+checknames_ns(ARGS_CHECKNAMES) {
+ isc_region_t region;
+ dns_name_t name;
+
+ REQUIRE(rdata->type == dns_rdatatype_ns);
+
+ UNUSED(owner);
+
+ dns_rdata_toregion(rdata, &region);
+ dns_name_init(&name, NULL);
+ dns_name_fromregion(&name, &region);
+ if (!dns_name_ishostname(&name, false)) {
+ if (bad != NULL) {
+ dns_name_clone(&name, bad);
+ }
+ return (false);
+ }
+ return (true);
+}
+
+static int
+casecompare_ns(ARGS_COMPARE) {
+ return (compare_ns(rdata1, rdata2));
+}
+
+#endif /* RDATA_GENERIC_NS_2_C */
diff --git a/lib/dns/rdata/generic/ns_2.h b/lib/dns/rdata/generic/ns_2.h
new file mode 100644
index 0000000..a9abd5d
--- /dev/null
+++ b/lib/dns/rdata/generic/ns_2.h
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/* */
+#pragma once
+
+typedef struct dns_rdata_ns {
+ dns_rdatacommon_t common;
+ isc_mem_t *mctx;
+ dns_name_t name;
+} dns_rdata_ns_t;
diff --git a/lib/dns/rdata/generic/nsec3_50.c b/lib/dns/rdata/generic/nsec3_50.c
new file mode 100644
index 0000000..556616b
--- /dev/null
+++ b/lib/dns/rdata/generic/nsec3_50.c
@@ -0,0 +1,425 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/*
+ * Copyright (C) 2004 Nominet, Ltd.
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND NOMINET DISCLAIMS ALL WARRANTIES WITH
+ * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+ * AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+ * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+ * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+ * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+ * PERFORMANCE OF THIS SOFTWARE.
+ */
+
+/* RFC 5155 */
+
+#ifndef RDATA_GENERIC_NSEC3_50_C
+#define RDATA_GENERIC_NSEC3_50_C
+
+#include <isc/base32.h>
+#include <isc/iterated_hash.h>
+
+#define RRTYPE_NSEC3_ATTRIBUTES DNS_RDATATYPEATTR_DNSSEC
+
+static isc_result_t
+fromtext_nsec3(ARGS_FROMTEXT) {
+ isc_token_t token;
+ unsigned int flags;
+ unsigned char hashalg;
+ isc_buffer_t b;
+ unsigned char buf[256];
+
+ REQUIRE(type == dns_rdatatype_nsec3);
+
+ UNUSED(type);
+ UNUSED(rdclass);
+ UNUSED(callbacks);
+ UNUSED(origin);
+ UNUSED(options);
+
+ /* Hash. */
+ RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_string,
+ false));
+ RETTOK(dns_hashalg_fromtext(&hashalg, &token.value.as_textregion));
+ RETERR(uint8_tobuffer(hashalg, target));
+
+ /* Flags. */
+ RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_number,
+ false));
+ flags = token.value.as_ulong;
+ if (flags > 255U) {
+ RETTOK(ISC_R_RANGE);
+ }
+ RETERR(uint8_tobuffer(flags, target));
+
+ /* Iterations. */
+ RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_number,
+ false));
+ if (token.value.as_ulong > 0xffffU) {
+ RETTOK(ISC_R_RANGE);
+ }
+ RETERR(uint16_tobuffer(token.value.as_ulong, target));
+
+ /* salt */
+ RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_string,
+ false));
+ if (token.value.as_textregion.length > (255 * 2)) {
+ RETTOK(DNS_R_TEXTTOOLONG);
+ }
+ if (strcmp(DNS_AS_STR(token), "-") == 0) {
+ RETERR(uint8_tobuffer(0, target));
+ } else {
+ RETERR(uint8_tobuffer(strlen(DNS_AS_STR(token)) / 2, target));
+ RETERR(isc_hex_decodestring(DNS_AS_STR(token), target));
+ }
+
+ /*
+ * Next hash a single base32hex word.
+ */
+ RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_string,
+ false));
+ isc_buffer_init(&b, buf, sizeof(buf));
+ RETTOK(isc_base32hexnp_decodestring(DNS_AS_STR(token), &b));
+ if (isc_buffer_usedlength(&b) > 0xffU) {
+ RETTOK(ISC_R_RANGE);
+ }
+ RETERR(uint8_tobuffer(isc_buffer_usedlength(&b), target));
+ RETERR(mem_tobuffer(target, &buf, isc_buffer_usedlength(&b)));
+
+ return (typemap_fromtext(lexer, target, true));
+}
+
+static isc_result_t
+totext_nsec3(ARGS_TOTEXT) {
+ isc_region_t sr;
+ unsigned int i, j;
+ unsigned char hash;
+ unsigned char flags;
+ char buf[sizeof("TYPE65535")];
+ uint32_t iterations;
+
+ REQUIRE(rdata->type == dns_rdatatype_nsec3);
+ REQUIRE(rdata->length != 0);
+
+ dns_rdata_toregion(rdata, &sr);
+
+ /* Hash */
+ hash = uint8_fromregion(&sr);
+ isc_region_consume(&sr, 1);
+ snprintf(buf, sizeof(buf), "%u ", hash);
+ RETERR(str_totext(buf, target));
+
+ /* Flags */
+ flags = uint8_fromregion(&sr);
+ isc_region_consume(&sr, 1);
+ snprintf(buf, sizeof(buf), "%u ", flags);
+ RETERR(str_totext(buf, target));
+
+ /* Iterations */
+ iterations = uint16_fromregion(&sr);
+ isc_region_consume(&sr, 2);
+ snprintf(buf, sizeof(buf), "%u ", iterations);
+ RETERR(str_totext(buf, target));
+
+ /* Salt */
+ j = uint8_fromregion(&sr);
+ isc_region_consume(&sr, 1);
+ INSIST(j <= sr.length);
+
+ if (j != 0) {
+ i = sr.length;
+ sr.length = j;
+ RETERR(isc_hex_totext(&sr, 1, "", target));
+ sr.length = i - j;
+ } else {
+ RETERR(str_totext("-", target));
+ }
+
+ if ((tctx->flags & DNS_STYLEFLAG_MULTILINE) != 0) {
+ RETERR(str_totext(" (", target));
+ }
+ RETERR(str_totext(tctx->linebreak, target));
+
+ /* Next hash */
+ j = uint8_fromregion(&sr);
+ isc_region_consume(&sr, 1);
+ INSIST(j <= sr.length);
+
+ i = sr.length;
+ sr.length = j;
+ RETERR(isc_base32hexnp_totext(&sr, 1, "", target));
+ sr.length = i - j;
+
+ /*
+ * Don't leave a trailing space when there's no typemap present.
+ */
+ if (((tctx->flags & DNS_STYLEFLAG_MULTILINE) == 0) && (sr.length > 0)) {
+ RETERR(str_totext(" ", target));
+ }
+ RETERR(typemap_totext(&sr, tctx, target));
+
+ if ((tctx->flags & DNS_STYLEFLAG_MULTILINE) != 0) {
+ RETERR(str_totext(" )", target));
+ }
+
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+fromwire_nsec3(ARGS_FROMWIRE) {
+ isc_region_t sr, rr;
+ unsigned int saltlen, hashlen;
+
+ REQUIRE(type == dns_rdatatype_nsec3);
+
+ UNUSED(type);
+ UNUSED(rdclass);
+ UNUSED(options);
+ UNUSED(dctx);
+
+ isc_buffer_activeregion(source, &sr);
+ rr = sr;
+
+ /* hash(1), flags(1), iteration(2), saltlen(1) */
+ if (sr.length < 5U) {
+ RETERR(DNS_R_FORMERR);
+ }
+ saltlen = sr.base[4];
+ isc_region_consume(&sr, 5);
+
+ if (sr.length < saltlen) {
+ RETERR(DNS_R_FORMERR);
+ }
+ isc_region_consume(&sr, saltlen);
+
+ if (sr.length < 1U) {
+ RETERR(DNS_R_FORMERR);
+ }
+ hashlen = sr.base[0];
+ isc_region_consume(&sr, 1);
+
+ if (hashlen < 1 || sr.length < hashlen) {
+ RETERR(DNS_R_FORMERR);
+ }
+ isc_region_consume(&sr, hashlen);
+
+ RETERR(typemap_test(&sr, true));
+
+ RETERR(mem_tobuffer(target, rr.base, rr.length));
+ isc_buffer_forward(source, rr.length);
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+towire_nsec3(ARGS_TOWIRE) {
+ isc_region_t sr;
+
+ REQUIRE(rdata->type == dns_rdatatype_nsec3);
+ REQUIRE(rdata->length != 0);
+
+ UNUSED(cctx);
+
+ dns_rdata_toregion(rdata, &sr);
+ return (mem_tobuffer(target, sr.base, sr.length));
+}
+
+static int
+compare_nsec3(ARGS_COMPARE) {
+ isc_region_t r1;
+ isc_region_t r2;
+
+ REQUIRE(rdata1->type == rdata2->type);
+ REQUIRE(rdata1->rdclass == rdata2->rdclass);
+ REQUIRE(rdata1->type == dns_rdatatype_nsec3);
+ REQUIRE(rdata1->length != 0);
+ REQUIRE(rdata2->length != 0);
+
+ dns_rdata_toregion(rdata1, &r1);
+ dns_rdata_toregion(rdata2, &r2);
+ return (isc_region_compare(&r1, &r2));
+}
+
+static isc_result_t
+fromstruct_nsec3(ARGS_FROMSTRUCT) {
+ dns_rdata_nsec3_t *nsec3 = source;
+ isc_region_t region;
+
+ REQUIRE(type == dns_rdatatype_nsec3);
+ REQUIRE(nsec3 != NULL);
+ REQUIRE(nsec3->common.rdtype == type);
+ REQUIRE(nsec3->common.rdclass == rdclass);
+ REQUIRE(nsec3->typebits != NULL || nsec3->len == 0);
+ REQUIRE(nsec3->hash == dns_hash_sha1);
+
+ UNUSED(type);
+ UNUSED(rdclass);
+
+ RETERR(uint8_tobuffer(nsec3->hash, target));
+ RETERR(uint8_tobuffer(nsec3->flags, target));
+ RETERR(uint16_tobuffer(nsec3->iterations, target));
+ RETERR(uint8_tobuffer(nsec3->salt_length, target));
+ RETERR(mem_tobuffer(target, nsec3->salt, nsec3->salt_length));
+ RETERR(uint8_tobuffer(nsec3->next_length, target));
+ RETERR(mem_tobuffer(target, nsec3->next, nsec3->next_length));
+
+ region.base = nsec3->typebits;
+ region.length = nsec3->len;
+ RETERR(typemap_test(&region, true));
+ return (mem_tobuffer(target, nsec3->typebits, nsec3->len));
+}
+
+static isc_result_t
+tostruct_nsec3(ARGS_TOSTRUCT) {
+ isc_region_t region;
+ dns_rdata_nsec3_t *nsec3 = target;
+
+ REQUIRE(rdata->type == dns_rdatatype_nsec3);
+ REQUIRE(nsec3 != NULL);
+ REQUIRE(rdata->length != 0);
+
+ nsec3->common.rdclass = rdata->rdclass;
+ nsec3->common.rdtype = rdata->type;
+ ISC_LINK_INIT(&nsec3->common, link);
+
+ region.base = rdata->data;
+ region.length = rdata->length;
+ nsec3->hash = uint8_consume_fromregion(&region);
+ nsec3->flags = uint8_consume_fromregion(&region);
+ nsec3->iterations = uint16_consume_fromregion(&region);
+
+ nsec3->salt_length = uint8_consume_fromregion(&region);
+ INSIST(nsec3->salt_length <= region.length);
+ nsec3->salt = mem_maybedup(mctx, region.base, nsec3->salt_length);
+ if (nsec3->salt == NULL) {
+ return (ISC_R_NOMEMORY);
+ }
+ isc_region_consume(&region, nsec3->salt_length);
+
+ nsec3->next_length = uint8_consume_fromregion(&region);
+ INSIST(nsec3->next_length <= region.length);
+ nsec3->next = mem_maybedup(mctx, region.base, nsec3->next_length);
+ if (nsec3->next == NULL) {
+ goto cleanup;
+ }
+ isc_region_consume(&region, nsec3->next_length);
+
+ nsec3->len = region.length;
+ nsec3->typebits = mem_maybedup(mctx, region.base, region.length);
+ if (nsec3->typebits == NULL) {
+ goto cleanup;
+ }
+
+ nsec3->mctx = mctx;
+ return (ISC_R_SUCCESS);
+
+cleanup:
+ if (nsec3->next != NULL) {
+ isc_mem_free(mctx, nsec3->next);
+ }
+ isc_mem_free(mctx, nsec3->salt);
+ return (ISC_R_NOMEMORY);
+}
+
+static void
+freestruct_nsec3(ARGS_FREESTRUCT) {
+ dns_rdata_nsec3_t *nsec3 = source;
+
+ REQUIRE(nsec3 != NULL);
+ REQUIRE(nsec3->common.rdtype == dns_rdatatype_nsec3);
+
+ if (nsec3->mctx == NULL) {
+ return;
+ }
+
+ if (nsec3->salt != NULL) {
+ isc_mem_free(nsec3->mctx, nsec3->salt);
+ }
+ if (nsec3->next != NULL) {
+ isc_mem_free(nsec3->mctx, nsec3->next);
+ }
+ if (nsec3->typebits != NULL) {
+ isc_mem_free(nsec3->mctx, nsec3->typebits);
+ }
+ nsec3->mctx = NULL;
+}
+
+static isc_result_t
+additionaldata_nsec3(ARGS_ADDLDATA) {
+ REQUIRE(rdata->type == dns_rdatatype_nsec3);
+
+ UNUSED(rdata);
+ UNUSED(owner);
+ UNUSED(add);
+ UNUSED(arg);
+
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+digest_nsec3(ARGS_DIGEST) {
+ isc_region_t r;
+
+ REQUIRE(rdata->type == dns_rdatatype_nsec3);
+
+ dns_rdata_toregion(rdata, &r);
+ return ((digest)(arg, &r));
+}
+
+static bool
+checkowner_nsec3(ARGS_CHECKOWNER) {
+ unsigned char owner[NSEC3_MAX_HASH_LENGTH];
+ isc_buffer_t buffer;
+ dns_label_t label;
+
+ REQUIRE(type == dns_rdatatype_nsec3);
+
+ UNUSED(type);
+ UNUSED(rdclass);
+ UNUSED(wildcard);
+
+ /*
+ * First label is a base32hex string without padding.
+ */
+ dns_name_getlabel(name, 0, &label);
+ isc_region_consume(&label, 1);
+ isc_buffer_init(&buffer, owner, sizeof(owner));
+ if (isc_base32hexnp_decoderegion(&label, &buffer) == ISC_R_SUCCESS) {
+ return (true);
+ }
+
+ return (false);
+}
+
+static bool
+checknames_nsec3(ARGS_CHECKNAMES) {
+ REQUIRE(rdata->type == dns_rdatatype_nsec3);
+
+ UNUSED(rdata);
+ UNUSED(owner);
+ UNUSED(bad);
+
+ return (true);
+}
+
+static int
+casecompare_nsec3(ARGS_COMPARE) {
+ return (compare_nsec3(rdata1, rdata2));
+}
+
+#endif /* RDATA_GENERIC_NSEC3_50_C */
diff --git a/lib/dns/rdata/generic/nsec3_50.h b/lib/dns/rdata/generic/nsec3_50.h
new file mode 100644
index 0000000..3152c85
--- /dev/null
+++ b/lib/dns/rdata/generic/nsec3_50.h
@@ -0,0 +1,109 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#pragma once
+
+/*!
+ * \brief Per RFC 5155 */
+
+#include <isc/iterated_hash.h>
+
+typedef struct dns_rdata_nsec3 {
+ dns_rdatacommon_t common;
+ isc_mem_t *mctx;
+ dns_hash_t hash;
+ unsigned char flags;
+ dns_iterations_t iterations;
+ unsigned char salt_length;
+ unsigned char next_length;
+ uint16_t len;
+ unsigned char *salt;
+ unsigned char *next;
+ unsigned char *typebits;
+} dns_rdata_nsec3_t;
+
+/*
+ * The corresponding NSEC3 interval is OPTOUT indicating possible
+ * insecure delegations.
+ */
+#define DNS_NSEC3FLAG_OPTOUT 0x01U
+
+/*%
+ * The following flags are used in the private-type record (implemented in
+ * lib/dns/private.c) which is used to store NSEC3PARAM data during the
+ * time when it is not legal to have an actual NSEC3PARAM record in the
+ * zone. They are defined here because the private-type record uses the
+ * same flags field for the OPTOUT flag above and for the private flags
+ * below. XXX: This should be considered for refactoring.
+ */
+
+/*%
+ * Non-standard, private type only.
+ *
+ * Create a corresponding NSEC3 chain.
+ * Once the NSEC3 chain is complete this flag will be removed to signal
+ * that there is a complete chain.
+ *
+ * This flag is automatically set when a NSEC3PARAM record is added to
+ * the zone via UPDATE.
+ *
+ * NSEC3PARAM records containing this flag should never be published,
+ * but if they are, they should be ignored by RFC 5155 compliant
+ * nameservers.
+ */
+#define DNS_NSEC3FLAG_CREATE 0x80U
+
+/*%
+ * Non-standard, private type only.
+ *
+ * The corresponding NSEC3 set is to be removed once the NSEC chain
+ * has been generated.
+ *
+ * This flag is automatically set when the last active NSEC3PARAM record
+ * is removed from the zone via UPDATE.
+ *
+ * NSEC3PARAM records containing this flag should never be published,
+ * but if they are, they should be ignored by RFC 5155 compliant
+ * nameservers.
+ */
+#define DNS_NSEC3FLAG_REMOVE 0x40U
+
+/*%
+ * Non-standard, private type only.
+ *
+ * When set with the CREATE flag, a corresponding NSEC3 chain will be
+ * created when the zone becomes capable of supporting one (i.e., when it
+ * has a DNSKEY RRset containing at least one NSEC3-capable algorithm).
+ * Without this flag, NSEC3 chain creation would be attempted immediately,
+ * fail, and the private type record would be removed. With it, the NSEC3
+ * parameters are stored until they can be used. When the zone has the
+ * necessary prerequisites for NSEC3, then the INITIAL flag can be cleared,
+ * and the record will be cleaned up normally.
+ *
+ * NSEC3PARAM records containing this flag should never be published, but
+ * if they are, they should be ignored by RFC 5155 compliant nameservers.
+ */
+#define DNS_NSEC3FLAG_INITIAL 0x20U
+
+/*%
+ * Non-standard, private type only.
+ *
+ * Prevent the creation of a NSEC chain before the last NSEC3 chain
+ * is removed. This will normally only be set when the zone is
+ * transitioning from secure with NSEC3 chains to insecure.
+ *
+ * NSEC3PARAM records containing this flag should never be published,
+ * but if they are, they should be ignored by RFC 5155 compliant
+ * nameservers.
+ */
+#define DNS_NSEC3FLAG_NONSEC 0x10U
diff --git a/lib/dns/rdata/generic/nsec3param_51.c b/lib/dns/rdata/generic/nsec3param_51.c
new file mode 100644
index 0000000..1690598
--- /dev/null
+++ b/lib/dns/rdata/generic/nsec3param_51.c
@@ -0,0 +1,322 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/*
+ * Copyright (C) 2004 Nominet, Ltd.
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND NOMINET DISCLAIMS ALL WARRANTIES WITH
+ * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+ * AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+ * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+ * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+ * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+ * PERFORMANCE OF THIS SOFTWARE.
+ */
+
+/* RFC 5155 */
+
+#ifndef RDATA_GENERIC_NSEC3PARAM_51_C
+#define RDATA_GENERIC_NSEC3PARAM_51_C
+
+#include <isc/base32.h>
+#include <isc/iterated_hash.h>
+
+#define RRTYPE_NSEC3PARAM_ATTRIBUTES (DNS_RDATATYPEATTR_DNSSEC)
+
+static isc_result_t
+fromtext_nsec3param(ARGS_FROMTEXT) {
+ isc_token_t token;
+ unsigned int flags = 0;
+ unsigned char hashalg;
+
+ REQUIRE(type == dns_rdatatype_nsec3param);
+
+ UNUSED(type);
+ UNUSED(rdclass);
+ UNUSED(callbacks);
+ UNUSED(origin);
+ UNUSED(options);
+
+ /* Hash. */
+ RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_string,
+ false));
+ RETTOK(dns_hashalg_fromtext(&hashalg, &token.value.as_textregion));
+ RETERR(uint8_tobuffer(hashalg, target));
+
+ /* Flags. */
+ RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_number,
+ false));
+ flags = token.value.as_ulong;
+ if (flags > 255U) {
+ RETTOK(ISC_R_RANGE);
+ }
+ RETERR(uint8_tobuffer(flags, target));
+
+ /* Iterations. */
+ RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_number,
+ false));
+ if (token.value.as_ulong > 0xffffU) {
+ RETTOK(ISC_R_RANGE);
+ }
+ RETERR(uint16_tobuffer(token.value.as_ulong, target));
+
+ /* Salt. */
+ RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_string,
+ false));
+ if (token.value.as_textregion.length > (255 * 2)) {
+ RETTOK(DNS_R_TEXTTOOLONG);
+ }
+ if (strcmp(DNS_AS_STR(token), "-") == 0) {
+ RETERR(uint8_tobuffer(0, target));
+ } else {
+ RETERR(uint8_tobuffer(strlen(DNS_AS_STR(token)) / 2, target));
+ RETERR(isc_hex_decodestring(DNS_AS_STR(token), target));
+ }
+
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+totext_nsec3param(ARGS_TOTEXT) {
+ isc_region_t sr;
+ unsigned int i, j;
+ unsigned char hash;
+ unsigned char flags;
+ char buf[sizeof("65535 ")];
+ uint32_t iterations;
+
+ REQUIRE(rdata->type == dns_rdatatype_nsec3param);
+ REQUIRE(rdata->length != 0);
+
+ UNUSED(tctx);
+
+ dns_rdata_toregion(rdata, &sr);
+
+ hash = uint8_fromregion(&sr);
+ isc_region_consume(&sr, 1);
+
+ flags = uint8_fromregion(&sr);
+ isc_region_consume(&sr, 1);
+
+ iterations = uint16_fromregion(&sr);
+ isc_region_consume(&sr, 2);
+
+ snprintf(buf, sizeof(buf), "%u ", hash);
+ RETERR(str_totext(buf, target));
+
+ snprintf(buf, sizeof(buf), "%u ", flags);
+ RETERR(str_totext(buf, target));
+
+ snprintf(buf, sizeof(buf), "%u ", iterations);
+ RETERR(str_totext(buf, target));
+
+ j = uint8_fromregion(&sr);
+ isc_region_consume(&sr, 1);
+ INSIST(j <= sr.length);
+
+ if (j != 0) {
+ i = sr.length;
+ sr.length = j;
+ RETERR(isc_hex_totext(&sr, 1, "", target));
+ sr.length = i - j;
+ } else {
+ RETERR(str_totext("-", target));
+ }
+
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+fromwire_nsec3param(ARGS_FROMWIRE) {
+ isc_region_t sr, rr;
+ unsigned int saltlen;
+
+ REQUIRE(type == dns_rdatatype_nsec3param);
+
+ UNUSED(type);
+ UNUSED(rdclass);
+ UNUSED(options);
+ UNUSED(dctx);
+
+ isc_buffer_activeregion(source, &sr);
+ rr = sr;
+
+ /* hash(1), flags(1), iterations(2), saltlen(1) */
+ if (sr.length < 5U) {
+ RETERR(DNS_R_FORMERR);
+ }
+ saltlen = sr.base[4];
+ isc_region_consume(&sr, 5);
+
+ if (sr.length != saltlen) {
+ RETERR(DNS_R_FORMERR);
+ }
+ isc_region_consume(&sr, saltlen);
+ RETERR(mem_tobuffer(target, rr.base, rr.length));
+ isc_buffer_forward(source, rr.length);
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+towire_nsec3param(ARGS_TOWIRE) {
+ isc_region_t sr;
+
+ REQUIRE(rdata->type == dns_rdatatype_nsec3param);
+ REQUIRE(rdata->length != 0);
+
+ UNUSED(cctx);
+
+ dns_rdata_toregion(rdata, &sr);
+ return (mem_tobuffer(target, sr.base, sr.length));
+}
+
+static int
+compare_nsec3param(ARGS_COMPARE) {
+ isc_region_t r1;
+ isc_region_t r2;
+
+ REQUIRE(rdata1->type == rdata2->type);
+ REQUIRE(rdata1->rdclass == rdata2->rdclass);
+ REQUIRE(rdata1->type == dns_rdatatype_nsec3param);
+ REQUIRE(rdata1->length != 0);
+ REQUIRE(rdata2->length != 0);
+
+ dns_rdata_toregion(rdata1, &r1);
+ dns_rdata_toregion(rdata2, &r2);
+ return (isc_region_compare(&r1, &r2));
+}
+
+static isc_result_t
+fromstruct_nsec3param(ARGS_FROMSTRUCT) {
+ dns_rdata_nsec3param_t *nsec3param = source;
+
+ REQUIRE(type == dns_rdatatype_nsec3param);
+ REQUIRE(nsec3param != NULL);
+ REQUIRE(nsec3param->common.rdtype == type);
+ REQUIRE(nsec3param->common.rdclass == rdclass);
+
+ UNUSED(type);
+ UNUSED(rdclass);
+
+ RETERR(uint8_tobuffer(nsec3param->hash, target));
+ RETERR(uint8_tobuffer(nsec3param->flags, target));
+ RETERR(uint16_tobuffer(nsec3param->iterations, target));
+ RETERR(uint8_tobuffer(nsec3param->salt_length, target));
+ RETERR(mem_tobuffer(target, nsec3param->salt, nsec3param->salt_length));
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+tostruct_nsec3param(ARGS_TOSTRUCT) {
+ isc_region_t region;
+ dns_rdata_nsec3param_t *nsec3param = target;
+
+ REQUIRE(rdata->type == dns_rdatatype_nsec3param);
+ REQUIRE(nsec3param != NULL);
+ REQUIRE(rdata->length != 0);
+
+ nsec3param->common.rdclass = rdata->rdclass;
+ nsec3param->common.rdtype = rdata->type;
+ ISC_LINK_INIT(&nsec3param->common, link);
+
+ region.base = rdata->data;
+ region.length = rdata->length;
+ nsec3param->hash = uint8_consume_fromregion(&region);
+ nsec3param->flags = uint8_consume_fromregion(&region);
+ nsec3param->iterations = uint16_consume_fromregion(&region);
+
+ nsec3param->salt_length = uint8_consume_fromregion(&region);
+ INSIST(nsec3param->salt_length == region.length);
+ nsec3param->salt = mem_maybedup(mctx, region.base,
+ nsec3param->salt_length);
+ if (nsec3param->salt == NULL) {
+ return (ISC_R_NOMEMORY);
+ }
+ isc_region_consume(&region, nsec3param->salt_length);
+
+ nsec3param->mctx = mctx;
+ return (ISC_R_SUCCESS);
+}
+
+static void
+freestruct_nsec3param(ARGS_FREESTRUCT) {
+ dns_rdata_nsec3param_t *nsec3param = source;
+
+ REQUIRE(nsec3param != NULL);
+ REQUIRE(nsec3param->common.rdtype == dns_rdatatype_nsec3param);
+
+ if (nsec3param->mctx == NULL) {
+ return;
+ }
+
+ if (nsec3param->salt != NULL) {
+ isc_mem_free(nsec3param->mctx, nsec3param->salt);
+ }
+ nsec3param->mctx = NULL;
+}
+
+static isc_result_t
+additionaldata_nsec3param(ARGS_ADDLDATA) {
+ REQUIRE(rdata->type == dns_rdatatype_nsec3param);
+
+ UNUSED(rdata);
+ UNUSED(owner);
+ UNUSED(add);
+ UNUSED(arg);
+
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+digest_nsec3param(ARGS_DIGEST) {
+ isc_region_t r;
+
+ REQUIRE(rdata->type == dns_rdatatype_nsec3param);
+
+ dns_rdata_toregion(rdata, &r);
+ return ((digest)(arg, &r));
+}
+
+static bool
+checkowner_nsec3param(ARGS_CHECKOWNER) {
+ REQUIRE(type == dns_rdatatype_nsec3param);
+
+ UNUSED(name);
+ UNUSED(type);
+ UNUSED(rdclass);
+ UNUSED(wildcard);
+
+ return (true);
+}
+
+static bool
+checknames_nsec3param(ARGS_CHECKNAMES) {
+ REQUIRE(rdata->type == dns_rdatatype_nsec3param);
+
+ UNUSED(rdata);
+ UNUSED(owner);
+ UNUSED(bad);
+
+ return (true);
+}
+
+static int
+casecompare_nsec3param(ARGS_COMPARE) {
+ return (compare_nsec3param(rdata1, rdata2));
+}
+
+#endif /* RDATA_GENERIC_NSEC3PARAM_51_C */
diff --git a/lib/dns/rdata/generic/nsec3param_51.h b/lib/dns/rdata/generic/nsec3param_51.h
new file mode 100644
index 0000000..9288a96
--- /dev/null
+++ b/lib/dns/rdata/generic/nsec3param_51.h
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#pragma once
+
+/*!
+ * \brief Per RFC 5155 */
+
+#include <isc/iterated_hash.h>
+
+typedef struct dns_rdata_nsec3param {
+ dns_rdatacommon_t common;
+ isc_mem_t *mctx;
+ dns_hash_t hash;
+ unsigned char flags; /* DNS_NSEC3FLAG_* */
+ dns_iterations_t iterations;
+ unsigned char salt_length;
+ unsigned char *salt;
+} dns_rdata_nsec3param_t;
diff --git a/lib/dns/rdata/generic/nsec_47.c b/lib/dns/rdata/generic/nsec_47.c
new file mode 100644
index 0000000..77700be
--- /dev/null
+++ b/lib/dns/rdata/generic/nsec_47.c
@@ -0,0 +1,291 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/* RFC 3845 */
+
+#ifndef RDATA_GENERIC_NSEC_47_C
+#define RDATA_GENERIC_NSEC_47_C
+
+/*
+ * The attributes do not include DNS_RDATATYPEATTR_SINGLETON
+ * because we must be able to handle a parent/child NSEC pair.
+ */
+#define RRTYPE_NSEC_ATTRIBUTES \
+ (DNS_RDATATYPEATTR_DNSSEC | DNS_RDATATYPEATTR_ZONECUTAUTH | \
+ DNS_RDATATYPEATTR_ATCNAME)
+
+static isc_result_t
+fromtext_nsec(ARGS_FROMTEXT) {
+ isc_token_t token;
+ dns_name_t name;
+ isc_buffer_t buffer;
+
+ REQUIRE(type == dns_rdatatype_nsec);
+
+ UNUSED(type);
+ UNUSED(rdclass);
+ UNUSED(callbacks);
+
+ /*
+ * Next domain.
+ */
+ RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_string,
+ false));
+ dns_name_init(&name, NULL);
+ buffer_fromregion(&buffer, &token.value.as_region);
+ if (origin == NULL) {
+ origin = dns_rootname;
+ }
+ RETTOK(dns_name_fromtext(&name, &buffer, origin, options, target));
+
+ return (typemap_fromtext(lexer, target, false));
+}
+
+static isc_result_t
+totext_nsec(ARGS_TOTEXT) {
+ isc_region_t sr;
+ dns_name_t name;
+
+ REQUIRE(rdata->type == dns_rdatatype_nsec);
+ REQUIRE(rdata->length != 0);
+
+ UNUSED(tctx);
+
+ dns_name_init(&name, NULL);
+ dns_rdata_toregion(rdata, &sr);
+ dns_name_fromregion(&name, &sr);
+ isc_region_consume(&sr, name_length(&name));
+ RETERR(dns_name_totext(&name, false, target));
+ /*
+ * Don't leave a trailing space when there's no typemap present.
+ */
+ if (sr.length > 0) {
+ RETERR(str_totext(" ", target));
+ }
+ return (typemap_totext(&sr, NULL, target));
+}
+
+static isc_result_t
+fromwire_nsec(ARGS_FROMWIRE) {
+ isc_region_t sr;
+ dns_name_t name;
+
+ REQUIRE(type == dns_rdatatype_nsec);
+
+ UNUSED(type);
+ UNUSED(rdclass);
+
+ dns_decompress_setmethods(dctx, DNS_COMPRESS_NONE);
+
+ dns_name_init(&name, NULL);
+ RETERR(dns_name_fromwire(&name, source, dctx, options, target));
+
+ isc_buffer_activeregion(source, &sr);
+ RETERR(typemap_test(&sr, false));
+ RETERR(mem_tobuffer(target, sr.base, sr.length));
+ isc_buffer_forward(source, sr.length);
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+towire_nsec(ARGS_TOWIRE) {
+ isc_region_t sr;
+ dns_name_t name;
+ dns_offsets_t offsets;
+
+ REQUIRE(rdata->type == dns_rdatatype_nsec);
+ REQUIRE(rdata->length != 0);
+
+ dns_compress_setmethods(cctx, DNS_COMPRESS_NONE);
+ dns_name_init(&name, offsets);
+ dns_rdata_toregion(rdata, &sr);
+ dns_name_fromregion(&name, &sr);
+ isc_region_consume(&sr, name_length(&name));
+ RETERR(dns_name_towire(&name, cctx, target));
+
+ return (mem_tobuffer(target, sr.base, sr.length));
+}
+
+static int
+compare_nsec(ARGS_COMPARE) {
+ isc_region_t r1;
+ isc_region_t r2;
+
+ REQUIRE(rdata1->type == rdata2->type);
+ REQUIRE(rdata1->rdclass == rdata2->rdclass);
+ REQUIRE(rdata1->type == dns_rdatatype_nsec);
+ REQUIRE(rdata1->length != 0);
+ REQUIRE(rdata2->length != 0);
+
+ dns_rdata_toregion(rdata1, &r1);
+ dns_rdata_toregion(rdata2, &r2);
+ return (isc_region_compare(&r1, &r2));
+}
+
+static isc_result_t
+fromstruct_nsec(ARGS_FROMSTRUCT) {
+ dns_rdata_nsec_t *nsec = source;
+ isc_region_t region;
+
+ REQUIRE(type == dns_rdatatype_nsec);
+ REQUIRE(nsec != NULL);
+ REQUIRE(nsec->common.rdtype == type);
+ REQUIRE(nsec->common.rdclass == rdclass);
+ REQUIRE(nsec->typebits != NULL || nsec->len == 0);
+
+ UNUSED(type);
+ UNUSED(rdclass);
+
+ dns_name_toregion(&nsec->next, &region);
+ RETERR(isc_buffer_copyregion(target, &region));
+
+ region.base = nsec->typebits;
+ region.length = nsec->len;
+ RETERR(typemap_test(&region, false));
+ return (mem_tobuffer(target, nsec->typebits, nsec->len));
+}
+
+static isc_result_t
+tostruct_nsec(ARGS_TOSTRUCT) {
+ isc_region_t region;
+ dns_rdata_nsec_t *nsec = target;
+ dns_name_t name;
+
+ REQUIRE(rdata->type == dns_rdatatype_nsec);
+ REQUIRE(nsec != NULL);
+ REQUIRE(rdata->length != 0);
+
+ nsec->common.rdclass = rdata->rdclass;
+ nsec->common.rdtype = rdata->type;
+ ISC_LINK_INIT(&nsec->common, link);
+
+ dns_name_init(&name, NULL);
+ dns_rdata_toregion(rdata, &region);
+ dns_name_fromregion(&name, &region);
+ isc_region_consume(&region, name_length(&name));
+ dns_name_init(&nsec->next, NULL);
+ name_duporclone(&name, mctx, &nsec->next);
+
+ nsec->len = region.length;
+ nsec->typebits = mem_maybedup(mctx, region.base, region.length);
+ if (nsec->typebits == NULL) {
+ goto cleanup;
+ }
+
+ nsec->mctx = mctx;
+ return (ISC_R_SUCCESS);
+
+cleanup:
+ if (mctx != NULL) {
+ dns_name_free(&nsec->next, mctx);
+ }
+ return (ISC_R_NOMEMORY);
+}
+
+static void
+freestruct_nsec(ARGS_FREESTRUCT) {
+ dns_rdata_nsec_t *nsec = source;
+
+ REQUIRE(nsec != NULL);
+ REQUIRE(nsec->common.rdtype == dns_rdatatype_nsec);
+
+ if (nsec->mctx == NULL) {
+ return;
+ }
+
+ dns_name_free(&nsec->next, nsec->mctx);
+ if (nsec->typebits != NULL) {
+ isc_mem_free(nsec->mctx, nsec->typebits);
+ }
+ nsec->mctx = NULL;
+}
+
+static isc_result_t
+additionaldata_nsec(ARGS_ADDLDATA) {
+ REQUIRE(rdata->type == dns_rdatatype_nsec);
+
+ UNUSED(rdata);
+ UNUSED(owner);
+ UNUSED(add);
+ UNUSED(arg);
+
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+digest_nsec(ARGS_DIGEST) {
+ isc_region_t r;
+
+ REQUIRE(rdata->type == dns_rdatatype_nsec);
+
+ dns_rdata_toregion(rdata, &r);
+ return ((digest)(arg, &r));
+}
+
+static bool
+checkowner_nsec(ARGS_CHECKOWNER) {
+ REQUIRE(type == dns_rdatatype_nsec);
+
+ UNUSED(name);
+ UNUSED(type);
+ UNUSED(rdclass);
+ UNUSED(wildcard);
+
+ return (true);
+}
+
+static bool
+checknames_nsec(ARGS_CHECKNAMES) {
+ REQUIRE(rdata->type == dns_rdatatype_nsec);
+
+ UNUSED(rdata);
+ UNUSED(owner);
+ UNUSED(bad);
+
+ return (true);
+}
+
+static int
+casecompare_nsec(ARGS_COMPARE) {
+ isc_region_t region1;
+ isc_region_t region2;
+ dns_name_t name1;
+ dns_name_t name2;
+ int order;
+
+ REQUIRE(rdata1->type == rdata2->type);
+ REQUIRE(rdata1->rdclass == rdata2->rdclass);
+ REQUIRE(rdata1->type == dns_rdatatype_nsec);
+ REQUIRE(rdata1->length != 0);
+ REQUIRE(rdata2->length != 0);
+
+ dns_name_init(&name1, NULL);
+ dns_name_init(&name2, NULL);
+
+ dns_rdata_toregion(rdata1, &region1);
+ dns_rdata_toregion(rdata2, &region2);
+
+ dns_name_fromregion(&name1, &region1);
+ dns_name_fromregion(&name2, &region2);
+
+ order = dns_name_rdatacompare(&name1, &name2);
+ if (order != 0) {
+ return (order);
+ }
+
+ isc_region_consume(&region1, name_length(&name1));
+ isc_region_consume(&region2, name_length(&name2));
+
+ return (isc_region_compare(&region1, &region2));
+}
+#endif /* RDATA_GENERIC_NSEC_47_C */
diff --git a/lib/dns/rdata/generic/nsec_47.h b/lib/dns/rdata/generic/nsec_47.h
new file mode 100644
index 0000000..eded7ac
--- /dev/null
+++ b/lib/dns/rdata/generic/nsec_47.h
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#pragma once
+
+/*!
+ * \brief Per RFC 3845 */
+
+typedef struct dns_rdata_nsec {
+ dns_rdatacommon_t common;
+ isc_mem_t *mctx;
+ dns_name_t next;
+ unsigned char *typebits;
+ uint16_t len;
+} dns_rdata_nsec_t;
diff --git a/lib/dns/rdata/generic/null_10.c b/lib/dns/rdata/generic/null_10.c
new file mode 100644
index 0000000..4a019dd
--- /dev/null
+++ b/lib/dns/rdata/generic/null_10.c
@@ -0,0 +1,187 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#ifndef RDATA_GENERIC_NULL_10_C
+#define RDATA_GENERIC_NULL_10_C
+
+#define RRTYPE_NULL_ATTRIBUTES (0)
+
+static isc_result_t
+fromtext_null(ARGS_FROMTEXT) {
+ REQUIRE(type == dns_rdatatype_null);
+
+ UNUSED(rdclass);
+ UNUSED(type);
+ UNUSED(lexer);
+ UNUSED(origin);
+ UNUSED(options);
+ UNUSED(target);
+ UNUSED(callbacks);
+
+ return (DNS_R_SYNTAX);
+}
+
+static isc_result_t
+totext_null(ARGS_TOTEXT) {
+ REQUIRE(rdata->type == dns_rdatatype_null);
+
+ return (unknown_totext(rdata, tctx, target));
+}
+
+static isc_result_t
+fromwire_null(ARGS_FROMWIRE) {
+ isc_region_t sr;
+
+ REQUIRE(type == dns_rdatatype_null);
+
+ UNUSED(type);
+ UNUSED(rdclass);
+ UNUSED(dctx);
+ UNUSED(options);
+
+ isc_buffer_activeregion(source, &sr);
+ isc_buffer_forward(source, sr.length);
+ return (mem_tobuffer(target, sr.base, sr.length));
+}
+
+static isc_result_t
+towire_null(ARGS_TOWIRE) {
+ REQUIRE(rdata->type == dns_rdatatype_null);
+
+ UNUSED(cctx);
+
+ return (mem_tobuffer(target, rdata->data, rdata->length));
+}
+
+static int
+compare_null(ARGS_COMPARE) {
+ isc_region_t r1;
+ isc_region_t r2;
+
+ REQUIRE(rdata1->type == rdata2->type);
+ REQUIRE(rdata1->rdclass == rdata2->rdclass);
+ REQUIRE(rdata1->type == dns_rdatatype_null);
+
+ dns_rdata_toregion(rdata1, &r1);
+ dns_rdata_toregion(rdata2, &r2);
+ return (isc_region_compare(&r1, &r2));
+}
+
+static isc_result_t
+fromstruct_null(ARGS_FROMSTRUCT) {
+ dns_rdata_null_t *null = source;
+
+ REQUIRE(type == dns_rdatatype_null);
+ REQUIRE(null != NULL);
+ REQUIRE(null->common.rdtype == type);
+ REQUIRE(null->common.rdclass == rdclass);
+ REQUIRE(null->data != NULL || null->length == 0);
+
+ UNUSED(type);
+ UNUSED(rdclass);
+
+ return (mem_tobuffer(target, null->data, null->length));
+}
+
+static isc_result_t
+tostruct_null(ARGS_TOSTRUCT) {
+ dns_rdata_null_t *null = target;
+ isc_region_t r;
+
+ REQUIRE(rdata->type == dns_rdatatype_null);
+ REQUIRE(null != NULL);
+
+ null->common.rdclass = rdata->rdclass;
+ null->common.rdtype = rdata->type;
+ ISC_LINK_INIT(&null->common, link);
+
+ dns_rdata_toregion(rdata, &r);
+ null->length = r.length;
+ null->data = mem_maybedup(mctx, r.base, r.length);
+ if (null->data == NULL) {
+ return (ISC_R_NOMEMORY);
+ }
+
+ null->mctx = mctx;
+ return (ISC_R_SUCCESS);
+}
+
+static void
+freestruct_null(ARGS_FREESTRUCT) {
+ dns_rdata_null_t *null = source;
+
+ REQUIRE(null != NULL);
+ REQUIRE(null->common.rdtype == dns_rdatatype_null);
+
+ if (null->mctx == NULL) {
+ return;
+ }
+
+ if (null->data != NULL) {
+ isc_mem_free(null->mctx, null->data);
+ }
+ null->mctx = NULL;
+}
+
+static isc_result_t
+additionaldata_null(ARGS_ADDLDATA) {
+ REQUIRE(rdata->type == dns_rdatatype_null);
+
+ UNUSED(rdata);
+ UNUSED(owner);
+ UNUSED(add);
+ UNUSED(arg);
+
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+digest_null(ARGS_DIGEST) {
+ isc_region_t r;
+
+ REQUIRE(rdata->type == dns_rdatatype_null);
+
+ dns_rdata_toregion(rdata, &r);
+
+ return ((digest)(arg, &r));
+}
+
+static bool
+checkowner_null(ARGS_CHECKOWNER) {
+ REQUIRE(type == dns_rdatatype_null);
+
+ UNUSED(name);
+ UNUSED(type);
+ UNUSED(rdclass);
+ UNUSED(wildcard);
+
+ return (true);
+}
+
+static bool
+checknames_null(ARGS_CHECKNAMES) {
+ REQUIRE(rdata->type == dns_rdatatype_null);
+
+ UNUSED(rdata);
+ UNUSED(owner);
+ UNUSED(bad);
+
+ return (true);
+}
+
+static int
+casecompare_null(ARGS_COMPARE) {
+ return (compare_null(rdata1, rdata2));
+}
+
+#endif /* RDATA_GENERIC_NULL_10_C */
diff --git a/lib/dns/rdata/generic/null_10.h b/lib/dns/rdata/generic/null_10.h
new file mode 100644
index 0000000..d015b7d
--- /dev/null
+++ b/lib/dns/rdata/generic/null_10.h
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/* */
+#pragma once
+
+typedef struct dns_rdata_null {
+ dns_rdatacommon_t common;
+ isc_mem_t *mctx;
+ uint16_t length;
+ unsigned char *data;
+} dns_rdata_null_t;
diff --git a/lib/dns/rdata/generic/nxt_30.c b/lib/dns/rdata/generic/nxt_30.c
new file mode 100644
index 0000000..93d2384
--- /dev/null
+++ b/lib/dns/rdata/generic/nxt_30.c
@@ -0,0 +1,351 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/* RFC2535 */
+
+#ifndef RDATA_GENERIC_NXT_30_C
+#define RDATA_GENERIC_NXT_30_C
+
+/*
+ * The attributes do not include DNS_RDATATYPEATTR_SINGLETON
+ * because we must be able to handle a parent/child NXT pair.
+ */
+#define RRTYPE_NXT_ATTRIBUTES (0)
+
+static isc_result_t
+fromtext_nxt(ARGS_FROMTEXT) {
+ isc_token_t token;
+ dns_name_t name;
+ isc_buffer_t buffer;
+ char *e;
+ unsigned char bm[8 * 1024]; /* 64k bits */
+ dns_rdatatype_t covered;
+ dns_rdatatype_t maxcovered = 0;
+ bool first = true;
+ long n;
+
+ REQUIRE(type == dns_rdatatype_nxt);
+
+ UNUSED(type);
+ UNUSED(rdclass);
+ UNUSED(callbacks);
+
+ /*
+ * Next domain.
+ */
+ RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_string,
+ false));
+ dns_name_init(&name, NULL);
+ buffer_fromregion(&buffer, &token.value.as_region);
+ if (origin == NULL) {
+ origin = dns_rootname;
+ }
+ RETTOK(dns_name_fromtext(&name, &buffer, origin, options, target));
+
+ memset(bm, 0, sizeof(bm));
+ do {
+ RETERR(isc_lex_getmastertoken(lexer, &token,
+ isc_tokentype_string, true));
+ if (token.type != isc_tokentype_string) {
+ break;
+ }
+ n = strtol(DNS_AS_STR(token), &e, 10);
+ if (e != DNS_AS_STR(token) && *e == '\0') {
+ covered = (dns_rdatatype_t)n;
+ } else if (dns_rdatatype_fromtext(&covered,
+ &token.value.as_textregion) ==
+ DNS_R_UNKNOWN)
+ {
+ RETTOK(DNS_R_UNKNOWN);
+ }
+ /*
+ * NXT is only specified for types 1..127.
+ */
+ if (covered < 1 || covered > 127) {
+ return (ISC_R_RANGE);
+ }
+ if (first || covered > maxcovered) {
+ maxcovered = covered;
+ }
+ first = false;
+ bm[covered / 8] |= (0x80 >> (covered % 8));
+ } while (1);
+ isc_lex_ungettoken(lexer, &token);
+ if (first) {
+ return (ISC_R_SUCCESS);
+ }
+ n = (maxcovered + 8) / 8;
+ return (mem_tobuffer(target, bm, n));
+}
+
+static isc_result_t
+totext_nxt(ARGS_TOTEXT) {
+ isc_region_t sr;
+ unsigned int i, j;
+ dns_name_t name;
+ dns_name_t prefix;
+ bool sub;
+
+ REQUIRE(rdata->type == dns_rdatatype_nxt);
+ REQUIRE(rdata->length != 0);
+
+ dns_name_init(&name, NULL);
+ dns_name_init(&prefix, NULL);
+ dns_rdata_toregion(rdata, &sr);
+ dns_name_fromregion(&name, &sr);
+ isc_region_consume(&sr, name_length(&name));
+ sub = name_prefix(&name, tctx->origin, &prefix);
+ RETERR(dns_name_totext(&prefix, sub, target));
+
+ for (i = 0; i < sr.length; i++) {
+ if (sr.base[i] != 0) {
+ for (j = 0; j < 8; j++) {
+ if ((sr.base[i] & (0x80 >> j)) != 0) {
+ {
+ dns_rdatatype_t t = i * 8 + j;
+ RETERR(str_totext(" ", target));
+ if (dns_rdatatype_isknown(t)) {
+ RETERR(dns_rdatatype_totext(
+ t, target));
+ } else {
+ char buf[sizeof("6553"
+ "5")];
+ snprintf(buf,
+ sizeof(buf),
+ "%u", t);
+ RETERR(str_totext(
+ buf, target));
+ }
+ }
+ }
+ }
+ }
+ }
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+fromwire_nxt(ARGS_FROMWIRE) {
+ isc_region_t sr;
+ dns_name_t name;
+
+ REQUIRE(type == dns_rdatatype_nxt);
+
+ UNUSED(type);
+ UNUSED(rdclass);
+
+ dns_decompress_setmethods(dctx, DNS_COMPRESS_NONE);
+
+ dns_name_init(&name, NULL);
+ RETERR(dns_name_fromwire(&name, source, dctx, options, target));
+
+ isc_buffer_activeregion(source, &sr);
+ if (sr.length > 0 && ((sr.base[0] & 0x80) != 0 || sr.length > 16 ||
+ sr.base[sr.length - 1] == 0))
+ {
+ return (DNS_R_BADBITMAP);
+ }
+ RETERR(mem_tobuffer(target, sr.base, sr.length));
+ isc_buffer_forward(source, sr.length);
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+towire_nxt(ARGS_TOWIRE) {
+ isc_region_t sr;
+ dns_name_t name;
+ dns_offsets_t offsets;
+
+ REQUIRE(rdata->type == dns_rdatatype_nxt);
+ REQUIRE(rdata->length != 0);
+
+ dns_compress_setmethods(cctx, DNS_COMPRESS_NONE);
+ dns_name_init(&name, offsets);
+ dns_rdata_toregion(rdata, &sr);
+ dns_name_fromregion(&name, &sr);
+ isc_region_consume(&sr, name_length(&name));
+ RETERR(dns_name_towire(&name, cctx, target));
+
+ return (mem_tobuffer(target, sr.base, sr.length));
+}
+
+static int
+compare_nxt(ARGS_COMPARE) {
+ isc_region_t r1;
+ isc_region_t r2;
+ dns_name_t name1;
+ dns_name_t name2;
+ int order;
+
+ REQUIRE(rdata1->type == rdata2->type);
+ REQUIRE(rdata1->rdclass == rdata2->rdclass);
+ REQUIRE(rdata1->type == dns_rdatatype_nxt);
+ REQUIRE(rdata1->length != 0);
+ REQUIRE(rdata2->length != 0);
+
+ dns_name_init(&name1, NULL);
+ dns_name_init(&name2, NULL);
+ dns_rdata_toregion(rdata1, &r1);
+ dns_rdata_toregion(rdata2, &r2);
+ dns_name_fromregion(&name1, &r1);
+ dns_name_fromregion(&name2, &r2);
+ order = dns_name_rdatacompare(&name1, &name2);
+ if (order != 0) {
+ return (order);
+ }
+
+ isc_region_consume(&r1, name_length(&name1));
+ isc_region_consume(&r2, name_length(&name2));
+
+ return (isc_region_compare(&r1, &r2));
+}
+
+static isc_result_t
+fromstruct_nxt(ARGS_FROMSTRUCT) {
+ dns_rdata_nxt_t *nxt = source;
+ isc_region_t region;
+
+ REQUIRE(type == dns_rdatatype_nxt);
+ REQUIRE(nxt != NULL);
+ REQUIRE(nxt->common.rdtype == type);
+ REQUIRE(nxt->common.rdclass == rdclass);
+ REQUIRE(nxt->typebits != NULL || nxt->len == 0);
+ if (nxt->typebits != NULL && (nxt->typebits[0] & 0x80) == 0) {
+ REQUIRE(nxt->len <= 16);
+ REQUIRE(nxt->typebits[nxt->len - 1] != 0);
+ }
+
+ UNUSED(type);
+ UNUSED(rdclass);
+
+ dns_name_toregion(&nxt->next, &region);
+ RETERR(isc_buffer_copyregion(target, &region));
+
+ return (mem_tobuffer(target, nxt->typebits, nxt->len));
+}
+
+static isc_result_t
+tostruct_nxt(ARGS_TOSTRUCT) {
+ isc_region_t region;
+ dns_rdata_nxt_t *nxt = target;
+ dns_name_t name;
+
+ REQUIRE(rdata->type == dns_rdatatype_nxt);
+ REQUIRE(nxt != NULL);
+ REQUIRE(rdata->length != 0);
+
+ nxt->common.rdclass = rdata->rdclass;
+ nxt->common.rdtype = rdata->type;
+ ISC_LINK_INIT(&nxt->common, link);
+
+ dns_name_init(&name, NULL);
+ dns_rdata_toregion(rdata, &region);
+ dns_name_fromregion(&name, &region);
+ isc_region_consume(&region, name_length(&name));
+ dns_name_init(&nxt->next, NULL);
+ name_duporclone(&name, mctx, &nxt->next);
+
+ nxt->len = region.length;
+ nxt->typebits = mem_maybedup(mctx, region.base, region.length);
+ if (nxt->typebits == NULL) {
+ goto cleanup;
+ }
+
+ nxt->mctx = mctx;
+ return (ISC_R_SUCCESS);
+
+cleanup:
+ if (mctx != NULL) {
+ dns_name_free(&nxt->next, mctx);
+ }
+ return (ISC_R_NOMEMORY);
+}
+
+static void
+freestruct_nxt(ARGS_FREESTRUCT) {
+ dns_rdata_nxt_t *nxt = source;
+
+ REQUIRE(nxt != NULL);
+ REQUIRE(nxt->common.rdtype == dns_rdatatype_nxt);
+
+ if (nxt->mctx == NULL) {
+ return;
+ }
+
+ dns_name_free(&nxt->next, nxt->mctx);
+ if (nxt->typebits != NULL) {
+ isc_mem_free(nxt->mctx, nxt->typebits);
+ }
+ nxt->mctx = NULL;
+}
+
+static isc_result_t
+additionaldata_nxt(ARGS_ADDLDATA) {
+ REQUIRE(rdata->type == dns_rdatatype_nxt);
+
+ UNUSED(rdata);
+ UNUSED(owner);
+ UNUSED(add);
+ UNUSED(arg);
+
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+digest_nxt(ARGS_DIGEST) {
+ isc_region_t r;
+ dns_name_t name;
+ isc_result_t result;
+
+ REQUIRE(rdata->type == dns_rdatatype_nxt);
+
+ dns_rdata_toregion(rdata, &r);
+ dns_name_init(&name, NULL);
+ dns_name_fromregion(&name, &r);
+ result = dns_name_digest(&name, digest, arg);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+ isc_region_consume(&r, name_length(&name));
+
+ return ((digest)(arg, &r));
+}
+
+static bool
+checkowner_nxt(ARGS_CHECKOWNER) {
+ REQUIRE(type == dns_rdatatype_nxt);
+
+ UNUSED(name);
+ UNUSED(type);
+ UNUSED(rdclass);
+ UNUSED(wildcard);
+
+ return (true);
+}
+
+static bool
+checknames_nxt(ARGS_CHECKNAMES) {
+ REQUIRE(rdata->type == dns_rdatatype_nxt);
+
+ UNUSED(rdata);
+ UNUSED(owner);
+ UNUSED(bad);
+
+ return (true);
+}
+
+static int
+casecompare_nxt(ARGS_COMPARE) {
+ return (compare_nxt(rdata1, rdata2));
+}
+#endif /* RDATA_GENERIC_NXT_30_C */
diff --git a/lib/dns/rdata/generic/nxt_30.h b/lib/dns/rdata/generic/nxt_30.h
new file mode 100644
index 0000000..fb3e8b2
--- /dev/null
+++ b/lib/dns/rdata/generic/nxt_30.h
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#pragma once
+
+/*!
+ * \brief RFC2535 */
+
+typedef struct dns_rdata_nxt {
+ dns_rdatacommon_t common;
+ isc_mem_t *mctx;
+ dns_name_t next;
+ unsigned char *typebits;
+ uint16_t len;
+} dns_rdata_nxt_t;
diff --git a/lib/dns/rdata/generic/openpgpkey_61.c b/lib/dns/rdata/generic/openpgpkey_61.c
new file mode 100644
index 0000000..d4f9fa5
--- /dev/null
+++ b/lib/dns/rdata/generic/openpgpkey_61.c
@@ -0,0 +1,250 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#ifndef RDATA_GENERIC_OPENPGPKEY_61_C
+#define RDATA_GENERIC_OPENPGPKEY_61_C
+
+#define RRTYPE_OPENPGPKEY_ATTRIBUTES 0
+
+static isc_result_t
+fromtext_openpgpkey(ARGS_FROMTEXT) {
+ REQUIRE(type == dns_rdatatype_openpgpkey);
+
+ UNUSED(type);
+ UNUSED(rdclass);
+ UNUSED(callbacks);
+ UNUSED(options);
+ UNUSED(origin);
+
+ /*
+ * Keyring.
+ */
+ return (isc_base64_tobuffer(lexer, target, -2));
+}
+
+static isc_result_t
+totext_openpgpkey(ARGS_TOTEXT) {
+ isc_region_t sr;
+
+ REQUIRE(rdata->type == dns_rdatatype_openpgpkey);
+ REQUIRE(rdata->length != 0);
+
+ dns_rdata_toregion(rdata, &sr);
+
+ /*
+ * Keyring
+ */
+ if ((tctx->flags & DNS_STYLEFLAG_MULTILINE) != 0) {
+ RETERR(str_totext("( ", target));
+ }
+
+ if ((tctx->flags & DNS_STYLEFLAG_NOCRYPTO) == 0) {
+ if (tctx->width == 0) { /* No splitting */
+ RETERR(isc_base64_totext(&sr, 60, "", target));
+ } else {
+ RETERR(isc_base64_totext(&sr, tctx->width - 2,
+ tctx->linebreak, target));
+ }
+ } else {
+ RETERR(str_totext("[omitted]", target));
+ }
+
+ if ((tctx->flags & DNS_STYLEFLAG_MULTILINE) != 0) {
+ RETERR(str_totext(" )", target));
+ }
+
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+fromwire_openpgpkey(ARGS_FROMWIRE) {
+ isc_region_t sr;
+
+ REQUIRE(type == dns_rdatatype_openpgpkey);
+
+ UNUSED(type);
+ UNUSED(rdclass);
+ UNUSED(dctx);
+ UNUSED(options);
+
+ /*
+ * Keyring.
+ */
+ isc_buffer_activeregion(source, &sr);
+ if (sr.length < 1) {
+ return (ISC_R_UNEXPECTEDEND);
+ }
+ isc_buffer_forward(source, sr.length);
+ return (mem_tobuffer(target, sr.base, sr.length));
+}
+
+static isc_result_t
+towire_openpgpkey(ARGS_TOWIRE) {
+ isc_region_t sr;
+
+ REQUIRE(rdata->type == dns_rdatatype_openpgpkey);
+ REQUIRE(rdata->length != 0);
+
+ UNUSED(cctx);
+
+ dns_rdata_toregion(rdata, &sr);
+ return (mem_tobuffer(target, sr.base, sr.length));
+}
+
+static int
+compare_openpgpkey(ARGS_COMPARE) {
+ isc_region_t r1;
+ isc_region_t r2;
+
+ REQUIRE(rdata1->type == rdata2->type);
+ REQUIRE(rdata1->rdclass == rdata2->rdclass);
+ REQUIRE(rdata1->type == dns_rdatatype_openpgpkey);
+ REQUIRE(rdata1->length != 0);
+ REQUIRE(rdata2->length != 0);
+
+ dns_rdata_toregion(rdata1, &r1);
+ dns_rdata_toregion(rdata2, &r2);
+ return (isc_region_compare(&r1, &r2));
+}
+
+static isc_result_t
+fromstruct_openpgpkey(ARGS_FROMSTRUCT) {
+ dns_rdata_openpgpkey_t *sig = source;
+
+ REQUIRE(type == dns_rdatatype_openpgpkey);
+ REQUIRE(sig != NULL);
+ REQUIRE(sig->common.rdtype == type);
+ REQUIRE(sig->common.rdclass == rdclass);
+ REQUIRE(sig->keyring != NULL && sig->length != 0);
+
+ UNUSED(type);
+ UNUSED(rdclass);
+
+ /*
+ * Keyring.
+ */
+ return (mem_tobuffer(target, sig->keyring, sig->length));
+}
+
+static isc_result_t
+tostruct_openpgpkey(ARGS_TOSTRUCT) {
+ isc_region_t sr;
+ dns_rdata_openpgpkey_t *sig = target;
+
+ REQUIRE(rdata->type == dns_rdatatype_openpgpkey);
+ REQUIRE(sig != NULL);
+ REQUIRE(rdata->length != 0);
+
+ sig->common.rdclass = rdata->rdclass;
+ sig->common.rdtype = rdata->type;
+ ISC_LINK_INIT(&sig->common, link);
+
+ dns_rdata_toregion(rdata, &sr);
+
+ /*
+ * Keyring.
+ */
+ sig->length = sr.length;
+ sig->keyring = mem_maybedup(mctx, sr.base, sig->length);
+ if (sig->keyring == NULL) {
+ goto cleanup;
+ }
+
+ sig->mctx = mctx;
+ return (ISC_R_SUCCESS);
+
+cleanup:
+ return (ISC_R_NOMEMORY);
+}
+
+static void
+freestruct_openpgpkey(ARGS_FREESTRUCT) {
+ dns_rdata_openpgpkey_t *sig = (dns_rdata_openpgpkey_t *)source;
+
+ REQUIRE(sig != NULL);
+ REQUIRE(sig->common.rdtype == dns_rdatatype_openpgpkey);
+
+ if (sig->mctx == NULL) {
+ return;
+ }
+
+ if (sig->keyring != NULL) {
+ isc_mem_free(sig->mctx, sig->keyring);
+ }
+ sig->mctx = NULL;
+}
+
+static isc_result_t
+additionaldata_openpgpkey(ARGS_ADDLDATA) {
+ REQUIRE(rdata->type == dns_rdatatype_openpgpkey);
+
+ UNUSED(rdata);
+ UNUSED(owner);
+ UNUSED(add);
+ UNUSED(arg);
+
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+digest_openpgpkey(ARGS_DIGEST) {
+ isc_region_t r;
+
+ REQUIRE(rdata->type == dns_rdatatype_openpgpkey);
+
+ dns_rdata_toregion(rdata, &r);
+
+ return ((digest)(arg, &r));
+}
+
+static bool
+checkowner_openpgpkey(ARGS_CHECKOWNER) {
+ REQUIRE(type == dns_rdatatype_openpgpkey);
+
+ UNUSED(name);
+ UNUSED(type);
+ UNUSED(rdclass);
+ UNUSED(wildcard);
+
+ return (true);
+}
+
+static bool
+checknames_openpgpkey(ARGS_CHECKNAMES) {
+ REQUIRE(rdata->type == dns_rdatatype_openpgpkey);
+
+ UNUSED(rdata);
+ UNUSED(owner);
+ UNUSED(bad);
+
+ return (true);
+}
+
+static int
+casecompare_openpgpkey(ARGS_COMPARE) {
+ isc_region_t r1;
+ isc_region_t r2;
+
+ REQUIRE(rdata1->type == rdata2->type);
+ REQUIRE(rdata1->rdclass == rdata2->rdclass);
+ REQUIRE(rdata1->type == dns_rdatatype_openpgpkey);
+ REQUIRE(rdata1->length != 0);
+ REQUIRE(rdata2->length != 0);
+
+ dns_rdata_toregion(rdata1, &r1);
+ dns_rdata_toregion(rdata2, &r2);
+
+ return (isc_region_compare(&r1, &r2));
+}
+
+#endif /* RDATA_GENERIC_OPENPGPKEY_61_C */
diff --git a/lib/dns/rdata/generic/openpgpkey_61.h b/lib/dns/rdata/generic/openpgpkey_61.h
new file mode 100644
index 0000000..702d343
--- /dev/null
+++ b/lib/dns/rdata/generic/openpgpkey_61.h
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#pragma once
+
+typedef struct dns_rdata_openpgpkey {
+ dns_rdatacommon_t common;
+ isc_mem_t *mctx;
+ uint16_t length;
+ unsigned char *keyring;
+} dns_rdata_openpgpkey_t;
diff --git a/lib/dns/rdata/generic/opt_41.c b/lib/dns/rdata/generic/opt_41.c
new file mode 100644
index 0000000..e5e7168
--- /dev/null
+++ b/lib/dns/rdata/generic/opt_41.c
@@ -0,0 +1,473 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/* RFC2671 */
+
+#ifndef RDATA_GENERIC_OPT_41_C
+#define RDATA_GENERIC_OPT_41_C
+
+#define RRTYPE_OPT_ATTRIBUTES \
+ (DNS_RDATATYPEATTR_SINGLETON | DNS_RDATATYPEATTR_META | \
+ DNS_RDATATYPEATTR_NOTQUESTION)
+
+#include <isc/utf8.h>
+
+static isc_result_t
+fromtext_opt(ARGS_FROMTEXT) {
+ /*
+ * OPT records do not have a text format.
+ */
+
+ REQUIRE(type == dns_rdatatype_opt);
+
+ UNUSED(type);
+ UNUSED(rdclass);
+ UNUSED(lexer);
+ UNUSED(origin);
+ UNUSED(options);
+ UNUSED(target);
+ UNUSED(callbacks);
+
+ return (ISC_R_NOTIMPLEMENTED);
+}
+
+static isc_result_t
+totext_opt(ARGS_TOTEXT) {
+ isc_region_t r;
+ isc_region_t or ;
+ uint16_t option;
+ uint16_t length;
+ char buf[sizeof("64000 64000")];
+
+ /*
+ * OPT records do not have a text format.
+ */
+
+ REQUIRE(rdata->type == dns_rdatatype_opt);
+
+ dns_rdata_toregion(rdata, &r);
+ while (r.length > 0) {
+ option = uint16_fromregion(&r);
+ isc_region_consume(&r, 2);
+ length = uint16_fromregion(&r);
+ isc_region_consume(&r, 2);
+ snprintf(buf, sizeof(buf), "%u %u", option, length);
+ RETERR(str_totext(buf, target));
+ INSIST(r.length >= length);
+ if (length > 0) {
+ if ((tctx->flags & DNS_STYLEFLAG_MULTILINE) != 0) {
+ RETERR(str_totext(" (", target));
+ }
+ RETERR(str_totext(tctx->linebreak, target));
+ or = r;
+ or.length = length;
+ if (tctx->width == 0) { /* No splitting */
+ RETERR(isc_base64_totext(& or, 60, "", target));
+ } else {
+ RETERR(isc_base64_totext(& or, tctx->width - 2,
+ tctx->linebreak,
+ target));
+ }
+ isc_region_consume(&r, length);
+ if ((tctx->flags & DNS_STYLEFLAG_MULTILINE) != 0) {
+ RETERR(str_totext(" )", target));
+ }
+ }
+ if (r.length > 0) {
+ RETERR(str_totext(" ", target));
+ }
+ }
+
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+fromwire_opt(ARGS_FROMWIRE) {
+ isc_region_t sregion;
+ isc_region_t tregion;
+ uint16_t opt;
+ uint16_t length;
+ unsigned int total;
+
+ REQUIRE(type == dns_rdatatype_opt);
+
+ UNUSED(type);
+ UNUSED(rdclass);
+ UNUSED(dctx);
+ UNUSED(options);
+
+ isc_buffer_activeregion(source, &sregion);
+ if (sregion.length == 0) {
+ return (ISC_R_SUCCESS);
+ }
+ total = 0;
+ while (sregion.length != 0) {
+ if (sregion.length < 4) {
+ return (ISC_R_UNEXPECTEDEND);
+ }
+ opt = uint16_fromregion(&sregion);
+ isc_region_consume(&sregion, 2);
+ length = uint16_fromregion(&sregion);
+ isc_region_consume(&sregion, 2);
+ total += 4;
+ if (sregion.length < length) {
+ return (ISC_R_UNEXPECTEDEND);
+ }
+ switch (opt) {
+ case DNS_OPT_LLQ:
+ if (length != 18U) {
+ return (DNS_R_OPTERR);
+ }
+ isc_region_consume(&sregion, length);
+ break;
+ case DNS_OPT_CLIENT_SUBNET: {
+ uint16_t family;
+ uint8_t addrlen;
+ uint8_t scope;
+ uint8_t addrbytes;
+
+ if (length < 4) {
+ return (DNS_R_OPTERR);
+ }
+ family = uint16_fromregion(&sregion);
+ isc_region_consume(&sregion, 2);
+ addrlen = uint8_fromregion(&sregion);
+ isc_region_consume(&sregion, 1);
+ scope = uint8_fromregion(&sregion);
+ isc_region_consume(&sregion, 1);
+
+ switch (family) {
+ case 0:
+ /*
+ * XXXMUKS: In queries and replies, if
+ * FAMILY is set to 0, SOURCE
+ * PREFIX-LENGTH and SCOPE PREFIX-LENGTH
+ * must be 0 and ADDRESS should not be
+ * present as the address and prefix
+ * lengths don't make sense because the
+ * family is unknown.
+ */
+ if (addrlen != 0U || scope != 0U) {
+ return (DNS_R_OPTERR);
+ }
+ break;
+ case 1:
+ if (addrlen > 32U || scope > 32U) {
+ return (DNS_R_OPTERR);
+ }
+ break;
+ case 2:
+ if (addrlen > 128U || scope > 128U) {
+ return (DNS_R_OPTERR);
+ }
+ break;
+ default:
+ return (DNS_R_OPTERR);
+ }
+ addrbytes = (addrlen + 7) / 8;
+ if (addrbytes + 4 != length) {
+ return (DNS_R_OPTERR);
+ }
+
+ if (addrbytes != 0U && (addrlen % 8) != 0) {
+ uint8_t bits = ~0U << (8 - (addrlen % 8));
+ bits &= sregion.base[addrbytes - 1];
+ if (bits != sregion.base[addrbytes - 1]) {
+ return (DNS_R_OPTERR);
+ }
+ }
+ isc_region_consume(&sregion, addrbytes);
+ break;
+ }
+ case DNS_OPT_EXPIRE:
+ /*
+ * Request has zero length. Response is 32 bits.
+ */
+ if (length != 0 && length != 4) {
+ return (DNS_R_OPTERR);
+ }
+ isc_region_consume(&sregion, length);
+ break;
+ case DNS_OPT_COOKIE:
+ /*
+ * Client cookie alone has length 8.
+ * Client + server cookie is 8 + [8..32].
+ */
+ if (length != 8 && (length < 16 || length > 40)) {
+ return (DNS_R_OPTERR);
+ }
+ isc_region_consume(&sregion, length);
+ break;
+ case DNS_OPT_KEY_TAG:
+ if (length == 0 || (length % 2) != 0) {
+ return (DNS_R_OPTERR);
+ }
+ isc_region_consume(&sregion, length);
+ break;
+ case DNS_OPT_EDE:
+ if (length < 2) {
+ return (DNS_R_OPTERR);
+ }
+ /* UTF-8 Byte Order Mark is not permitted. RFC 5198 */
+ if (isc_utf8_bom(sregion.base + 2, length - 2)) {
+ return (DNS_R_OPTERR);
+ }
+ /*
+ * The EXTRA-TEXT field is specified as UTF-8, and
+ * therefore must be validated for correctness
+ * according to RFC 3269 security considerations.
+ */
+ if (!isc_utf8_valid(sregion.base + 2, length - 2)) {
+ return (DNS_R_OPTERR);
+ }
+ isc_region_consume(&sregion, length);
+ break;
+ case DNS_OPT_CLIENT_TAG:
+ FALLTHROUGH;
+ case DNS_OPT_SERVER_TAG:
+ if (length != 2) {
+ return (DNS_R_OPTERR);
+ }
+ isc_region_consume(&sregion, length);
+ break;
+ default:
+ isc_region_consume(&sregion, length);
+ break;
+ }
+ total += length;
+ }
+
+ isc_buffer_activeregion(source, &sregion);
+ isc_buffer_availableregion(target, &tregion);
+ if (tregion.length < total) {
+ return (ISC_R_NOSPACE);
+ }
+ memmove(tregion.base, sregion.base, total);
+ isc_buffer_forward(source, total);
+ isc_buffer_add(target, total);
+
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+towire_opt(ARGS_TOWIRE) {
+ REQUIRE(rdata->type == dns_rdatatype_opt);
+
+ UNUSED(cctx);
+
+ return (mem_tobuffer(target, rdata->data, rdata->length));
+}
+
+static int
+compare_opt(ARGS_COMPARE) {
+ isc_region_t r1;
+ isc_region_t r2;
+
+ REQUIRE(rdata1->type == rdata2->type);
+ REQUIRE(rdata1->rdclass == rdata2->rdclass);
+ REQUIRE(rdata1->type == dns_rdatatype_opt);
+
+ dns_rdata_toregion(rdata1, &r1);
+ dns_rdata_toregion(rdata2, &r2);
+ return (isc_region_compare(&r1, &r2));
+}
+
+static isc_result_t
+fromstruct_opt(ARGS_FROMSTRUCT) {
+ dns_rdata_opt_t *opt = source;
+ isc_region_t region;
+ uint16_t length;
+
+ REQUIRE(type == dns_rdatatype_opt);
+ REQUIRE(opt != NULL);
+ REQUIRE(opt->common.rdtype == type);
+ REQUIRE(opt->common.rdclass == rdclass);
+ REQUIRE(opt->options != NULL || opt->length == 0);
+
+ UNUSED(type);
+ UNUSED(rdclass);
+
+ region.base = opt->options;
+ region.length = opt->length;
+ while (region.length >= 4) {
+ isc_region_consume(&region, 2); /* opt */
+ length = uint16_fromregion(&region);
+ isc_region_consume(&region, 2);
+ if (region.length < length) {
+ return (ISC_R_UNEXPECTEDEND);
+ }
+ isc_region_consume(&region, length);
+ }
+ if (region.length != 0) {
+ return (ISC_R_UNEXPECTEDEND);
+ }
+
+ return (mem_tobuffer(target, opt->options, opt->length));
+}
+
+static isc_result_t
+tostruct_opt(ARGS_TOSTRUCT) {
+ dns_rdata_opt_t *opt = target;
+ isc_region_t r;
+
+ REQUIRE(rdata->type == dns_rdatatype_opt);
+ REQUIRE(opt != NULL);
+
+ opt->common.rdclass = rdata->rdclass;
+ opt->common.rdtype = rdata->type;
+ ISC_LINK_INIT(&opt->common, link);
+
+ dns_rdata_toregion(rdata, &r);
+ opt->length = r.length;
+ opt->options = mem_maybedup(mctx, r.base, r.length);
+ if (opt->options == NULL) {
+ return (ISC_R_NOMEMORY);
+ }
+
+ opt->offset = 0;
+ opt->mctx = mctx;
+ return (ISC_R_SUCCESS);
+}
+
+static void
+freestruct_opt(ARGS_FREESTRUCT) {
+ dns_rdata_opt_t *opt = source;
+
+ REQUIRE(opt != NULL);
+ REQUIRE(opt->common.rdtype == dns_rdatatype_opt);
+
+ if (opt->mctx == NULL) {
+ return;
+ }
+
+ if (opt->options != NULL) {
+ isc_mem_free(opt->mctx, opt->options);
+ }
+ opt->mctx = NULL;
+}
+
+static isc_result_t
+additionaldata_opt(ARGS_ADDLDATA) {
+ REQUIRE(rdata->type == dns_rdatatype_opt);
+
+ UNUSED(rdata);
+ UNUSED(owner);
+ UNUSED(add);
+ UNUSED(arg);
+
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+digest_opt(ARGS_DIGEST) {
+ /*
+ * OPT records are not digested.
+ */
+
+ REQUIRE(rdata->type == dns_rdatatype_opt);
+
+ UNUSED(rdata);
+ UNUSED(digest);
+ UNUSED(arg);
+
+ return (ISC_R_NOTIMPLEMENTED);
+}
+
+static bool
+checkowner_opt(ARGS_CHECKOWNER) {
+ REQUIRE(type == dns_rdatatype_opt);
+
+ UNUSED(type);
+ UNUSED(rdclass);
+ UNUSED(wildcard);
+
+ return (dns_name_equal(name, dns_rootname));
+}
+
+static bool
+checknames_opt(ARGS_CHECKNAMES) {
+ REQUIRE(rdata->type == dns_rdatatype_opt);
+
+ UNUSED(rdata);
+ UNUSED(owner);
+ UNUSED(bad);
+
+ return (true);
+}
+
+static int
+casecompare_opt(ARGS_COMPARE) {
+ return (compare_opt(rdata1, rdata2));
+}
+
+isc_result_t
+dns_rdata_opt_first(dns_rdata_opt_t *opt) {
+ REQUIRE(opt != NULL);
+ REQUIRE(opt->common.rdtype == dns_rdatatype_opt);
+ REQUIRE(opt->options != NULL || opt->length == 0);
+
+ if (opt->length == 0) {
+ return (ISC_R_NOMORE);
+ }
+
+ opt->offset = 0;
+ return (ISC_R_SUCCESS);
+}
+
+isc_result_t
+dns_rdata_opt_next(dns_rdata_opt_t *opt) {
+ isc_region_t r;
+ uint16_t length;
+
+ REQUIRE(opt != NULL);
+ REQUIRE(opt->common.rdtype == dns_rdatatype_opt);
+ REQUIRE(opt->options != NULL && opt->length != 0);
+ REQUIRE(opt->offset < opt->length);
+
+ INSIST(opt->offset + 4 <= opt->length);
+ r.base = opt->options + opt->offset + 2;
+ r.length = opt->length - opt->offset - 2;
+ length = uint16_fromregion(&r);
+ INSIST(opt->offset + 4 + length <= opt->length);
+ opt->offset = opt->offset + 4 + length;
+ if (opt->offset == opt->length) {
+ return (ISC_R_NOMORE);
+ }
+ return (ISC_R_SUCCESS);
+}
+
+isc_result_t
+dns_rdata_opt_current(dns_rdata_opt_t *opt, dns_rdata_opt_opcode_t *opcode) {
+ isc_region_t r;
+
+ REQUIRE(opt != NULL);
+ REQUIRE(opcode != NULL);
+ REQUIRE(opt->common.rdtype == dns_rdatatype_opt);
+ REQUIRE(opt->options != NULL);
+ REQUIRE(opt->offset < opt->length);
+
+ INSIST(opt->offset + 4 <= opt->length);
+ r.base = opt->options + opt->offset;
+ r.length = opt->length - opt->offset;
+
+ opcode->opcode = uint16_fromregion(&r);
+ isc_region_consume(&r, 2);
+ opcode->length = uint16_fromregion(&r);
+ isc_region_consume(&r, 2);
+ opcode->data = r.base;
+ INSIST(opt->offset + 4 + opcode->length <= opt->length);
+
+ return (ISC_R_SUCCESS);
+}
+
+#endif /* RDATA_GENERIC_OPT_41_C */
diff --git a/lib/dns/rdata/generic/opt_41.h b/lib/dns/rdata/generic/opt_41.h
new file mode 100644
index 0000000..2198fde
--- /dev/null
+++ b/lib/dns/rdata/generic/opt_41.h
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#pragma once
+
+/*!
+ * \brief Per RFC2671 */
+
+typedef struct dns_rdata_opt_opcode {
+ uint16_t opcode;
+ uint16_t length;
+ unsigned char *data;
+} dns_rdata_opt_opcode_t;
+
+typedef struct dns_rdata_opt {
+ dns_rdatacommon_t common;
+ isc_mem_t *mctx;
+ unsigned char *options;
+ uint16_t length;
+ /* private */
+ uint16_t offset;
+} dns_rdata_opt_t;
+
+/*
+ * ISC_LANG_BEGINDECLS and ISC_LANG_ENDDECLS are already done
+ * via rdatastructpre.h and rdatastructsuf.h.
+ */
+
+isc_result_t
+dns_rdata_opt_first(dns_rdata_opt_t *);
+
+isc_result_t
+dns_rdata_opt_next(dns_rdata_opt_t *);
+
+isc_result_t
+dns_rdata_opt_current(dns_rdata_opt_t *, dns_rdata_opt_opcode_t *);
diff --git a/lib/dns/rdata/generic/proforma.c b/lib/dns/rdata/generic/proforma.c
new file mode 100644
index 0000000..6315a6f
--- /dev/null
+++ b/lib/dns/rdata/generic/proforma.c
@@ -0,0 +1,165 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#ifndef RDATA_GENERIC_ #_ #_C
+#define RDATA_GENERIC_ #_ #_C
+
+#define RRTYPE_ #_ATTRIBUTES(0)
+
+static isc_result_t fromtext_ #(ARGS_FROMTEXT) {
+ isc_token_t token;
+
+ REQUIRE(type == dns_rdatatype_proforma.c #);
+ REQUIRE(rdclass == #);
+
+ RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_string,
+ false));
+
+ return (ISC_R_NOTIMPLEMENTED);
+}
+
+static isc_result_t totext_ #(ARGS_TOTEXT) {
+ REQUIRE(rdata->type == dns_rdatatype_proforma.c #);
+ REQUIRE(rdata->rdclass == #);
+ REQUIRE(rdata->length != 0); /* XXX */
+
+ return (ISC_R_NOTIMPLEMENTED);
+}
+
+static isc_result_t fromwire_ #(ARGS_FROMWIRE) {
+ REQUIRE(type == dns_rdatatype_proforma.c #);
+ REQUIRE(rdclass == #);
+
+ /* NONE or GLOBAL14 */
+ dns_decompress_setmethods(dctx, DNS_COMPRESS_NONE);
+
+ return (ISC_R_NOTIMPLEMENTED);
+}
+
+static isc_result_t towire_ #(ARGS_TOWIRE) {
+ REQUIRE(rdata->type == dns_rdatatype_proforma.c #);
+ REQUIRE(rdata->rdclass == #);
+ REQUIRE(rdata->length != 0); /* XXX */
+
+ /* NONE or GLOBAL14 */
+ dns_compress_setmethods(cctx, DNS_COMPRESS_NONE);
+
+ return (ISC_R_NOTIMPLEMENTED);
+}
+
+static int compare_ #(ARGS_COMPARE) {
+ isc_region_t r1;
+ isc_region_t r2;
+
+ REQUIRE(rdata1->type == dns_rdatatype_proforma.crdata2->type);
+ REQUIRE(rdata1->rdclass == rdata2->rdclass);
+ REQUIRE(rdata1->type == dns_rdatatype_proforma.c #);
+ REQUIRE(rdata1->rdclass == #);
+ REQUIRE(rdata1->length != 0); /* XXX */
+ REQUIRE(rdata2->length != 0); /* XXX */
+
+ dns_rdata_toregion(rdata1, &r1);
+ dns_rdata_toregion(rdata2, &r2);
+ return (isc_region_compare(&r1, &r2));
+}
+
+static isc_result_t fromstruct_ #(ARGS_FROMSTRUCT) {
+ dns_rdata_ #_t *# = source;
+
+ REQUIRE(type == dns_rdatatype_proforma.c #);
+ REQUIRE(rdclass == #);
+ REQUIRE(# != NULL);
+ REQUIRE(#->common.rdtype == dns_rdatatype_proforma.ctype);
+ REQUIRE(#->common.rdclass == rdclass);
+
+ return (ISC_R_NOTIMPLEMENTED);
+}
+
+static isc_result_t tostruct_ #(ARGS_TOSTRUCT) {
+ REQUIRE(rdata->type == dns_rdatatype_proforma.c #);
+ REQUIRE(rdata->rdclass == #);
+ REQUIRE(rdata->length != 0); /* XXX */
+
+ return (ISC_R_NOTIMPLEMENTED);
+}
+
+static void freestruct_ #(ARGS_FREESTRUCT) {
+ dns_rdata_ #_t *# = source;
+
+ REQUIRE(# != NULL);
+ REQUIRE(#->common.rdtype == dns_rdatatype_proforma.c #);
+ REQUIRE(#->common.rdclass == #);
+}
+
+static isc_result_t additionaldata_ #(ARGS_ADDLDATA) {
+ REQUIRE(rdata->type == dns_rdatatype_proforma.c #);
+ REQUIRE(rdata->rdclass == #);
+
+ UNUSED(owner);
+ UNUSED(add);
+ UNUSED(arg);
+
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t digest_ #(ARGS_DIGEST) {
+ isc_region_t r;
+
+ REQUIRE(rdata->type == dns_rdatatype_proforma.c #);
+ REQUIRE(rdata->rdclass == #);
+
+ dns_rdata_toregion(rdata, &r);
+
+ return ((digest)(arg, &r));
+}
+
+static bool checkowner_ #(ARGS_CHECKOWNER) {
+ REQUIRE(type == dns_rdatatype_proforma.c #);
+ REQUIRE(rdclass == #);
+
+ UNUSED(name);
+ UNUSED(type);
+ UNUSED(rdclass);
+ UNUSED(wildcard);
+
+ return (true);
+}
+
+static bool checknames_ #(ARGS_CHECKNAMES) {
+ REQUIRE(rdata->type == dns_rdatatype_proforma.c #);
+ REQUIRE(rdata->rdclass == #);
+
+ UNUSED(rdata);
+ UNUSED(owner);
+ UNUSED(bad);
+
+ return (true);
+}
+
+static int casecompare_ #(ARGS_COMPARE) {
+ isc_region_t r1;
+ isc_region_t r2;
+
+ REQUIRE(rdata1->type == dns_rdatatype_proforma.crdata2->type);
+ REQUIRE(rdata1->rdclass == rdata2->rdclass);
+ REQUIRE(rdata1->type == dns_rdatatype_proforma.c #);
+ REQUIRE(rdata1->rdclass == #);
+ REQUIRE(rdata1->length != 0); /* XXX */
+ REQUIRE(rdata2->length != 0); /* XXX */
+
+ dns_rdata_toregion(rdata1, &r1);
+ dns_rdata_toregion(rdata2, &r2);
+ return (isc_region_compare(&r1, &r2));
+}
+
+#endif /* RDATA_GENERIC_#_#_C */
diff --git a/lib/dns/rdata/generic/proforma.h b/lib/dns/rdata/generic/proforma.h
new file mode 100644
index 0000000..03962d1
--- /dev/null
+++ b/lib/dns/rdata/generic/proforma.h
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/* */
+#pragma once
+
+typedef struct dns_rdata_ #{
+ dns_rdatacommon_t common;
+ isc_mem_t *mctx; /* if required */
+ /* type & class specific elements */
+}
+dns_rdata_ #_t;
diff --git a/lib/dns/rdata/generic/ptr_12.c b/lib/dns/rdata/generic/ptr_12.c
new file mode 100644
index 0000000..41e2e8e
--- /dev/null
+++ b/lib/dns/rdata/generic/ptr_12.c
@@ -0,0 +1,279 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#ifndef RDATA_GENERIC_PTR_12_C
+#define RDATA_GENERIC_PTR_12_C
+
+#define RRTYPE_PTR_ATTRIBUTES (0)
+
+static isc_result_t
+fromtext_ptr(ARGS_FROMTEXT) {
+ isc_token_t token;
+ dns_name_t name;
+ isc_buffer_t buffer;
+
+ REQUIRE(type == dns_rdatatype_ptr);
+
+ UNUSED(type);
+ UNUSED(rdclass);
+ UNUSED(callbacks);
+
+ RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_string,
+ false));
+
+ dns_name_init(&name, NULL);
+ buffer_fromregion(&buffer, &token.value.as_region);
+ if (origin == NULL) {
+ origin = dns_rootname;
+ }
+ RETTOK(dns_name_fromtext(&name, &buffer, origin, options, target));
+ if (rdclass == dns_rdataclass_in &&
+ (options & DNS_RDATA_CHECKNAMES) != 0 &&
+ (options & DNS_RDATA_CHECKREVERSE) != 0)
+ {
+ bool ok;
+ ok = dns_name_ishostname(&name, false);
+ if (!ok && (options & DNS_RDATA_CHECKNAMESFAIL) != 0) {
+ RETTOK(DNS_R_BADNAME);
+ }
+ if (!ok && callbacks != NULL) {
+ warn_badname(&name, lexer, callbacks);
+ }
+ }
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+totext_ptr(ARGS_TOTEXT) {
+ isc_region_t region;
+ dns_name_t name;
+ dns_name_t prefix;
+ bool sub;
+
+ REQUIRE(rdata->type == dns_rdatatype_ptr);
+ REQUIRE(rdata->length != 0);
+
+ dns_name_init(&name, NULL);
+ dns_name_init(&prefix, NULL);
+
+ dns_rdata_toregion(rdata, &region);
+ dns_name_fromregion(&name, &region);
+
+ sub = name_prefix(&name, tctx->origin, &prefix);
+
+ return (dns_name_totext(&prefix, sub, target));
+}
+
+static isc_result_t
+fromwire_ptr(ARGS_FROMWIRE) {
+ dns_name_t name;
+
+ REQUIRE(type == dns_rdatatype_ptr);
+
+ UNUSED(type);
+ UNUSED(rdclass);
+
+ dns_decompress_setmethods(dctx, DNS_COMPRESS_GLOBAL14);
+
+ dns_name_init(&name, NULL);
+ return (dns_name_fromwire(&name, source, dctx, options, target));
+}
+
+static isc_result_t
+towire_ptr(ARGS_TOWIRE) {
+ dns_name_t name;
+ dns_offsets_t offsets;
+ isc_region_t region;
+
+ REQUIRE(rdata->type == dns_rdatatype_ptr);
+ REQUIRE(rdata->length != 0);
+
+ dns_compress_setmethods(cctx, DNS_COMPRESS_GLOBAL14);
+
+ dns_name_init(&name, offsets);
+ dns_rdata_toregion(rdata, &region);
+ dns_name_fromregion(&name, &region);
+
+ return (dns_name_towire(&name, cctx, target));
+}
+
+static int
+compare_ptr(ARGS_COMPARE) {
+ dns_name_t name1;
+ dns_name_t name2;
+ isc_region_t region1;
+ isc_region_t region2;
+
+ REQUIRE(rdata1->type == rdata2->type);
+ REQUIRE(rdata1->rdclass == rdata2->rdclass);
+ REQUIRE(rdata1->type == dns_rdatatype_ptr);
+ REQUIRE(rdata1->length != 0);
+ REQUIRE(rdata2->length != 0);
+
+ dns_name_init(&name1, NULL);
+ dns_name_init(&name2, NULL);
+
+ dns_rdata_toregion(rdata1, &region1);
+ dns_rdata_toregion(rdata2, &region2);
+
+ dns_name_fromregion(&name1, &region1);
+ dns_name_fromregion(&name2, &region2);
+
+ return (dns_name_rdatacompare(&name1, &name2));
+}
+
+static isc_result_t
+fromstruct_ptr(ARGS_FROMSTRUCT) {
+ dns_rdata_ptr_t *ptr = source;
+ isc_region_t region;
+
+ REQUIRE(type == dns_rdatatype_ptr);
+ REQUIRE(ptr != NULL);
+ REQUIRE(ptr->common.rdtype == type);
+ REQUIRE(ptr->common.rdclass == rdclass);
+
+ UNUSED(type);
+ UNUSED(rdclass);
+
+ dns_name_toregion(&ptr->ptr, &region);
+ return (isc_buffer_copyregion(target, &region));
+}
+
+static isc_result_t
+tostruct_ptr(ARGS_TOSTRUCT) {
+ isc_region_t region;
+ dns_rdata_ptr_t *ptr = target;
+ dns_name_t name;
+
+ REQUIRE(rdata->type == dns_rdatatype_ptr);
+ REQUIRE(ptr != NULL);
+ REQUIRE(rdata->length != 0);
+
+ ptr->common.rdclass = rdata->rdclass;
+ ptr->common.rdtype = rdata->type;
+ ISC_LINK_INIT(&ptr->common, link);
+
+ dns_name_init(&name, NULL);
+ dns_rdata_toregion(rdata, &region);
+ dns_name_fromregion(&name, &region);
+ dns_name_init(&ptr->ptr, NULL);
+ name_duporclone(&name, mctx, &ptr->ptr);
+ ptr->mctx = mctx;
+ return (ISC_R_SUCCESS);
+}
+
+static void
+freestruct_ptr(ARGS_FREESTRUCT) {
+ dns_rdata_ptr_t *ptr = source;
+
+ REQUIRE(ptr != NULL);
+ REQUIRE(ptr->common.rdtype == dns_rdatatype_ptr);
+
+ if (ptr->mctx == NULL) {
+ return;
+ }
+
+ dns_name_free(&ptr->ptr, ptr->mctx);
+ ptr->mctx = NULL;
+}
+
+static isc_result_t
+additionaldata_ptr(ARGS_ADDLDATA) {
+ REQUIRE(rdata->type == dns_rdatatype_ptr);
+
+ UNUSED(rdata);
+ UNUSED(owner);
+ UNUSED(add);
+ UNUSED(arg);
+
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+digest_ptr(ARGS_DIGEST) {
+ isc_region_t r;
+ dns_name_t name;
+
+ REQUIRE(rdata->type == dns_rdatatype_ptr);
+
+ dns_rdata_toregion(rdata, &r);
+ dns_name_init(&name, NULL);
+ dns_name_fromregion(&name, &r);
+
+ return (dns_name_digest(&name, digest, arg));
+}
+
+static bool
+checkowner_ptr(ARGS_CHECKOWNER) {
+ REQUIRE(type == dns_rdatatype_ptr);
+
+ UNUSED(name);
+ UNUSED(type);
+ UNUSED(rdclass);
+ UNUSED(wildcard);
+
+ return (true);
+}
+
+static unsigned char ip6_arpa_data[] = "\003IP6\004ARPA";
+static unsigned char ip6_arpa_offsets[] = { 0, 4, 9 };
+static const dns_name_t ip6_arpa = DNS_NAME_INITABSOLUTE(ip6_arpa_data,
+ ip6_arpa_offsets);
+
+static unsigned char ip6_int_data[] = "\003IP6\003INT";
+static unsigned char ip6_int_offsets[] = { 0, 4, 8 };
+static const dns_name_t ip6_int = DNS_NAME_INITABSOLUTE(ip6_int_data,
+ ip6_int_offsets);
+
+static unsigned char in_addr_arpa_data[] = "\007IN-ADDR\004ARPA";
+static unsigned char in_addr_arpa_offsets[] = { 0, 8, 13 };
+static const dns_name_t in_addr_arpa =
+ DNS_NAME_INITABSOLUTE(in_addr_arpa_data, in_addr_arpa_offsets);
+
+static bool
+checknames_ptr(ARGS_CHECKNAMES) {
+ isc_region_t region;
+ dns_name_t name;
+
+ REQUIRE(rdata->type == dns_rdatatype_ptr);
+
+ if (rdata->rdclass != dns_rdataclass_in) {
+ return (true);
+ }
+
+ if (dns_name_isdnssd(owner)) {
+ return (true);
+ }
+
+ if (dns_name_issubdomain(owner, &in_addr_arpa) ||
+ dns_name_issubdomain(owner, &ip6_arpa) ||
+ dns_name_issubdomain(owner, &ip6_int))
+ {
+ dns_rdata_toregion(rdata, &region);
+ dns_name_init(&name, NULL);
+ dns_name_fromregion(&name, &region);
+ if (!dns_name_ishostname(&name, false)) {
+ if (bad != NULL) {
+ dns_name_clone(&name, bad);
+ }
+ return (false);
+ }
+ }
+ return (true);
+}
+
+static int
+casecompare_ptr(ARGS_COMPARE) {
+ return (compare_ptr(rdata1, rdata2));
+}
+#endif /* RDATA_GENERIC_PTR_12_C */
diff --git a/lib/dns/rdata/generic/ptr_12.h b/lib/dns/rdata/generic/ptr_12.h
new file mode 100644
index 0000000..8951eb3
--- /dev/null
+++ b/lib/dns/rdata/generic/ptr_12.h
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/* */
+#pragma once
+
+typedef struct dns_rdata_ptr {
+ dns_rdatacommon_t common;
+ isc_mem_t *mctx;
+ dns_name_t ptr;
+} dns_rdata_ptr_t;
diff --git a/lib/dns/rdata/generic/rkey_57.c b/lib/dns/rdata/generic/rkey_57.c
new file mode 100644
index 0000000..20cd0f0
--- /dev/null
+++ b/lib/dns/rdata/generic/rkey_57.c
@@ -0,0 +1,161 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#ifndef RDATA_GENERIC_RKEY_57_C
+#define RDATA_GENERIC_RKEY_57_C
+
+#define RRTYPE_RKEY_ATTRIBUTES 0
+
+static isc_result_t
+fromtext_rkey(ARGS_FROMTEXT) {
+ REQUIRE(type == dns_rdatatype_rkey);
+
+ return (generic_fromtext_key(CALL_FROMTEXT));
+}
+
+static isc_result_t
+totext_rkey(ARGS_TOTEXT) {
+ REQUIRE(rdata != NULL);
+ REQUIRE(rdata->type == dns_rdatatype_rkey);
+
+ return (generic_totext_key(CALL_TOTEXT));
+}
+
+static isc_result_t
+fromwire_rkey(ARGS_FROMWIRE) {
+ REQUIRE(type == dns_rdatatype_rkey);
+
+ return (generic_fromwire_key(CALL_FROMWIRE));
+}
+
+static isc_result_t
+towire_rkey(ARGS_TOWIRE) {
+ isc_region_t sr;
+
+ REQUIRE(rdata != NULL);
+ REQUIRE(rdata->type == dns_rdatatype_rkey);
+ REQUIRE(rdata->length != 0);
+
+ UNUSED(cctx);
+
+ dns_rdata_toregion(rdata, &sr);
+ return (mem_tobuffer(target, sr.base, sr.length));
+}
+
+static int
+compare_rkey(ARGS_COMPARE) {
+ isc_region_t r1;
+ isc_region_t r2;
+
+ REQUIRE(rdata1 != NULL);
+ REQUIRE(rdata2 != NULL);
+ REQUIRE(rdata1->type == rdata2->type);
+ REQUIRE(rdata1->rdclass == rdata2->rdclass);
+ REQUIRE(rdata1->type == dns_rdatatype_rkey);
+ REQUIRE(rdata1->length != 0);
+ REQUIRE(rdata2->length != 0);
+
+ dns_rdata_toregion(rdata1, &r1);
+ dns_rdata_toregion(rdata2, &r2);
+ return (isc_region_compare(&r1, &r2));
+}
+
+static isc_result_t
+fromstruct_rkey(ARGS_FROMSTRUCT) {
+ REQUIRE(type == dns_rdatatype_rkey);
+
+ return (generic_fromstruct_key(CALL_FROMSTRUCT));
+}
+
+static isc_result_t
+tostruct_rkey(ARGS_TOSTRUCT) {
+ dns_rdata_rkey_t *rkey = target;
+
+ REQUIRE(rkey != NULL);
+ REQUIRE(rdata != NULL);
+ REQUIRE(rdata->type == dns_rdatatype_rkey);
+
+ rkey->common.rdclass = rdata->rdclass;
+ rkey->common.rdtype = rdata->type;
+ ISC_LINK_INIT(&rkey->common, link);
+
+ return (generic_tostruct_key(CALL_TOSTRUCT));
+}
+
+static void
+freestruct_rkey(ARGS_FREESTRUCT) {
+ dns_rdata_rkey_t *rkey = (dns_rdata_rkey_t *)source;
+
+ REQUIRE(rkey != NULL);
+ REQUIRE(rkey->common.rdtype == dns_rdatatype_rkey);
+
+ generic_freestruct_key(source);
+}
+
+static isc_result_t
+additionaldata_rkey(ARGS_ADDLDATA) {
+ REQUIRE(rdata->type == dns_rdatatype_rkey);
+
+ UNUSED(rdata);
+ UNUSED(owner);
+ UNUSED(add);
+ UNUSED(arg);
+
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+digest_rkey(ARGS_DIGEST) {
+ isc_region_t r;
+
+ REQUIRE(rdata != NULL);
+ REQUIRE(rdata->type == dns_rdatatype_rkey);
+
+ dns_rdata_toregion(rdata, &r);
+
+ return ((digest)(arg, &r));
+}
+
+static bool
+checkowner_rkey(ARGS_CHECKOWNER) {
+ REQUIRE(type == dns_rdatatype_rkey);
+
+ UNUSED(name);
+ UNUSED(type);
+ UNUSED(rdclass);
+ UNUSED(wildcard);
+
+ return (true);
+}
+
+static bool
+checknames_rkey(ARGS_CHECKNAMES) {
+ REQUIRE(rdata != NULL);
+ REQUIRE(rdata->type == dns_rdatatype_rkey);
+
+ UNUSED(rdata);
+ UNUSED(owner);
+ UNUSED(bad);
+
+ return (true);
+}
+
+static int
+casecompare_rkey(ARGS_COMPARE) {
+ /*
+ * Treat ALG 253 (private DNS) subtype name case sensitively.
+ */
+ return (compare_rkey(rdata1, rdata2));
+}
+
+#endif /* RDATA_GENERIC_RKEY_57_C */
diff --git a/lib/dns/rdata/generic/rkey_57.h b/lib/dns/rdata/generic/rkey_57.h
new file mode 100644
index 0000000..3538be0
--- /dev/null
+++ b/lib/dns/rdata/generic/rkey_57.h
@@ -0,0 +1,16 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#pragma once
+
+typedef struct dns_rdata_key dns_rdata_rkey_t;
diff --git a/lib/dns/rdata/generic/rp_17.c b/lib/dns/rdata/generic/rp_17.c
new file mode 100644
index 0000000..62080c6
--- /dev/null
+++ b/lib/dns/rdata/generic/rp_17.c
@@ -0,0 +1,310 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/* RFC1183 */
+
+#ifndef RDATA_GENERIC_RP_17_C
+#define RDATA_GENERIC_RP_17_C
+
+#define RRTYPE_RP_ATTRIBUTES (0)
+
+static isc_result_t
+fromtext_rp(ARGS_FROMTEXT) {
+ isc_token_t token;
+ dns_name_t name;
+ isc_buffer_t buffer;
+ int i;
+ bool ok;
+
+ REQUIRE(type == dns_rdatatype_rp);
+
+ UNUSED(type);
+ UNUSED(rdclass);
+ UNUSED(callbacks);
+
+ if (origin == NULL) {
+ origin = dns_rootname;
+ }
+
+ for (i = 0; i < 2; i++) {
+ RETERR(isc_lex_getmastertoken(lexer, &token,
+ isc_tokentype_string, false));
+ dns_name_init(&name, NULL);
+ buffer_fromregion(&buffer, &token.value.as_region);
+ RETTOK(dns_name_fromtext(&name, &buffer, origin, options,
+ target));
+ ok = true;
+ if ((options & DNS_RDATA_CHECKNAMES) != 0 && i == 0) {
+ ok = dns_name_ismailbox(&name);
+ }
+ if (!ok && (options & DNS_RDATA_CHECKNAMESFAIL) != 0) {
+ RETTOK(DNS_R_BADNAME);
+ }
+ if (!ok && callbacks != NULL) {
+ warn_badname(&name, lexer, callbacks);
+ }
+ }
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+totext_rp(ARGS_TOTEXT) {
+ isc_region_t region;
+ dns_name_t rmail;
+ dns_name_t email;
+ dns_name_t prefix;
+ bool sub;
+
+ REQUIRE(rdata->type == dns_rdatatype_rp);
+ REQUIRE(rdata->length != 0);
+
+ dns_name_init(&rmail, NULL);
+ dns_name_init(&email, NULL);
+ dns_name_init(&prefix, NULL);
+
+ dns_rdata_toregion(rdata, &region);
+
+ dns_name_fromregion(&rmail, &region);
+ isc_region_consume(&region, rmail.length);
+
+ dns_name_fromregion(&email, &region);
+ isc_region_consume(&region, email.length);
+
+ sub = name_prefix(&rmail, tctx->origin, &prefix);
+ RETERR(dns_name_totext(&prefix, sub, target));
+
+ RETERR(str_totext(" ", target));
+
+ sub = name_prefix(&email, tctx->origin, &prefix);
+ return (dns_name_totext(&prefix, sub, target));
+}
+
+static isc_result_t
+fromwire_rp(ARGS_FROMWIRE) {
+ dns_name_t rmail;
+ dns_name_t email;
+
+ REQUIRE(type == dns_rdatatype_rp);
+
+ UNUSED(type);
+ UNUSED(rdclass);
+
+ dns_decompress_setmethods(dctx, DNS_COMPRESS_NONE);
+
+ dns_name_init(&rmail, NULL);
+ dns_name_init(&email, NULL);
+
+ RETERR(dns_name_fromwire(&rmail, source, dctx, options, target));
+ return (dns_name_fromwire(&email, source, dctx, options, target));
+}
+
+static isc_result_t
+towire_rp(ARGS_TOWIRE) {
+ isc_region_t region;
+ dns_name_t rmail;
+ dns_name_t email;
+ dns_offsets_t roffsets;
+ dns_offsets_t eoffsets;
+
+ REQUIRE(rdata->type == dns_rdatatype_rp);
+ REQUIRE(rdata->length != 0);
+
+ dns_compress_setmethods(cctx, DNS_COMPRESS_NONE);
+ dns_name_init(&rmail, roffsets);
+ dns_name_init(&email, eoffsets);
+
+ dns_rdata_toregion(rdata, &region);
+
+ dns_name_fromregion(&rmail, &region);
+ isc_region_consume(&region, rmail.length);
+
+ RETERR(dns_name_towire(&rmail, cctx, target));
+
+ dns_name_fromregion(&rmail, &region);
+ isc_region_consume(&region, rmail.length);
+
+ return (dns_name_towire(&rmail, cctx, target));
+}
+
+static int
+compare_rp(ARGS_COMPARE) {
+ isc_region_t region1;
+ isc_region_t region2;
+ dns_name_t name1;
+ dns_name_t name2;
+ int order;
+
+ REQUIRE(rdata1->type == rdata2->type);
+ REQUIRE(rdata1->rdclass == rdata2->rdclass);
+ REQUIRE(rdata1->type == dns_rdatatype_rp);
+ REQUIRE(rdata1->length != 0);
+ REQUIRE(rdata2->length != 0);
+
+ dns_name_init(&name1, NULL);
+ dns_name_init(&name2, NULL);
+
+ dns_rdata_toregion(rdata1, &region1);
+ dns_rdata_toregion(rdata2, &region2);
+
+ dns_name_fromregion(&name1, &region1);
+ dns_name_fromregion(&name2, &region2);
+
+ order = dns_name_rdatacompare(&name1, &name2);
+ if (order != 0) {
+ return (order);
+ }
+
+ isc_region_consume(&region1, name_length(&name1));
+ isc_region_consume(&region2, name_length(&name2));
+
+ dns_name_init(&name1, NULL);
+ dns_name_init(&name2, NULL);
+
+ dns_name_fromregion(&name1, &region1);
+ dns_name_fromregion(&name2, &region2);
+
+ return (dns_name_rdatacompare(&name1, &name2));
+}
+
+static isc_result_t
+fromstruct_rp(ARGS_FROMSTRUCT) {
+ dns_rdata_rp_t *rp = source;
+ isc_region_t region;
+
+ REQUIRE(type == dns_rdatatype_rp);
+ REQUIRE(rp != NULL);
+ REQUIRE(rp->common.rdtype == type);
+ REQUIRE(rp->common.rdclass == rdclass);
+
+ UNUSED(type);
+ UNUSED(rdclass);
+
+ dns_name_toregion(&rp->mail, &region);
+ RETERR(isc_buffer_copyregion(target, &region));
+ dns_name_toregion(&rp->text, &region);
+ return (isc_buffer_copyregion(target, &region));
+}
+
+static isc_result_t
+tostruct_rp(ARGS_TOSTRUCT) {
+ isc_region_t region;
+ dns_rdata_rp_t *rp = target;
+ dns_name_t name;
+
+ REQUIRE(rdata->type == dns_rdatatype_rp);
+ REQUIRE(rp != NULL);
+ REQUIRE(rdata->length != 0);
+
+ rp->common.rdclass = rdata->rdclass;
+ rp->common.rdtype = rdata->type;
+ ISC_LINK_INIT(&rp->common, link);
+
+ dns_name_init(&name, NULL);
+ dns_rdata_toregion(rdata, &region);
+ dns_name_fromregion(&name, &region);
+ dns_name_init(&rp->mail, NULL);
+ name_duporclone(&name, mctx, &rp->mail);
+ isc_region_consume(&region, name_length(&name));
+ dns_name_fromregion(&name, &region);
+ dns_name_init(&rp->text, NULL);
+ name_duporclone(&name, mctx, &rp->text);
+ rp->mctx = mctx;
+ return (ISC_R_SUCCESS);
+}
+
+static void
+freestruct_rp(ARGS_FREESTRUCT) {
+ dns_rdata_rp_t *rp = source;
+
+ REQUIRE(rp != NULL);
+ REQUIRE(rp->common.rdtype == dns_rdatatype_rp);
+
+ if (rp->mctx == NULL) {
+ return;
+ }
+
+ dns_name_free(&rp->mail, rp->mctx);
+ dns_name_free(&rp->text, rp->mctx);
+ rp->mctx = NULL;
+}
+
+static isc_result_t
+additionaldata_rp(ARGS_ADDLDATA) {
+ REQUIRE(rdata->type == dns_rdatatype_rp);
+
+ UNUSED(rdata);
+ UNUSED(owner);
+ UNUSED(add);
+ UNUSED(arg);
+
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+digest_rp(ARGS_DIGEST) {
+ isc_region_t r;
+ dns_name_t name;
+
+ REQUIRE(rdata->type == dns_rdatatype_rp);
+
+ dns_rdata_toregion(rdata, &r);
+ dns_name_init(&name, NULL);
+
+ dns_name_fromregion(&name, &r);
+ RETERR(dns_name_digest(&name, digest, arg));
+ isc_region_consume(&r, name_length(&name));
+
+ dns_name_init(&name, NULL);
+ dns_name_fromregion(&name, &r);
+
+ return (dns_name_digest(&name, digest, arg));
+}
+
+static bool
+checkowner_rp(ARGS_CHECKOWNER) {
+ REQUIRE(type == dns_rdatatype_rp);
+
+ UNUSED(name);
+ UNUSED(type);
+ UNUSED(rdclass);
+ UNUSED(wildcard);
+
+ return (true);
+}
+
+static bool
+checknames_rp(ARGS_CHECKNAMES) {
+ isc_region_t region;
+ dns_name_t name;
+
+ REQUIRE(rdata->type == dns_rdatatype_rp);
+
+ UNUSED(owner);
+
+ dns_rdata_toregion(rdata, &region);
+ dns_name_init(&name, NULL);
+ dns_name_fromregion(&name, &region);
+ if (!dns_name_ismailbox(&name)) {
+ if (bad != NULL) {
+ dns_name_clone(&name, bad);
+ }
+ return (false);
+ }
+ return (true);
+}
+
+static int
+casecompare_rp(ARGS_COMPARE) {
+ return (compare_rp(rdata1, rdata2));
+}
+#endif /* RDATA_GENERIC_RP_17_C */
diff --git a/lib/dns/rdata/generic/rp_17.h b/lib/dns/rdata/generic/rp_17.h
new file mode 100644
index 0000000..d27ffb9
--- /dev/null
+++ b/lib/dns/rdata/generic/rp_17.h
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#pragma once
+
+/*!
+ * \brief Per RFC1183 */
+
+typedef struct dns_rdata_rp {
+ dns_rdatacommon_t common;
+ isc_mem_t *mctx;
+ dns_name_t mail;
+ dns_name_t text;
+} dns_rdata_rp_t;
diff --git a/lib/dns/rdata/generic/rrsig_46.c b/lib/dns/rdata/generic/rrsig_46.c
new file mode 100644
index 0000000..553c9cf
--- /dev/null
+++ b/lib/dns/rdata/generic/rrsig_46.c
@@ -0,0 +1,640 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/* RFC2535 */
+
+#ifndef RDATA_GENERIC_RRSIG_46_C
+#define RDATA_GENERIC_RRSIG_46_C
+
+#define RRTYPE_RRSIG_ATTRIBUTES \
+ (DNS_RDATATYPEATTR_DNSSEC | DNS_RDATATYPEATTR_ZONECUTAUTH | \
+ DNS_RDATATYPEATTR_ATCNAME)
+
+static isc_result_t
+fromtext_rrsig(ARGS_FROMTEXT) {
+ isc_token_t token;
+ unsigned char c;
+ long i;
+ dns_rdatatype_t covered;
+ char *e;
+ isc_result_t result;
+ dns_name_t name;
+ isc_buffer_t buffer;
+ uint32_t time_signed, time_expire;
+
+ REQUIRE(type == dns_rdatatype_rrsig);
+
+ UNUSED(type);
+ UNUSED(rdclass);
+ UNUSED(callbacks);
+
+ /*
+ * Type covered.
+ */
+ RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_string,
+ false));
+ result = dns_rdatatype_fromtext(&covered, &token.value.as_textregion);
+ if (result != ISC_R_SUCCESS && result != ISC_R_NOTIMPLEMENTED) {
+ i = strtol(DNS_AS_STR(token), &e, 10);
+ if (i < 0 || i > 65535) {
+ RETTOK(ISC_R_RANGE);
+ }
+ if (*e != 0) {
+ RETTOK(result);
+ }
+ covered = (dns_rdatatype_t)i;
+ }
+ RETERR(uint16_tobuffer(covered, target));
+
+ /*
+ * Algorithm.
+ */
+ RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_string,
+ false));
+ RETTOK(dns_secalg_fromtext(&c, &token.value.as_textregion));
+ RETERR(mem_tobuffer(target, &c, 1));
+
+ /*
+ * Labels.
+ */
+ RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_number,
+ false));
+ if (token.value.as_ulong > 0xffU) {
+ RETTOK(ISC_R_RANGE);
+ }
+ c = (unsigned char)token.value.as_ulong;
+ RETERR(mem_tobuffer(target, &c, 1));
+
+ /*
+ * Original ttl.
+ */
+ RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_number,
+ false));
+ RETERR(uint32_tobuffer(token.value.as_ulong, target));
+
+ /*
+ * Signature expiration.
+ */
+ RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_string,
+ false));
+ if (strlen(DNS_AS_STR(token)) <= 10U && *DNS_AS_STR(token) != '-' &&
+ *DNS_AS_STR(token) != '+')
+ {
+ char *end;
+ unsigned long u;
+ uint64_t u64;
+
+ u64 = u = strtoul(DNS_AS_STR(token), &end, 10);
+ if (u == ULONG_MAX || *end != 0) {
+ RETTOK(DNS_R_SYNTAX);
+ }
+ if (u64 > 0xffffffffUL) {
+ RETTOK(ISC_R_RANGE);
+ }
+ time_expire = u;
+ } else {
+ RETTOK(dns_time32_fromtext(DNS_AS_STR(token), &time_expire));
+ }
+ RETERR(uint32_tobuffer(time_expire, target));
+
+ /*
+ * Time signed.
+ */
+ RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_string,
+ false));
+ if (strlen(DNS_AS_STR(token)) <= 10U && *DNS_AS_STR(token) != '-' &&
+ *DNS_AS_STR(token) != '+')
+ {
+ char *end;
+ unsigned long u;
+ uint64_t u64;
+
+ u64 = u = strtoul(DNS_AS_STR(token), &end, 10);
+ if (u == ULONG_MAX || *end != 0) {
+ RETTOK(DNS_R_SYNTAX);
+ }
+ if (u64 > 0xffffffffUL) {
+ RETTOK(ISC_R_RANGE);
+ }
+ time_signed = u;
+ } else {
+ RETTOK(dns_time32_fromtext(DNS_AS_STR(token), &time_signed));
+ }
+ RETERR(uint32_tobuffer(time_signed, target));
+
+ /*
+ * Key footprint.
+ */
+ RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_number,
+ false));
+ RETERR(uint16_tobuffer(token.value.as_ulong, target));
+
+ /*
+ * Signer.
+ */
+ RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_string,
+ false));
+ dns_name_init(&name, NULL);
+ buffer_fromregion(&buffer, &token.value.as_region);
+ if (origin == NULL) {
+ origin = dns_rootname;
+ }
+ RETTOK(dns_name_fromtext(&name, &buffer, origin, options, target));
+
+ /*
+ * Sig.
+ */
+ return (isc_base64_tobuffer(lexer, target, -2));
+}
+
+static isc_result_t
+totext_rrsig(ARGS_TOTEXT) {
+ isc_region_t sr;
+ char buf[sizeof("4294967295")]; /* Also TYPE65000. */
+ dns_rdatatype_t covered;
+ unsigned long ttl;
+ unsigned long when;
+ unsigned long exp;
+ unsigned long foot;
+ dns_name_t name;
+
+ REQUIRE(rdata->type == dns_rdatatype_rrsig);
+ REQUIRE(rdata->length != 0);
+
+ dns_rdata_toregion(rdata, &sr);
+
+ /*
+ * Type covered.
+ */
+ covered = uint16_fromregion(&sr);
+ isc_region_consume(&sr, 2);
+ /*
+ * XXXAG We should have something like dns_rdatatype_isknown()
+ * that does the right thing with type 0.
+ */
+ if (dns_rdatatype_isknown(covered) && covered != 0) {
+ RETERR(dns_rdatatype_totext(covered, target));
+ } else {
+ snprintf(buf, sizeof(buf), "TYPE%u", covered);
+ RETERR(str_totext(buf, target));
+ }
+ RETERR(str_totext(" ", target));
+
+ /*
+ * Algorithm.
+ */
+ snprintf(buf, sizeof(buf), "%u", sr.base[0]);
+ isc_region_consume(&sr, 1);
+ RETERR(str_totext(buf, target));
+ RETERR(str_totext(" ", target));
+
+ /*
+ * Labels.
+ */
+ snprintf(buf, sizeof(buf), "%u", sr.base[0]);
+ isc_region_consume(&sr, 1);
+ RETERR(str_totext(buf, target));
+ RETERR(str_totext(" ", target));
+
+ /*
+ * Ttl.
+ */
+ ttl = uint32_fromregion(&sr);
+ isc_region_consume(&sr, 4);
+ snprintf(buf, sizeof(buf), "%lu", ttl);
+ RETERR(str_totext(buf, target));
+
+ if ((tctx->flags & DNS_STYLEFLAG_MULTILINE) != 0) {
+ RETERR(str_totext(" (", target));
+ }
+ RETERR(str_totext(tctx->linebreak, target));
+
+ /*
+ * Sig exp.
+ */
+ exp = uint32_fromregion(&sr);
+ isc_region_consume(&sr, 4);
+ RETERR(dns_time32_totext(exp, target));
+ RETERR(str_totext(" ", target));
+
+ /*
+ * Time signed.
+ */
+ when = uint32_fromregion(&sr);
+ isc_region_consume(&sr, 4);
+ RETERR(dns_time32_totext(when, target));
+ RETERR(str_totext(" ", target));
+
+ /*
+ * Footprint.
+ */
+ foot = uint16_fromregion(&sr);
+ isc_region_consume(&sr, 2);
+ snprintf(buf, sizeof(buf), "%lu", foot);
+ RETERR(str_totext(buf, target));
+ RETERR(str_totext(" ", target));
+
+ /*
+ * Signer.
+ */
+ dns_name_init(&name, NULL);
+ dns_name_fromregion(&name, &sr);
+ isc_region_consume(&sr, name_length(&name));
+ RETERR(dns_name_totext(&name, false, target));
+
+ /*
+ * Sig.
+ */
+ RETERR(str_totext(tctx->linebreak, target));
+ if ((tctx->flags & DNS_STYLEFLAG_NOCRYPTO) == 0) {
+ if (tctx->width == 0) { /* No splitting */
+ RETERR(isc_base64_totext(&sr, 60, "", target));
+ } else {
+ RETERR(isc_base64_totext(&sr, tctx->width - 2,
+ tctx->linebreak, target));
+ }
+ } else {
+ RETERR(str_totext("[omitted]", target));
+ }
+
+ if ((tctx->flags & DNS_STYLEFLAG_MULTILINE) != 0) {
+ RETERR(str_totext(" )", target));
+ }
+
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+fromwire_rrsig(ARGS_FROMWIRE) {
+ isc_region_t sr;
+ dns_name_t name;
+
+ REQUIRE(type == dns_rdatatype_rrsig);
+
+ UNUSED(type);
+ UNUSED(rdclass);
+
+ dns_decompress_setmethods(dctx, DNS_COMPRESS_NONE);
+
+ isc_buffer_activeregion(source, &sr);
+ /*
+ * type covered: 2
+ * algorithm: 1
+ * labels: 1
+ * original ttl: 4
+ * signature expiration: 4
+ * time signed: 4
+ * key footprint: 2
+ */
+ if (sr.length < 18) {
+ return (ISC_R_UNEXPECTEDEND);
+ }
+
+ isc_buffer_forward(source, 18);
+ RETERR(mem_tobuffer(target, sr.base, 18));
+
+ /*
+ * Signer.
+ */
+ dns_name_init(&name, NULL);
+ RETERR(dns_name_fromwire(&name, source, dctx, options, target));
+
+ /*
+ * Sig.
+ */
+ isc_buffer_activeregion(source, &sr);
+ if (sr.length < 1) {
+ return (DNS_R_FORMERR);
+ }
+ isc_buffer_forward(source, sr.length);
+ return (mem_tobuffer(target, sr.base, sr.length));
+}
+
+static isc_result_t
+towire_rrsig(ARGS_TOWIRE) {
+ isc_region_t sr;
+ dns_name_t name;
+ dns_offsets_t offsets;
+
+ REQUIRE(rdata->type == dns_rdatatype_rrsig);
+ REQUIRE(rdata->length != 0);
+
+ dns_compress_setmethods(cctx, DNS_COMPRESS_NONE);
+ dns_rdata_toregion(rdata, &sr);
+ /*
+ * type covered: 2
+ * algorithm: 1
+ * labels: 1
+ * original ttl: 4
+ * signature expiration: 4
+ * time signed: 4
+ * key footprint: 2
+ */
+ RETERR(mem_tobuffer(target, sr.base, 18));
+ isc_region_consume(&sr, 18);
+
+ /*
+ * Signer.
+ */
+ dns_name_init(&name, offsets);
+ dns_name_fromregion(&name, &sr);
+ isc_region_consume(&sr, name_length(&name));
+ RETERR(dns_name_towire(&name, cctx, target));
+
+ /*
+ * Signature.
+ */
+ return (mem_tobuffer(target, sr.base, sr.length));
+}
+
+static int
+compare_rrsig(ARGS_COMPARE) {
+ isc_region_t r1;
+ isc_region_t r2;
+
+ REQUIRE(rdata1->type == rdata2->type);
+ REQUIRE(rdata1->rdclass == rdata2->rdclass);
+ REQUIRE(rdata1->type == dns_rdatatype_rrsig);
+ REQUIRE(rdata1->length != 0);
+ REQUIRE(rdata2->length != 0);
+
+ dns_rdata_toregion(rdata1, &r1);
+ dns_rdata_toregion(rdata2, &r2);
+ return (isc_region_compare(&r1, &r2));
+}
+
+static isc_result_t
+fromstruct_rrsig(ARGS_FROMSTRUCT) {
+ dns_rdata_rrsig_t *sig = source;
+
+ REQUIRE(type == dns_rdatatype_rrsig);
+ REQUIRE(sig != NULL);
+ REQUIRE(sig->common.rdtype == type);
+ REQUIRE(sig->common.rdclass == rdclass);
+ REQUIRE(sig->signature != NULL || sig->siglen == 0);
+
+ UNUSED(type);
+ UNUSED(rdclass);
+
+ /*
+ * Type covered.
+ */
+ RETERR(uint16_tobuffer(sig->covered, target));
+
+ /*
+ * Algorithm.
+ */
+ RETERR(uint8_tobuffer(sig->algorithm, target));
+
+ /*
+ * Labels.
+ */
+ RETERR(uint8_tobuffer(sig->labels, target));
+
+ /*
+ * Original TTL.
+ */
+ RETERR(uint32_tobuffer(sig->originalttl, target));
+
+ /*
+ * Expire time.
+ */
+ RETERR(uint32_tobuffer(sig->timeexpire, target));
+
+ /*
+ * Time signed.
+ */
+ RETERR(uint32_tobuffer(sig->timesigned, target));
+
+ /*
+ * Key ID.
+ */
+ RETERR(uint16_tobuffer(sig->keyid, target));
+
+ /*
+ * Signer name.
+ */
+ RETERR(name_tobuffer(&sig->signer, target));
+
+ /*
+ * Signature.
+ */
+ return (mem_tobuffer(target, sig->signature, sig->siglen));
+}
+
+static isc_result_t
+tostruct_rrsig(ARGS_TOSTRUCT) {
+ isc_region_t sr;
+ dns_rdata_rrsig_t *sig = target;
+ dns_name_t signer;
+
+ REQUIRE(rdata->type == dns_rdatatype_rrsig);
+ REQUIRE(sig != NULL);
+ REQUIRE(rdata->length != 0);
+
+ sig->common.rdclass = rdata->rdclass;
+ sig->common.rdtype = rdata->type;
+ ISC_LINK_INIT(&sig->common, link);
+
+ dns_rdata_toregion(rdata, &sr);
+
+ /*
+ * Type covered.
+ */
+ sig->covered = uint16_fromregion(&sr);
+ isc_region_consume(&sr, 2);
+
+ /*
+ * Algorithm.
+ */
+ sig->algorithm = uint8_fromregion(&sr);
+ isc_region_consume(&sr, 1);
+
+ /*
+ * Labels.
+ */
+ sig->labels = uint8_fromregion(&sr);
+ isc_region_consume(&sr, 1);
+
+ /*
+ * Original TTL.
+ */
+ sig->originalttl = uint32_fromregion(&sr);
+ isc_region_consume(&sr, 4);
+
+ /*
+ * Expire time.
+ */
+ sig->timeexpire = uint32_fromregion(&sr);
+ isc_region_consume(&sr, 4);
+
+ /*
+ * Time signed.
+ */
+ sig->timesigned = uint32_fromregion(&sr);
+ isc_region_consume(&sr, 4);
+
+ /*
+ * Key ID.
+ */
+ sig->keyid = uint16_fromregion(&sr);
+ isc_region_consume(&sr, 2);
+
+ dns_name_init(&signer, NULL);
+ dns_name_fromregion(&signer, &sr);
+ dns_name_init(&sig->signer, NULL);
+ name_duporclone(&signer, mctx, &sig->signer);
+ isc_region_consume(&sr, name_length(&sig->signer));
+
+ /*
+ * Signature.
+ */
+ sig->siglen = sr.length;
+ sig->signature = mem_maybedup(mctx, sr.base, sig->siglen);
+ if (sig->signature == NULL) {
+ goto cleanup;
+ }
+
+ sig->mctx = mctx;
+ return (ISC_R_SUCCESS);
+
+cleanup:
+ if (mctx != NULL) {
+ dns_name_free(&sig->signer, mctx);
+ }
+ return (ISC_R_NOMEMORY);
+}
+
+static void
+freestruct_rrsig(ARGS_FREESTRUCT) {
+ dns_rdata_rrsig_t *sig = (dns_rdata_rrsig_t *)source;
+
+ REQUIRE(sig != NULL);
+ REQUIRE(sig->common.rdtype == dns_rdatatype_rrsig);
+
+ if (sig->mctx == NULL) {
+ return;
+ }
+
+ dns_name_free(&sig->signer, sig->mctx);
+ if (sig->signature != NULL) {
+ isc_mem_free(sig->mctx, sig->signature);
+ }
+ sig->mctx = NULL;
+}
+
+static isc_result_t
+additionaldata_rrsig(ARGS_ADDLDATA) {
+ REQUIRE(rdata->type == dns_rdatatype_rrsig);
+
+ UNUSED(rdata);
+ UNUSED(owner);
+ UNUSED(add);
+ UNUSED(arg);
+
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+digest_rrsig(ARGS_DIGEST) {
+ REQUIRE(rdata->type == dns_rdatatype_rrsig);
+
+ UNUSED(rdata);
+ UNUSED(digest);
+ UNUSED(arg);
+
+ return (ISC_R_NOTIMPLEMENTED);
+}
+
+static dns_rdatatype_t
+covers_rrsig(dns_rdata_t *rdata) {
+ dns_rdatatype_t type;
+ isc_region_t r;
+
+ REQUIRE(rdata->type == dns_rdatatype_rrsig);
+
+ dns_rdata_toregion(rdata, &r);
+ type = uint16_fromregion(&r);
+
+ return (type);
+}
+
+static bool
+checkowner_rrsig(ARGS_CHECKOWNER) {
+ REQUIRE(type == dns_rdatatype_rrsig);
+
+ UNUSED(name);
+ UNUSED(type);
+ UNUSED(rdclass);
+ UNUSED(wildcard);
+
+ return (true);
+}
+
+static bool
+checknames_rrsig(ARGS_CHECKNAMES) {
+ REQUIRE(rdata->type == dns_rdatatype_rrsig);
+
+ UNUSED(rdata);
+ UNUSED(owner);
+ UNUSED(bad);
+
+ return (true);
+}
+
+static int
+casecompare_rrsig(ARGS_COMPARE) {
+ isc_region_t r1;
+ isc_region_t r2;
+ dns_name_t name1;
+ dns_name_t name2;
+ int order;
+
+ REQUIRE(rdata1->type == rdata2->type);
+ REQUIRE(rdata1->rdclass == rdata2->rdclass);
+ REQUIRE(rdata1->type == dns_rdatatype_rrsig);
+ REQUIRE(rdata1->length != 0);
+ REQUIRE(rdata2->length != 0);
+
+ dns_rdata_toregion(rdata1, &r1);
+ dns_rdata_toregion(rdata2, &r2);
+
+ INSIST(r1.length > 18);
+ INSIST(r2.length > 18);
+ r1.length = 18;
+ r2.length = 18;
+ order = isc_region_compare(&r1, &r2);
+ if (order != 0) {
+ return (order);
+ }
+
+ dns_name_init(&name1, NULL);
+ dns_name_init(&name2, NULL);
+ dns_rdata_toregion(rdata1, &r1);
+ dns_rdata_toregion(rdata2, &r2);
+ isc_region_consume(&r1, 18);
+ isc_region_consume(&r2, 18);
+ dns_name_fromregion(&name1, &r1);
+ dns_name_fromregion(&name2, &r2);
+ order = dns_name_rdatacompare(&name1, &name2);
+ if (order != 0) {
+ return (order);
+ }
+
+ isc_region_consume(&r1, name_length(&name1));
+ isc_region_consume(&r2, name_length(&name2));
+
+ return (isc_region_compare(&r1, &r2));
+}
+
+#endif /* RDATA_GENERIC_RRSIG_46_C */
diff --git a/lib/dns/rdata/generic/rrsig_46.h b/lib/dns/rdata/generic/rrsig_46.h
new file mode 100644
index 0000000..0869c2d
--- /dev/null
+++ b/lib/dns/rdata/generic/rrsig_46.h
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#pragma once
+
+/*!
+ * \brief Per RFC2535 */
+typedef struct dns_rdata_rrsig {
+ dns_rdatacommon_t common;
+ isc_mem_t *mctx;
+ dns_rdatatype_t covered;
+ dns_secalg_t algorithm;
+ uint8_t labels;
+ uint32_t originalttl;
+ uint32_t timeexpire;
+ uint32_t timesigned;
+ uint16_t keyid;
+ dns_name_t signer;
+ uint16_t siglen;
+ unsigned char *signature;
+} dns_rdata_rrsig_t;
diff --git a/lib/dns/rdata/generic/rt_21.c b/lib/dns/rdata/generic/rt_21.c
new file mode 100644
index 0000000..10a003f
--- /dev/null
+++ b/lib/dns/rdata/generic/rt_21.c
@@ -0,0 +1,324 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/* RFC1183 */
+
+#ifndef RDATA_GENERIC_RT_21_C
+#define RDATA_GENERIC_RT_21_C
+
+#define RRTYPE_RT_ATTRIBUTES (0)
+
+static isc_result_t
+fromtext_rt(ARGS_FROMTEXT) {
+ isc_token_t token;
+ dns_name_t name;
+ isc_buffer_t buffer;
+ bool ok;
+
+ REQUIRE(type == dns_rdatatype_rt);
+
+ UNUSED(type);
+ UNUSED(rdclass);
+ UNUSED(callbacks);
+
+ RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_number,
+ false));
+ if (token.value.as_ulong > 0xffffU) {
+ RETTOK(ISC_R_RANGE);
+ }
+ RETERR(uint16_tobuffer(token.value.as_ulong, target));
+
+ RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_string,
+ false));
+
+ dns_name_init(&name, NULL);
+ buffer_fromregion(&buffer, &token.value.as_region);
+ if (origin == NULL) {
+ origin = dns_rootname;
+ }
+ RETTOK(dns_name_fromtext(&name, &buffer, origin, options, target));
+ ok = true;
+ if ((options & DNS_RDATA_CHECKNAMES) != 0) {
+ ok = dns_name_ishostname(&name, false);
+ }
+ if (!ok && (options & DNS_RDATA_CHECKNAMESFAIL) != 0) {
+ RETTOK(DNS_R_BADNAME);
+ }
+ if (!ok && callbacks != NULL) {
+ warn_badname(&name, lexer, callbacks);
+ }
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+totext_rt(ARGS_TOTEXT) {
+ isc_region_t region;
+ dns_name_t name;
+ dns_name_t prefix;
+ bool sub;
+ char buf[sizeof("64000")];
+ unsigned short num;
+
+ REQUIRE(rdata->type == dns_rdatatype_rt);
+ REQUIRE(rdata->length != 0);
+
+ dns_name_init(&name, NULL);
+ dns_name_init(&prefix, NULL);
+
+ dns_rdata_toregion(rdata, &region);
+ num = uint16_fromregion(&region);
+ isc_region_consume(&region, 2);
+ snprintf(buf, sizeof(buf), "%u", num);
+ RETERR(str_totext(buf, target));
+ RETERR(str_totext(" ", target));
+ dns_name_fromregion(&name, &region);
+ sub = name_prefix(&name, tctx->origin, &prefix);
+ return (dns_name_totext(&prefix, sub, target));
+}
+
+static isc_result_t
+fromwire_rt(ARGS_FROMWIRE) {
+ dns_name_t name;
+ isc_region_t sregion;
+ isc_region_t tregion;
+
+ REQUIRE(type == dns_rdatatype_rt);
+
+ UNUSED(type);
+ UNUSED(rdclass);
+
+ dns_decompress_setmethods(dctx, DNS_COMPRESS_NONE);
+
+ dns_name_init(&name, NULL);
+
+ isc_buffer_activeregion(source, &sregion);
+ isc_buffer_availableregion(target, &tregion);
+ if (tregion.length < 2) {
+ return (ISC_R_NOSPACE);
+ }
+ if (sregion.length < 2) {
+ return (ISC_R_UNEXPECTEDEND);
+ }
+ memmove(tregion.base, sregion.base, 2);
+ isc_buffer_forward(source, 2);
+ isc_buffer_add(target, 2);
+ return (dns_name_fromwire(&name, source, dctx, options, target));
+}
+
+static isc_result_t
+towire_rt(ARGS_TOWIRE) {
+ dns_name_t name;
+ dns_offsets_t offsets;
+ isc_region_t region;
+ isc_region_t tr;
+
+ REQUIRE(rdata->type == dns_rdatatype_rt);
+ REQUIRE(rdata->length != 0);
+
+ dns_compress_setmethods(cctx, DNS_COMPRESS_NONE);
+ isc_buffer_availableregion(target, &tr);
+ dns_rdata_toregion(rdata, &region);
+ if (tr.length < 2) {
+ return (ISC_R_NOSPACE);
+ }
+ memmove(tr.base, region.base, 2);
+ isc_region_consume(&region, 2);
+ isc_buffer_add(target, 2);
+
+ dns_name_init(&name, offsets);
+ dns_name_fromregion(&name, &region);
+
+ return (dns_name_towire(&name, cctx, target));
+}
+
+static int
+compare_rt(ARGS_COMPARE) {
+ dns_name_t name1;
+ dns_name_t name2;
+ isc_region_t region1;
+ isc_region_t region2;
+ int order;
+
+ REQUIRE(rdata1->type == rdata2->type);
+ REQUIRE(rdata1->rdclass == rdata2->rdclass);
+ REQUIRE(rdata1->type == dns_rdatatype_rt);
+ REQUIRE(rdata1->length != 0);
+ REQUIRE(rdata2->length != 0);
+
+ order = memcmp(rdata1->data, rdata2->data, 2);
+ if (order != 0) {
+ return (order < 0 ? -1 : 1);
+ }
+
+ dns_name_init(&name1, NULL);
+ dns_name_init(&name2, NULL);
+
+ dns_rdata_toregion(rdata1, &region1);
+ dns_rdata_toregion(rdata2, &region2);
+
+ isc_region_consume(&region1, 2);
+ isc_region_consume(&region2, 2);
+
+ dns_name_fromregion(&name1, &region1);
+ dns_name_fromregion(&name2, &region2);
+
+ return (dns_name_rdatacompare(&name1, &name2));
+}
+
+static isc_result_t
+fromstruct_rt(ARGS_FROMSTRUCT) {
+ dns_rdata_rt_t *rt = source;
+ isc_region_t region;
+
+ REQUIRE(type == dns_rdatatype_rt);
+ REQUIRE(rt != NULL);
+ REQUIRE(rt->common.rdtype == type);
+ REQUIRE(rt->common.rdclass == rdclass);
+
+ UNUSED(type);
+ UNUSED(rdclass);
+
+ RETERR(uint16_tobuffer(rt->preference, target));
+ dns_name_toregion(&rt->host, &region);
+ return (isc_buffer_copyregion(target, &region));
+}
+
+static isc_result_t
+tostruct_rt(ARGS_TOSTRUCT) {
+ isc_region_t region;
+ dns_rdata_rt_t *rt = target;
+ dns_name_t name;
+
+ REQUIRE(rdata->type == dns_rdatatype_rt);
+ REQUIRE(rt != NULL);
+ REQUIRE(rdata->length != 0);
+
+ rt->common.rdclass = rdata->rdclass;
+ rt->common.rdtype = rdata->type;
+ ISC_LINK_INIT(&rt->common, link);
+
+ dns_name_init(&name, NULL);
+ dns_rdata_toregion(rdata, &region);
+ rt->preference = uint16_fromregion(&region);
+ isc_region_consume(&region, 2);
+ dns_name_fromregion(&name, &region);
+ dns_name_init(&rt->host, NULL);
+ name_duporclone(&name, mctx, &rt->host);
+
+ rt->mctx = mctx;
+ return (ISC_R_SUCCESS);
+}
+
+static void
+freestruct_rt(ARGS_FREESTRUCT) {
+ dns_rdata_rt_t *rt = source;
+
+ REQUIRE(rt != NULL);
+ REQUIRE(rt->common.rdtype == dns_rdatatype_rt);
+
+ if (rt->mctx == NULL) {
+ return;
+ }
+
+ dns_name_free(&rt->host, rt->mctx);
+ rt->mctx = NULL;
+}
+
+static isc_result_t
+additionaldata_rt(ARGS_ADDLDATA) {
+ dns_name_t name;
+ dns_offsets_t offsets;
+ isc_region_t region;
+ isc_result_t result;
+
+ REQUIRE(rdata->type == dns_rdatatype_rt);
+
+ UNUSED(owner);
+
+ dns_name_init(&name, offsets);
+ dns_rdata_toregion(rdata, &region);
+ isc_region_consume(&region, 2);
+ dns_name_fromregion(&name, &region);
+
+ result = (add)(arg, &name, dns_rdatatype_x25, NULL);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+ result = (add)(arg, &name, dns_rdatatype_isdn, NULL);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+ return ((add)(arg, &name, dns_rdatatype_a, NULL));
+}
+
+static isc_result_t
+digest_rt(ARGS_DIGEST) {
+ isc_region_t r1, r2;
+ isc_result_t result;
+ dns_name_t name;
+
+ REQUIRE(rdata->type == dns_rdatatype_rt);
+
+ dns_rdata_toregion(rdata, &r1);
+ r2 = r1;
+ isc_region_consume(&r2, 2);
+ r1.length = 2;
+ result = (digest)(arg, &r1);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+ dns_name_init(&name, NULL);
+ dns_name_fromregion(&name, &r2);
+ return (dns_name_digest(&name, digest, arg));
+}
+
+static bool
+checkowner_rt(ARGS_CHECKOWNER) {
+ REQUIRE(type == dns_rdatatype_rt);
+
+ UNUSED(name);
+ UNUSED(type);
+ UNUSED(rdclass);
+ UNUSED(wildcard);
+
+ return (true);
+}
+
+static bool
+checknames_rt(ARGS_CHECKNAMES) {
+ isc_region_t region;
+ dns_name_t name;
+
+ REQUIRE(rdata->type == dns_rdatatype_rt);
+
+ UNUSED(owner);
+
+ dns_rdata_toregion(rdata, &region);
+ isc_region_consume(&region, 2);
+ dns_name_init(&name, NULL);
+ dns_name_fromregion(&name, &region);
+ if (!dns_name_ishostname(&name, false)) {
+ if (bad != NULL) {
+ dns_name_clone(&name, bad);
+ }
+ return (false);
+ }
+ return (true);
+}
+
+static int
+casecompare_rt(ARGS_COMPARE) {
+ return (compare_rt(rdata1, rdata2));
+}
+
+#endif /* RDATA_GENERIC_RT_21_C */
diff --git a/lib/dns/rdata/generic/rt_21.h b/lib/dns/rdata/generic/rt_21.h
new file mode 100644
index 0000000..7df33d1
--- /dev/null
+++ b/lib/dns/rdata/generic/rt_21.h
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#pragma once
+
+/*!
+ * \brief Per RFC1183 */
+
+typedef struct dns_rdata_rt {
+ dns_rdatacommon_t common;
+ isc_mem_t *mctx;
+ uint16_t preference;
+ dns_name_t host;
+} dns_rdata_rt_t;
diff --git a/lib/dns/rdata/generic/sig_24.c b/lib/dns/rdata/generic/sig_24.c
new file mode 100644
index 0000000..85075c3
--- /dev/null
+++ b/lib/dns/rdata/generic/sig_24.c
@@ -0,0 +1,591 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/* RFC2535 */
+
+#ifndef RDATA_GENERIC_SIG_24_C
+#define RDATA_GENERIC_SIG_24_C
+
+#define RRTYPE_SIG_ATTRIBUTES (0)
+
+static isc_result_t
+fromtext_sig(ARGS_FROMTEXT) {
+ isc_token_t token;
+ unsigned char c;
+ long i;
+ dns_rdatatype_t covered;
+ char *e;
+ isc_result_t result;
+ dns_name_t name;
+ isc_buffer_t buffer;
+ uint32_t time_signed, time_expire;
+
+ REQUIRE(type == dns_rdatatype_sig);
+
+ UNUSED(type);
+ UNUSED(rdclass);
+ UNUSED(callbacks);
+
+ /*
+ * Type covered.
+ */
+ RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_string,
+ false));
+ result = dns_rdatatype_fromtext(&covered, &token.value.as_textregion);
+ if (result != ISC_R_SUCCESS && result != ISC_R_NOTIMPLEMENTED) {
+ i = strtol(DNS_AS_STR(token), &e, 10);
+ if (i < 0 || i > 65535) {
+ RETTOK(ISC_R_RANGE);
+ }
+ if (*e != 0) {
+ RETTOK(result);
+ }
+ covered = (dns_rdatatype_t)i;
+ }
+ RETERR(uint16_tobuffer(covered, target));
+
+ /*
+ * Algorithm.
+ */
+ RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_string,
+ false));
+ RETTOK(dns_secalg_fromtext(&c, &token.value.as_textregion));
+ RETERR(mem_tobuffer(target, &c, 1));
+
+ /*
+ * Labels.
+ */
+ RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_number,
+ false));
+ if (token.value.as_ulong > 0xffU) {
+ RETTOK(ISC_R_RANGE);
+ }
+ c = (unsigned char)token.value.as_ulong;
+ RETERR(mem_tobuffer(target, &c, 1));
+
+ /*
+ * Original ttl.
+ */
+ RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_number,
+ false));
+ RETERR(uint32_tobuffer(token.value.as_ulong, target));
+
+ /*
+ * Signature expiration.
+ */
+ RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_string,
+ false));
+ RETTOK(dns_time32_fromtext(DNS_AS_STR(token), &time_expire));
+ RETERR(uint32_tobuffer(time_expire, target));
+
+ /*
+ * Time signed.
+ */
+ RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_string,
+ false));
+ RETTOK(dns_time32_fromtext(DNS_AS_STR(token), &time_signed));
+ RETERR(uint32_tobuffer(time_signed, target));
+
+ /*
+ * Key footprint.
+ */
+ RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_number,
+ false));
+ RETERR(uint16_tobuffer(token.value.as_ulong, target));
+
+ /*
+ * Signer.
+ */
+ RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_string,
+ false));
+ dns_name_init(&name, NULL);
+ buffer_fromregion(&buffer, &token.value.as_region);
+ if (origin == NULL) {
+ origin = dns_rootname;
+ }
+ RETTOK(dns_name_fromtext(&name, &buffer, origin, options, target));
+
+ /*
+ * Sig.
+ */
+ return (isc_base64_tobuffer(lexer, target, -2));
+}
+
+static isc_result_t
+totext_sig(ARGS_TOTEXT) {
+ isc_region_t sr;
+ char buf[sizeof("4294967295")];
+ dns_rdatatype_t covered;
+ unsigned long ttl;
+ unsigned long when;
+ unsigned long exp;
+ unsigned long foot;
+ dns_name_t name;
+ dns_name_t prefix;
+ bool sub;
+
+ REQUIRE(rdata->type == dns_rdatatype_sig);
+ REQUIRE(rdata->length != 0);
+
+ dns_rdata_toregion(rdata, &sr);
+
+ /*
+ * Type covered.
+ */
+ covered = uint16_fromregion(&sr);
+ isc_region_consume(&sr, 2);
+ /*
+ * XXXAG We should have something like dns_rdatatype_isknown()
+ * that does the right thing with type 0.
+ */
+ if (dns_rdatatype_isknown(covered) && covered != 0) {
+ RETERR(dns_rdatatype_totext(covered, target));
+ } else {
+ snprintf(buf, sizeof(buf), "%u", covered);
+ RETERR(str_totext(buf, target));
+ }
+ RETERR(str_totext(" ", target));
+
+ /*
+ * Algorithm.
+ */
+ snprintf(buf, sizeof(buf), "%u", sr.base[0]);
+ isc_region_consume(&sr, 1);
+ RETERR(str_totext(buf, target));
+ RETERR(str_totext(" ", target));
+
+ /*
+ * Labels.
+ */
+ snprintf(buf, sizeof(buf), "%u", sr.base[0]);
+ isc_region_consume(&sr, 1);
+ RETERR(str_totext(buf, target));
+ RETERR(str_totext(" ", target));
+
+ /*
+ * Ttl.
+ */
+ ttl = uint32_fromregion(&sr);
+ isc_region_consume(&sr, 4);
+ snprintf(buf, sizeof(buf), "%lu", ttl);
+ RETERR(str_totext(buf, target));
+ RETERR(str_totext(" ", target));
+
+ /*
+ * Sig exp.
+ */
+ exp = uint32_fromregion(&sr);
+ isc_region_consume(&sr, 4);
+ RETERR(dns_time32_totext(exp, target));
+
+ if ((tctx->flags & DNS_STYLEFLAG_MULTILINE) != 0) {
+ RETERR(str_totext(" (", target));
+ }
+ RETERR(str_totext(tctx->linebreak, target));
+
+ /*
+ * Time signed.
+ */
+ when = uint32_fromregion(&sr);
+ isc_region_consume(&sr, 4);
+ RETERR(dns_time32_totext(when, target));
+ RETERR(str_totext(" ", target));
+
+ /*
+ * Footprint.
+ */
+ foot = uint16_fromregion(&sr);
+ isc_region_consume(&sr, 2);
+ snprintf(buf, sizeof(buf), "%lu", foot);
+ RETERR(str_totext(buf, target));
+ RETERR(str_totext(" ", target));
+
+ /*
+ * Signer.
+ */
+ dns_name_init(&name, NULL);
+ dns_name_init(&prefix, NULL);
+ dns_name_fromregion(&name, &sr);
+ isc_region_consume(&sr, name_length(&name));
+ sub = name_prefix(&name, tctx->origin, &prefix);
+ RETERR(dns_name_totext(&prefix, sub, target));
+
+ /*
+ * Sig.
+ */
+ RETERR(str_totext(tctx->linebreak, target));
+ if (tctx->width == 0) { /* No splitting */
+ RETERR(isc_base64_totext(&sr, 60, "", target));
+ } else {
+ RETERR(isc_base64_totext(&sr, tctx->width - 2, tctx->linebreak,
+ target));
+ }
+ if ((tctx->flags & DNS_STYLEFLAG_MULTILINE) != 0) {
+ RETERR(str_totext(" )", target));
+ }
+
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+fromwire_sig(ARGS_FROMWIRE) {
+ isc_region_t sr;
+ dns_name_t name;
+
+ REQUIRE(type == dns_rdatatype_sig);
+
+ UNUSED(type);
+ UNUSED(rdclass);
+
+ dns_decompress_setmethods(dctx, DNS_COMPRESS_NONE);
+
+ isc_buffer_activeregion(source, &sr);
+ /*
+ * type covered: 2
+ * algorithm: 1
+ * labels: 1
+ * original ttl: 4
+ * signature expiration: 4
+ * time signed: 4
+ * key footprint: 2
+ */
+ if (sr.length < 18) {
+ return (ISC_R_UNEXPECTEDEND);
+ }
+
+ isc_buffer_forward(source, 18);
+ RETERR(mem_tobuffer(target, sr.base, 18));
+
+ /*
+ * Signer.
+ */
+ dns_name_init(&name, NULL);
+ RETERR(dns_name_fromwire(&name, source, dctx, options, target));
+
+ /*
+ * Sig.
+ */
+ isc_buffer_activeregion(source, &sr);
+ if (sr.length == 0) {
+ return (ISC_R_UNEXPECTEDEND);
+ }
+ isc_buffer_forward(source, sr.length);
+ return (mem_tobuffer(target, sr.base, sr.length));
+}
+
+static isc_result_t
+towire_sig(ARGS_TOWIRE) {
+ isc_region_t sr;
+ dns_name_t name;
+ dns_offsets_t offsets;
+
+ REQUIRE(rdata->type == dns_rdatatype_sig);
+ REQUIRE(rdata->length != 0);
+
+ dns_compress_setmethods(cctx, DNS_COMPRESS_NONE);
+ dns_rdata_toregion(rdata, &sr);
+ /*
+ * type covered: 2
+ * algorithm: 1
+ * labels: 1
+ * original ttl: 4
+ * signature expiration: 4
+ * time signed: 4
+ * key footprint: 2
+ */
+ RETERR(mem_tobuffer(target, sr.base, 18));
+ isc_region_consume(&sr, 18);
+
+ /*
+ * Signer.
+ */
+ dns_name_init(&name, offsets);
+ dns_name_fromregion(&name, &sr);
+ isc_region_consume(&sr, name_length(&name));
+ RETERR(dns_name_towire(&name, cctx, target));
+
+ /*
+ * Signature.
+ */
+ return (mem_tobuffer(target, sr.base, sr.length));
+}
+
+static int
+compare_sig(ARGS_COMPARE) {
+ isc_region_t r1;
+ isc_region_t r2;
+ dns_name_t name1;
+ dns_name_t name2;
+ int order;
+
+ REQUIRE(rdata1->type == rdata2->type);
+ REQUIRE(rdata1->rdclass == rdata2->rdclass);
+ REQUIRE(rdata1->type == dns_rdatatype_sig);
+ REQUIRE(rdata1->length != 0);
+ REQUIRE(rdata2->length != 0);
+
+ dns_rdata_toregion(rdata1, &r1);
+ dns_rdata_toregion(rdata2, &r2);
+
+ INSIST(r1.length > 18);
+ INSIST(r2.length > 18);
+ r1.length = 18;
+ r2.length = 18;
+ order = isc_region_compare(&r1, &r2);
+ if (order != 0) {
+ return (order);
+ }
+
+ dns_name_init(&name1, NULL);
+ dns_name_init(&name2, NULL);
+ dns_rdata_toregion(rdata1, &r1);
+ dns_rdata_toregion(rdata2, &r2);
+ isc_region_consume(&r1, 18);
+ isc_region_consume(&r2, 18);
+ dns_name_fromregion(&name1, &r1);
+ dns_name_fromregion(&name2, &r2);
+ order = dns_name_rdatacompare(&name1, &name2);
+ if (order != 0) {
+ return (order);
+ }
+
+ isc_region_consume(&r1, name_length(&name1));
+ isc_region_consume(&r2, name_length(&name2));
+
+ return (isc_region_compare(&r1, &r2));
+}
+
+static isc_result_t
+fromstruct_sig(ARGS_FROMSTRUCT) {
+ dns_rdata_sig_t *sig = source;
+
+ REQUIRE(type == dns_rdatatype_sig);
+ REQUIRE(sig != NULL);
+ REQUIRE(sig->common.rdtype == type);
+ REQUIRE(sig->common.rdclass == rdclass);
+ REQUIRE(sig->signature != NULL || sig->siglen == 0);
+
+ UNUSED(type);
+ UNUSED(rdclass);
+
+ /*
+ * Type covered.
+ */
+ RETERR(uint16_tobuffer(sig->covered, target));
+
+ /*
+ * Algorithm.
+ */
+ RETERR(uint8_tobuffer(sig->algorithm, target));
+
+ /*
+ * Labels.
+ */
+ RETERR(uint8_tobuffer(sig->labels, target));
+
+ /*
+ * Original TTL.
+ */
+ RETERR(uint32_tobuffer(sig->originalttl, target));
+
+ /*
+ * Expire time.
+ */
+ RETERR(uint32_tobuffer(sig->timeexpire, target));
+
+ /*
+ * Time signed.
+ */
+ RETERR(uint32_tobuffer(sig->timesigned, target));
+
+ /*
+ * Key ID.
+ */
+ RETERR(uint16_tobuffer(sig->keyid, target));
+
+ /*
+ * Signer name.
+ */
+ RETERR(name_tobuffer(&sig->signer, target));
+
+ /*
+ * Signature.
+ */
+ return (mem_tobuffer(target, sig->signature, sig->siglen));
+}
+
+static isc_result_t
+tostruct_sig(ARGS_TOSTRUCT) {
+ isc_region_t sr;
+ dns_rdata_sig_t *sig = target;
+ dns_name_t signer;
+
+ REQUIRE(rdata->type == dns_rdatatype_sig);
+ REQUIRE(sig != NULL);
+ REQUIRE(rdata->length != 0);
+
+ sig->common.rdclass = rdata->rdclass;
+ sig->common.rdtype = rdata->type;
+ ISC_LINK_INIT(&sig->common, link);
+
+ dns_rdata_toregion(rdata, &sr);
+
+ /*
+ * Type covered.
+ */
+ sig->covered = uint16_fromregion(&sr);
+ isc_region_consume(&sr, 2);
+
+ /*
+ * Algorithm.
+ */
+ sig->algorithm = uint8_fromregion(&sr);
+ isc_region_consume(&sr, 1);
+
+ /*
+ * Labels.
+ */
+ sig->labels = uint8_fromregion(&sr);
+ isc_region_consume(&sr, 1);
+
+ /*
+ * Original TTL.
+ */
+ sig->originalttl = uint32_fromregion(&sr);
+ isc_region_consume(&sr, 4);
+
+ /*
+ * Expire time.
+ */
+ sig->timeexpire = uint32_fromregion(&sr);
+ isc_region_consume(&sr, 4);
+
+ /*
+ * Time signed.
+ */
+ sig->timesigned = uint32_fromregion(&sr);
+ isc_region_consume(&sr, 4);
+
+ /*
+ * Key ID.
+ */
+ sig->keyid = uint16_fromregion(&sr);
+ isc_region_consume(&sr, 2);
+
+ dns_name_init(&signer, NULL);
+ dns_name_fromregion(&signer, &sr);
+ dns_name_init(&sig->signer, NULL);
+ name_duporclone(&signer, mctx, &sig->signer);
+ isc_region_consume(&sr, name_length(&sig->signer));
+
+ /*
+ * Signature.
+ */
+ sig->siglen = sr.length;
+ sig->signature = mem_maybedup(mctx, sr.base, sig->siglen);
+ if (sig->signature == NULL) {
+ goto cleanup;
+ }
+
+ sig->mctx = mctx;
+ return (ISC_R_SUCCESS);
+
+cleanup:
+ if (mctx != NULL) {
+ dns_name_free(&sig->signer, mctx);
+ }
+ return (ISC_R_NOMEMORY);
+}
+
+static void
+freestruct_sig(ARGS_FREESTRUCT) {
+ dns_rdata_sig_t *sig = (dns_rdata_sig_t *)source;
+
+ REQUIRE(sig != NULL);
+ REQUIRE(sig->common.rdtype == dns_rdatatype_sig);
+
+ if (sig->mctx == NULL) {
+ return;
+ }
+
+ dns_name_free(&sig->signer, sig->mctx);
+ if (sig->signature != NULL) {
+ isc_mem_free(sig->mctx, sig->signature);
+ }
+ sig->mctx = NULL;
+}
+
+static isc_result_t
+additionaldata_sig(ARGS_ADDLDATA) {
+ REQUIRE(rdata->type == dns_rdatatype_sig);
+
+ UNUSED(rdata);
+ UNUSED(owner);
+ UNUSED(add);
+ UNUSED(arg);
+
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+digest_sig(ARGS_DIGEST) {
+ REQUIRE(rdata->type == dns_rdatatype_sig);
+
+ UNUSED(rdata);
+ UNUSED(digest);
+ UNUSED(arg);
+
+ return (ISC_R_NOTIMPLEMENTED);
+}
+
+static dns_rdatatype_t
+covers_sig(dns_rdata_t *rdata) {
+ dns_rdatatype_t type;
+ isc_region_t r;
+
+ REQUIRE(rdata->type == dns_rdatatype_sig);
+
+ dns_rdata_toregion(rdata, &r);
+ type = uint16_fromregion(&r);
+
+ return (type);
+}
+
+static bool
+checkowner_sig(ARGS_CHECKOWNER) {
+ REQUIRE(type == dns_rdatatype_sig);
+
+ UNUSED(name);
+ UNUSED(type);
+ UNUSED(rdclass);
+ UNUSED(wildcard);
+
+ return (true);
+}
+
+static bool
+checknames_sig(ARGS_CHECKNAMES) {
+ REQUIRE(rdata->type == dns_rdatatype_sig);
+
+ UNUSED(rdata);
+ UNUSED(owner);
+ UNUSED(bad);
+
+ return (true);
+}
+
+static int
+casecompare_sig(ARGS_COMPARE) {
+ return (compare_sig(rdata1, rdata2));
+}
+#endif /* RDATA_GENERIC_SIG_24_C */
diff --git a/lib/dns/rdata/generic/sig_24.h b/lib/dns/rdata/generic/sig_24.h
new file mode 100644
index 0000000..40bad22
--- /dev/null
+++ b/lib/dns/rdata/generic/sig_24.h
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#pragma once
+
+/*!
+ * \brief Per RFC2535 */
+
+typedef struct dns_rdata_sig_t {
+ dns_rdatacommon_t common;
+ isc_mem_t *mctx;
+ dns_rdatatype_t covered;
+ dns_secalg_t algorithm;
+ uint8_t labels;
+ uint32_t originalttl;
+ uint32_t timeexpire;
+ uint32_t timesigned;
+ uint16_t keyid;
+ dns_name_t signer;
+ uint16_t siglen;
+ unsigned char *signature;
+} dns_rdata_sig_t;
diff --git a/lib/dns/rdata/generic/sink_40.c b/lib/dns/rdata/generic/sink_40.c
new file mode 100644
index 0000000..8d76212
--- /dev/null
+++ b/lib/dns/rdata/generic/sink_40.c
@@ -0,0 +1,292 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#ifndef RDATA_GENERIC_SINK_40_C
+#define RDATA_GENERIC_SINK_40_C
+
+#include <dst/dst.h>
+
+#define RRTYPE_SINK_ATTRIBUTES (0)
+
+static isc_result_t
+fromtext_sink(ARGS_FROMTEXT) {
+ isc_token_t token;
+
+ REQUIRE(type == dns_rdatatype_sink);
+
+ UNUSED(type);
+ UNUSED(rdclass);
+ UNUSED(origin);
+ UNUSED(options);
+ UNUSED(callbacks);
+
+ /* meaning */
+ RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_number,
+ false));
+ if (token.value.as_ulong > 0xffU) {
+ RETTOK(ISC_R_RANGE);
+ }
+ RETERR(uint8_tobuffer(token.value.as_ulong, target));
+
+ /* coding */
+ RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_number,
+ false));
+ if (token.value.as_ulong > 0xffU) {
+ RETTOK(ISC_R_RANGE);
+ }
+ RETERR(uint8_tobuffer(token.value.as_ulong, target));
+
+ /* subcoding */
+ RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_number,
+ false));
+ if (token.value.as_ulong > 0xffU) {
+ RETTOK(ISC_R_RANGE);
+ }
+ RETERR(uint8_tobuffer(token.value.as_ulong, target));
+
+ return (isc_base64_tobuffer(lexer, target, -1));
+}
+
+static isc_result_t
+totext_sink(ARGS_TOTEXT) {
+ isc_region_t sr;
+ char buf[sizeof("255 255 255")];
+ uint8_t meaning, coding, subcoding;
+
+ REQUIRE(rdata->type == dns_rdatatype_sink);
+ REQUIRE(rdata->length >= 3);
+
+ dns_rdata_toregion(rdata, &sr);
+
+ /* Meaning, Coding and Subcoding */
+ meaning = uint8_fromregion(&sr);
+ isc_region_consume(&sr, 1);
+ coding = uint8_fromregion(&sr);
+ isc_region_consume(&sr, 1);
+ subcoding = uint8_fromregion(&sr);
+ isc_region_consume(&sr, 1);
+ snprintf(buf, sizeof(buf), "%u %u %u", meaning, coding, subcoding);
+ RETERR(str_totext(buf, target));
+
+ if (sr.length == 0U) {
+ return (ISC_R_SUCCESS);
+ }
+
+ /* data */
+ if ((tctx->flags & DNS_STYLEFLAG_MULTILINE) != 0) {
+ RETERR(str_totext(" (", target));
+ }
+
+ RETERR(str_totext(tctx->linebreak, target));
+
+ if (tctx->width == 0) { /* No splitting */
+ RETERR(isc_base64_totext(&sr, 60, "", target));
+ } else {
+ RETERR(isc_base64_totext(&sr, tctx->width - 2, tctx->linebreak,
+ target));
+ }
+
+ if ((tctx->flags & DNS_STYLEFLAG_MULTILINE) != 0) {
+ RETERR(str_totext(" )", target));
+ }
+
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+fromwire_sink(ARGS_FROMWIRE) {
+ isc_region_t sr;
+
+ REQUIRE(type == dns_rdatatype_sink);
+
+ UNUSED(type);
+ UNUSED(rdclass);
+ UNUSED(dctx);
+ UNUSED(options);
+
+ isc_buffer_activeregion(source, &sr);
+ if (sr.length < 3) {
+ return (ISC_R_UNEXPECTEDEND);
+ }
+
+ RETERR(mem_tobuffer(target, sr.base, sr.length));
+ isc_buffer_forward(source, sr.length);
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+towire_sink(ARGS_TOWIRE) {
+ REQUIRE(rdata->type == dns_rdatatype_sink);
+ REQUIRE(rdata->length >= 3);
+
+ UNUSED(cctx);
+
+ return (mem_tobuffer(target, rdata->data, rdata->length));
+}
+
+static int
+compare_sink(ARGS_COMPARE) {
+ isc_region_t r1;
+ isc_region_t r2;
+
+ REQUIRE(rdata1->type == rdata2->type);
+ REQUIRE(rdata1->rdclass == rdata2->rdclass);
+ REQUIRE(rdata1->type == dns_rdatatype_sink);
+ REQUIRE(rdata1->length >= 3);
+ REQUIRE(rdata2->length >= 3);
+
+ dns_rdata_toregion(rdata1, &r1);
+ dns_rdata_toregion(rdata2, &r2);
+ return (isc_region_compare(&r1, &r2));
+}
+
+static isc_result_t
+fromstruct_sink(ARGS_FROMSTRUCT) {
+ dns_rdata_sink_t *sink = source;
+
+ REQUIRE(type == dns_rdatatype_sink);
+ REQUIRE(sink != NULL);
+ REQUIRE(sink->common.rdtype == type);
+ REQUIRE(sink->common.rdclass == rdclass);
+
+ UNUSED(type);
+ UNUSED(rdclass);
+
+ /* Meaning */
+ RETERR(uint8_tobuffer(sink->meaning, target));
+
+ /* Coding */
+ RETERR(uint8_tobuffer(sink->coding, target));
+
+ /* Subcoding */
+ RETERR(uint8_tobuffer(sink->subcoding, target));
+
+ /* Data */
+ return (mem_tobuffer(target, sink->data, sink->datalen));
+}
+
+static isc_result_t
+tostruct_sink(ARGS_TOSTRUCT) {
+ dns_rdata_sink_t *sink = target;
+ isc_region_t sr;
+
+ REQUIRE(rdata->type == dns_rdatatype_sink);
+ REQUIRE(sink != NULL);
+ REQUIRE(rdata->length >= 3);
+
+ sink->common.rdclass = rdata->rdclass;
+ sink->common.rdtype = rdata->type;
+ ISC_LINK_INIT(&sink->common, link);
+
+ dns_rdata_toregion(rdata, &sr);
+
+ /* Meaning */
+ if (sr.length < 1) {
+ return (ISC_R_UNEXPECTEDEND);
+ }
+ sink->meaning = uint8_fromregion(&sr);
+ isc_region_consume(&sr, 1);
+
+ /* Coding */
+ if (sr.length < 1) {
+ return (ISC_R_UNEXPECTEDEND);
+ }
+ sink->coding = uint8_fromregion(&sr);
+ isc_region_consume(&sr, 1);
+
+ /* Subcoding */
+ if (sr.length < 1) {
+ return (ISC_R_UNEXPECTEDEND);
+ }
+ sink->subcoding = uint8_fromregion(&sr);
+ isc_region_consume(&sr, 1);
+
+ /* Data */
+ sink->datalen = sr.length;
+ sink->data = mem_maybedup(mctx, sr.base, sink->datalen);
+ if (sink->data == NULL) {
+ return (ISC_R_NOMEMORY);
+ }
+
+ sink->mctx = mctx;
+ return (ISC_R_SUCCESS);
+}
+
+static void
+freestruct_sink(ARGS_FREESTRUCT) {
+ dns_rdata_sink_t *sink = (dns_rdata_sink_t *)source;
+
+ REQUIRE(sink != NULL);
+ REQUIRE(sink->common.rdtype == dns_rdatatype_sink);
+
+ if (sink->mctx == NULL) {
+ return;
+ }
+
+ if (sink->data != NULL) {
+ isc_mem_free(sink->mctx, sink->data);
+ }
+ sink->mctx = NULL;
+}
+
+static isc_result_t
+additionaldata_sink(ARGS_ADDLDATA) {
+ REQUIRE(rdata->type == dns_rdatatype_sink);
+
+ UNUSED(rdata);
+ UNUSED(owner);
+ UNUSED(add);
+ UNUSED(arg);
+
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+digest_sink(ARGS_DIGEST) {
+ isc_region_t r;
+
+ REQUIRE(rdata->type == dns_rdatatype_sink);
+
+ dns_rdata_toregion(rdata, &r);
+
+ return ((digest)(arg, &r));
+}
+
+static bool
+checkowner_sink(ARGS_CHECKOWNER) {
+ REQUIRE(type == dns_rdatatype_sink);
+
+ UNUSED(name);
+ UNUSED(type);
+ UNUSED(rdclass);
+ UNUSED(wildcard);
+
+ return (true);
+}
+
+static bool
+checknames_sink(ARGS_CHECKNAMES) {
+ REQUIRE(rdata->type == dns_rdatatype_sink);
+
+ UNUSED(rdata);
+ UNUSED(owner);
+ UNUSED(bad);
+
+ return (true);
+}
+
+static int
+casecompare_sink(ARGS_COMPARE) {
+ return (compare_sink(rdata1, rdata2));
+}
+#endif /* RDATA_GENERIC_SINK_40_C */
diff --git a/lib/dns/rdata/generic/sink_40.h b/lib/dns/rdata/generic/sink_40.h
new file mode 100644
index 0000000..e2615fd
--- /dev/null
+++ b/lib/dns/rdata/generic/sink_40.h
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#pragma once
+
+typedef struct dns_rdata_sink_t {
+ dns_rdatacommon_t common;
+ isc_mem_t *mctx;
+ uint8_t meaning;
+ uint8_t coding;
+ uint8_t subcoding;
+ uint16_t datalen;
+ unsigned char *data;
+} dns_rdata_sink_t;
diff --git a/lib/dns/rdata/generic/smimea_53.c b/lib/dns/rdata/generic/smimea_53.c
new file mode 100644
index 0000000..ce154a0
--- /dev/null
+++ b/lib/dns/rdata/generic/smimea_53.c
@@ -0,0 +1,153 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#ifndef RDATA_GENERIC_SMIMEA_53_C
+#define RDATA_GENERIC_SMIMEA_53_C
+
+#define RRTYPE_SMIMEA_ATTRIBUTES 0
+
+static isc_result_t
+fromtext_smimea(ARGS_FROMTEXT) {
+ REQUIRE(type == dns_rdatatype_smimea);
+
+ return (generic_fromtext_tlsa(CALL_FROMTEXT));
+}
+
+static isc_result_t
+totext_smimea(ARGS_TOTEXT) {
+ REQUIRE(rdata != NULL);
+ REQUIRE(rdata->type == dns_rdatatype_smimea);
+
+ return (generic_totext_tlsa(CALL_TOTEXT));
+}
+
+static isc_result_t
+fromwire_smimea(ARGS_FROMWIRE) {
+ REQUIRE(type == dns_rdatatype_smimea);
+
+ return (generic_fromwire_tlsa(CALL_FROMWIRE));
+}
+
+static isc_result_t
+towire_smimea(ARGS_TOWIRE) {
+ isc_region_t sr;
+
+ REQUIRE(rdata->type == dns_rdatatype_smimea);
+ REQUIRE(rdata->length != 0);
+
+ UNUSED(cctx);
+
+ dns_rdata_toregion(rdata, &sr);
+ return (mem_tobuffer(target, sr.base, sr.length));
+}
+
+static int
+compare_smimea(ARGS_COMPARE) {
+ isc_region_t r1;
+ isc_region_t r2;
+
+ REQUIRE(rdata1->type == rdata2->type);
+ REQUIRE(rdata1->rdclass == rdata2->rdclass);
+ REQUIRE(rdata1->type == dns_rdatatype_smimea);
+ REQUIRE(rdata1->length != 0);
+ REQUIRE(rdata2->length != 0);
+
+ dns_rdata_toregion(rdata1, &r1);
+ dns_rdata_toregion(rdata2, &r2);
+ return (isc_region_compare(&r1, &r2));
+}
+
+static isc_result_t
+fromstruct_smimea(ARGS_FROMSTRUCT) {
+ REQUIRE(type == dns_rdatatype_smimea);
+
+ return (generic_fromstruct_tlsa(CALL_FROMSTRUCT));
+}
+
+static isc_result_t
+tostruct_smimea(ARGS_TOSTRUCT) {
+ dns_rdata_smimea_t *smimea = target;
+
+ REQUIRE(rdata != NULL);
+ REQUIRE(rdata->type == dns_rdatatype_smimea);
+ REQUIRE(smimea != NULL);
+
+ smimea->common.rdclass = rdata->rdclass;
+ smimea->common.rdtype = rdata->type;
+ ISC_LINK_INIT(&smimea->common, link);
+
+ return (generic_tostruct_tlsa(CALL_TOSTRUCT));
+}
+
+static void
+freestruct_smimea(ARGS_FREESTRUCT) {
+ dns_rdata_smimea_t *smimea = source;
+
+ REQUIRE(smimea != NULL);
+ REQUIRE(smimea->common.rdtype == dns_rdatatype_smimea);
+
+ generic_freestruct_tlsa(source);
+}
+
+static isc_result_t
+additionaldata_smimea(ARGS_ADDLDATA) {
+ REQUIRE(rdata->type == dns_rdatatype_smimea);
+
+ UNUSED(rdata);
+ UNUSED(owner);
+ UNUSED(add);
+ UNUSED(arg);
+
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+digest_smimea(ARGS_DIGEST) {
+ isc_region_t r;
+
+ REQUIRE(rdata->type == dns_rdatatype_smimea);
+
+ dns_rdata_toregion(rdata, &r);
+
+ return ((digest)(arg, &r));
+}
+
+static bool
+checkowner_smimea(ARGS_CHECKOWNER) {
+ REQUIRE(type == dns_rdatatype_smimea);
+
+ UNUSED(name);
+ UNUSED(type);
+ UNUSED(rdclass);
+ UNUSED(wildcard);
+
+ return (true);
+}
+
+static bool
+checknames_smimea(ARGS_CHECKNAMES) {
+ REQUIRE(rdata->type == dns_rdatatype_smimea);
+
+ UNUSED(rdata);
+ UNUSED(owner);
+ UNUSED(bad);
+
+ return (true);
+}
+
+static int
+casecompare_smimea(ARGS_COMPARE) {
+ return (compare_smimea(rdata1, rdata2));
+}
+
+#endif /* RDATA_GENERIC_SMIMEA_53_C */
diff --git a/lib/dns/rdata/generic/smimea_53.h b/lib/dns/rdata/generic/smimea_53.h
new file mode 100644
index 0000000..108eb20
--- /dev/null
+++ b/lib/dns/rdata/generic/smimea_53.h
@@ -0,0 +1,16 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#pragma once
+
+typedef struct dns_rdata_tlsa dns_rdata_smimea_t;
diff --git a/lib/dns/rdata/generic/soa_6.c b/lib/dns/rdata/generic/soa_6.c
new file mode 100644
index 0000000..24b5278
--- /dev/null
+++ b/lib/dns/rdata/generic/soa_6.c
@@ -0,0 +1,443 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#ifndef RDATA_GENERIC_SOA_6_C
+#define RDATA_GENERIC_SOA_6_C
+
+#define RRTYPE_SOA_ATTRIBUTES (DNS_RDATATYPEATTR_SINGLETON)
+
+static isc_result_t
+fromtext_soa(ARGS_FROMTEXT) {
+ isc_token_t token;
+ dns_name_t name;
+ isc_buffer_t buffer;
+ int i;
+ uint32_t n;
+ bool ok;
+
+ REQUIRE(type == dns_rdatatype_soa);
+
+ UNUSED(type);
+ UNUSED(rdclass);
+ UNUSED(callbacks);
+
+ if (origin == NULL) {
+ origin = dns_rootname;
+ }
+
+ for (i = 0; i < 2; i++) {
+ RETERR(isc_lex_getmastertoken(lexer, &token,
+ isc_tokentype_string, false));
+
+ dns_name_init(&name, NULL);
+ buffer_fromregion(&buffer, &token.value.as_region);
+ RETTOK(dns_name_fromtext(&name, &buffer, origin, options,
+ target));
+ ok = true;
+ if ((options & DNS_RDATA_CHECKNAMES) != 0) {
+ switch (i) {
+ case 0:
+ ok = dns_name_ishostname(&name, false);
+ break;
+ case 1:
+ ok = dns_name_ismailbox(&name);
+ break;
+ }
+ }
+ if (!ok && (options & DNS_RDATA_CHECKNAMESFAIL) != 0) {
+ RETTOK(DNS_R_BADNAME);
+ }
+ if (!ok && callbacks != NULL) {
+ warn_badname(&name, lexer, callbacks);
+ }
+ }
+
+ RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_number,
+ false));
+ RETERR(uint32_tobuffer(token.value.as_ulong, target));
+
+ for (i = 0; i < 4; i++) {
+ RETERR(isc_lex_getmastertoken(lexer, &token,
+ isc_tokentype_string, false));
+ RETTOK(dns_counter_fromtext(&token.value.as_textregion, &n));
+ RETERR(uint32_tobuffer(n, target));
+ }
+
+ return (ISC_R_SUCCESS);
+}
+
+static const char *soa_fieldnames[5] = { "serial", "refresh", "retry", "expire",
+ "minimum" };
+
+static isc_result_t
+totext_soa(ARGS_TOTEXT) {
+ isc_region_t dregion;
+ dns_name_t mname;
+ dns_name_t rname;
+ dns_name_t prefix;
+ bool sub;
+ int i;
+ bool multiline;
+ bool comm;
+
+ REQUIRE(rdata->type == dns_rdatatype_soa);
+ REQUIRE(rdata->length != 0);
+
+ multiline = ((tctx->flags & DNS_STYLEFLAG_MULTILINE) != 0);
+ if (multiline) {
+ comm = ((tctx->flags & DNS_STYLEFLAG_RRCOMMENT) != 0);
+ } else {
+ comm = false;
+ }
+
+ dns_name_init(&mname, NULL);
+ dns_name_init(&rname, NULL);
+ dns_name_init(&prefix, NULL);
+
+ dns_rdata_toregion(rdata, &dregion);
+
+ dns_name_fromregion(&mname, &dregion);
+ isc_region_consume(&dregion, name_length(&mname));
+
+ dns_name_fromregion(&rname, &dregion);
+ isc_region_consume(&dregion, name_length(&rname));
+
+ sub = name_prefix(&mname, tctx->origin, &prefix);
+ RETERR(dns_name_totext(&prefix, sub, target));
+
+ RETERR(str_totext(" ", target));
+
+ sub = name_prefix(&rname, tctx->origin, &prefix);
+ RETERR(dns_name_totext(&prefix, sub, target));
+
+ if (multiline) {
+ RETERR(str_totext(" (", target));
+ }
+ RETERR(str_totext(tctx->linebreak, target));
+
+ for (i = 0; i < 5; i++) {
+ char buf[sizeof("0123456789 ; ")];
+ unsigned long num;
+ num = uint32_fromregion(&dregion);
+ isc_region_consume(&dregion, 4);
+ snprintf(buf, sizeof(buf), comm ? "%-10lu ; " : "%lu", num);
+ RETERR(str_totext(buf, target));
+ if (comm) {
+ RETERR(str_totext(soa_fieldnames[i], target));
+ /* Print times in week/day/hour/minute/second form */
+ if (i >= 1) {
+ RETERR(str_totext(" (", target));
+ RETERR(dns_ttl_totext(num, true, true, target));
+ RETERR(str_totext(")", target));
+ }
+ RETERR(str_totext(tctx->linebreak, target));
+ } else if (i < 4) {
+ RETERR(str_totext(tctx->linebreak, target));
+ }
+ }
+
+ if (multiline) {
+ RETERR(str_totext(")", target));
+ }
+
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+fromwire_soa(ARGS_FROMWIRE) {
+ dns_name_t mname;
+ dns_name_t rname;
+ isc_region_t sregion;
+ isc_region_t tregion;
+
+ REQUIRE(type == dns_rdatatype_soa);
+
+ UNUSED(type);
+ UNUSED(rdclass);
+
+ dns_decompress_setmethods(dctx, DNS_COMPRESS_GLOBAL14);
+
+ dns_name_init(&mname, NULL);
+ dns_name_init(&rname, NULL);
+
+ RETERR(dns_name_fromwire(&mname, source, dctx, options, target));
+ RETERR(dns_name_fromwire(&rname, source, dctx, options, target));
+
+ isc_buffer_activeregion(source, &sregion);
+ isc_buffer_availableregion(target, &tregion);
+
+ if (sregion.length < 20) {
+ return (ISC_R_UNEXPECTEDEND);
+ }
+ if (tregion.length < 20) {
+ return (ISC_R_NOSPACE);
+ }
+
+ memmove(tregion.base, sregion.base, 20);
+ isc_buffer_forward(source, 20);
+ isc_buffer_add(target, 20);
+
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+towire_soa(ARGS_TOWIRE) {
+ isc_region_t sregion;
+ isc_region_t tregion;
+ dns_name_t mname;
+ dns_name_t rname;
+ dns_offsets_t moffsets;
+ dns_offsets_t roffsets;
+
+ REQUIRE(rdata->type == dns_rdatatype_soa);
+ REQUIRE(rdata->length != 0);
+
+ dns_compress_setmethods(cctx, DNS_COMPRESS_GLOBAL14);
+
+ dns_name_init(&mname, moffsets);
+ dns_name_init(&rname, roffsets);
+
+ dns_rdata_toregion(rdata, &sregion);
+
+ dns_name_fromregion(&mname, &sregion);
+ isc_region_consume(&sregion, name_length(&mname));
+ RETERR(dns_name_towire(&mname, cctx, target));
+
+ dns_name_fromregion(&rname, &sregion);
+ isc_region_consume(&sregion, name_length(&rname));
+ RETERR(dns_name_towire(&rname, cctx, target));
+
+ isc_buffer_availableregion(target, &tregion);
+ if (tregion.length < 20) {
+ return (ISC_R_NOSPACE);
+ }
+
+ memmove(tregion.base, sregion.base, 20);
+ isc_buffer_add(target, 20);
+ return (ISC_R_SUCCESS);
+}
+
+static int
+compare_soa(ARGS_COMPARE) {
+ isc_region_t region1;
+ isc_region_t region2;
+ dns_name_t name1;
+ dns_name_t name2;
+ int order;
+
+ REQUIRE(rdata1->type == rdata2->type);
+ REQUIRE(rdata1->rdclass == rdata2->rdclass);
+ REQUIRE(rdata1->type == dns_rdatatype_soa);
+ REQUIRE(rdata1->length != 0);
+ REQUIRE(rdata2->length != 0);
+
+ dns_name_init(&name1, NULL);
+ dns_name_init(&name2, NULL);
+
+ dns_rdata_toregion(rdata1, &region1);
+ dns_rdata_toregion(rdata2, &region2);
+
+ dns_name_fromregion(&name1, &region1);
+ dns_name_fromregion(&name2, &region2);
+
+ order = dns_name_rdatacompare(&name1, &name2);
+ if (order != 0) {
+ return (order);
+ }
+
+ isc_region_consume(&region1, name_length(&name1));
+ isc_region_consume(&region2, name_length(&name2));
+
+ dns_name_init(&name1, NULL);
+ dns_name_init(&name2, NULL);
+
+ dns_name_fromregion(&name1, &region1);
+ dns_name_fromregion(&name2, &region2);
+
+ order = dns_name_rdatacompare(&name1, &name2);
+ if (order != 0) {
+ return (order);
+ }
+
+ isc_region_consume(&region1, name_length(&name1));
+ isc_region_consume(&region2, name_length(&name2));
+
+ return (isc_region_compare(&region1, &region2));
+}
+
+static isc_result_t
+fromstruct_soa(ARGS_FROMSTRUCT) {
+ dns_rdata_soa_t *soa = source;
+ isc_region_t region;
+
+ REQUIRE(type == dns_rdatatype_soa);
+ REQUIRE(soa != NULL);
+ REQUIRE(soa->common.rdtype == type);
+ REQUIRE(soa->common.rdclass == rdclass);
+
+ UNUSED(type);
+ UNUSED(rdclass);
+
+ dns_name_toregion(&soa->origin, &region);
+ RETERR(isc_buffer_copyregion(target, &region));
+ dns_name_toregion(&soa->contact, &region);
+ RETERR(isc_buffer_copyregion(target, &region));
+ RETERR(uint32_tobuffer(soa->serial, target));
+ RETERR(uint32_tobuffer(soa->refresh, target));
+ RETERR(uint32_tobuffer(soa->retry, target));
+ RETERR(uint32_tobuffer(soa->expire, target));
+ return (uint32_tobuffer(soa->minimum, target));
+}
+
+static isc_result_t
+tostruct_soa(ARGS_TOSTRUCT) {
+ isc_region_t region;
+ dns_rdata_soa_t *soa = target;
+ dns_name_t name;
+
+ REQUIRE(rdata->type == dns_rdatatype_soa);
+ REQUIRE(soa != NULL);
+ REQUIRE(rdata->length != 0);
+
+ soa->common.rdclass = rdata->rdclass;
+ soa->common.rdtype = rdata->type;
+ ISC_LINK_INIT(&soa->common, link);
+
+ dns_rdata_toregion(rdata, &region);
+
+ dns_name_init(&name, NULL);
+ dns_name_fromregion(&name, &region);
+ isc_region_consume(&region, name_length(&name));
+ dns_name_init(&soa->origin, NULL);
+ name_duporclone(&name, mctx, &soa->origin);
+
+ dns_name_fromregion(&name, &region);
+ isc_region_consume(&region, name_length(&name));
+ dns_name_init(&soa->contact, NULL);
+ name_duporclone(&name, mctx, &soa->contact);
+
+ soa->serial = uint32_fromregion(&region);
+ isc_region_consume(&region, 4);
+
+ soa->refresh = uint32_fromregion(&region);
+ isc_region_consume(&region, 4);
+
+ soa->retry = uint32_fromregion(&region);
+ isc_region_consume(&region, 4);
+
+ soa->expire = uint32_fromregion(&region);
+ isc_region_consume(&region, 4);
+
+ soa->minimum = uint32_fromregion(&region);
+
+ soa->mctx = mctx;
+ return (ISC_R_SUCCESS);
+}
+
+static void
+freestruct_soa(ARGS_FREESTRUCT) {
+ dns_rdata_soa_t *soa = source;
+
+ REQUIRE(soa != NULL);
+ REQUIRE(soa->common.rdtype == dns_rdatatype_soa);
+
+ if (soa->mctx == NULL) {
+ return;
+ }
+
+ dns_name_free(&soa->origin, soa->mctx);
+ dns_name_free(&soa->contact, soa->mctx);
+ soa->mctx = NULL;
+}
+
+static isc_result_t
+additionaldata_soa(ARGS_ADDLDATA) {
+ REQUIRE(rdata->type == dns_rdatatype_soa);
+
+ UNUSED(rdata);
+ UNUSED(owner);
+ UNUSED(add);
+ UNUSED(arg);
+
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+digest_soa(ARGS_DIGEST) {
+ isc_region_t r;
+ dns_name_t name;
+
+ REQUIRE(rdata->type == dns_rdatatype_soa);
+
+ dns_rdata_toregion(rdata, &r);
+
+ dns_name_init(&name, NULL);
+ dns_name_fromregion(&name, &r);
+ RETERR(dns_name_digest(&name, digest, arg));
+ isc_region_consume(&r, name_length(&name));
+
+ dns_name_init(&name, NULL);
+ dns_name_fromregion(&name, &r);
+ RETERR(dns_name_digest(&name, digest, arg));
+ isc_region_consume(&r, name_length(&name));
+
+ return ((digest)(arg, &r));
+}
+
+static bool
+checkowner_soa(ARGS_CHECKOWNER) {
+ REQUIRE(type == dns_rdatatype_soa);
+
+ UNUSED(name);
+ UNUSED(type);
+ UNUSED(rdclass);
+ UNUSED(wildcard);
+
+ return (true);
+}
+
+static bool
+checknames_soa(ARGS_CHECKNAMES) {
+ isc_region_t region;
+ dns_name_t name;
+
+ REQUIRE(rdata->type == dns_rdatatype_soa);
+
+ UNUSED(owner);
+
+ dns_rdata_toregion(rdata, &region);
+ dns_name_init(&name, NULL);
+ dns_name_fromregion(&name, &region);
+ if (!dns_name_ishostname(&name, false)) {
+ if (bad != NULL) {
+ dns_name_clone(&name, bad);
+ }
+ return (false);
+ }
+ isc_region_consume(&region, name_length(&name));
+ dns_name_fromregion(&name, &region);
+ if (!dns_name_ismailbox(&name)) {
+ if (bad != NULL) {
+ dns_name_clone(&name, bad);
+ }
+ return (false);
+ }
+ return (true);
+}
+
+static int
+casecompare_soa(ARGS_COMPARE) {
+ return (compare_soa(rdata1, rdata2));
+}
+
+#endif /* RDATA_GENERIC_SOA_6_C */
diff --git a/lib/dns/rdata/generic/soa_6.h b/lib/dns/rdata/generic/soa_6.h
new file mode 100644
index 0000000..c6071bc
--- /dev/null
+++ b/lib/dns/rdata/generic/soa_6.h
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/* */
+#pragma once
+
+typedef struct dns_rdata_soa {
+ dns_rdatacommon_t common;
+ isc_mem_t *mctx;
+ dns_name_t origin;
+ dns_name_t contact;
+ uint32_t serial; /*%< host order */
+ uint32_t refresh; /*%< host order */
+ uint32_t retry; /*%< host order */
+ uint32_t expire; /*%< host order */
+ uint32_t minimum; /*%< host order */
+} dns_rdata_soa_t;
diff --git a/lib/dns/rdata/generic/spf_99.c b/lib/dns/rdata/generic/spf_99.c
new file mode 100644
index 0000000..20f939b
--- /dev/null
+++ b/lib/dns/rdata/generic/spf_99.c
@@ -0,0 +1,146 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#ifndef RDATA_GENERIC_SPF_99_C
+#define RDATA_GENERIC_SPF_99_C
+
+#define RRTYPE_SPF_ATTRIBUTES (0)
+
+static isc_result_t
+fromtext_spf(ARGS_FROMTEXT) {
+ REQUIRE(type == dns_rdatatype_spf);
+
+ return (generic_fromtext_txt(CALL_FROMTEXT));
+}
+
+static isc_result_t
+totext_spf(ARGS_TOTEXT) {
+ REQUIRE(rdata != NULL);
+ REQUIRE(rdata->type == dns_rdatatype_spf);
+
+ return (generic_totext_txt(CALL_TOTEXT));
+}
+
+static isc_result_t
+fromwire_spf(ARGS_FROMWIRE) {
+ REQUIRE(type == dns_rdatatype_spf);
+
+ return (generic_fromwire_txt(CALL_FROMWIRE));
+}
+
+static isc_result_t
+towire_spf(ARGS_TOWIRE) {
+ REQUIRE(rdata->type == dns_rdatatype_spf);
+
+ UNUSED(cctx);
+
+ return (mem_tobuffer(target, rdata->data, rdata->length));
+}
+
+static int
+compare_spf(ARGS_COMPARE) {
+ isc_region_t r1;
+ isc_region_t r2;
+
+ REQUIRE(rdata1->type == rdata2->type);
+ REQUIRE(rdata1->rdclass == rdata2->rdclass);
+ REQUIRE(rdata1->type == dns_rdatatype_spf);
+
+ dns_rdata_toregion(rdata1, &r1);
+ dns_rdata_toregion(rdata2, &r2);
+ return (isc_region_compare(&r1, &r2));
+}
+
+static isc_result_t
+fromstruct_spf(ARGS_FROMSTRUCT) {
+ REQUIRE(type == dns_rdatatype_spf);
+
+ return (generic_fromstruct_txt(CALL_FROMSTRUCT));
+}
+
+static isc_result_t
+tostruct_spf(ARGS_TOSTRUCT) {
+ dns_rdata_spf_t *spf = target;
+
+ REQUIRE(spf != NULL);
+ REQUIRE(rdata != NULL);
+ REQUIRE(rdata->type == dns_rdatatype_spf);
+
+ spf->common.rdclass = rdata->rdclass;
+ spf->common.rdtype = rdata->type;
+ ISC_LINK_INIT(&spf->common, link);
+
+ return (generic_tostruct_txt(CALL_TOSTRUCT));
+}
+
+static void
+freestruct_spf(ARGS_FREESTRUCT) {
+ dns_rdata_spf_t *spf = source;
+
+ REQUIRE(spf != NULL);
+ REQUIRE(spf->common.rdtype == dns_rdatatype_spf);
+
+ generic_freestruct_txt(source);
+}
+
+static isc_result_t
+additionaldata_spf(ARGS_ADDLDATA) {
+ REQUIRE(rdata->type == dns_rdatatype_spf);
+
+ UNUSED(rdata);
+ UNUSED(owner);
+ UNUSED(add);
+ UNUSED(arg);
+
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+digest_spf(ARGS_DIGEST) {
+ isc_region_t r;
+
+ REQUIRE(rdata->type == dns_rdatatype_spf);
+
+ dns_rdata_toregion(rdata, &r);
+
+ return ((digest)(arg, &r));
+}
+
+static bool
+checkowner_spf(ARGS_CHECKOWNER) {
+ REQUIRE(type == dns_rdatatype_spf);
+
+ UNUSED(name);
+ UNUSED(type);
+ UNUSED(rdclass);
+ UNUSED(wildcard);
+
+ return (true);
+}
+
+static bool
+checknames_spf(ARGS_CHECKNAMES) {
+ REQUIRE(rdata->type == dns_rdatatype_spf);
+
+ UNUSED(rdata);
+ UNUSED(owner);
+ UNUSED(bad);
+
+ return (true);
+}
+
+static int
+casecompare_spf(ARGS_COMPARE) {
+ return (compare_spf(rdata1, rdata2));
+}
+#endif /* RDATA_GENERIC_SPF_99_C */
diff --git a/lib/dns/rdata/generic/spf_99.h b/lib/dns/rdata/generic/spf_99.h
new file mode 100644
index 0000000..f0963ed
--- /dev/null
+++ b/lib/dns/rdata/generic/spf_99.h
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#pragma once
+
+typedef struct dns_rdata_spf_string {
+ uint8_t length;
+ unsigned char *data;
+} dns_rdata_spf_string_t;
+
+typedef struct dns_rdata_spf {
+ dns_rdatacommon_t common;
+ isc_mem_t *mctx;
+ unsigned char *txt;
+ uint16_t txt_len;
+ /* private */
+ uint16_t offset;
+} dns_rdata_spf_t;
+
+/*
+ * ISC_LANG_BEGINDECLS and ISC_LANG_ENDDECLS are already done
+ * via rdatastructpre.h and rdatastructsuf.h.
+ */
diff --git a/lib/dns/rdata/generic/sshfp_44.c b/lib/dns/rdata/generic/sshfp_44.c
new file mode 100644
index 0000000..7f1060a
--- /dev/null
+++ b/lib/dns/rdata/generic/sshfp_44.c
@@ -0,0 +1,297 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/* RFC 4255 */
+
+#ifndef RDATA_GENERIC_SSHFP_44_C
+#define RDATA_GENERIC_SSHFP_44_C
+
+#define RRTYPE_SSHFP_ATTRIBUTES (0)
+
+static isc_result_t
+fromtext_sshfp(ARGS_FROMTEXT) {
+ isc_token_t token;
+ int len = -1;
+
+ REQUIRE(type == dns_rdatatype_sshfp);
+
+ UNUSED(type);
+ UNUSED(rdclass);
+ UNUSED(origin);
+ UNUSED(options);
+ UNUSED(callbacks);
+
+ /*
+ * Algorithm.
+ */
+ RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_number,
+ false));
+ if (token.value.as_ulong > 0xffU) {
+ RETTOK(ISC_R_RANGE);
+ }
+ RETERR(uint8_tobuffer(token.value.as_ulong, target));
+
+ /*
+ * Digest type.
+ */
+ RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_number,
+ false));
+ if (token.value.as_ulong > 0xffU) {
+ RETTOK(ISC_R_RANGE);
+ }
+ RETERR(uint8_tobuffer(token.value.as_ulong, target));
+
+ /*
+ * Enforce known digest lengths.
+ */
+ switch (token.value.as_ulong) {
+ case 1:
+ len = ISC_SHA1_DIGESTLENGTH;
+ break;
+ case 2:
+ len = ISC_SHA256_DIGESTLENGTH;
+ break;
+ default:
+ break;
+ }
+
+ /*
+ * Digest.
+ */
+ return (isc_hex_tobuffer(lexer, target, len));
+}
+
+static isc_result_t
+totext_sshfp(ARGS_TOTEXT) {
+ isc_region_t sr;
+ char buf[sizeof("64000 ")];
+ unsigned int n;
+
+ REQUIRE(rdata->type == dns_rdatatype_sshfp);
+ REQUIRE(rdata->length != 0);
+
+ UNUSED(tctx);
+
+ dns_rdata_toregion(rdata, &sr);
+
+ /*
+ * Algorithm.
+ */
+ n = uint8_fromregion(&sr);
+ isc_region_consume(&sr, 1);
+ snprintf(buf, sizeof(buf), "%u ", n);
+ RETERR(str_totext(buf, target));
+
+ /*
+ * Digest type.
+ */
+ n = uint8_fromregion(&sr);
+ isc_region_consume(&sr, 1);
+ snprintf(buf, sizeof(buf), "%u", n);
+ RETERR(str_totext(buf, target));
+
+ if (sr.length == 0U) {
+ return (ISC_R_SUCCESS);
+ }
+
+ /*
+ * Digest.
+ */
+ if ((tctx->flags & DNS_STYLEFLAG_MULTILINE) != 0) {
+ RETERR(str_totext(" (", target));
+ }
+ RETERR(str_totext(tctx->linebreak, target));
+ if (tctx->width == 0) { /* No splitting */
+ RETERR(isc_hex_totext(&sr, 0, "", target));
+ } else {
+ RETERR(isc_hex_totext(&sr, tctx->width - 2, tctx->linebreak,
+ target));
+ }
+ if ((tctx->flags & DNS_STYLEFLAG_MULTILINE) != 0) {
+ RETERR(str_totext(" )", target));
+ }
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+fromwire_sshfp(ARGS_FROMWIRE) {
+ isc_region_t sr;
+
+ REQUIRE(type == dns_rdatatype_sshfp);
+
+ UNUSED(type);
+ UNUSED(rdclass);
+ UNUSED(dctx);
+ UNUSED(options);
+
+ isc_buffer_activeregion(source, &sr);
+ if (sr.length < 2) {
+ return (ISC_R_UNEXPECTEDEND);
+ }
+
+ if ((sr.base[1] == 1 && sr.length != ISC_SHA1_DIGESTLENGTH + 2) ||
+ (sr.base[1] == 2 && sr.length != ISC_SHA256_DIGESTLENGTH + 2))
+ {
+ return (DNS_R_FORMERR);
+ }
+
+ isc_buffer_forward(source, sr.length);
+ return (mem_tobuffer(target, sr.base, sr.length));
+}
+
+static isc_result_t
+towire_sshfp(ARGS_TOWIRE) {
+ isc_region_t sr;
+
+ REQUIRE(rdata->type == dns_rdatatype_sshfp);
+ REQUIRE(rdata->length != 0);
+
+ UNUSED(cctx);
+
+ dns_rdata_toregion(rdata, &sr);
+ return (mem_tobuffer(target, sr.base, sr.length));
+}
+
+static int
+compare_sshfp(ARGS_COMPARE) {
+ isc_region_t r1;
+ isc_region_t r2;
+
+ REQUIRE(rdata1->type == rdata2->type);
+ REQUIRE(rdata1->rdclass == rdata2->rdclass);
+ REQUIRE(rdata1->type == dns_rdatatype_sshfp);
+ REQUIRE(rdata1->length != 0);
+ REQUIRE(rdata2->length != 0);
+
+ dns_rdata_toregion(rdata1, &r1);
+ dns_rdata_toregion(rdata2, &r2);
+ return (isc_region_compare(&r1, &r2));
+}
+
+static isc_result_t
+fromstruct_sshfp(ARGS_FROMSTRUCT) {
+ dns_rdata_sshfp_t *sshfp = source;
+
+ REQUIRE(type == dns_rdatatype_sshfp);
+ REQUIRE(sshfp != NULL);
+ REQUIRE(sshfp->common.rdtype == type);
+ REQUIRE(sshfp->common.rdclass == rdclass);
+
+ UNUSED(type);
+ UNUSED(rdclass);
+
+ RETERR(uint8_tobuffer(sshfp->algorithm, target));
+ RETERR(uint8_tobuffer(sshfp->digest_type, target));
+
+ return (mem_tobuffer(target, sshfp->digest, sshfp->length));
+}
+
+static isc_result_t
+tostruct_sshfp(ARGS_TOSTRUCT) {
+ dns_rdata_sshfp_t *sshfp = target;
+ isc_region_t region;
+
+ REQUIRE(rdata->type == dns_rdatatype_sshfp);
+ REQUIRE(sshfp != NULL);
+ REQUIRE(rdata->length != 0);
+
+ sshfp->common.rdclass = rdata->rdclass;
+ sshfp->common.rdtype = rdata->type;
+ ISC_LINK_INIT(&sshfp->common, link);
+
+ dns_rdata_toregion(rdata, &region);
+
+ sshfp->algorithm = uint8_fromregion(&region);
+ isc_region_consume(&region, 1);
+ sshfp->digest_type = uint8_fromregion(&region);
+ isc_region_consume(&region, 1);
+ sshfp->length = region.length;
+
+ sshfp->digest = mem_maybedup(mctx, region.base, region.length);
+ if (sshfp->digest == NULL) {
+ return (ISC_R_NOMEMORY);
+ }
+
+ sshfp->mctx = mctx;
+ return (ISC_R_SUCCESS);
+}
+
+static void
+freestruct_sshfp(ARGS_FREESTRUCT) {
+ dns_rdata_sshfp_t *sshfp = source;
+
+ REQUIRE(sshfp != NULL);
+ REQUIRE(sshfp->common.rdtype == dns_rdatatype_sshfp);
+
+ if (sshfp->mctx == NULL) {
+ return;
+ }
+
+ if (sshfp->digest != NULL) {
+ isc_mem_free(sshfp->mctx, sshfp->digest);
+ }
+ sshfp->mctx = NULL;
+}
+
+static isc_result_t
+additionaldata_sshfp(ARGS_ADDLDATA) {
+ REQUIRE(rdata->type == dns_rdatatype_sshfp);
+
+ UNUSED(rdata);
+ UNUSED(owner);
+ UNUSED(add);
+ UNUSED(arg);
+
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+digest_sshfp(ARGS_DIGEST) {
+ isc_region_t r;
+
+ REQUIRE(rdata->type == dns_rdatatype_sshfp);
+
+ dns_rdata_toregion(rdata, &r);
+
+ return ((digest)(arg, &r));
+}
+
+static bool
+checkowner_sshfp(ARGS_CHECKOWNER) {
+ REQUIRE(type == dns_rdatatype_sshfp);
+
+ UNUSED(name);
+ UNUSED(type);
+ UNUSED(rdclass);
+ UNUSED(wildcard);
+
+ return (true);
+}
+
+static bool
+checknames_sshfp(ARGS_CHECKNAMES) {
+ REQUIRE(rdata->type == dns_rdatatype_sshfp);
+
+ UNUSED(rdata);
+ UNUSED(owner);
+ UNUSED(bad);
+
+ return (true);
+}
+
+static int
+casecompare_sshfp(ARGS_COMPARE) {
+ return (compare_sshfp(rdata1, rdata2));
+}
+
+#endif /* RDATA_GENERIC_SSHFP_44_C */
diff --git a/lib/dns/rdata/generic/sshfp_44.h b/lib/dns/rdata/generic/sshfp_44.h
new file mode 100644
index 0000000..a19806f
--- /dev/null
+++ b/lib/dns/rdata/generic/sshfp_44.h
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/*!
+ * \brief Per RFC 4255 */
+
+#pragma once
+
+typedef struct dns_rdata_sshfp {
+ dns_rdatacommon_t common;
+ isc_mem_t *mctx;
+ uint8_t algorithm;
+ uint8_t digest_type;
+ uint16_t length;
+ unsigned char *digest;
+} dns_rdata_sshfp_t;
diff --git a/lib/dns/rdata/generic/ta_32768.c b/lib/dns/rdata/generic/ta_32768.c
new file mode 100644
index 0000000..4a0688c
--- /dev/null
+++ b/lib/dns/rdata/generic/ta_32768.c
@@ -0,0 +1,163 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/* http://www.watson.org/~weiler/INI1999-19.pdf */
+
+#ifndef RDATA_GENERIC_TA_32768_C
+#define RDATA_GENERIC_TA_32768_C
+
+#define RRTYPE_TA_ATTRIBUTES 0
+
+static isc_result_t
+fromtext_ta(ARGS_FROMTEXT) {
+ REQUIRE(type == dns_rdatatype_ta);
+
+ return (generic_fromtext_ds(CALL_FROMTEXT));
+}
+
+static isc_result_t
+totext_ta(ARGS_TOTEXT) {
+ REQUIRE(rdata->type == dns_rdatatype_ta);
+
+ return (generic_totext_ds(CALL_TOTEXT));
+}
+
+static isc_result_t
+fromwire_ta(ARGS_FROMWIRE) {
+ REQUIRE(type == dns_rdatatype_ta);
+
+ return (generic_fromwire_ds(CALL_FROMWIRE));
+}
+
+static isc_result_t
+towire_ta(ARGS_TOWIRE) {
+ isc_region_t sr;
+
+ REQUIRE(rdata->type == dns_rdatatype_ta);
+ REQUIRE(rdata->length != 0);
+
+ UNUSED(cctx);
+
+ dns_rdata_toregion(rdata, &sr);
+ return (mem_tobuffer(target, sr.base, sr.length));
+}
+
+static int
+compare_ta(ARGS_COMPARE) {
+ isc_region_t r1;
+ isc_region_t r2;
+
+ REQUIRE(rdata1->type == rdata2->type);
+ REQUIRE(rdata1->rdclass == rdata2->rdclass);
+ REQUIRE(rdata1->type == dns_rdatatype_ta);
+ REQUIRE(rdata1->length != 0);
+ REQUIRE(rdata2->length != 0);
+
+ dns_rdata_toregion(rdata1, &r1);
+ dns_rdata_toregion(rdata2, &r2);
+ return (isc_region_compare(&r1, &r2));
+}
+
+static isc_result_t
+fromstruct_ta(ARGS_FROMSTRUCT) {
+ REQUIRE(type == dns_rdatatype_ta);
+
+ return (generic_fromstruct_ds(CALL_FROMSTRUCT));
+}
+
+static isc_result_t
+tostruct_ta(ARGS_TOSTRUCT) {
+ dns_rdata_ds_t *ds = target;
+
+ REQUIRE(rdata->type == dns_rdatatype_ta);
+ REQUIRE(ds != NULL);
+
+ /*
+ * Checked by generic_tostruct_ds().
+ */
+ ds->common.rdclass = rdata->rdclass;
+ ds->common.rdtype = rdata->type;
+ ISC_LINK_INIT(&ds->common, link);
+
+ return (generic_tostruct_ds(CALL_TOSTRUCT));
+}
+
+static void
+freestruct_ta(ARGS_FREESTRUCT) {
+ dns_rdata_ta_t *ds = source;
+
+ REQUIRE(ds != NULL);
+ REQUIRE(ds->common.rdtype == dns_rdatatype_ta);
+
+ if (ds->mctx == NULL) {
+ return;
+ }
+
+ if (ds->digest != NULL) {
+ isc_mem_free(ds->mctx, ds->digest);
+ }
+ ds->mctx = NULL;
+}
+
+static isc_result_t
+additionaldata_ta(ARGS_ADDLDATA) {
+ REQUIRE(rdata->type == dns_rdatatype_ta);
+
+ UNUSED(rdata);
+ UNUSED(owner);
+ UNUSED(add);
+ UNUSED(arg);
+
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+digest_ta(ARGS_DIGEST) {
+ isc_region_t r;
+
+ REQUIRE(rdata->type == dns_rdatatype_ta);
+
+ dns_rdata_toregion(rdata, &r);
+
+ return ((digest)(arg, &r));
+}
+
+static bool
+checkowner_ta(ARGS_CHECKOWNER) {
+ REQUIRE(type == dns_rdatatype_ta);
+
+ UNUSED(name);
+ UNUSED(type);
+ UNUSED(rdclass);
+ UNUSED(wildcard);
+
+ return (true);
+}
+
+static bool
+checknames_ta(ARGS_CHECKNAMES) {
+ REQUIRE(rdata->type == dns_rdatatype_ta);
+
+ UNUSED(rdata);
+ UNUSED(owner);
+ UNUSED(bad);
+
+ return (true);
+}
+
+static int
+casecompare_ta(ARGS_COMPARE) {
+ return (compare_ta(rdata1, rdata2));
+}
+
+#endif /* RDATA_GENERIC_TA_32768_C */
diff --git a/lib/dns/rdata/generic/ta_32768.h b/lib/dns/rdata/generic/ta_32768.h
new file mode 100644
index 0000000..e9111c5
--- /dev/null
+++ b/lib/dns/rdata/generic/ta_32768.h
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#pragma once
+
+/*
+ * TA records are identical to DS records.
+ */
+typedef struct dns_rdata_ds dns_rdata_ta_t;
diff --git a/lib/dns/rdata/generic/talink_58.c b/lib/dns/rdata/generic/talink_58.c
new file mode 100644
index 0000000..49ad8df
--- /dev/null
+++ b/lib/dns/rdata/generic/talink_58.c
@@ -0,0 +1,258 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#ifndef RDATA_GENERIC_TALINK_58_C
+#define RDATA_GENERIC_TALINK_58_C
+
+#define RRTYPE_TALINK_ATTRIBUTES 0
+
+static isc_result_t
+fromtext_talink(ARGS_FROMTEXT) {
+ isc_token_t token;
+ dns_name_t name;
+ isc_buffer_t buffer;
+ int i;
+
+ REQUIRE(type == dns_rdatatype_talink);
+
+ UNUSED(type);
+ UNUSED(rdclass);
+ UNUSED(callbacks);
+
+ if (origin == NULL) {
+ origin = dns_rootname;
+ }
+
+ for (i = 0; i < 2; i++) {
+ RETERR(isc_lex_getmastertoken(lexer, &token,
+ isc_tokentype_string, false));
+
+ dns_name_init(&name, NULL);
+ buffer_fromregion(&buffer, &token.value.as_region);
+ RETTOK(dns_name_fromtext(&name, &buffer, origin, options,
+ target));
+ }
+
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+totext_talink(ARGS_TOTEXT) {
+ isc_region_t dregion;
+ dns_name_t prev;
+ dns_name_t next;
+ dns_name_t prefix;
+ bool sub;
+
+ REQUIRE(rdata->type == dns_rdatatype_talink);
+ REQUIRE(rdata->length != 0);
+
+ dns_name_init(&prev, NULL);
+ dns_name_init(&next, NULL);
+ dns_name_init(&prefix, NULL);
+
+ dns_rdata_toregion(rdata, &dregion);
+
+ dns_name_fromregion(&prev, &dregion);
+ isc_region_consume(&dregion, name_length(&prev));
+
+ dns_name_fromregion(&next, &dregion);
+ isc_region_consume(&dregion, name_length(&next));
+
+ sub = name_prefix(&prev, tctx->origin, &prefix);
+ RETERR(dns_name_totext(&prefix, sub, target));
+
+ RETERR(str_totext(" ", target));
+
+ sub = name_prefix(&next, tctx->origin, &prefix);
+ return (dns_name_totext(&prefix, sub, target));
+}
+
+static isc_result_t
+fromwire_talink(ARGS_FROMWIRE) {
+ dns_name_t prev;
+ dns_name_t next;
+
+ REQUIRE(type == dns_rdatatype_talink);
+
+ UNUSED(type);
+ UNUSED(rdclass);
+
+ dns_decompress_setmethods(dctx, DNS_COMPRESS_NONE);
+
+ dns_name_init(&prev, NULL);
+ dns_name_init(&next, NULL);
+
+ RETERR(dns_name_fromwire(&prev, source, dctx, options, target));
+ return (dns_name_fromwire(&next, source, dctx, options, target));
+}
+
+static isc_result_t
+towire_talink(ARGS_TOWIRE) {
+ isc_region_t sregion;
+ dns_name_t prev;
+ dns_name_t next;
+ dns_offsets_t moffsets;
+ dns_offsets_t roffsets;
+
+ REQUIRE(rdata->type == dns_rdatatype_talink);
+ REQUIRE(rdata->length != 0);
+
+ dns_compress_setmethods(cctx, DNS_COMPRESS_NONE);
+
+ dns_name_init(&prev, moffsets);
+ dns_name_init(&next, roffsets);
+
+ dns_rdata_toregion(rdata, &sregion);
+
+ dns_name_fromregion(&prev, &sregion);
+ isc_region_consume(&sregion, name_length(&prev));
+ RETERR(dns_name_towire(&prev, cctx, target));
+
+ dns_name_fromregion(&next, &sregion);
+ isc_region_consume(&sregion, name_length(&next));
+ return (dns_name_towire(&next, cctx, target));
+}
+
+static int
+compare_talink(ARGS_COMPARE) {
+ isc_region_t region1;
+ isc_region_t region2;
+
+ REQUIRE(rdata1->type == rdata2->type);
+ REQUIRE(rdata1->rdclass == rdata2->rdclass);
+ REQUIRE(rdata1->type == dns_rdatatype_talink);
+ REQUIRE(rdata1->length != 0);
+ REQUIRE(rdata2->length != 0);
+
+ dns_rdata_toregion(rdata1, &region1);
+ dns_rdata_toregion(rdata2, &region2);
+ return (isc_region_compare(&region1, &region2));
+}
+
+static isc_result_t
+fromstruct_talink(ARGS_FROMSTRUCT) {
+ dns_rdata_talink_t *talink = source;
+ isc_region_t region;
+
+ REQUIRE(type == dns_rdatatype_talink);
+ REQUIRE(talink != NULL);
+ REQUIRE(talink->common.rdtype == type);
+ REQUIRE(talink->common.rdclass == rdclass);
+
+ UNUSED(type);
+ UNUSED(rdclass);
+
+ dns_name_toregion(&talink->prev, &region);
+ RETERR(isc_buffer_copyregion(target, &region));
+ dns_name_toregion(&talink->next, &region);
+ return (isc_buffer_copyregion(target, &region));
+}
+
+static isc_result_t
+tostruct_talink(ARGS_TOSTRUCT) {
+ isc_region_t region;
+ dns_rdata_talink_t *talink = target;
+ dns_name_t name;
+
+ REQUIRE(rdata->type == dns_rdatatype_talink);
+ REQUIRE(talink != NULL);
+ REQUIRE(rdata->length != 0);
+
+ talink->common.rdclass = rdata->rdclass;
+ talink->common.rdtype = rdata->type;
+ ISC_LINK_INIT(&talink->common, link);
+
+ dns_rdata_toregion(rdata, &region);
+
+ dns_name_init(&name, NULL);
+ dns_name_fromregion(&name, &region);
+ isc_region_consume(&region, name_length(&name));
+ dns_name_init(&talink->prev, NULL);
+ name_duporclone(&name, mctx, &talink->prev);
+
+ dns_name_fromregion(&name, &region);
+ isc_region_consume(&region, name_length(&name));
+ dns_name_init(&talink->next, NULL);
+ name_duporclone(&name, mctx, &talink->next);
+
+ talink->mctx = mctx;
+ return (ISC_R_SUCCESS);
+}
+
+static void
+freestruct_talink(ARGS_FREESTRUCT) {
+ dns_rdata_talink_t *talink = source;
+
+ REQUIRE(talink != NULL);
+ REQUIRE(talink->common.rdtype == dns_rdatatype_talink);
+
+ if (talink->mctx == NULL) {
+ return;
+ }
+
+ dns_name_free(&talink->prev, talink->mctx);
+ dns_name_free(&talink->next, talink->mctx);
+ talink->mctx = NULL;
+}
+
+static isc_result_t
+additionaldata_talink(ARGS_ADDLDATA) {
+ REQUIRE(rdata->type == dns_rdatatype_talink);
+
+ UNUSED(rdata);
+ UNUSED(owner);
+ UNUSED(add);
+ UNUSED(arg);
+
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+digest_talink(ARGS_DIGEST) {
+ isc_region_t r;
+
+ REQUIRE(rdata->type == dns_rdatatype_talink);
+
+ dns_rdata_toregion(rdata, &r);
+ return ((digest)(arg, &r));
+}
+
+static bool
+checkowner_talink(ARGS_CHECKOWNER) {
+ REQUIRE(type == dns_rdatatype_talink);
+
+ UNUSED(name);
+ UNUSED(type);
+ UNUSED(rdclass);
+ UNUSED(wildcard);
+
+ return (true);
+}
+
+static bool
+checknames_talink(ARGS_CHECKNAMES) {
+ REQUIRE(rdata->type == dns_rdatatype_talink);
+
+ UNUSED(bad);
+ UNUSED(owner);
+
+ return (true);
+}
+
+static int
+casecompare_talink(ARGS_COMPARE) {
+ return (compare_talink(rdata1, rdata2));
+}
+
+#endif /* RDATA_GENERIC_TALINK_58_C */
diff --git a/lib/dns/rdata/generic/talink_58.h b/lib/dns/rdata/generic/talink_58.h
new file mode 100644
index 0000000..5a7f075
--- /dev/null
+++ b/lib/dns/rdata/generic/talink_58.h
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/*
+ * http://www.iana.org/assignments/dns-parameters/TALINK/talink-completed-template
+ */
+
+#pragma once
+
+typedef struct dns_rdata_talink {
+ dns_rdatacommon_t common;
+ isc_mem_t *mctx;
+ dns_name_t prev;
+ dns_name_t next;
+} dns_rdata_talink_t;
diff --git a/lib/dns/rdata/generic/tkey_249.c b/lib/dns/rdata/generic/tkey_249.c
new file mode 100644
index 0000000..3605a34
--- /dev/null
+++ b/lib/dns/rdata/generic/tkey_249.c
@@ -0,0 +1,581 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/* draft-ietf-dnsext-tkey-01.txt */
+
+#ifndef RDATA_GENERIC_TKEY_249_C
+#define RDATA_GENERIC_TKEY_249_C
+
+#define RRTYPE_TKEY_ATTRIBUTES (DNS_RDATATYPEATTR_META)
+
+static isc_result_t
+fromtext_tkey(ARGS_FROMTEXT) {
+ isc_token_t token;
+ dns_rcode_t rcode;
+ dns_name_t name;
+ isc_buffer_t buffer;
+ long i;
+ char *e;
+
+ REQUIRE(type == dns_rdatatype_tkey);
+
+ UNUSED(type);
+ UNUSED(rdclass);
+ UNUSED(callbacks);
+
+ /*
+ * Algorithm.
+ */
+ RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_string,
+ false));
+ dns_name_init(&name, NULL);
+ buffer_fromregion(&buffer, &token.value.as_region);
+ if (origin == NULL) {
+ origin = dns_rootname;
+ }
+ RETTOK(dns_name_fromtext(&name, &buffer, origin, options, target));
+
+ /*
+ * Inception.
+ */
+ RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_number,
+ false));
+ RETERR(uint32_tobuffer(token.value.as_ulong, target));
+
+ /*
+ * Expiration.
+ */
+ RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_number,
+ false));
+ RETERR(uint32_tobuffer(token.value.as_ulong, target));
+
+ /*
+ * Mode.
+ */
+ RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_number,
+ false));
+ if (token.value.as_ulong > 0xffffU) {
+ RETTOK(ISC_R_RANGE);
+ }
+ RETERR(uint16_tobuffer(token.value.as_ulong, target));
+
+ /*
+ * Error.
+ */
+ RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_string,
+ false));
+ if (dns_tsigrcode_fromtext(&rcode, &token.value.as_textregion) !=
+ ISC_R_SUCCESS)
+ {
+ i = strtol(DNS_AS_STR(token), &e, 10);
+ if (*e != 0) {
+ RETTOK(DNS_R_UNKNOWN);
+ }
+ if (i < 0 || i > 0xffff) {
+ RETTOK(ISC_R_RANGE);
+ }
+ rcode = (dns_rcode_t)i;
+ }
+ RETERR(uint16_tobuffer(rcode, target));
+
+ /*
+ * Key Size.
+ */
+ RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_number,
+ false));
+ if (token.value.as_ulong > 0xffffU) {
+ RETTOK(ISC_R_RANGE);
+ }
+ RETERR(uint16_tobuffer(token.value.as_ulong, target));
+
+ /*
+ * Key Data.
+ */
+ RETERR(isc_base64_tobuffer(lexer, target, (int)token.value.as_ulong));
+
+ /*
+ * Other Size.
+ */
+ RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_number,
+ false));
+ if (token.value.as_ulong > 0xffffU) {
+ RETTOK(ISC_R_RANGE);
+ }
+ RETERR(uint16_tobuffer(token.value.as_ulong, target));
+
+ /*
+ * Other Data.
+ */
+ return (isc_base64_tobuffer(lexer, target, (int)token.value.as_ulong));
+}
+
+static isc_result_t
+totext_tkey(ARGS_TOTEXT) {
+ isc_region_t sr, dr;
+ char buf[sizeof("4294967295 ")];
+ unsigned long n;
+ dns_name_t name;
+ dns_name_t prefix;
+ bool sub;
+
+ REQUIRE(rdata->type == dns_rdatatype_tkey);
+ REQUIRE(rdata->length != 0);
+
+ dns_rdata_toregion(rdata, &sr);
+
+ /*
+ * Algorithm.
+ */
+ dns_name_init(&name, NULL);
+ dns_name_init(&prefix, NULL);
+ dns_name_fromregion(&name, &sr);
+ sub = name_prefix(&name, tctx->origin, &prefix);
+ RETERR(dns_name_totext(&prefix, sub, target));
+ RETERR(str_totext(" ", target));
+ isc_region_consume(&sr, name_length(&name));
+
+ /*
+ * Inception.
+ */
+ n = uint32_fromregion(&sr);
+ isc_region_consume(&sr, 4);
+ snprintf(buf, sizeof(buf), "%lu ", n);
+ RETERR(str_totext(buf, target));
+
+ /*
+ * Expiration.
+ */
+ n = uint32_fromregion(&sr);
+ isc_region_consume(&sr, 4);
+ snprintf(buf, sizeof(buf), "%lu ", n);
+ RETERR(str_totext(buf, target));
+
+ /*
+ * Mode.
+ */
+ n = uint16_fromregion(&sr);
+ isc_region_consume(&sr, 2);
+ snprintf(buf, sizeof(buf), "%lu ", n);
+ RETERR(str_totext(buf, target));
+
+ /*
+ * Error.
+ */
+ n = uint16_fromregion(&sr);
+ isc_region_consume(&sr, 2);
+ if (dns_tsigrcode_totext((dns_rcode_t)n, target) == ISC_R_SUCCESS) {
+ RETERR(str_totext(" ", target));
+ } else {
+ snprintf(buf, sizeof(buf), "%lu ", n);
+ RETERR(str_totext(buf, target));
+ }
+
+ /*
+ * Key Size.
+ */
+ n = uint16_fromregion(&sr);
+ isc_region_consume(&sr, 2);
+ snprintf(buf, sizeof(buf), "%lu", n);
+ RETERR(str_totext(buf, target));
+
+ /*
+ * Key Data.
+ */
+ REQUIRE(n <= sr.length);
+ dr = sr;
+ dr.length = n;
+ if ((tctx->flags & DNS_STYLEFLAG_MULTILINE) != 0) {
+ RETERR(str_totext(" (", target));
+ }
+ RETERR(str_totext(tctx->linebreak, target));
+ if (tctx->width == 0) { /* No splitting */
+ RETERR(isc_base64_totext(&dr, 60, "", target));
+ } else {
+ RETERR(isc_base64_totext(&dr, tctx->width - 2, tctx->linebreak,
+ target));
+ }
+ if ((tctx->flags & DNS_STYLEFLAG_MULTILINE) != 0) {
+ RETERR(str_totext(" ) ", target));
+ } else {
+ RETERR(str_totext(" ", target));
+ }
+ isc_region_consume(&sr, n);
+
+ /*
+ * Other Size.
+ */
+ n = uint16_fromregion(&sr);
+ isc_region_consume(&sr, 2);
+ snprintf(buf, sizeof(buf), "%lu", n);
+ RETERR(str_totext(buf, target));
+
+ /*
+ * Other Data.
+ */
+ REQUIRE(n <= sr.length);
+ if (n != 0U) {
+ dr = sr;
+ dr.length = n;
+ if ((tctx->flags & DNS_STYLEFLAG_MULTILINE) != 0) {
+ RETERR(str_totext(" (", target));
+ }
+ RETERR(str_totext(tctx->linebreak, target));
+ if (tctx->width == 0) { /* No splitting */
+ RETERR(isc_base64_totext(&dr, 60, "", target));
+ } else {
+ RETERR(isc_base64_totext(&dr, tctx->width - 2,
+ tctx->linebreak, target));
+ }
+ if ((tctx->flags & DNS_STYLEFLAG_MULTILINE) != 0) {
+ RETERR(str_totext(" )", target));
+ }
+ }
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+fromwire_tkey(ARGS_FROMWIRE) {
+ isc_region_t sr;
+ unsigned long n;
+ dns_name_t name;
+
+ REQUIRE(type == dns_rdatatype_tkey);
+
+ UNUSED(type);
+ UNUSED(rdclass);
+
+ dns_decompress_setmethods(dctx, DNS_COMPRESS_NONE);
+
+ /*
+ * Algorithm.
+ */
+ dns_name_init(&name, NULL);
+ RETERR(dns_name_fromwire(&name, source, dctx, options, target));
+
+ /*
+ * Inception: 4
+ * Expiration: 4
+ * Mode: 2
+ * Error: 2
+ */
+ isc_buffer_activeregion(source, &sr);
+ if (sr.length < 12) {
+ return (ISC_R_UNEXPECTEDEND);
+ }
+ RETERR(mem_tobuffer(target, sr.base, 12));
+ isc_region_consume(&sr, 12);
+ isc_buffer_forward(source, 12);
+
+ /*
+ * Key Length + Key Data.
+ */
+ if (sr.length < 2) {
+ return (ISC_R_UNEXPECTEDEND);
+ }
+ n = uint16_fromregion(&sr);
+ if (sr.length < n + 2) {
+ return (ISC_R_UNEXPECTEDEND);
+ }
+ RETERR(mem_tobuffer(target, sr.base, n + 2));
+ isc_region_consume(&sr, n + 2);
+ isc_buffer_forward(source, n + 2);
+
+ /*
+ * Other Length + Other Data.
+ */
+ if (sr.length < 2) {
+ return (ISC_R_UNEXPECTEDEND);
+ }
+ n = uint16_fromregion(&sr);
+ if (sr.length < n + 2) {
+ return (ISC_R_UNEXPECTEDEND);
+ }
+ isc_buffer_forward(source, n + 2);
+ return (mem_tobuffer(target, sr.base, n + 2));
+}
+
+static isc_result_t
+towire_tkey(ARGS_TOWIRE) {
+ isc_region_t sr;
+ dns_name_t name;
+ dns_offsets_t offsets;
+
+ REQUIRE(rdata->type == dns_rdatatype_tkey);
+ REQUIRE(rdata->length != 0);
+
+ dns_compress_setmethods(cctx, DNS_COMPRESS_NONE);
+ /*
+ * Algorithm.
+ */
+ dns_rdata_toregion(rdata, &sr);
+ dns_name_init(&name, offsets);
+ dns_name_fromregion(&name, &sr);
+ RETERR(dns_name_towire(&name, cctx, target));
+ isc_region_consume(&sr, name_length(&name));
+
+ return (mem_tobuffer(target, sr.base, sr.length));
+}
+
+static int
+compare_tkey(ARGS_COMPARE) {
+ isc_region_t r1;
+ isc_region_t r2;
+ dns_name_t name1;
+ dns_name_t name2;
+ int order;
+
+ REQUIRE(rdata1->type == rdata2->type);
+ REQUIRE(rdata1->rdclass == rdata2->rdclass);
+ REQUIRE(rdata1->type == dns_rdatatype_tkey);
+ REQUIRE(rdata1->length != 0);
+ REQUIRE(rdata2->length != 0);
+
+ /*
+ * Algorithm.
+ */
+ dns_rdata_toregion(rdata1, &r1);
+ dns_rdata_toregion(rdata2, &r2);
+ dns_name_init(&name1, NULL);
+ dns_name_init(&name2, NULL);
+ dns_name_fromregion(&name1, &r1);
+ dns_name_fromregion(&name2, &r2);
+ if ((order = dns_name_rdatacompare(&name1, &name2)) != 0) {
+ return (order);
+ }
+ isc_region_consume(&r1, name_length(&name1));
+ isc_region_consume(&r2, name_length(&name2));
+ return (isc_region_compare(&r1, &r2));
+}
+
+static isc_result_t
+fromstruct_tkey(ARGS_FROMSTRUCT) {
+ dns_rdata_tkey_t *tkey = source;
+
+ REQUIRE(type == dns_rdatatype_tkey);
+ REQUIRE(tkey != NULL);
+ REQUIRE(tkey->common.rdtype == type);
+ REQUIRE(tkey->common.rdclass == rdclass);
+
+ UNUSED(type);
+ UNUSED(rdclass);
+
+ /*
+ * Algorithm Name.
+ */
+ RETERR(name_tobuffer(&tkey->algorithm, target));
+
+ /*
+ * Inception: 32 bits.
+ */
+ RETERR(uint32_tobuffer(tkey->inception, target));
+
+ /*
+ * Expire: 32 bits.
+ */
+ RETERR(uint32_tobuffer(tkey->expire, target));
+
+ /*
+ * Mode: 16 bits.
+ */
+ RETERR(uint16_tobuffer(tkey->mode, target));
+
+ /*
+ * Error: 16 bits.
+ */
+ RETERR(uint16_tobuffer(tkey->error, target));
+
+ /*
+ * Key size: 16 bits.
+ */
+ RETERR(uint16_tobuffer(tkey->keylen, target));
+
+ /*
+ * Key.
+ */
+ RETERR(mem_tobuffer(target, tkey->key, tkey->keylen));
+
+ /*
+ * Other size: 16 bits.
+ */
+ RETERR(uint16_tobuffer(tkey->otherlen, target));
+
+ /*
+ * Other data.
+ */
+ return (mem_tobuffer(target, tkey->other, tkey->otherlen));
+}
+
+static isc_result_t
+tostruct_tkey(ARGS_TOSTRUCT) {
+ dns_rdata_tkey_t *tkey = target;
+ dns_name_t alg;
+ isc_region_t sr;
+
+ REQUIRE(rdata->type == dns_rdatatype_tkey);
+ REQUIRE(tkey != NULL);
+ REQUIRE(rdata->length != 0);
+
+ tkey->common.rdclass = rdata->rdclass;
+ tkey->common.rdtype = rdata->type;
+ ISC_LINK_INIT(&tkey->common, link);
+
+ dns_rdata_toregion(rdata, &sr);
+
+ /*
+ * Algorithm Name.
+ */
+ dns_name_init(&alg, NULL);
+ dns_name_fromregion(&alg, &sr);
+ dns_name_init(&tkey->algorithm, NULL);
+ name_duporclone(&alg, mctx, &tkey->algorithm);
+ isc_region_consume(&sr, name_length(&tkey->algorithm));
+
+ /*
+ * Inception.
+ */
+ tkey->inception = uint32_fromregion(&sr);
+ isc_region_consume(&sr, 4);
+
+ /*
+ * Expire.
+ */
+ tkey->expire = uint32_fromregion(&sr);
+ isc_region_consume(&sr, 4);
+
+ /*
+ * Mode.
+ */
+ tkey->mode = uint16_fromregion(&sr);
+ isc_region_consume(&sr, 2);
+
+ /*
+ * Error.
+ */
+ tkey->error = uint16_fromregion(&sr);
+ isc_region_consume(&sr, 2);
+
+ /*
+ * Key size.
+ */
+ tkey->keylen = uint16_fromregion(&sr);
+ isc_region_consume(&sr, 2);
+
+ /*
+ * Key.
+ */
+ INSIST(tkey->keylen + 2U <= sr.length);
+ tkey->key = mem_maybedup(mctx, sr.base, tkey->keylen);
+ if (tkey->key == NULL) {
+ goto cleanup;
+ }
+ isc_region_consume(&sr, tkey->keylen);
+
+ /*
+ * Other size.
+ */
+ tkey->otherlen = uint16_fromregion(&sr);
+ isc_region_consume(&sr, 2);
+
+ /*
+ * Other.
+ */
+ INSIST(tkey->otherlen <= sr.length);
+ tkey->other = mem_maybedup(mctx, sr.base, tkey->otherlen);
+ if (tkey->other == NULL) {
+ goto cleanup;
+ }
+
+ tkey->mctx = mctx;
+ return (ISC_R_SUCCESS);
+
+cleanup:
+ if (mctx != NULL) {
+ dns_name_free(&tkey->algorithm, mctx);
+ }
+ if (mctx != NULL && tkey->key != NULL) {
+ isc_mem_free(mctx, tkey->key);
+ }
+ return (ISC_R_NOMEMORY);
+}
+
+static void
+freestruct_tkey(ARGS_FREESTRUCT) {
+ dns_rdata_tkey_t *tkey = (dns_rdata_tkey_t *)source;
+
+ REQUIRE(tkey != NULL);
+
+ if (tkey->mctx == NULL) {
+ return;
+ }
+
+ dns_name_free(&tkey->algorithm, tkey->mctx);
+ if (tkey->key != NULL) {
+ isc_mem_free(tkey->mctx, tkey->key);
+ }
+ if (tkey->other != NULL) {
+ isc_mem_free(tkey->mctx, tkey->other);
+ }
+ tkey->mctx = NULL;
+}
+
+static isc_result_t
+additionaldata_tkey(ARGS_ADDLDATA) {
+ REQUIRE(rdata->type == dns_rdatatype_tkey);
+
+ UNUSED(rdata);
+ UNUSED(owner);
+ UNUSED(add);
+ UNUSED(arg);
+
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+digest_tkey(ARGS_DIGEST) {
+ UNUSED(rdata);
+ UNUSED(digest);
+ UNUSED(arg);
+
+ REQUIRE(rdata->type == dns_rdatatype_tkey);
+
+ return (ISC_R_NOTIMPLEMENTED);
+}
+
+static bool
+checkowner_tkey(ARGS_CHECKOWNER) {
+ REQUIRE(type == dns_rdatatype_tkey);
+
+ UNUSED(name);
+ UNUSED(type);
+ UNUSED(rdclass);
+ UNUSED(wildcard);
+
+ return (true);
+}
+
+static bool
+checknames_tkey(ARGS_CHECKNAMES) {
+ REQUIRE(rdata->type == dns_rdatatype_tkey);
+
+ UNUSED(rdata);
+ UNUSED(owner);
+ UNUSED(bad);
+
+ return (true);
+}
+
+static int
+casecompare_tkey(ARGS_COMPARE) {
+ return (compare_tkey(rdata1, rdata2));
+}
+#endif /* RDATA_GENERIC_TKEY_249_C */
diff --git a/lib/dns/rdata/generic/tkey_249.h b/lib/dns/rdata/generic/tkey_249.h
new file mode 100644
index 0000000..8edd074
--- /dev/null
+++ b/lib/dns/rdata/generic/tkey_249.h
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#pragma once
+
+/*!
+ * \brief Per draft-ietf-dnsind-tkey-00.txt */
+
+typedef struct dns_rdata_tkey {
+ dns_rdatacommon_t common;
+ isc_mem_t *mctx;
+ dns_name_t algorithm;
+ uint32_t inception;
+ uint32_t expire;
+ uint16_t mode;
+ uint16_t error;
+ uint16_t keylen;
+ unsigned char *key;
+ uint16_t otherlen;
+ unsigned char *other;
+} dns_rdata_tkey_t;
diff --git a/lib/dns/rdata/generic/tlsa_52.c b/lib/dns/rdata/generic/tlsa_52.c
new file mode 100644
index 0000000..8489dd9
--- /dev/null
+++ b/lib/dns/rdata/generic/tlsa_52.c
@@ -0,0 +1,340 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/* rfc6698.txt */
+
+#ifndef RDATA_GENERIC_TLSA_52_C
+#define RDATA_GENERIC_TLSA_52_C
+
+#define RRTYPE_TLSA_ATTRIBUTES 0
+
+static isc_result_t
+generic_fromtext_tlsa(ARGS_FROMTEXT) {
+ isc_token_t token;
+
+ UNUSED(type);
+ UNUSED(rdclass);
+ UNUSED(origin);
+ UNUSED(options);
+ UNUSED(callbacks);
+
+ /*
+ * Certificate Usage.
+ */
+ RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_number,
+ false));
+ if (token.value.as_ulong > 0xffU) {
+ RETTOK(ISC_R_RANGE);
+ }
+ RETERR(uint8_tobuffer(token.value.as_ulong, target));
+
+ /*
+ * Selector.
+ */
+ RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_number,
+ false));
+ if (token.value.as_ulong > 0xffU) {
+ RETTOK(ISC_R_RANGE);
+ }
+ RETERR(uint8_tobuffer(token.value.as_ulong, target));
+
+ /*
+ * Matching type.
+ */
+ RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_number,
+ false));
+ if (token.value.as_ulong > 0xffU) {
+ RETTOK(ISC_R_RANGE);
+ }
+ RETERR(uint8_tobuffer(token.value.as_ulong, target));
+
+ /*
+ * Certificate Association Data.
+ */
+ return (isc_hex_tobuffer(lexer, target, -2));
+}
+
+static isc_result_t
+generic_totext_tlsa(ARGS_TOTEXT) {
+ isc_region_t sr;
+ char buf[sizeof("64000 ")];
+ unsigned int n;
+
+ REQUIRE(rdata->length != 0);
+
+ UNUSED(tctx);
+
+ dns_rdata_toregion(rdata, &sr);
+
+ /*
+ * Certificate Usage.
+ */
+ n = uint8_fromregion(&sr);
+ isc_region_consume(&sr, 1);
+ snprintf(buf, sizeof(buf), "%u ", n);
+ RETERR(str_totext(buf, target));
+
+ /*
+ * Selector.
+ */
+ n = uint8_fromregion(&sr);
+ isc_region_consume(&sr, 1);
+ snprintf(buf, sizeof(buf), "%u ", n);
+ RETERR(str_totext(buf, target));
+
+ /*
+ * Matching type.
+ */
+ n = uint8_fromregion(&sr);
+ isc_region_consume(&sr, 1);
+ snprintf(buf, sizeof(buf), "%u", n);
+ RETERR(str_totext(buf, target));
+
+ /*
+ * Certificate Association Data.
+ */
+ if ((tctx->flags & DNS_STYLEFLAG_MULTILINE) != 0) {
+ RETERR(str_totext(" (", target));
+ }
+ RETERR(str_totext(tctx->linebreak, target));
+ if (tctx->width == 0) { /* No splitting */
+ RETERR(isc_hex_totext(&sr, 0, "", target));
+ } else {
+ RETERR(isc_hex_totext(&sr, tctx->width - 2, tctx->linebreak,
+ target));
+ }
+ if ((tctx->flags & DNS_STYLEFLAG_MULTILINE) != 0) {
+ RETERR(str_totext(" )", target));
+ }
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+generic_fromwire_tlsa(ARGS_FROMWIRE) {
+ isc_region_t sr;
+
+ UNUSED(type);
+ UNUSED(rdclass);
+ UNUSED(dctx);
+ UNUSED(options);
+
+ isc_buffer_activeregion(source, &sr);
+
+ /* Usage(1), Selector(1), Type(1), Data(1+) */
+ if (sr.length < 4) {
+ return (ISC_R_UNEXPECTEDEND);
+ }
+
+ isc_buffer_forward(source, sr.length);
+ return (mem_tobuffer(target, sr.base, sr.length));
+}
+
+static isc_result_t
+fromtext_tlsa(ARGS_FROMTEXT) {
+ REQUIRE(type == dns_rdatatype_tlsa);
+
+ return (generic_fromtext_tlsa(CALL_FROMTEXT));
+}
+
+static isc_result_t
+totext_tlsa(ARGS_TOTEXT) {
+ REQUIRE(rdata->type == dns_rdatatype_tlsa);
+
+ return (generic_totext_tlsa(CALL_TOTEXT));
+}
+
+static isc_result_t
+fromwire_tlsa(ARGS_FROMWIRE) {
+ REQUIRE(type == dns_rdatatype_tlsa);
+
+ return (generic_fromwire_tlsa(CALL_FROMWIRE));
+}
+
+static isc_result_t
+towire_tlsa(ARGS_TOWIRE) {
+ isc_region_t sr;
+
+ REQUIRE(rdata->type == dns_rdatatype_tlsa);
+ REQUIRE(rdata->length != 0);
+
+ UNUSED(cctx);
+
+ dns_rdata_toregion(rdata, &sr);
+ return (mem_tobuffer(target, sr.base, sr.length));
+}
+
+static int
+compare_tlsa(ARGS_COMPARE) {
+ isc_region_t r1;
+ isc_region_t r2;
+
+ REQUIRE(rdata1->type == rdata2->type);
+ REQUIRE(rdata1->rdclass == rdata2->rdclass);
+ REQUIRE(rdata1->type == dns_rdatatype_tlsa);
+ REQUIRE(rdata1->length != 0);
+ REQUIRE(rdata2->length != 0);
+
+ dns_rdata_toregion(rdata1, &r1);
+ dns_rdata_toregion(rdata2, &r2);
+ return (isc_region_compare(&r1, &r2));
+}
+
+static isc_result_t
+generic_fromstruct_tlsa(ARGS_FROMSTRUCT) {
+ dns_rdata_tlsa_t *tlsa = source;
+
+ REQUIRE(tlsa != NULL);
+ REQUIRE(tlsa->common.rdtype == type);
+ REQUIRE(tlsa->common.rdclass == rdclass);
+
+ UNUSED(type);
+ UNUSED(rdclass);
+
+ RETERR(uint8_tobuffer(tlsa->usage, target));
+ RETERR(uint8_tobuffer(tlsa->selector, target));
+ RETERR(uint8_tobuffer(tlsa->match, target));
+
+ return (mem_tobuffer(target, tlsa->data, tlsa->length));
+}
+
+static isc_result_t
+generic_tostruct_tlsa(ARGS_TOSTRUCT) {
+ dns_rdata_tlsa_t *tlsa = target;
+ isc_region_t region;
+
+ REQUIRE(tlsa != NULL);
+ REQUIRE(rdata->length != 0);
+
+ REQUIRE(tlsa != NULL);
+ REQUIRE(tlsa->common.rdclass == rdata->rdclass);
+ REQUIRE(tlsa->common.rdtype == rdata->type);
+ REQUIRE(!ISC_LINK_LINKED(&tlsa->common, link));
+
+ dns_rdata_toregion(rdata, &region);
+
+ tlsa->usage = uint8_fromregion(&region);
+ isc_region_consume(&region, 1);
+ tlsa->selector = uint8_fromregion(&region);
+ isc_region_consume(&region, 1);
+ tlsa->match = uint8_fromregion(&region);
+ isc_region_consume(&region, 1);
+ tlsa->length = region.length;
+
+ tlsa->data = mem_maybedup(mctx, region.base, region.length);
+ if (tlsa->data == NULL) {
+ return (ISC_R_NOMEMORY);
+ }
+
+ tlsa->mctx = mctx;
+ return (ISC_R_SUCCESS);
+}
+
+static void
+generic_freestruct_tlsa(ARGS_FREESTRUCT) {
+ dns_rdata_tlsa_t *tlsa = source;
+
+ REQUIRE(tlsa != NULL);
+
+ if (tlsa->mctx == NULL) {
+ return;
+ }
+
+ if (tlsa->data != NULL) {
+ isc_mem_free(tlsa->mctx, tlsa->data);
+ }
+ tlsa->mctx = NULL;
+}
+
+static isc_result_t
+fromstruct_tlsa(ARGS_FROMSTRUCT) {
+ REQUIRE(type == dns_rdatatype_tlsa);
+
+ return (generic_fromstruct_tlsa(CALL_FROMSTRUCT));
+}
+
+static isc_result_t
+tostruct_tlsa(ARGS_TOSTRUCT) {
+ dns_rdata_tlsa_t *tlsa = target;
+
+ REQUIRE(rdata->type == dns_rdatatype_tlsa);
+ REQUIRE(tlsa != NULL);
+
+ tlsa->common.rdclass = rdata->rdclass;
+ tlsa->common.rdtype = rdata->type;
+ ISC_LINK_INIT(&tlsa->common, link);
+
+ return (generic_tostruct_tlsa(CALL_TOSTRUCT));
+}
+
+static void
+freestruct_tlsa(ARGS_FREESTRUCT) {
+ dns_rdata_tlsa_t *tlsa = source;
+
+ REQUIRE(tlsa != NULL);
+ REQUIRE(tlsa->common.rdtype == dns_rdatatype_tlsa);
+
+ generic_freestruct_tlsa(source);
+}
+
+static isc_result_t
+additionaldata_tlsa(ARGS_ADDLDATA) {
+ REQUIRE(rdata->type == dns_rdatatype_tlsa);
+
+ UNUSED(rdata);
+ UNUSED(owner);
+ UNUSED(add);
+ UNUSED(arg);
+
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+digest_tlsa(ARGS_DIGEST) {
+ isc_region_t r;
+
+ REQUIRE(rdata->type == dns_rdatatype_tlsa);
+
+ dns_rdata_toregion(rdata, &r);
+
+ return ((digest)(arg, &r));
+}
+
+static bool
+checkowner_tlsa(ARGS_CHECKOWNER) {
+ REQUIRE(type == dns_rdatatype_tlsa);
+
+ UNUSED(name);
+ UNUSED(type);
+ UNUSED(rdclass);
+ UNUSED(wildcard);
+
+ return (true);
+}
+
+static bool
+checknames_tlsa(ARGS_CHECKNAMES) {
+ REQUIRE(rdata->type == dns_rdatatype_tlsa);
+
+ UNUSED(rdata);
+ UNUSED(owner);
+ UNUSED(bad);
+
+ return (true);
+}
+
+static int
+casecompare_tlsa(ARGS_COMPARE) {
+ return (compare_tlsa(rdata1, rdata2));
+}
+
+#endif /* RDATA_GENERIC_TLSA_52_C */
diff --git a/lib/dns/rdata/generic/tlsa_52.h b/lib/dns/rdata/generic/tlsa_52.h
new file mode 100644
index 0000000..fd75f95
--- /dev/null
+++ b/lib/dns/rdata/generic/tlsa_52.h
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#pragma once
+
+/*!
+ * \brief per rfc6698.txt
+ */
+typedef struct dns_rdata_tlsa {
+ dns_rdatacommon_t common;
+ isc_mem_t *mctx;
+ uint8_t usage;
+ uint8_t selector;
+ uint8_t match;
+ uint16_t length;
+ unsigned char *data;
+} dns_rdata_tlsa_t;
diff --git a/lib/dns/rdata/generic/txt_16.c b/lib/dns/rdata/generic/txt_16.c
new file mode 100644
index 0000000..75f359e
--- /dev/null
+++ b/lib/dns/rdata/generic/txt_16.c
@@ -0,0 +1,360 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#ifndef RDATA_GENERIC_TXT_16_C
+#define RDATA_GENERIC_TXT_16_C
+
+#define RRTYPE_TXT_ATTRIBUTES (0)
+
+static isc_result_t
+generic_fromtext_txt(ARGS_FROMTEXT) {
+ isc_token_t token;
+ int strings;
+
+ UNUSED(type);
+ UNUSED(rdclass);
+ UNUSED(origin);
+ UNUSED(options);
+ UNUSED(callbacks);
+
+ strings = 0;
+ if ((options & DNS_RDATA_UNKNOWNESCAPE) != 0) {
+ isc_textregion_t r;
+ DE_CONST("#", r.base);
+ r.length = 1;
+ RETERR(txt_fromtext(&r, target));
+ strings++;
+ }
+ for (;;) {
+ RETERR(isc_lex_getmastertoken(lexer, &token,
+ isc_tokentype_qstring, true));
+ if (token.type != isc_tokentype_qstring &&
+ token.type != isc_tokentype_string)
+ {
+ break;
+ }
+ RETTOK(txt_fromtext(&token.value.as_textregion, target));
+ strings++;
+ }
+ /* Let upper layer handle eol/eof. */
+ isc_lex_ungettoken(lexer, &token);
+ return (strings == 0 ? ISC_R_UNEXPECTEDEND : ISC_R_SUCCESS);
+}
+
+static isc_result_t
+generic_totext_txt(ARGS_TOTEXT) {
+ isc_region_t region;
+
+ UNUSED(tctx);
+
+ dns_rdata_toregion(rdata, &region);
+
+ while (region.length > 0) {
+ RETERR(txt_totext(&region, true, target));
+ if (region.length > 0) {
+ RETERR(str_totext(" ", target));
+ }
+ }
+
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+generic_fromwire_txt(ARGS_FROMWIRE) {
+ isc_result_t result;
+
+ UNUSED(type);
+ UNUSED(dctx);
+ UNUSED(rdclass);
+ UNUSED(options);
+
+ do {
+ result = txt_fromwire(source, target);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+ } while (!buffer_empty(source));
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+fromtext_txt(ARGS_FROMTEXT) {
+ REQUIRE(type == dns_rdatatype_txt);
+
+ return (generic_fromtext_txt(CALL_FROMTEXT));
+}
+
+static isc_result_t
+totext_txt(ARGS_TOTEXT) {
+ REQUIRE(rdata != NULL);
+ REQUIRE(rdata->type == dns_rdatatype_txt);
+
+ return (generic_totext_txt(CALL_TOTEXT));
+}
+
+static isc_result_t
+fromwire_txt(ARGS_FROMWIRE) {
+ REQUIRE(type == dns_rdatatype_txt);
+
+ return (generic_fromwire_txt(CALL_FROMWIRE));
+}
+
+static isc_result_t
+towire_txt(ARGS_TOWIRE) {
+ REQUIRE(rdata->type == dns_rdatatype_txt);
+
+ UNUSED(cctx);
+
+ return (mem_tobuffer(target, rdata->data, rdata->length));
+}
+
+static int
+compare_txt(ARGS_COMPARE) {
+ isc_region_t r1;
+ isc_region_t r2;
+
+ REQUIRE(rdata1->type == rdata2->type);
+ REQUIRE(rdata1->rdclass == rdata2->rdclass);
+ REQUIRE(rdata1->type == dns_rdatatype_txt);
+
+ dns_rdata_toregion(rdata1, &r1);
+ dns_rdata_toregion(rdata2, &r2);
+ return (isc_region_compare(&r1, &r2));
+}
+
+static isc_result_t
+generic_fromstruct_txt(ARGS_FROMSTRUCT) {
+ dns_rdata_txt_t *txt = source;
+ isc_region_t region;
+ uint8_t length;
+
+ REQUIRE(txt != NULL);
+ REQUIRE(txt->common.rdtype == type);
+ REQUIRE(txt->common.rdclass == rdclass);
+ REQUIRE(txt->txt != NULL && txt->txt_len != 0);
+
+ UNUSED(type);
+ UNUSED(rdclass);
+
+ region.base = txt->txt;
+ region.length = txt->txt_len;
+ while (region.length > 0) {
+ length = uint8_fromregion(&region);
+ isc_region_consume(&region, 1);
+ if (region.length < length) {
+ return (ISC_R_UNEXPECTEDEND);
+ }
+ isc_region_consume(&region, length);
+ }
+
+ return (mem_tobuffer(target, txt->txt, txt->txt_len));
+}
+
+static isc_result_t
+generic_tostruct_txt(ARGS_TOSTRUCT) {
+ dns_rdata_txt_t *txt = target;
+ isc_region_t r;
+
+ REQUIRE(txt != NULL);
+ REQUIRE(txt->common.rdclass == rdata->rdclass);
+ REQUIRE(txt->common.rdtype == rdata->type);
+ REQUIRE(!ISC_LINK_LINKED(&txt->common, link));
+
+ dns_rdata_toregion(rdata, &r);
+ txt->txt_len = r.length;
+ txt->txt = mem_maybedup(mctx, r.base, r.length);
+ if (txt->txt == NULL) {
+ return (ISC_R_NOMEMORY);
+ }
+
+ txt->offset = 0;
+ txt->mctx = mctx;
+ return (ISC_R_SUCCESS);
+}
+
+static void
+generic_freestruct_txt(ARGS_FREESTRUCT) {
+ dns_rdata_txt_t *txt = source;
+
+ REQUIRE(txt != NULL);
+
+ if (txt->mctx == NULL) {
+ return;
+ }
+
+ if (txt->txt != NULL) {
+ isc_mem_free(txt->mctx, txt->txt);
+ }
+ txt->mctx = NULL;
+}
+
+static isc_result_t
+fromstruct_txt(ARGS_FROMSTRUCT) {
+ REQUIRE(type == dns_rdatatype_txt);
+
+ return (generic_fromstruct_txt(CALL_FROMSTRUCT));
+}
+
+static isc_result_t
+tostruct_txt(ARGS_TOSTRUCT) {
+ dns_rdata_txt_t *txt = target;
+
+ REQUIRE(rdata->type == dns_rdatatype_txt);
+ REQUIRE(txt != NULL);
+
+ txt->common.rdclass = rdata->rdclass;
+ txt->common.rdtype = rdata->type;
+ ISC_LINK_INIT(&txt->common, link);
+
+ return (generic_tostruct_txt(CALL_TOSTRUCT));
+}
+
+static void
+freestruct_txt(ARGS_FREESTRUCT) {
+ dns_rdata_txt_t *txt = source;
+
+ REQUIRE(txt != NULL);
+ REQUIRE(txt->common.rdtype == dns_rdatatype_txt);
+
+ generic_freestruct_txt(source);
+}
+
+static isc_result_t
+additionaldata_txt(ARGS_ADDLDATA) {
+ REQUIRE(rdata->type == dns_rdatatype_txt);
+
+ UNUSED(rdata);
+ UNUSED(owner);
+ UNUSED(add);
+ UNUSED(arg);
+
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+digest_txt(ARGS_DIGEST) {
+ isc_region_t r;
+
+ REQUIRE(rdata->type == dns_rdatatype_txt);
+
+ dns_rdata_toregion(rdata, &r);
+
+ return ((digest)(arg, &r));
+}
+
+static bool
+checkowner_txt(ARGS_CHECKOWNER) {
+ REQUIRE(type == dns_rdatatype_txt);
+
+ UNUSED(name);
+ UNUSED(type);
+ UNUSED(rdclass);
+ UNUSED(wildcard);
+
+ return (true);
+}
+
+static bool
+checknames_txt(ARGS_CHECKNAMES) {
+ REQUIRE(rdata->type == dns_rdatatype_txt);
+
+ UNUSED(rdata);
+ UNUSED(owner);
+ UNUSED(bad);
+
+ return (true);
+}
+
+static int
+casecompare_txt(ARGS_COMPARE) {
+ return (compare_txt(rdata1, rdata2));
+}
+
+static isc_result_t
+generic_txt_first(dns_rdata_txt_t *txt) {
+ REQUIRE(txt != NULL);
+ REQUIRE(txt->txt != NULL || txt->txt_len == 0);
+
+ if (txt->txt_len == 0) {
+ return (ISC_R_NOMORE);
+ }
+
+ txt->offset = 0;
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+generic_txt_next(dns_rdata_txt_t *txt) {
+ isc_region_t r;
+ uint8_t length;
+
+ REQUIRE(txt != NULL);
+ REQUIRE(txt->txt != NULL && txt->txt_len != 0);
+
+ INSIST(txt->offset + 1 <= txt->txt_len);
+ r.base = txt->txt + txt->offset;
+ r.length = txt->txt_len - txt->offset;
+ length = uint8_fromregion(&r);
+ INSIST(txt->offset + 1 + length <= txt->txt_len);
+ txt->offset = txt->offset + 1 + length;
+ if (txt->offset == txt->txt_len) {
+ return (ISC_R_NOMORE);
+ }
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+generic_txt_current(dns_rdata_txt_t *txt, dns_rdata_txt_string_t *string) {
+ isc_region_t r;
+
+ REQUIRE(txt != NULL);
+ REQUIRE(string != NULL);
+ REQUIRE(txt->txt != NULL);
+ REQUIRE(txt->offset < txt->txt_len);
+
+ INSIST(txt->offset + 1 <= txt->txt_len);
+ r.base = txt->txt + txt->offset;
+ r.length = txt->txt_len - txt->offset;
+
+ string->length = uint8_fromregion(&r);
+ isc_region_consume(&r, 1);
+ string->data = r.base;
+ INSIST(txt->offset + 1 + string->length <= txt->txt_len);
+
+ return (ISC_R_SUCCESS);
+}
+
+isc_result_t
+dns_rdata_txt_first(dns_rdata_txt_t *txt) {
+ REQUIRE(txt != NULL);
+ REQUIRE(txt->common.rdtype == dns_rdatatype_txt);
+
+ return (generic_txt_first(txt));
+}
+
+isc_result_t
+dns_rdata_txt_next(dns_rdata_txt_t *txt) {
+ REQUIRE(txt != NULL);
+ REQUIRE(txt->common.rdtype == dns_rdatatype_txt);
+
+ return (generic_txt_next(txt));
+}
+
+isc_result_t
+dns_rdata_txt_current(dns_rdata_txt_t *txt, dns_rdata_txt_string_t *string) {
+ REQUIRE(txt != NULL);
+ REQUIRE(txt->common.rdtype == dns_rdatatype_txt);
+
+ return (generic_txt_current(txt, string));
+}
+#endif /* RDATA_GENERIC_TXT_16_C */
diff --git a/lib/dns/rdata/generic/txt_16.h b/lib/dns/rdata/generic/txt_16.h
new file mode 100644
index 0000000..3e4c58a
--- /dev/null
+++ b/lib/dns/rdata/generic/txt_16.h
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/* */
+#pragma once
+
+typedef struct dns_rdata_txt_string {
+ uint8_t length;
+ unsigned char *data;
+} dns_rdata_txt_string_t;
+
+typedef struct dns_rdata_txt {
+ dns_rdatacommon_t common;
+ isc_mem_t *mctx;
+ unsigned char *txt;
+ uint16_t txt_len;
+ /* private */
+ uint16_t offset;
+} dns_rdata_txt_t;
+
+/*
+ * ISC_LANG_BEGINDECLS and ISC_LANG_ENDDECLS are already done
+ * via rdatastructpre.h and rdatastructsuf.h.
+ */
+
+isc_result_t
+dns_rdata_txt_first(dns_rdata_txt_t *);
+
+isc_result_t
+dns_rdata_txt_next(dns_rdata_txt_t *);
+
+isc_result_t
+dns_rdata_txt_current(dns_rdata_txt_t *, dns_rdata_txt_string_t *);
diff --git a/lib/dns/rdata/generic/uri_256.c b/lib/dns/rdata/generic/uri_256.c
new file mode 100644
index 0000000..4618823
--- /dev/null
+++ b/lib/dns/rdata/generic/uri_256.c
@@ -0,0 +1,319 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#ifndef GENERIC_URI_256_C
+#define GENERIC_URI_256_C 1
+
+#define RRTYPE_URI_ATTRIBUTES (0)
+
+static isc_result_t
+fromtext_uri(ARGS_FROMTEXT) {
+ isc_token_t token;
+
+ REQUIRE(type == dns_rdatatype_uri);
+
+ UNUSED(type);
+ UNUSED(rdclass);
+ UNUSED(origin);
+ UNUSED(options);
+ UNUSED(callbacks);
+
+ /*
+ * Priority
+ */
+ RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_number,
+ false));
+ if (token.value.as_ulong > 0xffffU) {
+ RETTOK(ISC_R_RANGE);
+ }
+ RETERR(uint16_tobuffer(token.value.as_ulong, target));
+
+ /*
+ * Weight
+ */
+ RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_number,
+ false));
+ if (token.value.as_ulong > 0xffffU) {
+ RETTOK(ISC_R_RANGE);
+ }
+ RETERR(uint16_tobuffer(token.value.as_ulong, target));
+
+ /*
+ * Target URI
+ */
+ RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_qstring,
+ false));
+ if (token.type != isc_tokentype_qstring) {
+ RETTOK(DNS_R_SYNTAX);
+ }
+ RETTOK(multitxt_fromtext(&token.value.as_textregion, target));
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+totext_uri(ARGS_TOTEXT) {
+ isc_region_t region;
+ unsigned short priority, weight;
+ char buf[sizeof("65000 ")];
+
+ UNUSED(tctx);
+
+ REQUIRE(rdata->type == dns_rdatatype_uri);
+ REQUIRE(rdata->length != 0);
+
+ dns_rdata_toregion(rdata, &region);
+
+ /*
+ * Priority
+ */
+ priority = uint16_fromregion(&region);
+ isc_region_consume(&region, 2);
+ snprintf(buf, sizeof(buf), "%u ", priority);
+ RETERR(str_totext(buf, target));
+
+ /*
+ * Weight
+ */
+ weight = uint16_fromregion(&region);
+ isc_region_consume(&region, 2);
+ snprintf(buf, sizeof(buf), "%u ", weight);
+ RETERR(str_totext(buf, target));
+
+ /*
+ * Target URI
+ */
+ RETERR(multitxt_totext(&region, target));
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+fromwire_uri(ARGS_FROMWIRE) {
+ isc_region_t region;
+
+ REQUIRE(type == dns_rdatatype_uri);
+
+ UNUSED(type);
+ UNUSED(rdclass);
+ UNUSED(dctx);
+ UNUSED(options);
+
+ /*
+ * Priority, weight
+ */
+ isc_buffer_activeregion(source, &region);
+ if (region.length < 4) {
+ return (ISC_R_UNEXPECTEDEND);
+ }
+
+ /*
+ * Priority, weight and target URI
+ */
+ isc_buffer_forward(source, region.length);
+ return (mem_tobuffer(target, region.base, region.length));
+}
+
+static isc_result_t
+towire_uri(ARGS_TOWIRE) {
+ isc_region_t region;
+
+ REQUIRE(rdata->type == dns_rdatatype_uri);
+ REQUIRE(rdata->length != 0);
+
+ UNUSED(cctx);
+
+ dns_rdata_toregion(rdata, &region);
+ return (mem_tobuffer(target, region.base, region.length));
+}
+
+static int
+compare_uri(ARGS_COMPARE) {
+ isc_region_t r1;
+ isc_region_t r2;
+ int order;
+
+ REQUIRE(rdata1->type == rdata2->type);
+ REQUIRE(rdata1->rdclass == rdata2->rdclass);
+ REQUIRE(rdata1->type == dns_rdatatype_uri);
+ REQUIRE(rdata1->length != 0);
+ REQUIRE(rdata2->length != 0);
+
+ dns_rdata_toregion(rdata1, &r1);
+ dns_rdata_toregion(rdata2, &r2);
+
+ /*
+ * Priority
+ */
+ order = memcmp(r1.base, r2.base, 2);
+ if (order != 0) {
+ return (order < 0 ? -1 : 1);
+ }
+ isc_region_consume(&r1, 2);
+ isc_region_consume(&r2, 2);
+
+ /*
+ * Weight
+ */
+ order = memcmp(r1.base, r2.base, 2);
+ if (order != 0) {
+ return (order < 0 ? -1 : 1);
+ }
+ isc_region_consume(&r1, 2);
+ isc_region_consume(&r2, 2);
+
+ return (isc_region_compare(&r1, &r2));
+}
+
+static isc_result_t
+fromstruct_uri(ARGS_FROMSTRUCT) {
+ dns_rdata_uri_t *uri = source;
+
+ REQUIRE(type == dns_rdatatype_uri);
+ REQUIRE(uri != NULL);
+ REQUIRE(uri->common.rdtype == type);
+ REQUIRE(uri->common.rdclass == rdclass);
+ REQUIRE(uri->target != NULL && uri->tgt_len != 0);
+
+ UNUSED(type);
+ UNUSED(rdclass);
+
+ /*
+ * Priority
+ */
+ RETERR(uint16_tobuffer(uri->priority, target));
+
+ /*
+ * Weight
+ */
+ RETERR(uint16_tobuffer(uri->weight, target));
+
+ /*
+ * Target URI
+ */
+ return (mem_tobuffer(target, uri->target, uri->tgt_len));
+}
+
+static isc_result_t
+tostruct_uri(ARGS_TOSTRUCT) {
+ dns_rdata_uri_t *uri = target;
+ isc_region_t sr;
+
+ REQUIRE(rdata->type == dns_rdatatype_uri);
+ REQUIRE(uri != NULL);
+ REQUIRE(rdata->length != 0);
+
+ uri->common.rdclass = rdata->rdclass;
+ uri->common.rdtype = rdata->type;
+ ISC_LINK_INIT(&uri->common, link);
+
+ dns_rdata_toregion(rdata, &sr);
+
+ /*
+ * Priority
+ */
+ if (sr.length < 2) {
+ return (ISC_R_UNEXPECTEDEND);
+ }
+ uri->priority = uint16_fromregion(&sr);
+ isc_region_consume(&sr, 2);
+
+ /*
+ * Weight
+ */
+ if (sr.length < 2) {
+ return (ISC_R_UNEXPECTEDEND);
+ }
+ uri->weight = uint16_fromregion(&sr);
+ isc_region_consume(&sr, 2);
+
+ /*
+ * Target URI
+ */
+ uri->tgt_len = sr.length;
+ uri->target = mem_maybedup(mctx, sr.base, sr.length);
+ if (uri->target == NULL) {
+ return (ISC_R_NOMEMORY);
+ }
+
+ uri->mctx = mctx;
+ return (ISC_R_SUCCESS);
+}
+
+static void
+freestruct_uri(ARGS_FREESTRUCT) {
+ dns_rdata_uri_t *uri = (dns_rdata_uri_t *)source;
+
+ REQUIRE(uri != NULL);
+ REQUIRE(uri->common.rdtype == dns_rdatatype_uri);
+
+ if (uri->mctx == NULL) {
+ return;
+ }
+
+ if (uri->target != NULL) {
+ isc_mem_free(uri->mctx, uri->target);
+ }
+ uri->mctx = NULL;
+}
+
+static isc_result_t
+additionaldata_uri(ARGS_ADDLDATA) {
+ REQUIRE(rdata->type == dns_rdatatype_uri);
+
+ UNUSED(rdata);
+ UNUSED(owner);
+ UNUSED(add);
+ UNUSED(arg);
+
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+digest_uri(ARGS_DIGEST) {
+ isc_region_t r;
+
+ REQUIRE(rdata->type == dns_rdatatype_uri);
+
+ dns_rdata_toregion(rdata, &r);
+
+ return ((digest)(arg, &r));
+}
+
+static bool
+checkowner_uri(ARGS_CHECKOWNER) {
+ REQUIRE(type == dns_rdatatype_uri);
+
+ UNUSED(name);
+ UNUSED(type);
+ UNUSED(rdclass);
+ UNUSED(wildcard);
+
+ return (true);
+}
+
+static bool
+checknames_uri(ARGS_CHECKNAMES) {
+ REQUIRE(rdata->type == dns_rdatatype_uri);
+
+ UNUSED(rdata);
+ UNUSED(owner);
+ UNUSED(bad);
+
+ return (true);
+}
+
+static int
+casecompare_uri(ARGS_COMPARE) {
+ return (compare_uri(rdata1, rdata2));
+}
+
+#endif /* GENERIC_URI_256_C */
diff --git a/lib/dns/rdata/generic/uri_256.h b/lib/dns/rdata/generic/uri_256.h
new file mode 100644
index 0000000..be99e95
--- /dev/null
+++ b/lib/dns/rdata/generic/uri_256.h
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#pragma once
+
+typedef struct dns_rdata_uri {
+ dns_rdatacommon_t common;
+ isc_mem_t *mctx;
+ uint16_t priority;
+ uint16_t weight;
+ unsigned char *target;
+ uint16_t tgt_len;
+} dns_rdata_uri_t;
diff --git a/lib/dns/rdata/generic/x25_19.c b/lib/dns/rdata/generic/x25_19.c
new file mode 100644
index 0000000..a0fe705
--- /dev/null
+++ b/lib/dns/rdata/generic/x25_19.c
@@ -0,0 +1,233 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/* RFC1183 */
+
+#ifndef RDATA_GENERIC_X25_19_C
+#define RDATA_GENERIC_X25_19_C
+
+#define RRTYPE_X25_ATTRIBUTES (0)
+
+static isc_result_t
+fromtext_x25(ARGS_FROMTEXT) {
+ isc_token_t token;
+ unsigned int i;
+
+ REQUIRE(type == dns_rdatatype_x25);
+
+ UNUSED(type);
+ UNUSED(rdclass);
+ UNUSED(origin);
+ UNUSED(options);
+ UNUSED(callbacks);
+
+ RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_qstring,
+ false));
+ if (token.value.as_textregion.length < 4) {
+ RETTOK(DNS_R_SYNTAX);
+ }
+ for (i = 0; i < token.value.as_textregion.length; i++) {
+ if (!isdigit((unsigned char)token.value.as_textregion.base[i]))
+ {
+ RETTOK(ISC_R_RANGE);
+ }
+ }
+ RETTOK(txt_fromtext(&token.value.as_textregion, target));
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+totext_x25(ARGS_TOTEXT) {
+ isc_region_t region;
+
+ UNUSED(tctx);
+
+ REQUIRE(rdata->type == dns_rdatatype_x25);
+ REQUIRE(rdata->length != 0);
+
+ dns_rdata_toregion(rdata, &region);
+ return (txt_totext(&region, true, target));
+}
+
+static isc_result_t
+fromwire_x25(ARGS_FROMWIRE) {
+ isc_region_t sr;
+ unsigned int i;
+
+ REQUIRE(type == dns_rdatatype_x25);
+
+ UNUSED(type);
+ UNUSED(dctx);
+ UNUSED(rdclass);
+ UNUSED(options);
+
+ isc_buffer_activeregion(source, &sr);
+ if (sr.length < 5 || sr.base[0] != (sr.length - 1)) {
+ return (DNS_R_FORMERR);
+ }
+ for (i = 1; i < sr.length; i++) {
+ if (sr.base[i] < 0x30 || sr.base[i] > 0x39) {
+ return (DNS_R_FORMERR);
+ }
+ }
+ return (txt_fromwire(source, target));
+}
+
+static isc_result_t
+towire_x25(ARGS_TOWIRE) {
+ UNUSED(cctx);
+
+ REQUIRE(rdata->type == dns_rdatatype_x25);
+ REQUIRE(rdata->length != 0);
+
+ return (mem_tobuffer(target, rdata->data, rdata->length));
+}
+
+static int
+compare_x25(ARGS_COMPARE) {
+ isc_region_t r1;
+ isc_region_t r2;
+
+ REQUIRE(rdata1->type == rdata2->type);
+ REQUIRE(rdata1->rdclass == rdata2->rdclass);
+ REQUIRE(rdata1->type == dns_rdatatype_x25);
+ REQUIRE(rdata1->length != 0);
+ REQUIRE(rdata2->length != 0);
+
+ dns_rdata_toregion(rdata1, &r1);
+ dns_rdata_toregion(rdata2, &r2);
+ return (isc_region_compare(&r1, &r2));
+}
+
+static isc_result_t
+fromstruct_x25(ARGS_FROMSTRUCT) {
+ dns_rdata_x25_t *x25 = source;
+ uint8_t i;
+
+ REQUIRE(type == dns_rdatatype_x25);
+ REQUIRE(x25 != NULL);
+ REQUIRE(x25->common.rdtype == type);
+ REQUIRE(x25->common.rdclass == rdclass);
+ REQUIRE(x25->x25 != NULL && x25->x25_len != 0);
+
+ UNUSED(type);
+ UNUSED(rdclass);
+
+ if (x25->x25_len < 4) {
+ return (ISC_R_RANGE);
+ }
+
+ for (i = 0; i < x25->x25_len; i++) {
+ if (!isdigit((unsigned char)x25->x25[i])) {
+ return (ISC_R_RANGE);
+ }
+ }
+
+ RETERR(uint8_tobuffer(x25->x25_len, target));
+ return (mem_tobuffer(target, x25->x25, x25->x25_len));
+}
+
+static isc_result_t
+tostruct_x25(ARGS_TOSTRUCT) {
+ dns_rdata_x25_t *x25 = target;
+ isc_region_t r;
+
+ REQUIRE(rdata->type == dns_rdatatype_x25);
+ REQUIRE(x25 != NULL);
+ REQUIRE(rdata->length != 0);
+
+ x25->common.rdclass = rdata->rdclass;
+ x25->common.rdtype = rdata->type;
+ ISC_LINK_INIT(&x25->common, link);
+
+ dns_rdata_toregion(rdata, &r);
+ x25->x25_len = uint8_fromregion(&r);
+ isc_region_consume(&r, 1);
+ x25->x25 = mem_maybedup(mctx, r.base, x25->x25_len);
+ if (x25->x25 == NULL) {
+ return (ISC_R_NOMEMORY);
+ }
+
+ x25->mctx = mctx;
+ return (ISC_R_SUCCESS);
+}
+
+static void
+freestruct_x25(ARGS_FREESTRUCT) {
+ dns_rdata_x25_t *x25 = source;
+
+ REQUIRE(x25 != NULL);
+ REQUIRE(x25->common.rdtype == dns_rdatatype_x25);
+
+ if (x25->mctx == NULL) {
+ return;
+ }
+
+ if (x25->x25 != NULL) {
+ isc_mem_free(x25->mctx, x25->x25);
+ }
+ x25->mctx = NULL;
+}
+
+static isc_result_t
+additionaldata_x25(ARGS_ADDLDATA) {
+ REQUIRE(rdata->type == dns_rdatatype_x25);
+
+ UNUSED(rdata);
+ UNUSED(owner);
+ UNUSED(add);
+ UNUSED(arg);
+
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+digest_x25(ARGS_DIGEST) {
+ isc_region_t r;
+
+ REQUIRE(rdata->type == dns_rdatatype_x25);
+
+ dns_rdata_toregion(rdata, &r);
+
+ return ((digest)(arg, &r));
+}
+
+static bool
+checkowner_x25(ARGS_CHECKOWNER) {
+ REQUIRE(type == dns_rdatatype_x25);
+
+ UNUSED(name);
+ UNUSED(type);
+ UNUSED(rdclass);
+ UNUSED(wildcard);
+
+ return (true);
+}
+
+static bool
+checknames_x25(ARGS_CHECKNAMES) {
+ REQUIRE(rdata->type == dns_rdatatype_x25);
+
+ UNUSED(rdata);
+ UNUSED(owner);
+ UNUSED(bad);
+
+ return (true);
+}
+
+static int
+casecompare_x25(ARGS_COMPARE) {
+ return (compare_x25(rdata1, rdata2));
+}
+
+#endif /* RDATA_GENERIC_X25_19_C */
diff --git a/lib/dns/rdata/generic/x25_19.h b/lib/dns/rdata/generic/x25_19.h
new file mode 100644
index 0000000..74a5ee1
--- /dev/null
+++ b/lib/dns/rdata/generic/x25_19.h
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#pragma once
+
+/*!
+ * \brief Per RFC1183 */
+
+typedef struct dns_rdata_x25 {
+ dns_rdatacommon_t common;
+ isc_mem_t *mctx;
+ unsigned char *x25;
+ uint8_t x25_len;
+} dns_rdata_x25_t;
diff --git a/lib/dns/rdata/generic/zonemd_63.c b/lib/dns/rdata/generic/zonemd_63.c
new file mode 100644
index 0000000..1bf9573
--- /dev/null
+++ b/lib/dns/rdata/generic/zonemd_63.c
@@ -0,0 +1,351 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/* RFC 8976 */
+
+#ifndef RDATA_GENERIC_ZONEMD_63_C
+#define RDATA_GENERIC_ZONEMD_63_C
+
+#define RRTYPE_ZONEMD_ATTRIBUTES 0
+
+static isc_result_t
+fromtext_zonemd(ARGS_FROMTEXT) {
+ isc_token_t token;
+ int digest_type, length;
+ isc_buffer_t save;
+ isc_result_t result;
+
+ UNUSED(type);
+ UNUSED(rdclass);
+ UNUSED(origin);
+ UNUSED(options);
+ UNUSED(callbacks);
+
+ /*
+ * Zone Serial.
+ */
+ RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_number,
+ false));
+ RETERR(uint32_tobuffer(token.value.as_ulong, target));
+
+ /*
+ * Digest Scheme.
+ */
+ RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_number,
+ false));
+ RETERR(uint8_tobuffer(token.value.as_ulong, target));
+
+ /*
+ * Digest Type.
+ */
+ RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_number,
+ false));
+ digest_type = token.value.as_ulong;
+ RETERR(uint8_tobuffer(digest_type, target));
+
+ /*
+ * Digest.
+ */
+ switch (digest_type) {
+ case DNS_ZONEMD_DIGEST_SHA384:
+ length = ISC_SHA384_DIGESTLENGTH;
+ break;
+ case DNS_ZONEMD_DIGEST_SHA512:
+ length = ISC_SHA512_DIGESTLENGTH;
+ break;
+ default:
+ length = -2;
+ break;
+ }
+
+ save = *target;
+ result = isc_hex_tobuffer(lexer, target, length);
+ /* Minimum length of digest is 12 octets. */
+ if (isc_buffer_usedlength(target) - isc_buffer_usedlength(&save) < 12) {
+ return (ISC_R_UNEXPECTEDEND);
+ }
+ return (result);
+}
+
+static isc_result_t
+totext_zonemd(ARGS_TOTEXT) {
+ isc_region_t sr;
+ char buf[sizeof("0123456789")];
+ unsigned long num;
+
+ REQUIRE(rdata->length > 6);
+
+ UNUSED(tctx);
+
+ dns_rdata_toregion(rdata, &sr);
+
+ /*
+ * Zone Serial.
+ */
+ num = uint32_fromregion(&sr);
+ isc_region_consume(&sr, 4);
+ snprintf(buf, sizeof(buf), "%lu", num);
+ RETERR(str_totext(buf, target));
+
+ RETERR(str_totext(" ", target));
+
+ /*
+ * Digest scheme.
+ */
+ num = uint8_fromregion(&sr);
+ isc_region_consume(&sr, 1);
+ snprintf(buf, sizeof(buf), "%lu", num);
+ RETERR(str_totext(buf, target));
+
+ RETERR(str_totext(" ", target));
+
+ /*
+ * Digest type.
+ */
+ num = uint8_fromregion(&sr);
+ isc_region_consume(&sr, 1);
+ snprintf(buf, sizeof(buf), "%lu", num);
+ RETERR(str_totext(buf, target));
+
+ /*
+ * Digest.
+ */
+ if ((tctx->flags & DNS_STYLEFLAG_MULTILINE) != 0) {
+ RETERR(str_totext(" (", target));
+ }
+ RETERR(str_totext(tctx->linebreak, target));
+ if ((tctx->flags & DNS_STYLEFLAG_NOCRYPTO) == 0) {
+ if (tctx->width == 0) { /* No splitting */
+ RETERR(isc_hex_totext(&sr, 0, "", target));
+ } else {
+ RETERR(isc_hex_totext(&sr, tctx->width - 2,
+ tctx->linebreak, target));
+ }
+ } else {
+ RETERR(str_totext("[omitted]", target));
+ }
+ if ((tctx->flags & DNS_STYLEFLAG_MULTILINE) != 0) {
+ RETERR(str_totext(" )", target));
+ }
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+fromwire_zonemd(ARGS_FROMWIRE) {
+ isc_region_t sr;
+ size_t digestlen = 0;
+
+ UNUSED(type);
+ UNUSED(rdclass);
+ UNUSED(dctx);
+ UNUSED(options);
+
+ isc_buffer_activeregion(source, &sr);
+
+ /*
+ * If we do not recognize the digest type, ensure that the digest
+ * meets minimum length (12).
+ *
+ * If we do recognize the digest type, ensure that the digest is of the
+ * correct length.
+ */
+ if (sr.length < 18) {
+ return (ISC_R_UNEXPECTEDEND);
+ }
+
+ switch (sr.base[5]) {
+ case DNS_ZONEMD_DIGEST_SHA384:
+ digestlen = ISC_SHA384_DIGESTLENGTH;
+ break;
+ case DNS_ZONEMD_DIGEST_SHA512:
+ digestlen = ISC_SHA512_DIGESTLENGTH;
+ break;
+ default:
+ break;
+ }
+
+ if (digestlen != 0 && sr.length < 6 + digestlen) {
+ return (ISC_R_UNEXPECTEDEND);
+ }
+
+ /*
+ * Only specify the number of octets to consume if we recognize the
+ * digest type.
+ *
+ * If there is extra data, dns_rdata_fromwire() will detect that.
+ */
+ if (digestlen != 0) {
+ sr.length = 6 + digestlen;
+ }
+
+ isc_buffer_forward(source, sr.length);
+ return (mem_tobuffer(target, sr.base, sr.length));
+}
+
+static isc_result_t
+towire_zonemd(ARGS_TOWIRE) {
+ isc_region_t sr;
+
+ REQUIRE(rdata->type == dns_rdatatype_zonemd);
+ REQUIRE(rdata->length != 0);
+
+ UNUSED(cctx);
+
+ dns_rdata_toregion(rdata, &sr);
+ return (mem_tobuffer(target, sr.base, sr.length));
+}
+
+static int
+compare_zonemd(ARGS_COMPARE) {
+ isc_region_t r1;
+ isc_region_t r2;
+
+ REQUIRE(rdata1->type == rdata2->type);
+ REQUIRE(rdata1->rdclass == rdata2->rdclass);
+ REQUIRE(rdata1->type == dns_rdatatype_zonemd);
+ REQUIRE(rdata1->length != 0);
+ REQUIRE(rdata2->length != 0);
+
+ dns_rdata_toregion(rdata1, &r1);
+ dns_rdata_toregion(rdata2, &r2);
+ return (isc_region_compare(&r1, &r2));
+}
+
+static isc_result_t
+fromstruct_zonemd(ARGS_FROMSTRUCT) {
+ dns_rdata_zonemd_t *zonemd = source;
+
+ REQUIRE(zonemd != NULL);
+ REQUIRE(zonemd->common.rdtype == type);
+ REQUIRE(zonemd->common.rdclass == rdclass);
+
+ UNUSED(type);
+ UNUSED(rdclass);
+
+ switch (zonemd->digest_type) {
+ case DNS_ZONEMD_DIGEST_SHA384:
+ REQUIRE(zonemd->length == ISC_SHA384_DIGESTLENGTH);
+ break;
+ case DNS_ZONEMD_DIGEST_SHA512:
+ REQUIRE(zonemd->length == ISC_SHA512_DIGESTLENGTH);
+ break;
+ }
+
+ RETERR(uint32_tobuffer(zonemd->serial, target));
+ RETERR(uint8_tobuffer(zonemd->scheme, target));
+ RETERR(uint8_tobuffer(zonemd->digest_type, target));
+
+ return (mem_tobuffer(target, zonemd->digest, zonemd->length));
+}
+
+static isc_result_t
+tostruct_zonemd(ARGS_TOSTRUCT) {
+ dns_rdata_zonemd_t *zonemd = target;
+ isc_region_t region;
+
+ REQUIRE(rdata->type == dns_rdatatype_zonemd);
+ REQUIRE(zonemd != NULL);
+ REQUIRE(rdata->length != 0);
+
+ zonemd->common.rdclass = rdata->rdclass;
+ zonemd->common.rdtype = rdata->type;
+ ISC_LINK_INIT(&zonemd->common, link);
+
+ dns_rdata_toregion(rdata, &region);
+
+ zonemd->serial = uint32_fromregion(&region);
+ isc_region_consume(&region, 4);
+ zonemd->scheme = uint8_fromregion(&region);
+ isc_region_consume(&region, 1);
+ zonemd->digest_type = uint8_fromregion(&region);
+ isc_region_consume(&region, 1);
+ zonemd->length = region.length;
+
+ zonemd->digest = mem_maybedup(mctx, region.base, region.length);
+ if (zonemd->digest == NULL) {
+ return (ISC_R_NOMEMORY);
+ }
+
+ zonemd->mctx = mctx;
+ return (ISC_R_SUCCESS);
+}
+
+static void
+freestruct_zonemd(ARGS_FREESTRUCT) {
+ dns_rdata_zonemd_t *zonemd = source;
+
+ REQUIRE(zonemd != NULL);
+ REQUIRE(zonemd->common.rdtype == dns_rdatatype_zonemd);
+
+ if (zonemd->mctx == NULL) {
+ return;
+ }
+
+ if (zonemd->digest != NULL) {
+ isc_mem_free(zonemd->mctx, zonemd->digest);
+ }
+ zonemd->mctx = NULL;
+}
+
+static isc_result_t
+additionaldata_zonemd(ARGS_ADDLDATA) {
+ REQUIRE(rdata->type == dns_rdatatype_zonemd);
+
+ UNUSED(rdata);
+ UNUSED(owner);
+ UNUSED(add);
+ UNUSED(arg);
+
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+digest_zonemd(ARGS_DIGEST) {
+ isc_region_t r;
+
+ REQUIRE(rdata->type == dns_rdatatype_zonemd);
+
+ dns_rdata_toregion(rdata, &r);
+
+ return ((digest)(arg, &r));
+}
+
+static bool
+checkowner_zonemd(ARGS_CHECKOWNER) {
+ REQUIRE(type == dns_rdatatype_zonemd);
+
+ UNUSED(name);
+ UNUSED(type);
+ UNUSED(rdclass);
+ UNUSED(wildcard);
+
+ return (true);
+}
+
+static bool
+checknames_zonemd(ARGS_CHECKNAMES) {
+ REQUIRE(rdata->type == dns_rdatatype_zonemd);
+
+ UNUSED(rdata);
+ UNUSED(owner);
+ UNUSED(bad);
+
+ return (true);
+}
+
+static int
+casecompare_zonemd(ARGS_COMPARE) {
+ return (compare_zonemd(rdata1, rdata2));
+}
+
+#endif /* RDATA_GENERIC_ZONEMD_63_C */
diff --git a/lib/dns/rdata/generic/zonemd_63.h b/lib/dns/rdata/generic/zonemd_63.h
new file mode 100644
index 0000000..5856b0d
--- /dev/null
+++ b/lib/dns/rdata/generic/zonemd_63.h
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#pragma once
+
+/* Known digest type(s). */
+#define DNS_ZONEMD_DIGEST_SHA384 (1)
+#define DNS_ZONEMD_DIGEST_SHA512 (2)
+
+/*
+ * \brief per RFC 8976
+ */
+typedef struct dns_rdata_zonemd {
+ dns_rdatacommon_t common;
+ isc_mem_t *mctx;
+ uint32_t serial;
+ uint8_t scheme;
+ uint8_t digest_type;
+ unsigned char *digest;
+ uint16_t length;
+} dns_rdata_zonemd_t;
diff --git a/lib/dns/rdata/hs_4/a_1.c b/lib/dns/rdata/hs_4/a_1.c
new file mode 100644
index 0000000..26246b7
--- /dev/null
+++ b/lib/dns/rdata/hs_4/a_1.c
@@ -0,0 +1,233 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#pragma once
+
+#include <isc/net.h>
+
+#define RRTYPE_A_ATTRIBUTES (0)
+
+static isc_result_t
+fromtext_hs_a(ARGS_FROMTEXT) {
+ isc_token_t token;
+ struct in_addr addr;
+ isc_region_t region;
+
+ REQUIRE(type == dns_rdatatype_a);
+ REQUIRE(rdclass == dns_rdataclass_hs);
+
+ UNUSED(type);
+ UNUSED(origin);
+ UNUSED(options);
+ UNUSED(rdclass);
+ UNUSED(callbacks);
+
+ RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_string,
+ false));
+
+ if (inet_pton(AF_INET, DNS_AS_STR(token), &addr) != 1) {
+ RETTOK(DNS_R_BADDOTTEDQUAD);
+ }
+ isc_buffer_availableregion(target, &region);
+ if (region.length < 4) {
+ return (ISC_R_NOSPACE);
+ }
+ memmove(region.base, &addr, 4);
+ isc_buffer_add(target, 4);
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+totext_hs_a(ARGS_TOTEXT) {
+ isc_region_t region;
+
+ REQUIRE(rdata->type == dns_rdatatype_a);
+ REQUIRE(rdata->rdclass == dns_rdataclass_hs);
+ REQUIRE(rdata->length == 4);
+
+ UNUSED(tctx);
+
+ dns_rdata_toregion(rdata, &region);
+ return (inet_totext(AF_INET, tctx->flags, &region, target));
+}
+
+static isc_result_t
+fromwire_hs_a(ARGS_FROMWIRE) {
+ isc_region_t sregion;
+ isc_region_t tregion;
+
+ REQUIRE(type == dns_rdatatype_a);
+ REQUIRE(rdclass == dns_rdataclass_hs);
+
+ UNUSED(type);
+ UNUSED(dctx);
+ UNUSED(options);
+ UNUSED(rdclass);
+
+ isc_buffer_activeregion(source, &sregion);
+ isc_buffer_availableregion(target, &tregion);
+ if (sregion.length < 4) {
+ return (ISC_R_UNEXPECTEDEND);
+ }
+ if (tregion.length < 4) {
+ return (ISC_R_NOSPACE);
+ }
+
+ memmove(tregion.base, sregion.base, 4);
+ isc_buffer_forward(source, 4);
+ isc_buffer_add(target, 4);
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+towire_hs_a(ARGS_TOWIRE) {
+ isc_region_t region;
+
+ REQUIRE(rdata->type == dns_rdatatype_a);
+ REQUIRE(rdata->rdclass == dns_rdataclass_hs);
+ REQUIRE(rdata->length == 4);
+
+ UNUSED(cctx);
+
+ isc_buffer_availableregion(target, &region);
+ if (region.length < rdata->length) {
+ return (ISC_R_NOSPACE);
+ }
+ memmove(region.base, rdata->data, rdata->length);
+ isc_buffer_add(target, 4);
+ return (ISC_R_SUCCESS);
+}
+
+static int
+compare_hs_a(ARGS_COMPARE) {
+ int order;
+
+ REQUIRE(rdata1->type == rdata2->type);
+ REQUIRE(rdata1->rdclass == rdata2->rdclass);
+ REQUIRE(rdata1->type == dns_rdatatype_a);
+ REQUIRE(rdata1->rdclass == dns_rdataclass_hs);
+ REQUIRE(rdata1->length == 4);
+ REQUIRE(rdata2->length == 4);
+
+ order = memcmp(rdata1->data, rdata2->data, 4);
+ if (order != 0) {
+ order = (order < 0) ? -1 : 1;
+ }
+
+ return (order);
+}
+
+static isc_result_t
+fromstruct_hs_a(ARGS_FROMSTRUCT) {
+ dns_rdata_hs_a_t *a = source;
+ uint32_t n;
+
+ REQUIRE(type == dns_rdatatype_a);
+ REQUIRE(rdclass == dns_rdataclass_hs);
+ REQUIRE(a != NULL);
+ REQUIRE(a->common.rdtype == type);
+ REQUIRE(a->common.rdclass == rdclass);
+
+ UNUSED(type);
+ UNUSED(rdclass);
+
+ n = ntohl(a->in_addr.s_addr);
+
+ return (uint32_tobuffer(n, target));
+}
+
+static isc_result_t
+tostruct_hs_a(ARGS_TOSTRUCT) {
+ dns_rdata_hs_a_t *a = target;
+ uint32_t n;
+ isc_region_t region;
+
+ REQUIRE(rdata->type == dns_rdatatype_a);
+ REQUIRE(rdata->rdclass == dns_rdataclass_hs);
+ REQUIRE(rdata->length == 4);
+ REQUIRE(a != NULL);
+
+ UNUSED(mctx);
+
+ a->common.rdclass = rdata->rdclass;
+ a->common.rdtype = rdata->type;
+ ISC_LINK_INIT(&a->common, link);
+
+ dns_rdata_toregion(rdata, &region);
+ n = uint32_fromregion(&region);
+ a->in_addr.s_addr = htonl(n);
+
+ return (ISC_R_SUCCESS);
+}
+
+static void
+freestruct_hs_a(ARGS_FREESTRUCT) {
+ UNUSED(source);
+
+ REQUIRE(source != NULL);
+}
+
+static isc_result_t
+additionaldata_hs_a(ARGS_ADDLDATA) {
+ REQUIRE(rdata->type == dns_rdatatype_a);
+ REQUIRE(rdata->rdclass == dns_rdataclass_hs);
+
+ UNUSED(rdata);
+ UNUSED(owner);
+ UNUSED(add);
+ UNUSED(arg);
+
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+digest_hs_a(ARGS_DIGEST) {
+ isc_region_t r;
+
+ REQUIRE(rdata->type == dns_rdatatype_a);
+ REQUIRE(rdata->rdclass == dns_rdataclass_hs);
+
+ dns_rdata_toregion(rdata, &r);
+
+ return ((digest)(arg, &r));
+}
+
+static bool
+checkowner_hs_a(ARGS_CHECKOWNER) {
+ REQUIRE(type == dns_rdatatype_a);
+ REQUIRE(rdclass == dns_rdataclass_hs);
+
+ UNUSED(name);
+ UNUSED(type);
+ UNUSED(rdclass);
+ UNUSED(wildcard);
+
+ return (true);
+}
+
+static bool
+checknames_hs_a(ARGS_CHECKNAMES) {
+ REQUIRE(rdata->type == dns_rdatatype_a);
+ REQUIRE(rdata->rdclass == dns_rdataclass_hs);
+
+ UNUSED(rdata);
+ UNUSED(owner);
+ UNUSED(bad);
+
+ return (true);
+}
+
+static int
+casecompare_hs_a(ARGS_COMPARE) {
+ return (compare_hs_a(rdata1, rdata2));
+}
diff --git a/lib/dns/rdata/hs_4/a_1.h b/lib/dns/rdata/hs_4/a_1.h
new file mode 100644
index 0000000..f57d547
--- /dev/null
+++ b/lib/dns/rdata/hs_4/a_1.h
@@ -0,0 +1,20 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/* */
+#pragma once
+
+typedef struct dns_rdata_hs_a {
+ dns_rdatacommon_t common;
+ struct in_addr in_addr;
+} dns_rdata_hs_a_t;
diff --git a/lib/dns/rdata/in_1/a6_38.c b/lib/dns/rdata/in_1/a6_38.c
new file mode 100644
index 0000000..80941d6
--- /dev/null
+++ b/lib/dns/rdata/in_1/a6_38.c
@@ -0,0 +1,487 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/* RFC2874 */
+
+#ifndef RDATA_IN_1_A6_28_C
+#define RDATA_IN_1_A6_28_C
+
+#include <isc/net.h>
+
+#define RRTYPE_A6_ATTRIBUTES (0)
+
+static isc_result_t
+fromtext_in_a6(ARGS_FROMTEXT) {
+ isc_token_t token;
+ unsigned char addr[16];
+ unsigned char prefixlen;
+ unsigned char octets;
+ unsigned char mask;
+ dns_name_t name;
+ isc_buffer_t buffer;
+ bool ok;
+
+ REQUIRE(type == dns_rdatatype_a6);
+ REQUIRE(rdclass == dns_rdataclass_in);
+
+ UNUSED(type);
+ UNUSED(rdclass);
+ UNUSED(callbacks);
+
+ /*
+ * Prefix length.
+ */
+ RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_number,
+ false));
+ if (token.value.as_ulong > 128U) {
+ RETTOK(ISC_R_RANGE);
+ }
+
+ prefixlen = (unsigned char)token.value.as_ulong;
+ RETERR(mem_tobuffer(target, &prefixlen, 1));
+
+ /*
+ * Suffix.
+ */
+ if (prefixlen != 128) {
+ /*
+ * Prefix 0..127.
+ */
+ octets = prefixlen / 8;
+ /*
+ * Octets 0..15.
+ */
+ RETERR(isc_lex_getmastertoken(lexer, &token,
+ isc_tokentype_string, false));
+ if (inet_pton(AF_INET6, DNS_AS_STR(token), addr) != 1) {
+ RETTOK(DNS_R_BADAAAA);
+ }
+ mask = 0xff >> (prefixlen % 8);
+ addr[octets] &= mask;
+ RETERR(mem_tobuffer(target, &addr[octets], 16 - octets));
+ }
+
+ if (prefixlen == 0) {
+ return (ISC_R_SUCCESS);
+ }
+
+ RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_string,
+ false));
+ dns_name_init(&name, NULL);
+ buffer_fromregion(&buffer, &token.value.as_region);
+ if (origin == NULL) {
+ origin = dns_rootname;
+ }
+ RETTOK(dns_name_fromtext(&name, &buffer, origin, options, target));
+ ok = true;
+ if ((options & DNS_RDATA_CHECKNAMES) != 0) {
+ ok = dns_name_ishostname(&name, false);
+ }
+ if (!ok && (options & DNS_RDATA_CHECKNAMESFAIL) != 0) {
+ RETTOK(DNS_R_BADNAME);
+ }
+ if (!ok && callbacks != NULL) {
+ warn_badname(&name, lexer, callbacks);
+ }
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+totext_in_a6(ARGS_TOTEXT) {
+ isc_region_t sr, ar;
+ unsigned char addr[16];
+ unsigned char prefixlen;
+ unsigned char octets;
+ unsigned char mask;
+ char buf[sizeof("128")];
+ dns_name_t name;
+ dns_name_t prefix;
+ bool sub;
+
+ REQUIRE(rdata->type == dns_rdatatype_a6);
+ REQUIRE(rdata->rdclass == dns_rdataclass_in);
+ REQUIRE(rdata->length != 0);
+
+ dns_rdata_toregion(rdata, &sr);
+ prefixlen = sr.base[0];
+ INSIST(prefixlen <= 128);
+ isc_region_consume(&sr, 1);
+ snprintf(buf, sizeof(buf), "%u", prefixlen);
+ RETERR(str_totext(buf, target));
+ RETERR(str_totext(" ", target));
+
+ if (prefixlen != 128) {
+ octets = prefixlen / 8;
+ memset(addr, 0, sizeof(addr));
+ memmove(&addr[octets], sr.base, 16 - octets);
+ mask = 0xff >> (prefixlen % 8);
+ addr[octets] &= mask;
+ ar.base = addr;
+ ar.length = sizeof(addr);
+ RETERR(inet_totext(AF_INET6, tctx->flags, &ar, target));
+ isc_region_consume(&sr, 16 - octets);
+ }
+
+ if (prefixlen == 0) {
+ return (ISC_R_SUCCESS);
+ }
+
+ RETERR(str_totext(" ", target));
+ dns_name_init(&name, NULL);
+ dns_name_init(&prefix, NULL);
+ dns_name_fromregion(&name, &sr);
+ sub = name_prefix(&name, tctx->origin, &prefix);
+ return (dns_name_totext(&prefix, sub, target));
+}
+
+static isc_result_t
+fromwire_in_a6(ARGS_FROMWIRE) {
+ isc_region_t sr;
+ unsigned char prefixlen;
+ unsigned char octets;
+ unsigned char mask;
+ dns_name_t name;
+
+ REQUIRE(type == dns_rdatatype_a6);
+ REQUIRE(rdclass == dns_rdataclass_in);
+
+ UNUSED(type);
+ UNUSED(rdclass);
+
+ dns_decompress_setmethods(dctx, DNS_COMPRESS_NONE);
+
+ isc_buffer_activeregion(source, &sr);
+ /*
+ * Prefix length.
+ */
+ if (sr.length < 1) {
+ return (ISC_R_UNEXPECTEDEND);
+ }
+ prefixlen = sr.base[0];
+ if (prefixlen > 128) {
+ return (ISC_R_RANGE);
+ }
+ isc_region_consume(&sr, 1);
+ RETERR(mem_tobuffer(target, &prefixlen, 1));
+ isc_buffer_forward(source, 1);
+
+ /*
+ * Suffix.
+ */
+ if (prefixlen != 128) {
+ octets = 16 - prefixlen / 8;
+ if (sr.length < octets) {
+ return (ISC_R_UNEXPECTEDEND);
+ }
+ mask = 0xff >> (prefixlen % 8);
+ if ((sr.base[0] & ~mask) != 0) {
+ return (DNS_R_FORMERR);
+ }
+ RETERR(mem_tobuffer(target, sr.base, octets));
+ isc_buffer_forward(source, octets);
+ }
+
+ if (prefixlen == 0) {
+ return (ISC_R_SUCCESS);
+ }
+
+ dns_name_init(&name, NULL);
+ return (dns_name_fromwire(&name, source, dctx, options, target));
+}
+
+static isc_result_t
+towire_in_a6(ARGS_TOWIRE) {
+ isc_region_t sr;
+ dns_name_t name;
+ dns_offsets_t offsets;
+ unsigned char prefixlen;
+ unsigned char octets;
+
+ REQUIRE(rdata->type == dns_rdatatype_a6);
+ REQUIRE(rdata->rdclass == dns_rdataclass_in);
+ REQUIRE(rdata->length != 0);
+
+ dns_compress_setmethods(cctx, DNS_COMPRESS_NONE);
+ dns_rdata_toregion(rdata, &sr);
+ prefixlen = sr.base[0];
+ INSIST(prefixlen <= 128);
+
+ octets = 1 + 16 - prefixlen / 8;
+ RETERR(mem_tobuffer(target, sr.base, octets));
+ isc_region_consume(&sr, octets);
+
+ if (prefixlen == 0) {
+ return (ISC_R_SUCCESS);
+ }
+
+ dns_name_init(&name, offsets);
+ dns_name_fromregion(&name, &sr);
+ return (dns_name_towire(&name, cctx, target));
+}
+
+static int
+compare_in_a6(ARGS_COMPARE) {
+ int order;
+ unsigned char prefixlen1, prefixlen2;
+ unsigned char octets;
+ dns_name_t name1;
+ dns_name_t name2;
+ isc_region_t region1;
+ isc_region_t region2;
+
+ REQUIRE(rdata1->type == rdata2->type);
+ REQUIRE(rdata1->rdclass == rdata2->rdclass);
+ REQUIRE(rdata1->type == dns_rdatatype_a6);
+ REQUIRE(rdata1->rdclass == dns_rdataclass_in);
+ REQUIRE(rdata1->length != 0);
+ REQUIRE(rdata2->length != 0);
+
+ dns_rdata_toregion(rdata1, &region1);
+ dns_rdata_toregion(rdata2, &region2);
+ prefixlen1 = region1.base[0];
+ prefixlen2 = region2.base[0];
+ isc_region_consume(&region1, 1);
+ isc_region_consume(&region2, 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(&region1, octets);
+ isc_region_consume(&region2, octets);
+ }
+
+ dns_name_init(&name1, NULL);
+ dns_name_init(&name2, NULL);
+ dns_name_fromregion(&name1, &region1);
+ dns_name_fromregion(&name2, &region2);
+ return (dns_name_rdatacompare(&name1, &name2));
+}
+
+static isc_result_t
+fromstruct_in_a6(ARGS_FROMSTRUCT) {
+ dns_rdata_in_a6_t *a6 = source;
+ isc_region_t region;
+ int octets;
+ uint8_t bits;
+ uint8_t first;
+ uint8_t mask;
+
+ REQUIRE(type == dns_rdatatype_a6);
+ REQUIRE(rdclass == dns_rdataclass_in);
+ REQUIRE(a6 != NULL);
+ REQUIRE(a6->common.rdtype == type);
+ REQUIRE(a6->common.rdclass == rdclass);
+
+ UNUSED(type);
+ UNUSED(rdclass);
+
+ if (a6->prefixlen > 128) {
+ return (ISC_R_RANGE);
+ }
+
+ RETERR(uint8_tobuffer(a6->prefixlen, target));
+
+ /* Suffix */
+ if (a6->prefixlen != 128) {
+ octets = 16 - a6->prefixlen / 8;
+ bits = a6->prefixlen % 8;
+ if (bits != 0) {
+ mask = 0xffU >> bits;
+ first = a6->in6_addr.s6_addr[16 - octets] & mask;
+ RETERR(uint8_tobuffer(first, target));
+ octets--;
+ }
+ if (octets > 0) {
+ RETERR(mem_tobuffer(target,
+ a6->in6_addr.s6_addr + 16 - octets,
+ octets));
+ }
+ }
+
+ if (a6->prefixlen == 0) {
+ return (ISC_R_SUCCESS);
+ }
+ dns_name_toregion(&a6->prefix, &region);
+ return (isc_buffer_copyregion(target, &region));
+}
+
+static isc_result_t
+tostruct_in_a6(ARGS_TOSTRUCT) {
+ dns_rdata_in_a6_t *a6 = target;
+ unsigned char octets;
+ dns_name_t name;
+ isc_region_t r;
+
+ REQUIRE(rdata->type == dns_rdatatype_a6);
+ REQUIRE(rdata->rdclass == dns_rdataclass_in);
+ REQUIRE(a6 != NULL);
+ REQUIRE(rdata->length != 0);
+
+ a6->common.rdclass = rdata->rdclass;
+ a6->common.rdtype = rdata->type;
+ ISC_LINK_INIT(&a6->common, link);
+
+ dns_rdata_toregion(rdata, &r);
+
+ a6->prefixlen = uint8_fromregion(&r);
+ isc_region_consume(&r, 1);
+ memset(a6->in6_addr.s6_addr, 0, sizeof(a6->in6_addr.s6_addr));
+
+ /*
+ * Suffix.
+ */
+ if (a6->prefixlen != 128) {
+ octets = 16 - a6->prefixlen / 8;
+ INSIST(r.length >= octets);
+ memmove(a6->in6_addr.s6_addr + 16 - octets, r.base, octets);
+ isc_region_consume(&r, octets);
+ }
+
+ /*
+ * Prefix.
+ */
+ dns_name_init(&a6->prefix, NULL);
+ if (a6->prefixlen != 0) {
+ dns_name_init(&name, NULL);
+ dns_name_fromregion(&name, &r);
+ name_duporclone(&name, mctx, &a6->prefix);
+ }
+ a6->mctx = mctx;
+ return (ISC_R_SUCCESS);
+}
+
+static void
+freestruct_in_a6(ARGS_FREESTRUCT) {
+ dns_rdata_in_a6_t *a6 = source;
+
+ REQUIRE(a6 != NULL);
+ REQUIRE(a6->common.rdclass == dns_rdataclass_in);
+ REQUIRE(a6->common.rdtype == dns_rdatatype_a6);
+
+ if (a6->mctx == NULL) {
+ return;
+ }
+
+ if (dns_name_dynamic(&a6->prefix)) {
+ dns_name_free(&a6->prefix, a6->mctx);
+ }
+ a6->mctx = NULL;
+}
+
+static isc_result_t
+additionaldata_in_a6(ARGS_ADDLDATA) {
+ REQUIRE(rdata->type == dns_rdatatype_a6);
+ REQUIRE(rdata->rdclass == dns_rdataclass_in);
+
+ UNUSED(rdata);
+ UNUSED(owner);
+ UNUSED(add);
+ UNUSED(arg);
+
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+digest_in_a6(ARGS_DIGEST) {
+ isc_region_t r1, r2;
+ unsigned char prefixlen, octets;
+ isc_result_t result;
+ dns_name_t name;
+
+ REQUIRE(rdata->type == dns_rdatatype_a6);
+ REQUIRE(rdata->rdclass == dns_rdataclass_in);
+
+ dns_rdata_toregion(rdata, &r1);
+ r2 = r1;
+ prefixlen = r1.base[0];
+ octets = 1 + 16 - prefixlen / 8;
+
+ r1.length = octets;
+ result = (digest)(arg, &r1);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+ if (prefixlen == 0) {
+ return (ISC_R_SUCCESS);
+ }
+
+ isc_region_consume(&r2, octets);
+ dns_name_init(&name, NULL);
+ dns_name_fromregion(&name, &r2);
+ return (dns_name_digest(&name, digest, arg));
+}
+
+static bool
+checkowner_in_a6(ARGS_CHECKOWNER) {
+ REQUIRE(type == dns_rdatatype_a6);
+ REQUIRE(rdclass == dns_rdataclass_in);
+
+ UNUSED(type);
+ UNUSED(rdclass);
+
+ return (dns_name_ishostname(name, wildcard));
+}
+
+static bool
+checknames_in_a6(ARGS_CHECKNAMES) {
+ isc_region_t region;
+ dns_name_t name;
+ unsigned int prefixlen;
+
+ REQUIRE(rdata->type == dns_rdatatype_a6);
+ REQUIRE(rdata->rdclass == dns_rdataclass_in);
+
+ UNUSED(owner);
+
+ dns_rdata_toregion(rdata, &region);
+ prefixlen = uint8_fromregion(&region);
+ if (prefixlen == 0) {
+ return (true);
+ }
+ isc_region_consume(&region, 1 + 16 - prefixlen / 8);
+ dns_name_init(&name, NULL);
+ dns_name_fromregion(&name, &region);
+ if (!dns_name_ishostname(&name, false)) {
+ if (bad != NULL) {
+ dns_name_clone(&name, bad);
+ }
+ return (false);
+ }
+ return (true);
+}
+
+static int
+casecompare_in_a6(ARGS_COMPARE) {
+ return (compare_in_a6(rdata1, rdata2));
+}
+
+#endif /* RDATA_IN_1_A6_38_C */
diff --git a/lib/dns/rdata/in_1/a6_38.h b/lib/dns/rdata/in_1/a6_38.h
new file mode 100644
index 0000000..7c09cc5
--- /dev/null
+++ b/lib/dns/rdata/in_1/a6_38.h
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#pragma once
+
+/*!
+ * \brief Per RFC2874 */
+
+typedef struct dns_rdata_in_a6 {
+ dns_rdatacommon_t common;
+ isc_mem_t *mctx;
+ dns_name_t prefix;
+ uint8_t prefixlen;
+ struct in6_addr in6_addr;
+} dns_rdata_in_a6_t;
diff --git a/lib/dns/rdata/in_1/a_1.c b/lib/dns/rdata/in_1/a_1.c
new file mode 100644
index 0000000..cf5549d
--- /dev/null
+++ b/lib/dns/rdata/in_1/a_1.c
@@ -0,0 +1,279 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#ifndef RDATA_IN_1_A_1_C
+#define RDATA_IN_1_A_1_C
+
+#include <string.h>
+
+#include <isc/net.h>
+
+#define RRTYPE_A_ATTRIBUTES (0)
+
+static isc_result_t
+fromtext_in_a(ARGS_FROMTEXT) {
+ isc_token_t token;
+ struct in_addr addr;
+ isc_region_t region;
+
+ REQUIRE(type == dns_rdatatype_a);
+ REQUIRE(rdclass == dns_rdataclass_in);
+
+ UNUSED(type);
+ UNUSED(origin);
+ UNUSED(options);
+ UNUSED(rdclass);
+ UNUSED(callbacks);
+
+ RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_string,
+ false));
+
+ if (inet_pton(AF_INET, DNS_AS_STR(token), &addr) != 1) {
+ RETTOK(DNS_R_BADDOTTEDQUAD);
+ }
+ isc_buffer_availableregion(target, &region);
+ if (region.length < 4) {
+ return (ISC_R_NOSPACE);
+ }
+ memmove(region.base, &addr, 4);
+ isc_buffer_add(target, 4);
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+totext_in_a(ARGS_TOTEXT) {
+ isc_region_t region;
+
+ REQUIRE(rdata->type == dns_rdatatype_a);
+ REQUIRE(rdata->rdclass == dns_rdataclass_in);
+ REQUIRE(rdata->length == 4);
+
+ UNUSED(tctx);
+
+ dns_rdata_toregion(rdata, &region);
+ return (inet_totext(AF_INET, tctx->flags, &region, target));
+}
+
+static isc_result_t
+fromwire_in_a(ARGS_FROMWIRE) {
+ isc_region_t sregion;
+ isc_region_t tregion;
+
+ REQUIRE(type == dns_rdatatype_a);
+ REQUIRE(rdclass == dns_rdataclass_in);
+
+ UNUSED(type);
+ UNUSED(dctx);
+ UNUSED(options);
+ UNUSED(rdclass);
+
+ isc_buffer_activeregion(source, &sregion);
+ isc_buffer_availableregion(target, &tregion);
+ if (sregion.length < 4) {
+ return (ISC_R_UNEXPECTEDEND);
+ }
+ if (tregion.length < 4) {
+ return (ISC_R_NOSPACE);
+ }
+
+ memmove(tregion.base, sregion.base, 4);
+ isc_buffer_forward(source, 4);
+ isc_buffer_add(target, 4);
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+towire_in_a(ARGS_TOWIRE) {
+ isc_region_t region;
+
+ REQUIRE(rdata->type == dns_rdatatype_a);
+ REQUIRE(rdata->rdclass == dns_rdataclass_in);
+ REQUIRE(rdata->length == 4);
+
+ UNUSED(cctx);
+
+ isc_buffer_availableregion(target, &region);
+ if (region.length < rdata->length) {
+ return (ISC_R_NOSPACE);
+ }
+ memmove(region.base, rdata->data, rdata->length);
+ isc_buffer_add(target, 4);
+ return (ISC_R_SUCCESS);
+}
+
+static int
+compare_in_a(ARGS_COMPARE) {
+ isc_region_t r1;
+ isc_region_t r2;
+
+ REQUIRE(rdata1->type == rdata2->type);
+ REQUIRE(rdata1->rdclass == rdata2->rdclass);
+ REQUIRE(rdata1->type == dns_rdatatype_a);
+ REQUIRE(rdata1->rdclass == dns_rdataclass_in);
+ REQUIRE(rdata1->length == 4);
+ REQUIRE(rdata2->length == 4);
+
+ dns_rdata_toregion(rdata1, &r1);
+ dns_rdata_toregion(rdata2, &r2);
+ return (isc_region_compare(&r1, &r2));
+}
+
+static isc_result_t
+fromstruct_in_a(ARGS_FROMSTRUCT) {
+ dns_rdata_in_a_t *a = source;
+ uint32_t n;
+
+ REQUIRE(type == dns_rdatatype_a);
+ REQUIRE(rdclass == dns_rdataclass_in);
+ REQUIRE(a != NULL);
+ REQUIRE(a->common.rdtype == type);
+ REQUIRE(a->common.rdclass == rdclass);
+
+ UNUSED(type);
+ UNUSED(rdclass);
+
+ n = ntohl(a->in_addr.s_addr);
+
+ return (uint32_tobuffer(n, target));
+}
+
+static isc_result_t
+tostruct_in_a(ARGS_TOSTRUCT) {
+ dns_rdata_in_a_t *a = target;
+ uint32_t n;
+ isc_region_t region;
+
+ REQUIRE(a != NULL);
+ REQUIRE(rdata->type == dns_rdatatype_a);
+ REQUIRE(rdata->rdclass == dns_rdataclass_in);
+ REQUIRE(rdata->length == 4);
+
+ UNUSED(mctx);
+
+ a->common.rdclass = rdata->rdclass;
+ a->common.rdtype = rdata->type;
+ ISC_LINK_INIT(&a->common, link);
+
+ dns_rdata_toregion(rdata, &region);
+ n = uint32_fromregion(&region);
+ a->in_addr.s_addr = htonl(n);
+
+ return (ISC_R_SUCCESS);
+}
+
+static void
+freestruct_in_a(ARGS_FREESTRUCT) {
+ dns_rdata_in_a_t *a = source;
+
+ REQUIRE(a != NULL);
+ REQUIRE(a->common.rdtype == dns_rdatatype_a);
+ REQUIRE(a->common.rdclass == dns_rdataclass_in);
+
+ UNUSED(a);
+}
+
+static isc_result_t
+additionaldata_in_a(ARGS_ADDLDATA) {
+ REQUIRE(rdata->type == dns_rdatatype_a);
+ REQUIRE(rdata->rdclass == dns_rdataclass_in);
+
+ UNUSED(rdata);
+ UNUSED(owner);
+ UNUSED(add);
+ UNUSED(arg);
+
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+digest_in_a(ARGS_DIGEST) {
+ isc_region_t r;
+
+ REQUIRE(rdata->type == dns_rdatatype_a);
+ REQUIRE(rdata->rdclass == dns_rdataclass_in);
+
+ dns_rdata_toregion(rdata, &r);
+
+ return ((digest)(arg, &r));
+}
+
+static bool
+checkowner_in_a(ARGS_CHECKOWNER) {
+ dns_name_t prefix, suffix;
+ unsigned int labels, i;
+
+ REQUIRE(type == dns_rdatatype_a);
+ REQUIRE(rdclass == dns_rdataclass_in);
+
+ UNUSED(type);
+ UNUSED(rdclass);
+
+ labels = dns_name_countlabels(name);
+ if (labels > 2U) {
+ /*
+ * Handle Active Directory gc._msdcs.<forest> name.
+ */
+ dns_name_init(&prefix, NULL);
+ dns_name_init(&suffix, NULL);
+ dns_name_split(name, labels - 2, &prefix, &suffix);
+ if (dns_name_equal(&gc_msdcs, &prefix) &&
+ dns_name_ishostname(&suffix, false))
+ {
+ return (true);
+ }
+
+ /*
+ * Handle SPF exists targets when the seperating label is:
+ * - "_spf" RFC7208, section 5.7
+ * - "_spf_verify" RFC7208, Appendix D1
+ * - "_spf_rate" RFC7208, Appendix D1
+ */
+ for (i = 0; i < labels - 2; i++) {
+ dns_label_t label;
+ dns_name_getlabel(name, i, &label);
+ if ((label.length == 5 &&
+ strncasecmp((char *)label.base, "\x04_spf", 5) ==
+ 0) ||
+ (label.length == 12 &&
+ strncasecmp((char *)label.base, "\x0b_spf_verify",
+ 12) == 0) ||
+ (label.length == 10 &&
+ strncasecmp((char *)label.base, "\x09_spf_rate",
+ 10) == 0))
+ {
+ return (true);
+ }
+ }
+ }
+
+ return (dns_name_ishostname(name, wildcard));
+}
+
+static bool
+checknames_in_a(ARGS_CHECKNAMES) {
+ REQUIRE(rdata->type == dns_rdatatype_a);
+ REQUIRE(rdata->rdclass == dns_rdataclass_in);
+
+ UNUSED(rdata);
+ UNUSED(owner);
+ UNUSED(bad);
+
+ return (true);
+}
+
+static int
+casecompare_in_a(ARGS_COMPARE) {
+ return (compare_in_a(rdata1, rdata2));
+}
+
+#endif /* RDATA_IN_1_A_1_C */
diff --git a/lib/dns/rdata/in_1/a_1.h b/lib/dns/rdata/in_1/a_1.h
new file mode 100644
index 0000000..38ffd8f
--- /dev/null
+++ b/lib/dns/rdata/in_1/a_1.h
@@ -0,0 +1,20 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/* */
+#pragma once
+
+typedef struct dns_rdata_in_a {
+ dns_rdatacommon_t common;
+ struct in_addr in_addr;
+} dns_rdata_in_a_t;
diff --git a/lib/dns/rdata/in_1/aaaa_28.c b/lib/dns/rdata/in_1/aaaa_28.c
new file mode 100644
index 0000000..e23bd47
--- /dev/null
+++ b/lib/dns/rdata/in_1/aaaa_28.c
@@ -0,0 +1,266 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/* RFC1886 */
+
+#ifndef RDATA_IN_1_AAAA_28_C
+#define RDATA_IN_1_AAAA_28_C
+
+#include <isc/net.h>
+
+#define RRTYPE_AAAA_ATTRIBUTES (0)
+
+static isc_result_t
+fromtext_in_aaaa(ARGS_FROMTEXT) {
+ isc_token_t token;
+ unsigned char addr[16];
+ isc_region_t region;
+
+ REQUIRE(type == dns_rdatatype_aaaa);
+ REQUIRE(rdclass == dns_rdataclass_in);
+
+ UNUSED(type);
+ UNUSED(origin);
+ UNUSED(options);
+ UNUSED(rdclass);
+ UNUSED(callbacks);
+
+ RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_string,
+ false));
+
+ if (inet_pton(AF_INET6, DNS_AS_STR(token), addr) != 1) {
+ RETTOK(DNS_R_BADAAAA);
+ }
+ isc_buffer_availableregion(target, &region);
+ if (region.length < 16) {
+ return (ISC_R_NOSPACE);
+ }
+ memmove(region.base, addr, 16);
+ isc_buffer_add(target, 16);
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+totext_in_aaaa(ARGS_TOTEXT) {
+ isc_region_t region;
+
+ REQUIRE(rdata->type == dns_rdatatype_aaaa);
+ REQUIRE(rdata->rdclass == dns_rdataclass_in);
+ REQUIRE(rdata->length == 16);
+
+ if ((tctx->flags & DNS_STYLEFLAG_EXPANDAAAA) != 0) {
+ char buf[5 * 8];
+ const char *sep = "";
+ int i, n;
+ unsigned int len = 0;
+
+ for (i = 0; i < 16; i += 2) {
+ INSIST(len < sizeof(buf));
+ n = snprintf(buf + len, sizeof(buf) - len, "%s%02x%02x",
+ sep, rdata->data[i], rdata->data[i + 1]);
+ if (n < 0) {
+ return (ISC_R_FAILURE);
+ }
+ len += n;
+ sep = ":";
+ }
+ return (str_totext(buf, target));
+ }
+ dns_rdata_toregion(rdata, &region);
+ return (inet_totext(AF_INET6, tctx->flags, &region, target));
+}
+
+static isc_result_t
+fromwire_in_aaaa(ARGS_FROMWIRE) {
+ isc_region_t sregion;
+ isc_region_t tregion;
+
+ REQUIRE(type == dns_rdatatype_aaaa);
+ REQUIRE(rdclass == dns_rdataclass_in);
+
+ UNUSED(type);
+ UNUSED(dctx);
+ UNUSED(options);
+ UNUSED(rdclass);
+
+ isc_buffer_activeregion(source, &sregion);
+ isc_buffer_availableregion(target, &tregion);
+ if (sregion.length < 16) {
+ return (ISC_R_UNEXPECTEDEND);
+ }
+ if (tregion.length < 16) {
+ return (ISC_R_NOSPACE);
+ }
+
+ memmove(tregion.base, sregion.base, 16);
+ isc_buffer_forward(source, 16);
+ isc_buffer_add(target, 16);
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+towire_in_aaaa(ARGS_TOWIRE) {
+ isc_region_t region;
+
+ UNUSED(cctx);
+
+ REQUIRE(rdata->type == dns_rdatatype_aaaa);
+ REQUIRE(rdata->rdclass == dns_rdataclass_in);
+ REQUIRE(rdata->length == 16);
+
+ isc_buffer_availableregion(target, &region);
+ if (region.length < rdata->length) {
+ return (ISC_R_NOSPACE);
+ }
+ memmove(region.base, rdata->data, rdata->length);
+ isc_buffer_add(target, 16);
+ return (ISC_R_SUCCESS);
+}
+
+static int
+compare_in_aaaa(ARGS_COMPARE) {
+ isc_region_t r1;
+ isc_region_t r2;
+
+ REQUIRE(rdata1->type == rdata2->type);
+ REQUIRE(rdata1->rdclass == rdata2->rdclass);
+ REQUIRE(rdata1->type == dns_rdatatype_aaaa);
+ REQUIRE(rdata1->rdclass == dns_rdataclass_in);
+ REQUIRE(rdata1->length == 16);
+ REQUIRE(rdata2->length == 16);
+
+ dns_rdata_toregion(rdata1, &r1);
+ dns_rdata_toregion(rdata2, &r2);
+ return (isc_region_compare(&r1, &r2));
+}
+
+static isc_result_t
+fromstruct_in_aaaa(ARGS_FROMSTRUCT) {
+ dns_rdata_in_aaaa_t *aaaa = source;
+
+ REQUIRE(type == dns_rdatatype_aaaa);
+ REQUIRE(rdclass == dns_rdataclass_in);
+ REQUIRE(aaaa != NULL);
+ REQUIRE(aaaa->common.rdtype == type);
+ REQUIRE(aaaa->common.rdclass == rdclass);
+
+ UNUSED(type);
+ UNUSED(rdclass);
+
+ return (mem_tobuffer(target, aaaa->in6_addr.s6_addr, 16));
+}
+
+static isc_result_t
+tostruct_in_aaaa(ARGS_TOSTRUCT) {
+ dns_rdata_in_aaaa_t *aaaa = target;
+ isc_region_t r;
+
+ REQUIRE(rdata->type == dns_rdatatype_aaaa);
+ REQUIRE(rdata->rdclass == dns_rdataclass_in);
+ REQUIRE(aaaa != NULL);
+ REQUIRE(rdata->length == 16);
+
+ UNUSED(mctx);
+
+ aaaa->common.rdclass = rdata->rdclass;
+ aaaa->common.rdtype = rdata->type;
+ ISC_LINK_INIT(&aaaa->common, link);
+
+ dns_rdata_toregion(rdata, &r);
+ INSIST(r.length == 16);
+ memmove(aaaa->in6_addr.s6_addr, r.base, 16);
+
+ return (ISC_R_SUCCESS);
+}
+
+static void
+freestruct_in_aaaa(ARGS_FREESTRUCT) {
+ dns_rdata_in_aaaa_t *aaaa = source;
+
+ REQUIRE(aaaa != NULL);
+ REQUIRE(aaaa->common.rdclass == dns_rdataclass_in);
+ REQUIRE(aaaa->common.rdtype == dns_rdatatype_aaaa);
+
+ UNUSED(aaaa);
+}
+
+static isc_result_t
+additionaldata_in_aaaa(ARGS_ADDLDATA) {
+ REQUIRE(rdata->type == dns_rdatatype_aaaa);
+ REQUIRE(rdata->rdclass == dns_rdataclass_in);
+
+ UNUSED(rdata);
+ UNUSED(owner);
+ UNUSED(add);
+ UNUSED(arg);
+
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+digest_in_aaaa(ARGS_DIGEST) {
+ isc_region_t r;
+
+ REQUIRE(rdata->type == dns_rdatatype_aaaa);
+ REQUIRE(rdata->rdclass == dns_rdataclass_in);
+
+ dns_rdata_toregion(rdata, &r);
+
+ return ((digest)(arg, &r));
+}
+
+static bool
+checkowner_in_aaaa(ARGS_CHECKOWNER) {
+ dns_name_t prefix, suffix;
+
+ REQUIRE(type == dns_rdatatype_aaaa);
+ REQUIRE(rdclass == dns_rdataclass_in);
+
+ UNUSED(type);
+ UNUSED(rdclass);
+
+ /*
+ * Handle Active Directory gc._msdcs.<forest> name.
+ */
+ if (dns_name_countlabels(name) > 2U) {
+ dns_name_init(&prefix, NULL);
+ dns_name_init(&suffix, NULL);
+ dns_name_split(name, dns_name_countlabels(name) - 2, &prefix,
+ &suffix);
+ if (dns_name_equal(&gc_msdcs, &prefix) &&
+ dns_name_ishostname(&suffix, false))
+ {
+ return (true);
+ }
+ }
+
+ return (dns_name_ishostname(name, wildcard));
+}
+
+static bool
+checknames_in_aaaa(ARGS_CHECKNAMES) {
+ REQUIRE(rdata->type == dns_rdatatype_aaaa);
+ REQUIRE(rdata->rdclass == dns_rdataclass_in);
+
+ UNUSED(rdata);
+ UNUSED(owner);
+ UNUSED(bad);
+
+ return (true);
+}
+
+static int
+casecompare_in_aaaa(ARGS_COMPARE) {
+ return (compare_in_aaaa(rdata1, rdata2));
+}
+#endif /* RDATA_IN_1_AAAA_28_C */
diff --git a/lib/dns/rdata/in_1/aaaa_28.h b/lib/dns/rdata/in_1/aaaa_28.h
new file mode 100644
index 0000000..915cc0f
--- /dev/null
+++ b/lib/dns/rdata/in_1/aaaa_28.h
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#pragma once
+
+/*!
+ * \brief Per RFC1886 */
+
+typedef struct dns_rdata_in_aaaa {
+ dns_rdatacommon_t common;
+ struct in6_addr in6_addr;
+} dns_rdata_in_aaaa_t;
diff --git a/lib/dns/rdata/in_1/apl_42.c b/lib/dns/rdata/in_1/apl_42.c
new file mode 100644
index 0000000..03593ab
--- /dev/null
+++ b/lib/dns/rdata/in_1/apl_42.c
@@ -0,0 +1,483 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/* RFC3123 */
+
+#ifndef RDATA_IN_1_APL_42_C
+#define RDATA_IN_1_APL_42_C
+
+#define RRTYPE_APL_ATTRIBUTES (0)
+
+static isc_result_t
+fromtext_in_apl(ARGS_FROMTEXT) {
+ isc_token_t token;
+ unsigned char addr[16];
+ unsigned long afi;
+ uint8_t prefix;
+ uint8_t len;
+ bool neg;
+ char *cp, *ap, *slash;
+ int n;
+
+ REQUIRE(type == dns_rdatatype_apl);
+ REQUIRE(rdclass == dns_rdataclass_in);
+
+ UNUSED(type);
+ UNUSED(rdclass);
+ UNUSED(origin);
+ UNUSED(options);
+ UNUSED(callbacks);
+
+ do {
+ RETERR(isc_lex_getmastertoken(lexer, &token,
+ isc_tokentype_string, true));
+ if (token.type != isc_tokentype_string) {
+ break;
+ }
+
+ cp = DNS_AS_STR(token);
+ neg = (*cp == '!');
+ if (neg) {
+ cp++;
+ }
+ afi = strtoul(cp, &ap, 10);
+ if (*ap++ != ':' || cp == ap) {
+ RETTOK(DNS_R_SYNTAX);
+ }
+ if (afi > 0xffffU) {
+ RETTOK(ISC_R_RANGE);
+ }
+ slash = strchr(ap, '/');
+ if (slash == NULL || slash == ap) {
+ RETTOK(DNS_R_SYNTAX);
+ }
+ RETTOK(isc_parse_uint8(&prefix, slash + 1, 10));
+ switch (afi) {
+ case 1:
+ *slash = '\0';
+ n = inet_pton(AF_INET, ap, addr);
+ *slash = '/';
+ if (n != 1) {
+ RETTOK(DNS_R_BADDOTTEDQUAD);
+ }
+ if (prefix > 32) {
+ RETTOK(ISC_R_RANGE);
+ }
+ for (len = 4; len > 0; len--) {
+ if (addr[len - 1] != 0) {
+ break;
+ }
+ }
+ break;
+
+ case 2:
+ *slash = '\0';
+ n = inet_pton(AF_INET6, ap, addr);
+ *slash = '/';
+ if (n != 1) {
+ RETTOK(DNS_R_BADAAAA);
+ }
+ if (prefix > 128) {
+ RETTOK(ISC_R_RANGE);
+ }
+ for (len = 16; len > 0; len--) {
+ if (addr[len - 1] != 0) {
+ break;
+ }
+ }
+ break;
+
+ default:
+ RETTOK(ISC_R_NOTIMPLEMENTED);
+ }
+ RETERR(uint16_tobuffer(afi, target));
+ RETERR(uint8_tobuffer(prefix, target));
+ RETERR(uint8_tobuffer(len | ((neg) ? 0x80 : 0), target));
+ RETERR(mem_tobuffer(target, addr, len));
+ } while (1);
+
+ /*
+ * Let upper layer handle eol/eof.
+ */
+ isc_lex_ungettoken(lexer, &token);
+
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+totext_in_apl(ARGS_TOTEXT) {
+ isc_region_t sr;
+ isc_region_t ir;
+ uint16_t afi;
+ uint8_t prefix;
+ uint8_t len;
+ bool neg;
+ unsigned char buf[16];
+ char txt[sizeof(" !64000:")];
+ const char *sep = "";
+ int n;
+
+ REQUIRE(rdata->type == dns_rdatatype_apl);
+ REQUIRE(rdata->rdclass == dns_rdataclass_in);
+
+ UNUSED(tctx);
+
+ dns_rdata_toregion(rdata, &sr);
+ ir.base = buf;
+ ir.length = sizeof(buf);
+
+ while (sr.length > 0) {
+ INSIST(sr.length >= 4);
+ afi = uint16_fromregion(&sr);
+ isc_region_consume(&sr, 2);
+ prefix = *sr.base;
+ isc_region_consume(&sr, 1);
+ len = (*sr.base & 0x7f);
+ neg = (*sr.base & 0x80);
+ isc_region_consume(&sr, 1);
+ INSIST(len <= sr.length);
+ n = snprintf(txt, sizeof(txt), "%s%s%u:", sep, neg ? "!" : "",
+ afi);
+ INSIST(n < (int)sizeof(txt));
+ RETERR(str_totext(txt, target));
+ switch (afi) {
+ case 1:
+ INSIST(len <= 4);
+ INSIST(prefix <= 32);
+ memset(buf, 0, sizeof(buf));
+ memmove(buf, sr.base, len);
+ RETERR(inet_totext(AF_INET, tctx->flags, &ir, target));
+ break;
+
+ case 2:
+ INSIST(len <= 16);
+ INSIST(prefix <= 128);
+ memset(buf, 0, sizeof(buf));
+ memmove(buf, sr.base, len);
+ RETERR(inet_totext(AF_INET6, tctx->flags, &ir, target));
+ break;
+
+ default:
+ return (ISC_R_NOTIMPLEMENTED);
+ }
+ n = snprintf(txt, sizeof(txt), "/%u", prefix);
+ INSIST(n < (int)sizeof(txt));
+ RETERR(str_totext(txt, target));
+ isc_region_consume(&sr, len);
+ sep = " ";
+ }
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+fromwire_in_apl(ARGS_FROMWIRE) {
+ isc_region_t sr, sr2;
+ isc_region_t tr;
+ uint16_t afi;
+ uint8_t prefix;
+ uint8_t len;
+
+ REQUIRE(type == dns_rdatatype_apl);
+ REQUIRE(rdclass == dns_rdataclass_in);
+
+ UNUSED(type);
+ UNUSED(dctx);
+ UNUSED(rdclass);
+ UNUSED(options);
+
+ isc_buffer_activeregion(source, &sr);
+ isc_buffer_availableregion(target, &tr);
+ if (sr.length > tr.length) {
+ return (ISC_R_NOSPACE);
+ }
+ sr2 = sr;
+
+ /* Zero or more items */
+ while (sr.length > 0) {
+ if (sr.length < 4) {
+ return (ISC_R_UNEXPECTEDEND);
+ }
+ afi = uint16_fromregion(&sr);
+ isc_region_consume(&sr, 2);
+ prefix = *sr.base;
+ isc_region_consume(&sr, 1);
+ len = (*sr.base & 0x7f);
+ isc_region_consume(&sr, 1);
+ if (len > sr.length) {
+ return (ISC_R_UNEXPECTEDEND);
+ }
+ switch (afi) {
+ case 1:
+ if (prefix > 32 || len > 4) {
+ return (ISC_R_RANGE);
+ }
+ break;
+ case 2:
+ if (prefix > 128 || len > 16) {
+ return (ISC_R_RANGE);
+ }
+ }
+ if (len > 0 && sr.base[len - 1] == 0) {
+ return (DNS_R_FORMERR);
+ }
+ isc_region_consume(&sr, len);
+ }
+ isc_buffer_forward(source, sr2.length);
+ return (mem_tobuffer(target, sr2.base, sr2.length));
+}
+
+static isc_result_t
+towire_in_apl(ARGS_TOWIRE) {
+ UNUSED(cctx);
+
+ REQUIRE(rdata->type == dns_rdatatype_apl);
+ REQUIRE(rdata->rdclass == dns_rdataclass_in);
+
+ return (mem_tobuffer(target, rdata->data, rdata->length));
+}
+
+static int
+compare_in_apl(ARGS_COMPARE) {
+ isc_region_t r1;
+ isc_region_t r2;
+
+ REQUIRE(rdata1->type == rdata2->type);
+ REQUIRE(rdata1->rdclass == rdata2->rdclass);
+ REQUIRE(rdata1->type == dns_rdatatype_apl);
+ REQUIRE(rdata1->rdclass == dns_rdataclass_in);
+
+ dns_rdata_toregion(rdata1, &r1);
+ dns_rdata_toregion(rdata2, &r2);
+ return (isc_region_compare(&r1, &r2));
+}
+
+static isc_result_t
+fromstruct_in_apl(ARGS_FROMSTRUCT) {
+ dns_rdata_in_apl_t *apl = source;
+ isc_buffer_t b;
+
+ REQUIRE(type == dns_rdatatype_apl);
+ REQUIRE(rdclass == dns_rdataclass_in);
+ REQUIRE(apl != NULL);
+ REQUIRE(apl->common.rdtype == type);
+ REQUIRE(apl->common.rdclass == rdclass);
+ REQUIRE(apl->apl != NULL || apl->apl_len == 0);
+
+ isc_buffer_init(&b, apl->apl, apl->apl_len);
+ isc_buffer_add(&b, apl->apl_len);
+ isc_buffer_setactive(&b, apl->apl_len);
+ return (fromwire_in_apl(rdclass, type, &b, NULL, false, target));
+}
+
+static isc_result_t
+tostruct_in_apl(ARGS_TOSTRUCT) {
+ dns_rdata_in_apl_t *apl = target;
+ isc_region_t r;
+
+ REQUIRE(apl != NULL);
+ REQUIRE(rdata->type == dns_rdatatype_apl);
+ REQUIRE(rdata->rdclass == dns_rdataclass_in);
+
+ apl->common.rdclass = rdata->rdclass;
+ apl->common.rdtype = rdata->type;
+ ISC_LINK_INIT(&apl->common, link);
+
+ dns_rdata_toregion(rdata, &r);
+ apl->apl_len = r.length;
+ apl->apl = mem_maybedup(mctx, r.base, r.length);
+ if (apl->apl == NULL) {
+ return (ISC_R_NOMEMORY);
+ }
+
+ apl->offset = 0;
+ apl->mctx = mctx;
+ return (ISC_R_SUCCESS);
+}
+
+static void
+freestruct_in_apl(ARGS_FREESTRUCT) {
+ dns_rdata_in_apl_t *apl = source;
+
+ REQUIRE(apl != NULL);
+ REQUIRE(apl->common.rdtype == dns_rdatatype_apl);
+ REQUIRE(apl->common.rdclass == dns_rdataclass_in);
+
+ if (apl->mctx == NULL) {
+ return;
+ }
+ if (apl->apl != NULL) {
+ isc_mem_free(apl->mctx, apl->apl);
+ }
+ apl->mctx = NULL;
+}
+
+isc_result_t
+dns_rdata_apl_first(dns_rdata_in_apl_t *apl) {
+ uint32_t length;
+
+ REQUIRE(apl != NULL);
+ REQUIRE(apl->common.rdtype == dns_rdatatype_apl);
+ REQUIRE(apl->common.rdclass == dns_rdataclass_in);
+ REQUIRE(apl->apl != NULL || apl->apl_len == 0);
+
+ /*
+ * If no APL return ISC_R_NOMORE.
+ */
+ if (apl->apl == NULL) {
+ return (ISC_R_NOMORE);
+ }
+
+ /*
+ * Sanity check data.
+ */
+ INSIST(apl->apl_len > 3U);
+ length = apl->apl[apl->offset + 3] & 0x7f;
+ INSIST(4 + length <= apl->apl_len);
+
+ apl->offset = 0;
+ return (ISC_R_SUCCESS);
+}
+
+isc_result_t
+dns_rdata_apl_next(dns_rdata_in_apl_t *apl) {
+ uint32_t length;
+
+ REQUIRE(apl != NULL);
+ REQUIRE(apl->common.rdtype == dns_rdatatype_apl);
+ REQUIRE(apl->common.rdclass == dns_rdataclass_in);
+ REQUIRE(apl->apl != NULL || apl->apl_len == 0);
+
+ /*
+ * No APL or have already reached the end return ISC_R_NOMORE.
+ */
+ if (apl->apl == NULL || apl->offset == apl->apl_len) {
+ return (ISC_R_NOMORE);
+ }
+
+ /*
+ * Sanity check data.
+ */
+ INSIST(apl->offset < apl->apl_len);
+ INSIST(apl->apl_len > 3U);
+ INSIST(apl->offset <= apl->apl_len - 4U);
+ length = apl->apl[apl->offset + 3] & 0x7f;
+ /*
+ * 16 to 32 bits promotion as 'length' is 32 bits so there is
+ * no overflow problems.
+ */
+ INSIST(4 + length + apl->offset <= apl->apl_len);
+
+ apl->offset += 4 + length;
+ return ((apl->offset < apl->apl_len) ? ISC_R_SUCCESS : ISC_R_NOMORE);
+}
+
+isc_result_t
+dns_rdata_apl_current(dns_rdata_in_apl_t *apl, dns_rdata_apl_ent_t *ent) {
+ uint32_t length;
+
+ REQUIRE(apl != NULL);
+ REQUIRE(apl->common.rdtype == dns_rdatatype_apl);
+ REQUIRE(apl->common.rdclass == dns_rdataclass_in);
+ REQUIRE(ent != NULL);
+ REQUIRE(apl->apl != NULL || apl->apl_len == 0);
+ REQUIRE(apl->offset <= apl->apl_len);
+
+ if (apl->offset == apl->apl_len) {
+ return (ISC_R_NOMORE);
+ }
+
+ /*
+ * Sanity check data.
+ */
+ INSIST(apl->apl_len > 3U);
+ INSIST(apl->offset <= apl->apl_len - 4U);
+ length = (apl->apl[apl->offset + 3] & 0x7f);
+ /*
+ * 16 to 32 bits promotion as 'length' is 32 bits so there is
+ * no overflow problems.
+ */
+ INSIST(4 + length + apl->offset <= apl->apl_len);
+
+ ent->family = (apl->apl[apl->offset] << 8) + apl->apl[apl->offset + 1];
+ ent->prefix = apl->apl[apl->offset + 2];
+ ent->length = length;
+ ent->negative = (apl->apl[apl->offset + 3] & 0x80);
+ if (ent->length != 0) {
+ ent->data = &apl->apl[apl->offset + 4];
+ } else {
+ ent->data = NULL;
+ }
+ return (ISC_R_SUCCESS);
+}
+
+unsigned int
+dns_rdata_apl_count(const dns_rdata_in_apl_t *apl) {
+ return (apl->apl_len);
+}
+
+static isc_result_t
+additionaldata_in_apl(ARGS_ADDLDATA) {
+ REQUIRE(rdata->type == dns_rdatatype_apl);
+ REQUIRE(rdata->rdclass == dns_rdataclass_in);
+
+ UNUSED(rdata);
+ UNUSED(owner);
+ UNUSED(add);
+ UNUSED(arg);
+
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+digest_in_apl(ARGS_DIGEST) {
+ isc_region_t r;
+
+ REQUIRE(rdata->type == dns_rdatatype_apl);
+ REQUIRE(rdata->rdclass == dns_rdataclass_in);
+
+ dns_rdata_toregion(rdata, &r);
+
+ return ((digest)(arg, &r));
+}
+
+static bool
+checkowner_in_apl(ARGS_CHECKOWNER) {
+ REQUIRE(type == dns_rdatatype_apl);
+ REQUIRE(rdclass == dns_rdataclass_in);
+
+ UNUSED(name);
+ UNUSED(type);
+ UNUSED(rdclass);
+ UNUSED(wildcard);
+
+ return (true);
+}
+
+static bool
+checknames_in_apl(ARGS_CHECKNAMES) {
+ REQUIRE(rdata->type == dns_rdatatype_apl);
+ REQUIRE(rdata->rdclass == dns_rdataclass_in);
+
+ UNUSED(rdata);
+ UNUSED(owner);
+ UNUSED(bad);
+
+ return (true);
+}
+
+static int
+casecompare_in_apl(ARGS_COMPARE) {
+ return (compare_in_apl(rdata1, rdata2));
+}
+
+#endif /* RDATA_IN_1_APL_42_C */
diff --git a/lib/dns/rdata/in_1/apl_42.h b/lib/dns/rdata/in_1/apl_42.h
new file mode 100644
index 0000000..0a3f7ac
--- /dev/null
+++ b/lib/dns/rdata/in_1/apl_42.h
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/* */
+#pragma once
+
+typedef struct dns_rdata_apl_ent {
+ bool negative;
+ uint16_t family;
+ uint8_t prefix;
+ uint8_t length;
+ unsigned char *data;
+} dns_rdata_apl_ent_t;
+
+typedef struct dns_rdata_in_apl {
+ dns_rdatacommon_t common;
+ isc_mem_t *mctx;
+ /* type & class specific elements */
+ unsigned char *apl;
+ uint16_t apl_len;
+ /* private */
+ uint16_t offset;
+} dns_rdata_in_apl_t;
+
+/*
+ * ISC_LANG_BEGINDECLS and ISC_LANG_ENDDECLS are already done
+ * via rdatastructpre.h and rdatastructsuf.h.
+ */
+
+isc_result_t
+dns_rdata_apl_first(dns_rdata_in_apl_t *);
+
+isc_result_t
+dns_rdata_apl_next(dns_rdata_in_apl_t *);
+
+isc_result_t
+dns_rdata_apl_current(dns_rdata_in_apl_t *, dns_rdata_apl_ent_t *);
+
+unsigned int
+dns_rdata_apl_count(const dns_rdata_in_apl_t *apl);
diff --git a/lib/dns/rdata/in_1/atma_34.c b/lib/dns/rdata/in_1/atma_34.c
new file mode 100644
index 0000000..b1a207a
--- /dev/null
+++ b/lib/dns/rdata/in_1/atma_34.c
@@ -0,0 +1,318 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/* http://www.broadband-forum.org/ftp/pub/approved-specs/af-dans-0152.000.pdf */
+
+#ifndef RDATA_IN_1_ATMA_22_C
+#define RDATA_IN_1_ATMA_22_C
+
+#define RRTYPE_ATMA_ATTRIBUTES (0)
+
+static isc_result_t
+fromtext_in_atma(ARGS_FROMTEXT) {
+ isc_token_t token;
+ isc_textregion_t *sr;
+ int n;
+ bool valid = false;
+ bool lastwasperiod = true; /* leading periods not allowed */
+ int digits = 0;
+ unsigned char c = 0;
+
+ REQUIRE(type == dns_rdatatype_atma);
+ REQUIRE(rdclass == dns_rdataclass_in);
+
+ UNUSED(type);
+ UNUSED(origin);
+ UNUSED(options);
+ UNUSED(rdclass);
+ UNUSED(callbacks);
+
+ /* ATM End System Address (AESA) format or E.164 */
+ RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_string,
+ false));
+ sr = &token.value.as_textregion;
+ if (sr->length < 1) {
+ RETTOK(ISC_R_UNEXPECTEDEND);
+ }
+
+ if (sr->base[0] != '+') {
+ /*
+ * Format 0: ATM End System Address (AESA) format.
+ */
+ c = 0;
+ RETERR(mem_tobuffer(target, &c, 1));
+ while (sr->length > 0) {
+ if (sr->base[0] == '.') {
+ if (lastwasperiod) {
+ RETTOK(DNS_R_SYNTAX);
+ }
+ isc_textregion_consume(sr, 1);
+ lastwasperiod = true;
+ continue;
+ }
+ if ((n = hexvalue(sr->base[0])) == -1) {
+ RETTOK(DNS_R_SYNTAX);
+ }
+ c <<= 4;
+ c += n;
+ if (++digits == 2) {
+ RETERR(mem_tobuffer(target, &c, 1));
+ valid = true;
+ digits = 0;
+ c = 0;
+ }
+ isc_textregion_consume(sr, 1);
+ lastwasperiod = false;
+ }
+ if (digits != 0 || !valid || lastwasperiod) {
+ RETTOK(ISC_R_UNEXPECTEDEND);
+ }
+ } else {
+ /*
+ * Format 1: E.164.
+ */
+ c = 1;
+ RETERR(mem_tobuffer(target, &c, 1));
+ isc_textregion_consume(sr, 1);
+ while (sr->length > 0) {
+ if (sr->base[0] == '.') {
+ if (lastwasperiod) {
+ RETTOK(DNS_R_SYNTAX);
+ }
+ isc_textregion_consume(sr, 1);
+ lastwasperiod = true;
+ continue;
+ }
+ if (!isdigit((unsigned char)sr->base[0])) {
+ RETTOK(DNS_R_SYNTAX);
+ }
+ RETERR(mem_tobuffer(target, sr->base, 1));
+ isc_textregion_consume(sr, 1);
+ lastwasperiod = false;
+ }
+ if (lastwasperiod) {
+ RETTOK(ISC_R_UNEXPECTEDEND);
+ }
+ }
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+totext_in_atma(ARGS_TOTEXT) {
+ isc_region_t region;
+ char buf[sizeof("xx")];
+
+ REQUIRE(rdata->type == dns_rdatatype_atma);
+ REQUIRE(rdata->rdclass == dns_rdataclass_in);
+ REQUIRE(rdata->length != 0);
+
+ UNUSED(tctx);
+
+ dns_rdata_toregion(rdata, &region);
+ INSIST(region.length > 1);
+ switch (region.base[0]) {
+ case 0:
+ isc_region_consume(&region, 1);
+ while (region.length != 0) {
+ snprintf(buf, sizeof(buf), "%02x", region.base[0]);
+ isc_region_consume(&region, 1);
+ RETERR(str_totext(buf, target));
+ }
+ break;
+ case 1:
+ RETERR(str_totext("+", target));
+ isc_region_consume(&region, 1);
+ RETERR(mem_tobuffer(target, region.base, region.length));
+ break;
+ default:
+ return (ISC_R_NOTIMPLEMENTED);
+ }
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+fromwire_in_atma(ARGS_FROMWIRE) {
+ isc_region_t region;
+
+ REQUIRE(type == dns_rdatatype_atma);
+ REQUIRE(rdclass == dns_rdataclass_in);
+
+ UNUSED(type);
+ UNUSED(dctx);
+ UNUSED(options);
+ UNUSED(rdclass);
+
+ isc_buffer_activeregion(source, &region);
+ if (region.length < 2) {
+ return (ISC_R_UNEXPECTEDEND);
+ }
+ if (region.base[0] == 1) {
+ unsigned int i;
+ for (i = 1; i < region.length; i++) {
+ if (!isdigit((unsigned char)region.base[i])) {
+ return (DNS_R_FORMERR);
+ }
+ }
+ }
+ RETERR(mem_tobuffer(target, region.base, region.length));
+ isc_buffer_forward(source, region.length);
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+towire_in_atma(ARGS_TOWIRE) {
+ REQUIRE(rdata->type == dns_rdatatype_atma);
+ REQUIRE(rdata->rdclass == dns_rdataclass_in);
+ REQUIRE(rdata->length != 0);
+
+ UNUSED(cctx);
+
+ return (mem_tobuffer(target, rdata->data, rdata->length));
+}
+
+static int
+compare_in_atma(ARGS_COMPARE) {
+ isc_region_t r1;
+ isc_region_t r2;
+
+ REQUIRE(rdata1->type == rdata2->type);
+ REQUIRE(rdata1->rdclass == rdata2->rdclass);
+ REQUIRE(rdata1->type == dns_rdatatype_atma);
+ REQUIRE(rdata1->rdclass == dns_rdataclass_in);
+ REQUIRE(rdata1->length != 0);
+ REQUIRE(rdata2->length != 0);
+
+ dns_rdata_toregion(rdata1, &r1);
+ dns_rdata_toregion(rdata2, &r2);
+ return (isc_region_compare(&r1, &r2));
+}
+
+static isc_result_t
+fromstruct_in_atma(ARGS_FROMSTRUCT) {
+ dns_rdata_in_atma_t *atma = source;
+
+ REQUIRE(type == dns_rdatatype_atma);
+ REQUIRE(rdclass == dns_rdataclass_in);
+ REQUIRE(atma != NULL);
+ REQUIRE(atma->common.rdtype == type);
+ REQUIRE(atma->common.rdclass == rdclass);
+ REQUIRE(atma->atma != NULL || atma->atma_len == 0);
+
+ UNUSED(type);
+ UNUSED(rdclass);
+
+ RETERR(mem_tobuffer(target, &atma->format, 1));
+ return (mem_tobuffer(target, atma->atma, atma->atma_len));
+}
+
+static isc_result_t
+tostruct_in_atma(ARGS_TOSTRUCT) {
+ dns_rdata_in_atma_t *atma = target;
+ isc_region_t r;
+
+ REQUIRE(rdata->type == dns_rdatatype_atma);
+ REQUIRE(rdata->rdclass == dns_rdataclass_in);
+ REQUIRE(atma != NULL);
+ REQUIRE(rdata->length != 0);
+
+ atma->common.rdclass = rdata->rdclass;
+ atma->common.rdtype = rdata->type;
+ ISC_LINK_INIT(&atma->common, link);
+
+ dns_rdata_toregion(rdata, &r);
+ atma->format = r.base[0];
+ isc_region_consume(&r, 1);
+ atma->atma_len = r.length;
+ atma->atma = mem_maybedup(mctx, r.base, r.length);
+ if (atma->atma == NULL) {
+ return (ISC_R_NOMEMORY);
+ }
+
+ atma->mctx = mctx;
+ return (ISC_R_SUCCESS);
+}
+
+static void
+freestruct_in_atma(ARGS_FREESTRUCT) {
+ dns_rdata_in_atma_t *atma = source;
+
+ REQUIRE(atma != NULL);
+ REQUIRE(atma->common.rdclass == dns_rdataclass_in);
+ REQUIRE(atma->common.rdtype == dns_rdatatype_atma);
+
+ if (atma->mctx == NULL) {
+ return;
+ }
+
+ if (atma->atma != NULL) {
+ isc_mem_free(atma->mctx, atma->atma);
+ }
+ atma->mctx = NULL;
+}
+
+static isc_result_t
+additionaldata_in_atma(ARGS_ADDLDATA) {
+ REQUIRE(rdata->type == dns_rdatatype_atma);
+ REQUIRE(rdata->rdclass == dns_rdataclass_in);
+
+ UNUSED(rdata);
+ UNUSED(owner);
+ UNUSED(add);
+ UNUSED(arg);
+
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+digest_in_atma(ARGS_DIGEST) {
+ isc_region_t r;
+
+ REQUIRE(rdata->type == dns_rdatatype_atma);
+ REQUIRE(rdata->rdclass == dns_rdataclass_in);
+
+ dns_rdata_toregion(rdata, &r);
+
+ return ((digest)(arg, &r));
+}
+
+static bool
+checkowner_in_atma(ARGS_CHECKOWNER) {
+ REQUIRE(type == dns_rdatatype_atma);
+ REQUIRE(rdclass == dns_rdataclass_in);
+
+ UNUSED(name);
+ UNUSED(type);
+ UNUSED(rdclass);
+ UNUSED(wildcard);
+
+ return (true);
+}
+
+static bool
+checknames_in_atma(ARGS_CHECKNAMES) {
+ REQUIRE(rdata->type == dns_rdatatype_atma);
+ REQUIRE(rdata->rdclass == dns_rdataclass_in);
+
+ UNUSED(rdata);
+ UNUSED(owner);
+ UNUSED(bad);
+
+ return (true);
+}
+
+static int
+casecompare_in_atma(ARGS_COMPARE) {
+ return (compare_in_atma(rdata1, rdata2));
+}
+
+#endif /* RDATA_IN_1_atma_22_C */
diff --git a/lib/dns/rdata/in_1/atma_34.h b/lib/dns/rdata/in_1/atma_34.h
new file mode 100644
index 0000000..412b428
--- /dev/null
+++ b/lib/dns/rdata/in_1/atma_34.h
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#pragma once
+
+/*!
+ * \brief Per RFC1706 */
+
+typedef struct dns_rdata_in_atma {
+ dns_rdatacommon_t common;
+ isc_mem_t *mctx;
+ unsigned char format;
+ unsigned char *atma;
+ uint16_t atma_len;
+} dns_rdata_in_atma_t;
diff --git a/lib/dns/rdata/in_1/dhcid_49.c b/lib/dns/rdata/in_1/dhcid_49.c
new file mode 100644
index 0000000..b3f664b
--- /dev/null
+++ b/lib/dns/rdata/in_1/dhcid_49.c
@@ -0,0 +1,236 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/* RFC 4701 */
+
+#ifndef RDATA_IN_1_DHCID_49_C
+#define RDATA_IN_1_DHCID_49_C 1
+
+#define RRTYPE_DHCID_ATTRIBUTES 0
+
+static isc_result_t
+fromtext_in_dhcid(ARGS_FROMTEXT) {
+ REQUIRE(type == dns_rdatatype_dhcid);
+ REQUIRE(rdclass == dns_rdataclass_in);
+
+ UNUSED(type);
+ UNUSED(rdclass);
+ UNUSED(origin);
+ UNUSED(options);
+ UNUSED(callbacks);
+
+ return (isc_base64_tobuffer(lexer, target, -2));
+}
+
+static isc_result_t
+totext_in_dhcid(ARGS_TOTEXT) {
+ isc_region_t sr, sr2;
+ /* " ; 64000 255 64000" */
+ char buf[5 + 3 * 11 + 1];
+
+ REQUIRE(rdata->type == dns_rdatatype_dhcid);
+ REQUIRE(rdata->rdclass == dns_rdataclass_in);
+ REQUIRE(rdata->length != 0);
+
+ dns_rdata_toregion(rdata, &sr);
+ sr2 = sr;
+
+ if ((tctx->flags & DNS_STYLEFLAG_MULTILINE) != 0) {
+ RETERR(str_totext("( " /*)*/, target));
+ }
+ if (tctx->width == 0) { /* No splitting */
+ RETERR(isc_base64_totext(&sr, 60, "", target));
+ } else {
+ RETERR(isc_base64_totext(&sr, tctx->width - 2, tctx->linebreak,
+ target));
+ }
+ if ((tctx->flags & DNS_STYLEFLAG_MULTILINE) != 0) {
+ RETERR(str_totext(/* ( */ " )", target));
+ if (rdata->length > 2) {
+ snprintf(buf, sizeof(buf), " ; %u %u %u",
+ sr2.base[0] * 256U + sr2.base[1], sr2.base[2],
+ rdata->length - 3U);
+ RETERR(str_totext(buf, target));
+ }
+ }
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+fromwire_in_dhcid(ARGS_FROMWIRE) {
+ isc_region_t sr;
+
+ REQUIRE(type == dns_rdatatype_dhcid);
+ REQUIRE(rdclass == dns_rdataclass_in);
+
+ UNUSED(type);
+ UNUSED(rdclass);
+ UNUSED(dctx);
+ UNUSED(options);
+
+ isc_buffer_activeregion(source, &sr);
+ if (sr.length == 0) {
+ return (ISC_R_UNEXPECTEDEND);
+ }
+
+ isc_buffer_forward(source, sr.length);
+ return (mem_tobuffer(target, sr.base, sr.length));
+}
+
+static isc_result_t
+towire_in_dhcid(ARGS_TOWIRE) {
+ isc_region_t sr;
+
+ REQUIRE(rdata->type == dns_rdatatype_dhcid);
+ REQUIRE(rdata->rdclass == dns_rdataclass_in);
+ REQUIRE(rdata->length != 0);
+
+ UNUSED(cctx);
+
+ dns_rdata_toregion(rdata, &sr);
+ return (mem_tobuffer(target, sr.base, sr.length));
+}
+
+static int
+compare_in_dhcid(ARGS_COMPARE) {
+ isc_region_t r1;
+ isc_region_t r2;
+
+ REQUIRE(rdata1->type == rdata2->type);
+ REQUIRE(rdata1->rdclass == rdata2->rdclass);
+ REQUIRE(rdata1->type == dns_rdatatype_dhcid);
+ REQUIRE(rdata1->rdclass == dns_rdataclass_in);
+ REQUIRE(rdata1->length != 0);
+ REQUIRE(rdata2->length != 0);
+
+ dns_rdata_toregion(rdata1, &r1);
+ dns_rdata_toregion(rdata2, &r2);
+ return (isc_region_compare(&r1, &r2));
+}
+
+static isc_result_t
+fromstruct_in_dhcid(ARGS_FROMSTRUCT) {
+ dns_rdata_in_dhcid_t *dhcid = source;
+
+ REQUIRE(type == dns_rdatatype_dhcid);
+ REQUIRE(rdclass == dns_rdataclass_in);
+ REQUIRE(dhcid != NULL);
+ REQUIRE(dhcid->common.rdtype == type);
+ REQUIRE(dhcid->common.rdclass == rdclass);
+ REQUIRE(dhcid->length != 0);
+
+ UNUSED(type);
+ UNUSED(rdclass);
+
+ return (mem_tobuffer(target, dhcid->dhcid, dhcid->length));
+}
+
+static isc_result_t
+tostruct_in_dhcid(ARGS_TOSTRUCT) {
+ dns_rdata_in_dhcid_t *dhcid = target;
+ isc_region_t region;
+
+ REQUIRE(rdata->type == dns_rdatatype_dhcid);
+ REQUIRE(rdata->rdclass == dns_rdataclass_in);
+ REQUIRE(dhcid != NULL);
+ REQUIRE(rdata->length != 0);
+
+ dhcid->common.rdclass = rdata->rdclass;
+ dhcid->common.rdtype = rdata->type;
+ ISC_LINK_INIT(&dhcid->common, link);
+
+ dns_rdata_toregion(rdata, &region);
+
+ dhcid->dhcid = mem_maybedup(mctx, region.base, region.length);
+ if (dhcid->dhcid == NULL) {
+ return (ISC_R_NOMEMORY);
+ }
+
+ dhcid->mctx = mctx;
+ return (ISC_R_SUCCESS);
+}
+
+static void
+freestruct_in_dhcid(ARGS_FREESTRUCT) {
+ dns_rdata_in_dhcid_t *dhcid = source;
+
+ REQUIRE(dhcid != NULL);
+ REQUIRE(dhcid->common.rdtype == dns_rdatatype_dhcid);
+ REQUIRE(dhcid->common.rdclass == dns_rdataclass_in);
+
+ if (dhcid->mctx == NULL) {
+ return;
+ }
+
+ if (dhcid->dhcid != NULL) {
+ isc_mem_free(dhcid->mctx, dhcid->dhcid);
+ }
+ dhcid->mctx = NULL;
+}
+
+static isc_result_t
+additionaldata_in_dhcid(ARGS_ADDLDATA) {
+ REQUIRE(rdata->type == dns_rdatatype_dhcid);
+ REQUIRE(rdata->rdclass == dns_rdataclass_in);
+
+ UNUSED(rdata);
+ UNUSED(owner);
+ UNUSED(add);
+ UNUSED(arg);
+
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+digest_in_dhcid(ARGS_DIGEST) {
+ isc_region_t r;
+
+ REQUIRE(rdata->type == dns_rdatatype_dhcid);
+ REQUIRE(rdata->rdclass == dns_rdataclass_in);
+
+ dns_rdata_toregion(rdata, &r);
+
+ return ((digest)(arg, &r));
+}
+
+static bool
+checkowner_in_dhcid(ARGS_CHECKOWNER) {
+ REQUIRE(type == dns_rdatatype_dhcid);
+ REQUIRE(rdclass == dns_rdataclass_in);
+
+ UNUSED(name);
+ UNUSED(type);
+ UNUSED(rdclass);
+ UNUSED(wildcard);
+
+ return (true);
+}
+
+static bool
+checknames_in_dhcid(ARGS_CHECKNAMES) {
+ REQUIRE(rdata->type == dns_rdatatype_dhcid);
+ REQUIRE(rdata->rdclass == dns_rdataclass_in);
+
+ UNUSED(rdata);
+ UNUSED(owner);
+ UNUSED(bad);
+
+ return (true);
+}
+
+static int
+casecompare_in_dhcid(ARGS_COMPARE) {
+ return (compare_in_dhcid(rdata1, rdata2));
+}
+
+#endif /* RDATA_IN_1_DHCID_49_C */
diff --git a/lib/dns/rdata/in_1/dhcid_49.h b/lib/dns/rdata/in_1/dhcid_49.h
new file mode 100644
index 0000000..338476e
--- /dev/null
+++ b/lib/dns/rdata/in_1/dhcid_49.h
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/* */
+#pragma once
+
+typedef struct dns_rdata_in_dhcid {
+ dns_rdatacommon_t common;
+ isc_mem_t *mctx;
+ unsigned char *dhcid;
+ unsigned int length;
+} dns_rdata_in_dhcid_t;
diff --git a/lib/dns/rdata/in_1/eid_31.c b/lib/dns/rdata/in_1/eid_31.c
new file mode 100644
index 0000000..e5046ca
--- /dev/null
+++ b/lib/dns/rdata/in_1/eid_31.c
@@ -0,0 +1,225 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/* http://ana-3.lcs.mit.edu/~jnc/nimrod/dns.txt */
+
+#ifndef RDATA_IN_1_EID_31_C
+#define RDATA_IN_1_EID_31_C
+
+#define RRTYPE_EID_ATTRIBUTES (0)
+
+static isc_result_t
+fromtext_in_eid(ARGS_FROMTEXT) {
+ REQUIRE(type == dns_rdatatype_eid);
+ REQUIRE(rdclass == dns_rdataclass_in);
+
+ UNUSED(type);
+ UNUSED(origin);
+ UNUSED(options);
+ UNUSED(rdclass);
+ UNUSED(callbacks);
+
+ return (isc_hex_tobuffer(lexer, target, -2));
+}
+
+static isc_result_t
+totext_in_eid(ARGS_TOTEXT) {
+ isc_region_t region;
+
+ REQUIRE(rdata->type == dns_rdatatype_eid);
+ REQUIRE(rdata->rdclass == dns_rdataclass_in);
+ REQUIRE(rdata->length != 0);
+
+ dns_rdata_toregion(rdata, &region);
+
+ if ((tctx->flags & DNS_STYLEFLAG_MULTILINE) != 0) {
+ RETERR(str_totext("( ", target));
+ }
+ if (tctx->width == 0) {
+ RETERR(isc_hex_totext(&region, 60, "", target));
+ } else {
+ RETERR(isc_hex_totext(&region, tctx->width - 2, tctx->linebreak,
+ target));
+ }
+ if ((tctx->flags & DNS_STYLEFLAG_MULTILINE) != 0) {
+ RETERR(str_totext(" )", target));
+ }
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+fromwire_in_eid(ARGS_FROMWIRE) {
+ isc_region_t region;
+
+ REQUIRE(type == dns_rdatatype_eid);
+ REQUIRE(rdclass == dns_rdataclass_in);
+
+ UNUSED(type);
+ UNUSED(dctx);
+ UNUSED(options);
+ UNUSED(rdclass);
+
+ isc_buffer_activeregion(source, &region);
+ if (region.length < 1) {
+ return (ISC_R_UNEXPECTEDEND);
+ }
+
+ RETERR(mem_tobuffer(target, region.base, region.length));
+ isc_buffer_forward(source, region.length);
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+towire_in_eid(ARGS_TOWIRE) {
+ REQUIRE(rdata->type == dns_rdatatype_eid);
+ REQUIRE(rdata->rdclass == dns_rdataclass_in);
+ REQUIRE(rdata->length != 0);
+
+ UNUSED(cctx);
+
+ return (mem_tobuffer(target, rdata->data, rdata->length));
+}
+
+static int
+compare_in_eid(ARGS_COMPARE) {
+ isc_region_t r1;
+ isc_region_t r2;
+
+ REQUIRE(rdata1->type == rdata2->type);
+ REQUIRE(rdata1->rdclass == rdata2->rdclass);
+ REQUIRE(rdata1->type == dns_rdatatype_eid);
+ REQUIRE(rdata1->rdclass == dns_rdataclass_in);
+ REQUIRE(rdata1->length != 0);
+ REQUIRE(rdata2->length != 0);
+
+ dns_rdata_toregion(rdata1, &r1);
+ dns_rdata_toregion(rdata2, &r2);
+ return (isc_region_compare(&r1, &r2));
+}
+
+static isc_result_t
+fromstruct_in_eid(ARGS_FROMSTRUCT) {
+ dns_rdata_in_eid_t *eid = source;
+
+ REQUIRE(type == dns_rdatatype_eid);
+ REQUIRE(rdclass == dns_rdataclass_in);
+ REQUIRE(eid != NULL);
+ REQUIRE(eid->common.rdtype == type);
+ REQUIRE(eid->common.rdclass == rdclass);
+ REQUIRE(eid->eid != NULL || eid->eid_len == 0);
+
+ UNUSED(type);
+ UNUSED(rdclass);
+
+ return (mem_tobuffer(target, eid->eid, eid->eid_len));
+}
+
+static isc_result_t
+tostruct_in_eid(ARGS_TOSTRUCT) {
+ dns_rdata_in_eid_t *eid = target;
+ isc_region_t r;
+
+ REQUIRE(rdata->type == dns_rdatatype_eid);
+ REQUIRE(rdata->rdclass == dns_rdataclass_in);
+ REQUIRE(eid != NULL);
+ REQUIRE(rdata->length != 0);
+
+ eid->common.rdclass = rdata->rdclass;
+ eid->common.rdtype = rdata->type;
+ ISC_LINK_INIT(&eid->common, link);
+
+ dns_rdata_toregion(rdata, &r);
+ eid->eid_len = r.length;
+ eid->eid = mem_maybedup(mctx, r.base, r.length);
+ if (eid->eid == NULL) {
+ return (ISC_R_NOMEMORY);
+ }
+
+ eid->mctx = mctx;
+ return (ISC_R_SUCCESS);
+}
+
+static void
+freestruct_in_eid(ARGS_FREESTRUCT) {
+ dns_rdata_in_eid_t *eid = source;
+
+ REQUIRE(eid != NULL);
+ REQUIRE(eid->common.rdclass == dns_rdataclass_in);
+ REQUIRE(eid->common.rdtype == dns_rdatatype_eid);
+
+ if (eid->mctx == NULL) {
+ return;
+ }
+
+ if (eid->eid != NULL) {
+ isc_mem_free(eid->mctx, eid->eid);
+ }
+ eid->mctx = NULL;
+}
+
+static isc_result_t
+additionaldata_in_eid(ARGS_ADDLDATA) {
+ REQUIRE(rdata->type == dns_rdatatype_eid);
+ REQUIRE(rdata->rdclass == dns_rdataclass_in);
+
+ UNUSED(rdata);
+ UNUSED(owner);
+ UNUSED(add);
+ UNUSED(arg);
+
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+digest_in_eid(ARGS_DIGEST) {
+ isc_region_t r;
+
+ REQUIRE(rdata->type == dns_rdatatype_eid);
+ REQUIRE(rdata->rdclass == dns_rdataclass_in);
+
+ dns_rdata_toregion(rdata, &r);
+
+ return ((digest)(arg, &r));
+}
+
+static bool
+checkowner_in_eid(ARGS_CHECKOWNER) {
+ REQUIRE(type == dns_rdatatype_eid);
+ REQUIRE(rdclass == dns_rdataclass_in);
+
+ UNUSED(name);
+ UNUSED(type);
+ UNUSED(rdclass);
+ UNUSED(wildcard);
+
+ return (true);
+}
+
+static bool
+checknames_in_eid(ARGS_CHECKNAMES) {
+ REQUIRE(rdata->type == dns_rdatatype_eid);
+ REQUIRE(rdata->rdclass == dns_rdataclass_in);
+
+ UNUSED(rdata);
+ UNUSED(owner);
+ UNUSED(bad);
+
+ return (true);
+}
+
+static int
+casecompare_in_eid(ARGS_COMPARE) {
+ return (compare_in_eid(rdata1, rdata2));
+}
+
+#endif /* RDATA_IN_1_EID_31_C */
diff --git a/lib/dns/rdata/in_1/eid_31.h b/lib/dns/rdata/in_1/eid_31.h
new file mode 100644
index 0000000..d32bd3e
--- /dev/null
+++ b/lib/dns/rdata/in_1/eid_31.h
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#pragma once
+
+/*!
+ * \brief http://ana-3.lcs.mit.edu/~jnc/nimrod/dns.txt
+ */
+
+typedef struct dns_rdata_in_eid {
+ dns_rdatacommon_t common;
+ isc_mem_t *mctx;
+ unsigned char *eid;
+ uint16_t eid_len;
+} dns_rdata_in_eid_t;
diff --git a/lib/dns/rdata/in_1/https_65.c b/lib/dns/rdata/in_1/https_65.c
new file mode 100644
index 0000000..af1ad7f
--- /dev/null
+++ b/lib/dns/rdata/in_1/https_65.c
@@ -0,0 +1,183 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/* draft-ietf-dnsop-svcb-https-02 */
+
+#pragma once
+
+#define RRTYPE_HTTPS_ATTRIBUTES (DNS_RDATATYPEATTR_FOLLOWADDITIONAL)
+
+/*
+ * Most of these functions refer to equivalent functions for SVCB,
+ * since wire and presentation formats are identical.
+ */
+
+static isc_result_t
+fromtext_in_https(ARGS_FROMTEXT) {
+ REQUIRE(type == dns_rdatatype_https);
+ REQUIRE(rdclass == dns_rdataclass_in);
+
+ return (generic_fromtext_in_svcb(CALL_FROMTEXT));
+}
+
+static isc_result_t
+totext_in_https(ARGS_TOTEXT) {
+ REQUIRE(rdata->type == dns_rdatatype_https);
+ REQUIRE(rdata->rdclass == dns_rdataclass_in);
+ REQUIRE(rdata->length != 0);
+
+ return (generic_totext_in_svcb(CALL_TOTEXT));
+}
+
+static isc_result_t
+fromwire_in_https(ARGS_FROMWIRE) {
+ REQUIRE(type == dns_rdatatype_https);
+ REQUIRE(rdclass == dns_rdataclass_in);
+
+ return (generic_fromwire_in_svcb(CALL_FROMWIRE));
+}
+
+static isc_result_t
+towire_in_https(ARGS_TOWIRE) {
+ REQUIRE(rdata->type == dns_rdatatype_https);
+ REQUIRE(rdata->length != 0);
+
+ return (generic_towire_in_svcb(CALL_TOWIRE));
+}
+
+static int
+compare_in_https(ARGS_COMPARE) {
+ isc_region_t region1;
+ isc_region_t region2;
+
+ REQUIRE(rdata1->type == rdata2->type);
+ REQUIRE(rdata1->rdclass == rdata2->rdclass);
+ REQUIRE(rdata1->type == dns_rdatatype_https);
+ REQUIRE(rdata1->rdclass == dns_rdataclass_in);
+ REQUIRE(rdata1->length != 0);
+ REQUIRE(rdata2->length != 0);
+
+ dns_rdata_toregion(rdata1, &region1);
+ dns_rdata_toregion(rdata2, &region2);
+
+ return (isc_region_compare(&region1, &region2));
+}
+
+static isc_result_t
+fromstruct_in_https(ARGS_FROMSTRUCT) {
+ dns_rdata_in_https_t *https = source;
+
+ REQUIRE(type == dns_rdatatype_https);
+ REQUIRE(rdclass == dns_rdataclass_in);
+ REQUIRE(https != NULL);
+ REQUIRE(https->common.rdtype == type);
+ REQUIRE(https->common.rdclass == rdclass);
+
+ return (generic_fromstruct_in_svcb(CALL_FROMSTRUCT));
+}
+
+static isc_result_t
+tostruct_in_https(ARGS_TOSTRUCT) {
+ dns_rdata_in_https_t *https = target;
+
+ REQUIRE(rdata->rdclass == dns_rdataclass_in);
+ REQUIRE(rdata->type == dns_rdatatype_https);
+ REQUIRE(https != NULL);
+ REQUIRE(rdata->length != 0);
+
+ return (generic_tostruct_in_svcb(CALL_TOSTRUCT));
+}
+
+static void
+freestruct_in_https(ARGS_FREESTRUCT) {
+ dns_rdata_in_https_t *https = source;
+
+ REQUIRE(https != NULL);
+ REQUIRE(https->common.rdclass == dns_rdataclass_in);
+ REQUIRE(https->common.rdtype == dns_rdatatype_https);
+
+ generic_freestruct_in_svcb(CALL_FREESTRUCT);
+}
+
+static isc_result_t
+additionaldata_in_https(ARGS_ADDLDATA) {
+ REQUIRE(rdata->type == dns_rdatatype_https);
+ REQUIRE(rdata->rdclass == dns_rdataclass_in);
+
+ return (generic_additionaldata_in_svcb(CALL_ADDLDATA));
+}
+
+static isc_result_t
+digest_in_https(ARGS_DIGEST) {
+ isc_region_t region1;
+
+ REQUIRE(rdata->type == dns_rdatatype_https);
+ REQUIRE(rdata->rdclass == dns_rdataclass_in);
+
+ dns_rdata_toregion(rdata, &region1);
+ return ((digest)(arg, &region1));
+}
+
+static bool
+checkowner_in_https(ARGS_CHECKOWNER) {
+ REQUIRE(type == dns_rdatatype_https);
+ REQUIRE(rdclass == dns_rdataclass_in);
+
+ UNUSED(name);
+ UNUSED(type);
+ UNUSED(rdclass);
+ UNUSED(wildcard);
+
+ return (true);
+}
+
+static bool
+checknames_in_https(ARGS_CHECKNAMES) {
+ REQUIRE(rdata->type == dns_rdatatype_https);
+ REQUIRE(rdata->rdclass == dns_rdataclass_in);
+
+ return (generic_checknames_in_svcb(CALL_CHECKNAMES));
+}
+
+static int
+casecompare_in_https(ARGS_COMPARE) {
+ return (compare_in_https(rdata1, rdata2));
+}
+
+isc_result_t
+dns_rdata_in_https_first(dns_rdata_in_https_t *https) {
+ REQUIRE(https != NULL);
+ REQUIRE(https->common.rdtype == dns_rdatatype_https);
+ REQUIRE(https->common.rdclass == dns_rdataclass_in);
+
+ return (generic_rdata_in_svcb_first(https));
+}
+
+isc_result_t
+dns_rdata_in_https_next(dns_rdata_in_https_t *https) {
+ REQUIRE(https != NULL);
+ REQUIRE(https->common.rdtype == dns_rdatatype_https);
+ REQUIRE(https->common.rdclass == dns_rdataclass_in);
+
+ return (generic_rdata_in_svcb_next(https));
+}
+
+void
+dns_rdata_in_https_current(dns_rdata_in_https_t *https, isc_region_t *region) {
+ REQUIRE(https != NULL);
+ REQUIRE(https->common.rdtype == dns_rdatatype_https);
+ REQUIRE(https->common.rdclass == dns_rdataclass_in);
+ REQUIRE(region != NULL);
+
+ generic_rdata_in_svcb_current(https, region);
+}
diff --git a/lib/dns/rdata/in_1/https_65.h b/lib/dns/rdata/in_1/https_65.h
new file mode 100644
index 0000000..589f983
--- /dev/null
+++ b/lib/dns/rdata/in_1/https_65.h
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#pragma once
+
+/*!
+ * \brief Per draft-ietf-dnsop-svcb-https-02
+ */
+
+/*
+ * Wire and presentation formats for HTTPS are identical to SVCB.
+ */
+typedef struct dns_rdata_in_svcb dns_rdata_in_https_t;
+
+isc_result_t
+dns_rdata_in_https_first(dns_rdata_in_https_t *);
+
+isc_result_t
+dns_rdata_in_https_next(dns_rdata_in_https_t *);
+
+void
+dns_rdata_in_https_current(dns_rdata_in_https_t *, isc_region_t *);
diff --git a/lib/dns/rdata/in_1/kx_36.c b/lib/dns/rdata/in_1/kx_36.c
new file mode 100644
index 0000000..ce48db1
--- /dev/null
+++ b/lib/dns/rdata/in_1/kx_36.c
@@ -0,0 +1,291 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/* RFC2230 */
+
+#ifndef RDATA_IN_1_KX_36_C
+#define RDATA_IN_1_KX_36_C
+
+#define RRTYPE_KX_ATTRIBUTES (0)
+
+static isc_result_t
+fromtext_in_kx(ARGS_FROMTEXT) {
+ isc_token_t token;
+ dns_name_t name;
+ isc_buffer_t buffer;
+
+ REQUIRE(type == dns_rdatatype_kx);
+ REQUIRE(rdclass == dns_rdataclass_in);
+
+ UNUSED(type);
+ UNUSED(rdclass);
+ UNUSED(callbacks);
+
+ RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_number,
+ false));
+ if (token.value.as_ulong > 0xffffU) {
+ RETTOK(ISC_R_RANGE);
+ }
+ RETERR(uint16_tobuffer(token.value.as_ulong, target));
+
+ RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_string,
+ false));
+ dns_name_init(&name, NULL);
+ buffer_fromregion(&buffer, &token.value.as_region);
+ if (origin == NULL) {
+ origin = dns_rootname;
+ }
+ RETTOK(dns_name_fromtext(&name, &buffer, origin, options, target));
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+totext_in_kx(ARGS_TOTEXT) {
+ isc_region_t region;
+ dns_name_t name;
+ dns_name_t prefix;
+ bool sub;
+ char buf[sizeof("64000")];
+ unsigned short num;
+
+ REQUIRE(rdata->type == dns_rdatatype_kx);
+ REQUIRE(rdata->rdclass == dns_rdataclass_in);
+ REQUIRE(rdata->length != 0);
+
+ dns_name_init(&name, NULL);
+ dns_name_init(&prefix, NULL);
+
+ dns_rdata_toregion(rdata, &region);
+ num = uint16_fromregion(&region);
+ isc_region_consume(&region, 2);
+ snprintf(buf, sizeof(buf), "%u", num);
+ RETERR(str_totext(buf, target));
+
+ RETERR(str_totext(" ", target));
+
+ dns_name_fromregion(&name, &region);
+ sub = name_prefix(&name, tctx->origin, &prefix);
+ return (dns_name_totext(&prefix, sub, target));
+}
+
+static isc_result_t
+fromwire_in_kx(ARGS_FROMWIRE) {
+ dns_name_t name;
+ isc_region_t sregion;
+
+ REQUIRE(type == dns_rdatatype_kx);
+ REQUIRE(rdclass == dns_rdataclass_in);
+
+ UNUSED(type);
+ UNUSED(rdclass);
+
+ dns_decompress_setmethods(dctx, DNS_COMPRESS_NONE);
+
+ dns_name_init(&name, NULL);
+
+ isc_buffer_activeregion(source, &sregion);
+ if (sregion.length < 2) {
+ return (ISC_R_UNEXPECTEDEND);
+ }
+ RETERR(mem_tobuffer(target, sregion.base, 2));
+ isc_buffer_forward(source, 2);
+ return (dns_name_fromwire(&name, source, dctx, options, target));
+}
+
+static isc_result_t
+towire_in_kx(ARGS_TOWIRE) {
+ dns_name_t name;
+ dns_offsets_t offsets;
+ isc_region_t region;
+
+ REQUIRE(rdata->type == dns_rdatatype_kx);
+ REQUIRE(rdata->rdclass == dns_rdataclass_in);
+ REQUIRE(rdata->length != 0);
+
+ dns_compress_setmethods(cctx, DNS_COMPRESS_NONE);
+ dns_rdata_toregion(rdata, &region);
+ RETERR(mem_tobuffer(target, region.base, 2));
+ isc_region_consume(&region, 2);
+
+ dns_name_init(&name, offsets);
+ dns_name_fromregion(&name, &region);
+
+ return (dns_name_towire(&name, cctx, target));
+}
+
+static int
+compare_in_kx(ARGS_COMPARE) {
+ dns_name_t name1;
+ dns_name_t name2;
+ isc_region_t region1;
+ isc_region_t region2;
+ int order;
+
+ REQUIRE(rdata1->type == rdata2->type);
+ REQUIRE(rdata1->rdclass == rdata2->rdclass);
+ REQUIRE(rdata1->type == dns_rdatatype_kx);
+ REQUIRE(rdata1->rdclass == dns_rdataclass_in);
+ REQUIRE(rdata1->length != 0);
+ REQUIRE(rdata2->length != 0);
+
+ order = memcmp(rdata1->data, rdata2->data, 2);
+ if (order != 0) {
+ return (order < 0 ? -1 : 1);
+ }
+
+ dns_name_init(&name1, NULL);
+ dns_name_init(&name2, NULL);
+
+ dns_rdata_toregion(rdata1, &region1);
+ dns_rdata_toregion(rdata2, &region2);
+
+ isc_region_consume(&region1, 2);
+ isc_region_consume(&region2, 2);
+
+ dns_name_fromregion(&name1, &region1);
+ dns_name_fromregion(&name2, &region2);
+
+ return (dns_name_rdatacompare(&name1, &name2));
+}
+
+static isc_result_t
+fromstruct_in_kx(ARGS_FROMSTRUCT) {
+ dns_rdata_in_kx_t *kx = source;
+ isc_region_t region;
+
+ REQUIRE(type == dns_rdatatype_kx);
+ REQUIRE(rdclass == dns_rdataclass_in);
+ REQUIRE(kx != NULL);
+ REQUIRE(kx->common.rdtype == type);
+ REQUIRE(kx->common.rdclass == rdclass);
+
+ UNUSED(type);
+ UNUSED(rdclass);
+
+ RETERR(uint16_tobuffer(kx->preference, target));
+ dns_name_toregion(&kx->exchange, &region);
+ return (isc_buffer_copyregion(target, &region));
+}
+
+static isc_result_t
+tostruct_in_kx(ARGS_TOSTRUCT) {
+ isc_region_t region;
+ dns_rdata_in_kx_t *kx = target;
+ dns_name_t name;
+
+ REQUIRE(rdata->type == dns_rdatatype_kx);
+ REQUIRE(rdata->rdclass == dns_rdataclass_in);
+ REQUIRE(kx != NULL);
+ REQUIRE(rdata->length != 0);
+
+ kx->common.rdclass = rdata->rdclass;
+ kx->common.rdtype = rdata->type;
+ ISC_LINK_INIT(&kx->common, link);
+
+ dns_name_init(&name, NULL);
+ dns_rdata_toregion(rdata, &region);
+
+ kx->preference = uint16_fromregion(&region);
+ isc_region_consume(&region, 2);
+
+ dns_name_fromregion(&name, &region);
+ dns_name_init(&kx->exchange, NULL);
+ name_duporclone(&name, mctx, &kx->exchange);
+ kx->mctx = mctx;
+ return (ISC_R_SUCCESS);
+}
+
+static void
+freestruct_in_kx(ARGS_FREESTRUCT) {
+ dns_rdata_in_kx_t *kx = source;
+
+ REQUIRE(kx != NULL);
+ REQUIRE(kx->common.rdclass == dns_rdataclass_in);
+ REQUIRE(kx->common.rdtype == dns_rdatatype_kx);
+
+ if (kx->mctx == NULL) {
+ return;
+ }
+
+ dns_name_free(&kx->exchange, kx->mctx);
+ kx->mctx = NULL;
+}
+
+static isc_result_t
+additionaldata_in_kx(ARGS_ADDLDATA) {
+ dns_name_t name;
+ dns_offsets_t offsets;
+ isc_region_t region;
+
+ REQUIRE(rdata->type == dns_rdatatype_kx);
+ REQUIRE(rdata->rdclass == dns_rdataclass_in);
+
+ UNUSED(owner);
+
+ dns_name_init(&name, offsets);
+ dns_rdata_toregion(rdata, &region);
+ isc_region_consume(&region, 2);
+ dns_name_fromregion(&name, &region);
+
+ return ((add)(arg, &name, dns_rdatatype_a, NULL));
+}
+
+static isc_result_t
+digest_in_kx(ARGS_DIGEST) {
+ isc_region_t r1, r2;
+ dns_name_t name;
+
+ REQUIRE(rdata->type == dns_rdatatype_kx);
+ REQUIRE(rdata->rdclass == dns_rdataclass_in);
+
+ dns_rdata_toregion(rdata, &r1);
+ r2 = r1;
+ isc_region_consume(&r2, 2);
+ r1.length = 2;
+ RETERR((digest)(arg, &r1));
+ dns_name_init(&name, NULL);
+ dns_name_fromregion(&name, &r2);
+ return (dns_name_digest(&name, digest, arg));
+}
+
+static bool
+checkowner_in_kx(ARGS_CHECKOWNER) {
+ REQUIRE(type == dns_rdatatype_kx);
+ REQUIRE(rdclass == dns_rdataclass_in);
+
+ UNUSED(name);
+ UNUSED(type);
+ UNUSED(rdclass);
+ UNUSED(wildcard);
+
+ return (true);
+}
+
+static bool
+checknames_in_kx(ARGS_CHECKNAMES) {
+ REQUIRE(rdata->type == dns_rdatatype_kx);
+ REQUIRE(rdata->rdclass == dns_rdataclass_in);
+
+ UNUSED(rdata);
+ UNUSED(owner);
+ UNUSED(bad);
+
+ return (true);
+}
+
+static int
+casecompare_in_kx(ARGS_COMPARE) {
+ return (compare_in_kx(rdata1, rdata2));
+}
+
+#endif /* RDATA_IN_1_KX_36_C */
diff --git a/lib/dns/rdata/in_1/kx_36.h b/lib/dns/rdata/in_1/kx_36.h
new file mode 100644
index 0000000..9e2243b
--- /dev/null
+++ b/lib/dns/rdata/in_1/kx_36.h
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#pragma once
+
+/*!
+ * \brief Per RFC2230 */
+
+typedef struct dns_rdata_in_kx {
+ dns_rdatacommon_t common;
+ isc_mem_t *mctx;
+ uint16_t preference;
+ dns_name_t exchange;
+} dns_rdata_in_kx_t;
diff --git a/lib/dns/rdata/in_1/nimloc_32.c b/lib/dns/rdata/in_1/nimloc_32.c
new file mode 100644
index 0000000..6a9cdb4
--- /dev/null
+++ b/lib/dns/rdata/in_1/nimloc_32.c
@@ -0,0 +1,225 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/* http://ana-3.lcs.mit.edu/~jnc/nimrod/dns.txt */
+
+#ifndef RDATA_IN_1_NIMLOC_32_C
+#define RDATA_IN_1_NIMLOC_32_C
+
+#define RRTYPE_NIMLOC_ATTRIBUTES (0)
+
+static isc_result_t
+fromtext_in_nimloc(ARGS_FROMTEXT) {
+ REQUIRE(type == dns_rdatatype_nimloc);
+ REQUIRE(rdclass == dns_rdataclass_in);
+
+ UNUSED(type);
+ UNUSED(origin);
+ UNUSED(options);
+ UNUSED(rdclass);
+ UNUSED(callbacks);
+
+ return (isc_hex_tobuffer(lexer, target, -2));
+}
+
+static isc_result_t
+totext_in_nimloc(ARGS_TOTEXT) {
+ isc_region_t region;
+
+ REQUIRE(rdata->type == dns_rdatatype_nimloc);
+ REQUIRE(rdata->rdclass == dns_rdataclass_in);
+ REQUIRE(rdata->length != 0);
+
+ dns_rdata_toregion(rdata, &region);
+
+ if ((tctx->flags & DNS_STYLEFLAG_MULTILINE) != 0) {
+ RETERR(str_totext("( ", target));
+ }
+ if (tctx->width == 0) {
+ RETERR(isc_hex_totext(&region, 60, "", target));
+ } else {
+ RETERR(isc_hex_totext(&region, tctx->width - 2, tctx->linebreak,
+ target));
+ }
+ if ((tctx->flags & DNS_STYLEFLAG_MULTILINE) != 0) {
+ RETERR(str_totext(" )", target));
+ }
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+fromwire_in_nimloc(ARGS_FROMWIRE) {
+ isc_region_t region;
+
+ REQUIRE(type == dns_rdatatype_nimloc);
+ REQUIRE(rdclass == dns_rdataclass_in);
+
+ UNUSED(type);
+ UNUSED(dctx);
+ UNUSED(options);
+ UNUSED(rdclass);
+
+ isc_buffer_activeregion(source, &region);
+ if (region.length < 1) {
+ return (ISC_R_UNEXPECTEDEND);
+ }
+
+ RETERR(mem_tobuffer(target, region.base, region.length));
+ isc_buffer_forward(source, region.length);
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+towire_in_nimloc(ARGS_TOWIRE) {
+ REQUIRE(rdata->type == dns_rdatatype_nimloc);
+ REQUIRE(rdata->rdclass == dns_rdataclass_in);
+ REQUIRE(rdata->length != 0);
+
+ UNUSED(cctx);
+
+ return (mem_tobuffer(target, rdata->data, rdata->length));
+}
+
+static int
+compare_in_nimloc(ARGS_COMPARE) {
+ isc_region_t r1;
+ isc_region_t r2;
+
+ REQUIRE(rdata1->type == rdata2->type);
+ REQUIRE(rdata1->rdclass == rdata2->rdclass);
+ REQUIRE(rdata1->type == dns_rdatatype_nimloc);
+ REQUIRE(rdata1->rdclass == dns_rdataclass_in);
+ REQUIRE(rdata1->length != 0);
+ REQUIRE(rdata2->length != 0);
+
+ dns_rdata_toregion(rdata1, &r1);
+ dns_rdata_toregion(rdata2, &r2);
+ return (isc_region_compare(&r1, &r2));
+}
+
+static isc_result_t
+fromstruct_in_nimloc(ARGS_FROMSTRUCT) {
+ dns_rdata_in_nimloc_t *nimloc = source;
+
+ REQUIRE(type == dns_rdatatype_nimloc);
+ REQUIRE(rdclass == dns_rdataclass_in);
+ REQUIRE(nimloc != NULL);
+ REQUIRE(nimloc->common.rdtype == type);
+ REQUIRE(nimloc->common.rdclass == rdclass);
+ REQUIRE(nimloc->nimloc != NULL || nimloc->nimloc_len == 0);
+
+ UNUSED(type);
+ UNUSED(rdclass);
+
+ return (mem_tobuffer(target, nimloc->nimloc, nimloc->nimloc_len));
+}
+
+static isc_result_t
+tostruct_in_nimloc(ARGS_TOSTRUCT) {
+ dns_rdata_in_nimloc_t *nimloc = target;
+ isc_region_t r;
+
+ REQUIRE(rdata->type == dns_rdatatype_nimloc);
+ REQUIRE(rdata->rdclass == dns_rdataclass_in);
+ REQUIRE(nimloc != NULL);
+ REQUIRE(rdata->length != 0);
+
+ nimloc->common.rdclass = rdata->rdclass;
+ nimloc->common.rdtype = rdata->type;
+ ISC_LINK_INIT(&nimloc->common, link);
+
+ dns_rdata_toregion(rdata, &r);
+ nimloc->nimloc_len = r.length;
+ nimloc->nimloc = mem_maybedup(mctx, r.base, r.length);
+ if (nimloc->nimloc == NULL) {
+ return (ISC_R_NOMEMORY);
+ }
+
+ nimloc->mctx = mctx;
+ return (ISC_R_SUCCESS);
+}
+
+static void
+freestruct_in_nimloc(ARGS_FREESTRUCT) {
+ dns_rdata_in_nimloc_t *nimloc = source;
+
+ REQUIRE(nimloc != NULL);
+ REQUIRE(nimloc->common.rdclass == dns_rdataclass_in);
+ REQUIRE(nimloc->common.rdtype == dns_rdatatype_nimloc);
+
+ if (nimloc->mctx == NULL) {
+ return;
+ }
+
+ if (nimloc->nimloc != NULL) {
+ isc_mem_free(nimloc->mctx, nimloc->nimloc);
+ }
+ nimloc->mctx = NULL;
+}
+
+static isc_result_t
+additionaldata_in_nimloc(ARGS_ADDLDATA) {
+ REQUIRE(rdata->type == dns_rdatatype_nimloc);
+ REQUIRE(rdata->rdclass == dns_rdataclass_in);
+
+ UNUSED(rdata);
+ UNUSED(owner);
+ UNUSED(add);
+ UNUSED(arg);
+
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+digest_in_nimloc(ARGS_DIGEST) {
+ isc_region_t r;
+
+ REQUIRE(rdata->type == dns_rdatatype_nimloc);
+ REQUIRE(rdata->rdclass == dns_rdataclass_in);
+
+ dns_rdata_toregion(rdata, &r);
+
+ return ((digest)(arg, &r));
+}
+
+static bool
+checkowner_in_nimloc(ARGS_CHECKOWNER) {
+ REQUIRE(type == dns_rdatatype_nimloc);
+ REQUIRE(rdclass == dns_rdataclass_in);
+
+ UNUSED(name);
+ UNUSED(type);
+ UNUSED(rdclass);
+ UNUSED(wildcard);
+
+ return (true);
+}
+
+static bool
+checknames_in_nimloc(ARGS_CHECKNAMES) {
+ REQUIRE(rdata->type == dns_rdatatype_nimloc);
+ REQUIRE(rdata->rdclass == dns_rdataclass_in);
+
+ UNUSED(rdata);
+ UNUSED(owner);
+ UNUSED(bad);
+
+ return (true);
+}
+
+static int
+casecompare_in_nimloc(ARGS_COMPARE) {
+ return (compare_in_nimloc(rdata1, rdata2));
+}
+
+#endif /* RDATA_IN_1_NIMLOC_32_C */
diff --git a/lib/dns/rdata/in_1/nimloc_32.h b/lib/dns/rdata/in_1/nimloc_32.h
new file mode 100644
index 0000000..faa9196
--- /dev/null
+++ b/lib/dns/rdata/in_1/nimloc_32.h
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#pragma once
+
+/*!
+ * \brief http://ana-3.lcs.mit.edu/~jnc/nimrod/dns.txt
+ */
+
+typedef struct dns_rdata_in_nimloc {
+ dns_rdatacommon_t common;
+ isc_mem_t *mctx;
+ unsigned char *nimloc;
+ uint16_t nimloc_len;
+} dns_rdata_in_nimloc_t;
diff --git a/lib/dns/rdata/in_1/nsap-ptr_23.c b/lib/dns/rdata/in_1/nsap-ptr_23.c
new file mode 100644
index 0000000..9620fed
--- /dev/null
+++ b/lib/dns/rdata/in_1/nsap-ptr_23.c
@@ -0,0 +1,244 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/* RFC1348. Obsoleted in RFC 1706 - use PTR instead. */
+
+#ifndef RDATA_IN_1_NSAP_PTR_23_C
+#define RDATA_IN_1_NSAP_PTR_23_C
+
+#define RRTYPE_NSAP_PTR_ATTRIBUTES (0)
+
+static isc_result_t
+fromtext_in_nsap_ptr(ARGS_FROMTEXT) {
+ isc_token_t token;
+ dns_name_t name;
+ isc_buffer_t buffer;
+
+ REQUIRE(type == dns_rdatatype_nsap_ptr);
+ REQUIRE(rdclass == dns_rdataclass_in);
+
+ UNUSED(type);
+ UNUSED(rdclass);
+ UNUSED(callbacks);
+
+ RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_string,
+ false));
+
+ dns_name_init(&name, NULL);
+ buffer_fromregion(&buffer, &token.value.as_region);
+ if (origin == NULL) {
+ origin = dns_rootname;
+ }
+ RETTOK(dns_name_fromtext(&name, &buffer, origin, options, target));
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+totext_in_nsap_ptr(ARGS_TOTEXT) {
+ isc_region_t region;
+ dns_name_t name;
+ dns_name_t prefix;
+ bool sub;
+
+ REQUIRE(rdata->type == dns_rdatatype_nsap_ptr);
+ REQUIRE(rdata->rdclass == dns_rdataclass_in);
+ REQUIRE(rdata->length != 0);
+
+ dns_name_init(&name, NULL);
+ dns_name_init(&prefix, NULL);
+
+ dns_rdata_toregion(rdata, &region);
+ dns_name_fromregion(&name, &region);
+
+ sub = name_prefix(&name, tctx->origin, &prefix);
+
+ return (dns_name_totext(&prefix, sub, target));
+}
+
+static isc_result_t
+fromwire_in_nsap_ptr(ARGS_FROMWIRE) {
+ dns_name_t name;
+
+ REQUIRE(type == dns_rdatatype_nsap_ptr);
+ REQUIRE(rdclass == dns_rdataclass_in);
+
+ UNUSED(type);
+ UNUSED(rdclass);
+
+ dns_decompress_setmethods(dctx, DNS_COMPRESS_NONE);
+
+ dns_name_init(&name, NULL);
+ return (dns_name_fromwire(&name, source, dctx, options, target));
+}
+
+static isc_result_t
+towire_in_nsap_ptr(ARGS_TOWIRE) {
+ dns_name_t name;
+ dns_offsets_t offsets;
+ isc_region_t region;
+
+ REQUIRE(rdata->type == dns_rdatatype_nsap_ptr);
+ REQUIRE(rdata->rdclass == dns_rdataclass_in);
+ REQUIRE(rdata->length != 0);
+
+ dns_compress_setmethods(cctx, DNS_COMPRESS_NONE);
+ dns_name_init(&name, offsets);
+ dns_rdata_toregion(rdata, &region);
+ dns_name_fromregion(&name, &region);
+
+ return (dns_name_towire(&name, cctx, target));
+}
+
+static int
+compare_in_nsap_ptr(ARGS_COMPARE) {
+ dns_name_t name1;
+ dns_name_t name2;
+ isc_region_t region1;
+ isc_region_t region2;
+
+ REQUIRE(rdata1->type == rdata2->type);
+ REQUIRE(rdata1->rdclass == rdata2->rdclass);
+ REQUIRE(rdata1->type == dns_rdatatype_nsap_ptr);
+ REQUIRE(rdata1->rdclass == dns_rdataclass_in);
+ REQUIRE(rdata1->length != 0);
+ REQUIRE(rdata2->length != 0);
+
+ dns_name_init(&name1, NULL);
+ dns_name_init(&name2, NULL);
+
+ dns_rdata_toregion(rdata1, &region1);
+ dns_rdata_toregion(rdata2, &region2);
+
+ dns_name_fromregion(&name1, &region1);
+ dns_name_fromregion(&name2, &region2);
+
+ return (dns_name_rdatacompare(&name1, &name2));
+}
+
+static isc_result_t
+fromstruct_in_nsap_ptr(ARGS_FROMSTRUCT) {
+ dns_rdata_in_nsap_ptr_t *nsap_ptr = source;
+ isc_region_t region;
+
+ REQUIRE(type == dns_rdatatype_nsap_ptr);
+ REQUIRE(rdclass == dns_rdataclass_in);
+ REQUIRE(nsap_ptr != NULL);
+ REQUIRE(nsap_ptr->common.rdtype == type);
+ REQUIRE(nsap_ptr->common.rdclass == rdclass);
+
+ UNUSED(type);
+ UNUSED(rdclass);
+
+ dns_name_toregion(&nsap_ptr->owner, &region);
+ return (isc_buffer_copyregion(target, &region));
+}
+
+static isc_result_t
+tostruct_in_nsap_ptr(ARGS_TOSTRUCT) {
+ isc_region_t region;
+ dns_rdata_in_nsap_ptr_t *nsap_ptr = target;
+ dns_name_t name;
+
+ REQUIRE(rdata->type == dns_rdatatype_nsap_ptr);
+ REQUIRE(rdata->rdclass == dns_rdataclass_in);
+ REQUIRE(nsap_ptr != NULL);
+ REQUIRE(rdata->length != 0);
+
+ nsap_ptr->common.rdclass = rdata->rdclass;
+ nsap_ptr->common.rdtype = rdata->type;
+ ISC_LINK_INIT(&nsap_ptr->common, link);
+
+ dns_name_init(&name, NULL);
+ dns_rdata_toregion(rdata, &region);
+ dns_name_fromregion(&name, &region);
+ dns_name_init(&nsap_ptr->owner, NULL);
+ name_duporclone(&name, mctx, &nsap_ptr->owner);
+ nsap_ptr->mctx = mctx;
+ return (ISC_R_SUCCESS);
+}
+
+static void
+freestruct_in_nsap_ptr(ARGS_FREESTRUCT) {
+ dns_rdata_in_nsap_ptr_t *nsap_ptr = source;
+
+ REQUIRE(nsap_ptr != NULL);
+ REQUIRE(nsap_ptr->common.rdclass == dns_rdataclass_in);
+ REQUIRE(nsap_ptr->common.rdtype == dns_rdatatype_nsap_ptr);
+
+ if (nsap_ptr->mctx == NULL) {
+ return;
+ }
+
+ dns_name_free(&nsap_ptr->owner, nsap_ptr->mctx);
+ nsap_ptr->mctx = NULL;
+}
+
+static isc_result_t
+additionaldata_in_nsap_ptr(ARGS_ADDLDATA) {
+ REQUIRE(rdata->type == dns_rdatatype_nsap_ptr);
+ REQUIRE(rdata->rdclass == dns_rdataclass_in);
+
+ UNUSED(rdata);
+ UNUSED(owner);
+ UNUSED(add);
+ UNUSED(arg);
+
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+digest_in_nsap_ptr(ARGS_DIGEST) {
+ isc_region_t r;
+ dns_name_t name;
+
+ REQUIRE(rdata->type == dns_rdatatype_nsap_ptr);
+ REQUIRE(rdata->rdclass == dns_rdataclass_in);
+
+ dns_rdata_toregion(rdata, &r);
+ dns_name_init(&name, NULL);
+ dns_name_fromregion(&name, &r);
+
+ return (dns_name_digest(&name, digest, arg));
+}
+
+static bool
+checkowner_in_nsap_ptr(ARGS_CHECKOWNER) {
+ REQUIRE(type == dns_rdatatype_nsap_ptr);
+ REQUIRE(rdclass == dns_rdataclass_in);
+
+ UNUSED(name);
+ UNUSED(type);
+ UNUSED(rdclass);
+ UNUSED(wildcard);
+
+ return (true);
+}
+
+static bool
+checknames_in_nsap_ptr(ARGS_CHECKNAMES) {
+ REQUIRE(rdata->type == dns_rdatatype_nsap_ptr);
+ REQUIRE(rdata->rdclass == dns_rdataclass_in);
+
+ UNUSED(rdata);
+ UNUSED(owner);
+ UNUSED(bad);
+
+ return (true);
+}
+
+static int
+casecompare_in_nsap_ptr(ARGS_COMPARE) {
+ return (compare_in_nsap_ptr(rdata1, rdata2));
+}
+
+#endif /* RDATA_IN_1_NSAP_PTR_23_C */
diff --git a/lib/dns/rdata/in_1/nsap-ptr_23.h b/lib/dns/rdata/in_1/nsap-ptr_23.h
new file mode 100644
index 0000000..45e5b16
--- /dev/null
+++ b/lib/dns/rdata/in_1/nsap-ptr_23.h
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#pragma once
+
+/*!
+ * \brief Per RFC1348. Obsoleted in RFC 1706 - use PTR instead. */
+
+typedef struct dns_rdata_in_nsap_ptr {
+ dns_rdatacommon_t common;
+ isc_mem_t *mctx;
+ dns_name_t owner;
+} dns_rdata_in_nsap_ptr_t;
diff --git a/lib/dns/rdata/in_1/nsap_22.c b/lib/dns/rdata/in_1/nsap_22.c
new file mode 100644
index 0000000..fc92014
--- /dev/null
+++ b/lib/dns/rdata/in_1/nsap_22.c
@@ -0,0 +1,260 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/* RFC1706 */
+
+#ifndef RDATA_IN_1_NSAP_22_C
+#define RDATA_IN_1_NSAP_22_C
+
+#define RRTYPE_NSAP_ATTRIBUTES (0)
+
+static isc_result_t
+fromtext_in_nsap(ARGS_FROMTEXT) {
+ isc_token_t token;
+ isc_textregion_t *sr;
+ int n;
+ bool valid = false;
+ int digits = 0;
+ unsigned char c = 0;
+
+ REQUIRE(type == dns_rdatatype_nsap);
+ REQUIRE(rdclass == dns_rdataclass_in);
+
+ UNUSED(type);
+ UNUSED(origin);
+ UNUSED(options);
+ UNUSED(rdclass);
+ UNUSED(callbacks);
+
+ /* 0x<hex.string.with.periods> */
+ RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_string,
+ false));
+ sr = &token.value.as_textregion;
+ if (sr->length < 2) {
+ RETTOK(ISC_R_UNEXPECTEDEND);
+ }
+ if (sr->base[0] != '0' || (sr->base[1] != 'x' && sr->base[1] != 'X')) {
+ RETTOK(DNS_R_SYNTAX);
+ }
+ isc_textregion_consume(sr, 2);
+ while (sr->length > 0) {
+ if (sr->base[0] == '.') {
+ isc_textregion_consume(sr, 1);
+ continue;
+ }
+ if ((n = hexvalue(sr->base[0])) == -1) {
+ RETTOK(DNS_R_SYNTAX);
+ }
+ c <<= 4;
+ c += n;
+ if (++digits == 2) {
+ RETERR(mem_tobuffer(target, &c, 1));
+ valid = true;
+ digits = 0;
+ c = 0;
+ }
+ isc_textregion_consume(sr, 1);
+ }
+ if (digits != 0 || !valid) {
+ RETTOK(ISC_R_UNEXPECTEDEND);
+ }
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+totext_in_nsap(ARGS_TOTEXT) {
+ isc_region_t region;
+ char buf[sizeof("xx")];
+
+ REQUIRE(rdata->type == dns_rdatatype_nsap);
+ REQUIRE(rdata->rdclass == dns_rdataclass_in);
+ REQUIRE(rdata->length != 0);
+
+ UNUSED(tctx);
+
+ dns_rdata_toregion(rdata, &region);
+ RETERR(str_totext("0x", target));
+ while (region.length != 0) {
+ snprintf(buf, sizeof(buf), "%02x", region.base[0]);
+ isc_region_consume(&region, 1);
+ RETERR(str_totext(buf, target));
+ }
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+fromwire_in_nsap(ARGS_FROMWIRE) {
+ isc_region_t region;
+
+ REQUIRE(type == dns_rdatatype_nsap);
+ REQUIRE(rdclass == dns_rdataclass_in);
+
+ UNUSED(type);
+ UNUSED(dctx);
+ UNUSED(options);
+ UNUSED(rdclass);
+
+ isc_buffer_activeregion(source, &region);
+ if (region.length < 1) {
+ return (ISC_R_UNEXPECTEDEND);
+ }
+
+ RETERR(mem_tobuffer(target, region.base, region.length));
+ isc_buffer_forward(source, region.length);
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+towire_in_nsap(ARGS_TOWIRE) {
+ REQUIRE(rdata->type == dns_rdatatype_nsap);
+ REQUIRE(rdata->rdclass == dns_rdataclass_in);
+ REQUIRE(rdata->length != 0);
+
+ UNUSED(cctx);
+
+ return (mem_tobuffer(target, rdata->data, rdata->length));
+}
+
+static int
+compare_in_nsap(ARGS_COMPARE) {
+ isc_region_t r1;
+ isc_region_t r2;
+
+ REQUIRE(rdata1->type == rdata2->type);
+ REQUIRE(rdata1->rdclass == rdata2->rdclass);
+ REQUIRE(rdata1->type == dns_rdatatype_nsap);
+ REQUIRE(rdata1->rdclass == dns_rdataclass_in);
+ REQUIRE(rdata1->length != 0);
+ REQUIRE(rdata2->length != 0);
+
+ dns_rdata_toregion(rdata1, &r1);
+ dns_rdata_toregion(rdata2, &r2);
+ return (isc_region_compare(&r1, &r2));
+}
+
+static isc_result_t
+fromstruct_in_nsap(ARGS_FROMSTRUCT) {
+ dns_rdata_in_nsap_t *nsap = source;
+
+ REQUIRE(type == dns_rdatatype_nsap);
+ REQUIRE(rdclass == dns_rdataclass_in);
+ REQUIRE(nsap != NULL);
+ REQUIRE(nsap->common.rdtype == type);
+ REQUIRE(nsap->common.rdclass == rdclass);
+ REQUIRE(nsap->nsap != NULL || nsap->nsap_len == 0);
+
+ UNUSED(type);
+ UNUSED(rdclass);
+
+ return (mem_tobuffer(target, nsap->nsap, nsap->nsap_len));
+}
+
+static isc_result_t
+tostruct_in_nsap(ARGS_TOSTRUCT) {
+ dns_rdata_in_nsap_t *nsap = target;
+ isc_region_t r;
+
+ REQUIRE(rdata->type == dns_rdatatype_nsap);
+ REQUIRE(rdata->rdclass == dns_rdataclass_in);
+ REQUIRE(nsap != NULL);
+ REQUIRE(rdata->length != 0);
+
+ nsap->common.rdclass = rdata->rdclass;
+ nsap->common.rdtype = rdata->type;
+ ISC_LINK_INIT(&nsap->common, link);
+
+ dns_rdata_toregion(rdata, &r);
+ nsap->nsap_len = r.length;
+ nsap->nsap = mem_maybedup(mctx, r.base, r.length);
+ if (nsap->nsap == NULL) {
+ return (ISC_R_NOMEMORY);
+ }
+
+ nsap->mctx = mctx;
+ return (ISC_R_SUCCESS);
+}
+
+static void
+freestruct_in_nsap(ARGS_FREESTRUCT) {
+ dns_rdata_in_nsap_t *nsap = source;
+
+ REQUIRE(nsap != NULL);
+ REQUIRE(nsap->common.rdclass == dns_rdataclass_in);
+ REQUIRE(nsap->common.rdtype == dns_rdatatype_nsap);
+
+ if (nsap->mctx == NULL) {
+ return;
+ }
+
+ if (nsap->nsap != NULL) {
+ isc_mem_free(nsap->mctx, nsap->nsap);
+ }
+ nsap->mctx = NULL;
+}
+
+static isc_result_t
+additionaldata_in_nsap(ARGS_ADDLDATA) {
+ REQUIRE(rdata->type == dns_rdatatype_nsap);
+ REQUIRE(rdata->rdclass == dns_rdataclass_in);
+
+ UNUSED(rdata);
+ UNUSED(owner);
+ UNUSED(add);
+ UNUSED(arg);
+
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+digest_in_nsap(ARGS_DIGEST) {
+ isc_region_t r;
+
+ REQUIRE(rdata->type == dns_rdatatype_nsap);
+ REQUIRE(rdata->rdclass == dns_rdataclass_in);
+
+ dns_rdata_toregion(rdata, &r);
+
+ return ((digest)(arg, &r));
+}
+
+static bool
+checkowner_in_nsap(ARGS_CHECKOWNER) {
+ REQUIRE(type == dns_rdatatype_nsap);
+ REQUIRE(rdclass == dns_rdataclass_in);
+
+ UNUSED(name);
+ UNUSED(type);
+ UNUSED(rdclass);
+ UNUSED(wildcard);
+
+ return (true);
+}
+
+static bool
+checknames_in_nsap(ARGS_CHECKNAMES) {
+ REQUIRE(rdata->type == dns_rdatatype_nsap);
+ REQUIRE(rdata->rdclass == dns_rdataclass_in);
+
+ UNUSED(rdata);
+ UNUSED(owner);
+ UNUSED(bad);
+
+ return (true);
+}
+
+static int
+casecompare_in_nsap(ARGS_COMPARE) {
+ return (compare_in_nsap(rdata1, rdata2));
+}
+
+#endif /* RDATA_IN_1_NSAP_22_C */
diff --git a/lib/dns/rdata/in_1/nsap_22.h b/lib/dns/rdata/in_1/nsap_22.h
new file mode 100644
index 0000000..fd5803c
--- /dev/null
+++ b/lib/dns/rdata/in_1/nsap_22.h
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#pragma once
+
+/*!
+ * \brief Per RFC1706 */
+
+typedef struct dns_rdata_in_nsap {
+ dns_rdatacommon_t common;
+ isc_mem_t *mctx;
+ unsigned char *nsap;
+ uint16_t nsap_len;
+} dns_rdata_in_nsap_t;
diff --git a/lib/dns/rdata/in_1/px_26.c b/lib/dns/rdata/in_1/px_26.c
new file mode 100644
index 0000000..441c3c4
--- /dev/null
+++ b/lib/dns/rdata/in_1/px_26.c
@@ -0,0 +1,372 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/* RFC2163 */
+
+#ifndef RDATA_IN_1_PX_26_C
+#define RDATA_IN_1_PX_26_C
+
+#define RRTYPE_PX_ATTRIBUTES (0)
+
+static isc_result_t
+fromtext_in_px(ARGS_FROMTEXT) {
+ isc_token_t token;
+ dns_name_t name;
+ isc_buffer_t buffer;
+
+ REQUIRE(type == dns_rdatatype_px);
+ REQUIRE(rdclass == dns_rdataclass_in);
+
+ UNUSED(type);
+ UNUSED(rdclass);
+ UNUSED(callbacks);
+
+ if (origin == NULL) {
+ origin = dns_rootname;
+ }
+
+ /*
+ * Preference.
+ */
+ RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_number,
+ false));
+ if (token.value.as_ulong > 0xffffU) {
+ RETTOK(ISC_R_RANGE);
+ }
+ RETERR(uint16_tobuffer(token.value.as_ulong, target));
+
+ /*
+ * MAP822.
+ */
+ RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_string,
+ false));
+ dns_name_init(&name, NULL);
+ buffer_fromregion(&buffer, &token.value.as_region);
+ RETTOK(dns_name_fromtext(&name, &buffer, origin, options, target));
+
+ /*
+ * MAPX400.
+ */
+ RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_string,
+ false));
+ dns_name_init(&name, NULL);
+ buffer_fromregion(&buffer, &token.value.as_region);
+ RETTOK(dns_name_fromtext(&name, &buffer, origin, options, target));
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+totext_in_px(ARGS_TOTEXT) {
+ isc_region_t region;
+ dns_name_t name;
+ dns_name_t prefix;
+ bool sub;
+ char buf[sizeof("64000")];
+ unsigned short num;
+
+ REQUIRE(rdata->type == dns_rdatatype_px);
+ REQUIRE(rdata->rdclass == dns_rdataclass_in);
+ REQUIRE(rdata->length != 0);
+
+ dns_name_init(&name, NULL);
+ dns_name_init(&prefix, NULL);
+
+ /*
+ * Preference.
+ */
+ dns_rdata_toregion(rdata, &region);
+ num = uint16_fromregion(&region);
+ isc_region_consume(&region, 2);
+ snprintf(buf, sizeof(buf), "%u", num);
+ RETERR(str_totext(buf, target));
+ RETERR(str_totext(" ", target));
+
+ /*
+ * MAP822.
+ */
+ dns_name_fromregion(&name, &region);
+ sub = name_prefix(&name, tctx->origin, &prefix);
+ isc_region_consume(&region, name_length(&name));
+ RETERR(dns_name_totext(&prefix, sub, target));
+ RETERR(str_totext(" ", target));
+
+ /*
+ * MAPX400.
+ */
+ dns_name_fromregion(&name, &region);
+ sub = name_prefix(&name, tctx->origin, &prefix);
+ return (dns_name_totext(&prefix, sub, target));
+}
+
+static isc_result_t
+fromwire_in_px(ARGS_FROMWIRE) {
+ dns_name_t name;
+ isc_region_t sregion;
+
+ REQUIRE(type == dns_rdatatype_px);
+ REQUIRE(rdclass == dns_rdataclass_in);
+
+ UNUSED(type);
+ UNUSED(rdclass);
+
+ dns_decompress_setmethods(dctx, DNS_COMPRESS_NONE);
+
+ dns_name_init(&name, NULL);
+
+ /*
+ * Preference.
+ */
+ isc_buffer_activeregion(source, &sregion);
+ if (sregion.length < 2) {
+ return (ISC_R_UNEXPECTEDEND);
+ }
+ RETERR(mem_tobuffer(target, sregion.base, 2));
+ isc_buffer_forward(source, 2);
+
+ /*
+ * MAP822.
+ */
+ RETERR(dns_name_fromwire(&name, source, dctx, options, target));
+
+ /*
+ * MAPX400.
+ */
+ return (dns_name_fromwire(&name, source, dctx, options, target));
+}
+
+static isc_result_t
+towire_in_px(ARGS_TOWIRE) {
+ dns_name_t name;
+ dns_offsets_t offsets;
+ isc_region_t region;
+
+ REQUIRE(rdata->type == dns_rdatatype_px);
+ REQUIRE(rdata->rdclass == dns_rdataclass_in);
+ REQUIRE(rdata->length != 0);
+
+ dns_compress_setmethods(cctx, DNS_COMPRESS_NONE);
+ /*
+ * Preference.
+ */
+ dns_rdata_toregion(rdata, &region);
+ RETERR(mem_tobuffer(target, region.base, 2));
+ isc_region_consume(&region, 2);
+
+ /*
+ * MAP822.
+ */
+ dns_name_init(&name, offsets);
+ dns_name_fromregion(&name, &region);
+ RETERR(dns_name_towire(&name, cctx, target));
+ isc_region_consume(&region, name_length(&name));
+
+ /*
+ * MAPX400.
+ */
+ dns_name_init(&name, offsets);
+ dns_name_fromregion(&name, &region);
+ return (dns_name_towire(&name, cctx, target));
+}
+
+static int
+compare_in_px(ARGS_COMPARE) {
+ dns_name_t name1;
+ dns_name_t name2;
+ isc_region_t region1;
+ isc_region_t region2;
+ int order;
+
+ REQUIRE(rdata1->type == rdata2->type);
+ REQUIRE(rdata1->rdclass == rdata2->rdclass);
+ REQUIRE(rdata1->type == dns_rdatatype_px);
+ REQUIRE(rdata1->rdclass == dns_rdataclass_in);
+ REQUIRE(rdata1->length != 0);
+ REQUIRE(rdata2->length != 0);
+
+ order = memcmp(rdata1->data, rdata2->data, 2);
+ if (order != 0) {
+ return (order < 0 ? -1 : 1);
+ }
+
+ dns_name_init(&name1, NULL);
+ dns_name_init(&name2, NULL);
+
+ dns_rdata_toregion(rdata1, &region1);
+ dns_rdata_toregion(rdata2, &region2);
+
+ isc_region_consume(&region1, 2);
+ isc_region_consume(&region2, 2);
+
+ dns_name_fromregion(&name1, &region1);
+ dns_name_fromregion(&name2, &region2);
+
+ order = dns_name_rdatacompare(&name1, &name2);
+ if (order != 0) {
+ return (order);
+ }
+
+ isc_region_consume(&region1, name_length(&name1));
+ isc_region_consume(&region2, name_length(&name2));
+
+ dns_name_fromregion(&name1, &region1);
+ dns_name_fromregion(&name2, &region2);
+
+ return (dns_name_rdatacompare(&name1, &name2));
+}
+
+static isc_result_t
+fromstruct_in_px(ARGS_FROMSTRUCT) {
+ dns_rdata_in_px_t *px = source;
+ isc_region_t region;
+
+ REQUIRE(type == dns_rdatatype_px);
+ REQUIRE(rdclass == dns_rdataclass_in);
+ REQUIRE(px != NULL);
+ REQUIRE(px->common.rdtype == type);
+ REQUIRE(px->common.rdclass == rdclass);
+
+ UNUSED(type);
+ UNUSED(rdclass);
+
+ RETERR(uint16_tobuffer(px->preference, target));
+ dns_name_toregion(&px->map822, &region);
+ RETERR(isc_buffer_copyregion(target, &region));
+ dns_name_toregion(&px->mapx400, &region);
+ return (isc_buffer_copyregion(target, &region));
+}
+
+static isc_result_t
+tostruct_in_px(ARGS_TOSTRUCT) {
+ dns_rdata_in_px_t *px = target;
+ dns_name_t name;
+ isc_region_t region;
+
+ REQUIRE(rdata->type == dns_rdatatype_px);
+ REQUIRE(rdata->rdclass == dns_rdataclass_in);
+ REQUIRE(px != NULL);
+ REQUIRE(rdata->length != 0);
+
+ px->common.rdclass = rdata->rdclass;
+ px->common.rdtype = rdata->type;
+ ISC_LINK_INIT(&px->common, link);
+
+ dns_name_init(&name, NULL);
+ dns_rdata_toregion(rdata, &region);
+
+ px->preference = uint16_fromregion(&region);
+ isc_region_consume(&region, 2);
+
+ dns_name_fromregion(&name, &region);
+
+ dns_name_init(&px->map822, NULL);
+ name_duporclone(&name, mctx, &px->map822);
+ isc_region_consume(&region, name_length(&px->map822));
+
+ dns_name_init(&px->mapx400, NULL);
+ name_duporclone(&name, mctx, &px->mapx400);
+
+ px->mctx = mctx;
+ return (ISC_R_SUCCESS);
+}
+
+static void
+freestruct_in_px(ARGS_FREESTRUCT) {
+ dns_rdata_in_px_t *px = source;
+
+ REQUIRE(px != NULL);
+ REQUIRE(px->common.rdclass == dns_rdataclass_in);
+ REQUIRE(px->common.rdtype == dns_rdatatype_px);
+
+ if (px->mctx == NULL) {
+ return;
+ }
+
+ dns_name_free(&px->map822, px->mctx);
+ dns_name_free(&px->mapx400, px->mctx);
+ px->mctx = NULL;
+}
+
+static isc_result_t
+additionaldata_in_px(ARGS_ADDLDATA) {
+ REQUIRE(rdata->type == dns_rdatatype_px);
+ REQUIRE(rdata->rdclass == dns_rdataclass_in);
+
+ UNUSED(rdata);
+ UNUSED(owner);
+ UNUSED(add);
+ UNUSED(arg);
+
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+digest_in_px(ARGS_DIGEST) {
+ isc_region_t r1, r2;
+ dns_name_t name;
+ isc_result_t result;
+
+ REQUIRE(rdata->type == dns_rdatatype_px);
+ REQUIRE(rdata->rdclass == dns_rdataclass_in);
+
+ dns_rdata_toregion(rdata, &r1);
+ r2 = r1;
+ isc_region_consume(&r2, 2);
+ r1.length = 2;
+ result = (digest)(arg, &r1);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+ dns_name_init(&name, NULL);
+ dns_name_fromregion(&name, &r2);
+ result = dns_name_digest(&name, digest, arg);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+ isc_region_consume(&r2, name_length(&name));
+ dns_name_init(&name, NULL);
+ dns_name_fromregion(&name, &r2);
+
+ return (dns_name_digest(&name, digest, arg));
+}
+
+static bool
+checkowner_in_px(ARGS_CHECKOWNER) {
+ REQUIRE(type == dns_rdatatype_px);
+ REQUIRE(rdclass == dns_rdataclass_in);
+
+ UNUSED(name);
+ UNUSED(type);
+ UNUSED(rdclass);
+ UNUSED(wildcard);
+
+ return (true);
+}
+
+static bool
+checknames_in_px(ARGS_CHECKNAMES) {
+ REQUIRE(rdata->type == dns_rdatatype_px);
+ REQUIRE(rdata->rdclass == dns_rdataclass_in);
+
+ UNUSED(rdata);
+ UNUSED(owner);
+ UNUSED(bad);
+
+ return (true);
+}
+
+static int
+casecompare_in_px(ARGS_COMPARE) {
+ return (compare_in_px(rdata1, rdata2));
+}
+
+#endif /* RDATA_IN_1_PX_26_C */
diff --git a/lib/dns/rdata/in_1/px_26.h b/lib/dns/rdata/in_1/px_26.h
new file mode 100644
index 0000000..36a1c0e
--- /dev/null
+++ b/lib/dns/rdata/in_1/px_26.h
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#pragma once
+
+/*!
+ * \brief Per RFC2163 */
+
+typedef struct dns_rdata_in_px {
+ dns_rdatacommon_t common;
+ isc_mem_t *mctx;
+ uint16_t preference;
+ dns_name_t map822;
+ dns_name_t mapx400;
+} dns_rdata_in_px_t;
diff --git a/lib/dns/rdata/in_1/srv_33.c b/lib/dns/rdata/in_1/srv_33.c
new file mode 100644
index 0000000..6818b03
--- /dev/null
+++ b/lib/dns/rdata/in_1/srv_33.c
@@ -0,0 +1,413 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/* RFC2782 */
+
+#ifndef RDATA_IN_1_SRV_33_C
+#define RDATA_IN_1_SRV_33_C
+
+#define RRTYPE_SRV_ATTRIBUTES (DNS_RDATATYPEATTR_FOLLOWADDITIONAL)
+
+static isc_result_t
+fromtext_in_srv(ARGS_FROMTEXT) {
+ isc_token_t token;
+ dns_name_t name;
+ isc_buffer_t buffer;
+ bool ok;
+
+ REQUIRE(type == dns_rdatatype_srv);
+ REQUIRE(rdclass == dns_rdataclass_in);
+
+ UNUSED(type);
+ UNUSED(rdclass);
+ UNUSED(callbacks);
+
+ /*
+ * Priority.
+ */
+ RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_number,
+ false));
+ if (token.value.as_ulong > 0xffffU) {
+ RETTOK(ISC_R_RANGE);
+ }
+ RETERR(uint16_tobuffer(token.value.as_ulong, target));
+
+ /*
+ * Weight.
+ */
+ RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_number,
+ false));
+ if (token.value.as_ulong > 0xffffU) {
+ RETTOK(ISC_R_RANGE);
+ }
+ RETERR(uint16_tobuffer(token.value.as_ulong, target));
+
+ /*
+ * Port.
+ */
+ RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_number,
+ false));
+ if (token.value.as_ulong > 0xffffU) {
+ RETTOK(ISC_R_RANGE);
+ }
+ RETERR(uint16_tobuffer(token.value.as_ulong, target));
+
+ /*
+ * Target.
+ */
+ RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_string,
+ false));
+ dns_name_init(&name, NULL);
+ buffer_fromregion(&buffer, &token.value.as_region);
+ if (origin == NULL) {
+ origin = dns_rootname;
+ }
+ RETTOK(dns_name_fromtext(&name, &buffer, origin, options, target));
+ ok = true;
+ if ((options & DNS_RDATA_CHECKNAMES) != 0) {
+ ok = dns_name_ishostname(&name, false);
+ }
+ if (!ok && (options & DNS_RDATA_CHECKNAMESFAIL) != 0) {
+ RETTOK(DNS_R_BADNAME);
+ }
+ if (!ok && callbacks != NULL) {
+ warn_badname(&name, lexer, callbacks);
+ }
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+totext_in_srv(ARGS_TOTEXT) {
+ isc_region_t region;
+ dns_name_t name;
+ dns_name_t prefix;
+ bool sub;
+ char buf[sizeof("64000")];
+ unsigned short num;
+
+ REQUIRE(rdata->type == dns_rdatatype_srv);
+ REQUIRE(rdata->rdclass == dns_rdataclass_in);
+ REQUIRE(rdata->length != 0);
+
+ dns_name_init(&name, NULL);
+ dns_name_init(&prefix, NULL);
+
+ /*
+ * Priority.
+ */
+ dns_rdata_toregion(rdata, &region);
+ num = uint16_fromregion(&region);
+ isc_region_consume(&region, 2);
+ snprintf(buf, sizeof(buf), "%u", num);
+ RETERR(str_totext(buf, target));
+ RETERR(str_totext(" ", target));
+
+ /*
+ * Weight.
+ */
+ num = uint16_fromregion(&region);
+ isc_region_consume(&region, 2);
+ snprintf(buf, sizeof(buf), "%u", num);
+ RETERR(str_totext(buf, target));
+ RETERR(str_totext(" ", target));
+
+ /*
+ * Port.
+ */
+ num = uint16_fromregion(&region);
+ isc_region_consume(&region, 2);
+ snprintf(buf, sizeof(buf), "%u", num);
+ RETERR(str_totext(buf, target));
+ RETERR(str_totext(" ", target));
+
+ /*
+ * Target.
+ */
+ dns_name_fromregion(&name, &region);
+ sub = name_prefix(&name, tctx->origin, &prefix);
+ return (dns_name_totext(&prefix, sub, target));
+}
+
+static isc_result_t
+fromwire_in_srv(ARGS_FROMWIRE) {
+ dns_name_t name;
+ isc_region_t sr;
+
+ REQUIRE(type == dns_rdatatype_srv);
+ REQUIRE(rdclass == dns_rdataclass_in);
+
+ UNUSED(type);
+ UNUSED(rdclass);
+
+ dns_decompress_setmethods(dctx, DNS_COMPRESS_NONE);
+
+ dns_name_init(&name, NULL);
+
+ /*
+ * Priority, weight, port.
+ */
+ isc_buffer_activeregion(source, &sr);
+ if (sr.length < 6) {
+ return (ISC_R_UNEXPECTEDEND);
+ }
+ RETERR(mem_tobuffer(target, sr.base, 6));
+ isc_buffer_forward(source, 6);
+
+ /*
+ * Target.
+ */
+ return (dns_name_fromwire(&name, source, dctx, options, target));
+}
+
+static isc_result_t
+towire_in_srv(ARGS_TOWIRE) {
+ dns_name_t name;
+ dns_offsets_t offsets;
+ isc_region_t sr;
+
+ REQUIRE(rdata->type == dns_rdatatype_srv);
+ REQUIRE(rdata->length != 0);
+
+ dns_compress_setmethods(cctx, DNS_COMPRESS_NONE);
+ /*
+ * Priority, weight, port.
+ */
+ dns_rdata_toregion(rdata, &sr);
+ RETERR(mem_tobuffer(target, sr.base, 6));
+ isc_region_consume(&sr, 6);
+
+ /*
+ * Target.
+ */
+ dns_name_init(&name, offsets);
+ dns_name_fromregion(&name, &sr);
+ return (dns_name_towire(&name, cctx, target));
+}
+
+static int
+compare_in_srv(ARGS_COMPARE) {
+ dns_name_t name1;
+ dns_name_t name2;
+ isc_region_t region1;
+ isc_region_t region2;
+ int order;
+
+ REQUIRE(rdata1->type == rdata2->type);
+ REQUIRE(rdata1->rdclass == rdata2->rdclass);
+ REQUIRE(rdata1->type == dns_rdatatype_srv);
+ REQUIRE(rdata1->rdclass == dns_rdataclass_in);
+ REQUIRE(rdata1->length != 0);
+ REQUIRE(rdata2->length != 0);
+
+ /*
+ * Priority, weight, port.
+ */
+ order = memcmp(rdata1->data, rdata2->data, 6);
+ if (order != 0) {
+ return (order < 0 ? -1 : 1);
+ }
+
+ /*
+ * Target.
+ */
+ dns_name_init(&name1, NULL);
+ dns_name_init(&name2, NULL);
+
+ dns_rdata_toregion(rdata1, &region1);
+ dns_rdata_toregion(rdata2, &region2);
+
+ isc_region_consume(&region1, 6);
+ isc_region_consume(&region2, 6);
+
+ dns_name_fromregion(&name1, &region1);
+ dns_name_fromregion(&name2, &region2);
+
+ return (dns_name_rdatacompare(&name1, &name2));
+}
+
+static isc_result_t
+fromstruct_in_srv(ARGS_FROMSTRUCT) {
+ dns_rdata_in_srv_t *srv = source;
+ isc_region_t region;
+
+ REQUIRE(type == dns_rdatatype_srv);
+ REQUIRE(rdclass == dns_rdataclass_in);
+ REQUIRE(srv != NULL);
+ REQUIRE(srv->common.rdtype == type);
+ REQUIRE(srv->common.rdclass == rdclass);
+
+ UNUSED(type);
+ UNUSED(rdclass);
+
+ RETERR(uint16_tobuffer(srv->priority, target));
+ RETERR(uint16_tobuffer(srv->weight, target));
+ RETERR(uint16_tobuffer(srv->port, target));
+ dns_name_toregion(&srv->target, &region);
+ return (isc_buffer_copyregion(target, &region));
+}
+
+static isc_result_t
+tostruct_in_srv(ARGS_TOSTRUCT) {
+ isc_region_t region;
+ dns_rdata_in_srv_t *srv = target;
+ dns_name_t name;
+
+ REQUIRE(rdata->rdclass == dns_rdataclass_in);
+ REQUIRE(rdata->type == dns_rdatatype_srv);
+ REQUIRE(srv != NULL);
+ REQUIRE(rdata->length != 0);
+
+ srv->common.rdclass = rdata->rdclass;
+ srv->common.rdtype = rdata->type;
+ ISC_LINK_INIT(&srv->common, link);
+
+ dns_name_init(&name, NULL);
+ dns_rdata_toregion(rdata, &region);
+ srv->priority = uint16_fromregion(&region);
+ isc_region_consume(&region, 2);
+ srv->weight = uint16_fromregion(&region);
+ isc_region_consume(&region, 2);
+ srv->port = uint16_fromregion(&region);
+ isc_region_consume(&region, 2);
+ dns_name_fromregion(&name, &region);
+ dns_name_init(&srv->target, NULL);
+ name_duporclone(&name, mctx, &srv->target);
+ srv->mctx = mctx;
+ return (ISC_R_SUCCESS);
+}
+
+static void
+freestruct_in_srv(ARGS_FREESTRUCT) {
+ dns_rdata_in_srv_t *srv = source;
+
+ REQUIRE(srv != NULL);
+ REQUIRE(srv->common.rdclass == dns_rdataclass_in);
+ REQUIRE(srv->common.rdtype == dns_rdatatype_srv);
+
+ if (srv->mctx == NULL) {
+ return;
+ }
+
+ dns_name_free(&srv->target, srv->mctx);
+ srv->mctx = NULL;
+}
+
+static isc_result_t
+additionaldata_in_srv(ARGS_ADDLDATA) {
+ char buf[sizeof("_65000._tcp")];
+ dns_fixedname_t fixed;
+ dns_name_t name;
+ dns_offsets_t offsets;
+ isc_region_t region;
+ uint16_t port;
+ isc_result_t result;
+
+ REQUIRE(rdata->type == dns_rdatatype_srv);
+ REQUIRE(rdata->rdclass == dns_rdataclass_in);
+
+ UNUSED(owner);
+
+ dns_name_init(&name, offsets);
+ dns_rdata_toregion(rdata, &region);
+ isc_region_consume(&region, 4);
+ port = uint16_fromregion(&region);
+ isc_region_consume(&region, 2);
+ dns_name_fromregion(&name, &region);
+
+ if (dns_name_equal(&name, dns_rootname)) {
+ return (ISC_R_SUCCESS);
+ }
+
+ result = (add)(arg, &name, dns_rdatatype_a, NULL);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+
+ dns_fixedname_init(&fixed);
+ snprintf(buf, sizeof(buf), "_%u._tcp", port);
+ result = dns_name_fromstring2(dns_fixedname_name(&fixed), buf, NULL, 0,
+ NULL);
+ if (result != ISC_R_SUCCESS) {
+ return (ISC_R_SUCCESS);
+ }
+
+ result = dns_name_concatenate(dns_fixedname_name(&fixed), &name,
+ dns_fixedname_name(&fixed), NULL);
+ if (result != ISC_R_SUCCESS) {
+ return (ISC_R_SUCCESS);
+ }
+
+ return ((add)(arg, dns_fixedname_name(&fixed), dns_rdatatype_tlsa,
+ NULL));
+}
+
+static isc_result_t
+digest_in_srv(ARGS_DIGEST) {
+ isc_region_t r1, r2;
+ dns_name_t name;
+
+ REQUIRE(rdata->type == dns_rdatatype_srv);
+ REQUIRE(rdata->rdclass == dns_rdataclass_in);
+
+ dns_rdata_toregion(rdata, &r1);
+ r2 = r1;
+ isc_region_consume(&r2, 6);
+ r1.length = 6;
+ RETERR((digest)(arg, &r1));
+ dns_name_init(&name, NULL);
+ dns_name_fromregion(&name, &r2);
+ return (dns_name_digest(&name, digest, arg));
+}
+
+static bool
+checkowner_in_srv(ARGS_CHECKOWNER) {
+ REQUIRE(type == dns_rdatatype_srv);
+ REQUIRE(rdclass == dns_rdataclass_in);
+
+ UNUSED(name);
+ UNUSED(type);
+ UNUSED(rdclass);
+ UNUSED(wildcard);
+
+ return (true);
+}
+
+static bool
+checknames_in_srv(ARGS_CHECKNAMES) {
+ isc_region_t region;
+ dns_name_t name;
+
+ REQUIRE(rdata->type == dns_rdatatype_srv);
+ REQUIRE(rdata->rdclass == dns_rdataclass_in);
+
+ UNUSED(owner);
+
+ dns_rdata_toregion(rdata, &region);
+ isc_region_consume(&region, 6);
+ dns_name_init(&name, NULL);
+ dns_name_fromregion(&name, &region);
+ if (!dns_name_ishostname(&name, false)) {
+ if (bad != NULL) {
+ dns_name_clone(&name, bad);
+ }
+ return (false);
+ }
+ return (true);
+}
+
+static int
+casecompare_in_srv(ARGS_COMPARE) {
+ return (compare_in_srv(rdata1, rdata2));
+}
+
+#endif /* RDATA_IN_1_SRV_33_C */
diff --git a/lib/dns/rdata/in_1/srv_33.h b/lib/dns/rdata/in_1/srv_33.h
new file mode 100644
index 0000000..fbfa92c
--- /dev/null
+++ b/lib/dns/rdata/in_1/srv_33.h
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#pragma once
+
+/*!
+ * \brief Per RFC2782 */
+
+typedef struct dns_rdata_in_srv {
+ dns_rdatacommon_t common;
+ isc_mem_t *mctx;
+ uint16_t priority;
+ uint16_t weight;
+ uint16_t port;
+ dns_name_t target;
+} dns_rdata_in_srv_t;
diff --git a/lib/dns/rdata/in_1/svcb_64.c b/lib/dns/rdata/in_1/svcb_64.c
new file mode 100644
index 0000000..0d9da89
--- /dev/null
+++ b/lib/dns/rdata/in_1/svcb_64.c
@@ -0,0 +1,1338 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/* draft-ietf-dnsop-svcb-https-02 */
+
+#ifndef RDATA_IN_1_SVCB_64_C
+#define RDATA_IN_1_SVCB_64_C
+
+#define RRTYPE_SVCB_ATTRIBUTES (DNS_RDATATYPEATTR_FOLLOWADDITIONAL)
+
+#define SVCB_MAN_KEY 0
+#define SVCB_ALPN_KEY 1
+#define SVCB_NO_DEFAULT_ALPN_KEY 2
+#define MAX_CNAMES 16 /* See ns/query.c MAX_RESTARTS */
+
+/*
+ * Service Binding Parameter Registry
+ */
+enum encoding {
+ sbpr_text,
+ sbpr_port,
+ sbpr_ipv4s,
+ sbpr_ipv6s,
+ sbpr_base64,
+ sbpr_empty,
+ sbpr_alpn,
+ sbpr_keylist,
+ sbpr_dohpath
+};
+static const struct {
+ const char *name; /* Restricted to lowercase LDH by registry. */
+ unsigned int value;
+ enum encoding encoding;
+ bool initial; /* Part of the first defined set of encodings. */
+} sbpr[] = {
+ { "mandatory", 0, sbpr_keylist, true },
+ { "alpn", 1, sbpr_alpn, true },
+ { "no-default-alpn", 2, sbpr_empty, true },
+ { "port", 3, sbpr_port, true },
+ { "ipv4hint", 4, sbpr_ipv4s, true },
+ { "ech", 5, sbpr_base64, true },
+ { "ipv6hint", 6, sbpr_ipv6s, true },
+ { "dohpath", 7, sbpr_dohpath, false },
+};
+
+static isc_result_t
+alpn_fromtxt(isc_textregion_t *source, isc_buffer_t *target) {
+ isc_textregion_t source0 = *source;
+ do {
+ RETERR(commatxt_fromtext(&source0, true, target));
+ } while (source0.length != 0);
+ return (ISC_R_SUCCESS);
+}
+
+static int
+svckeycmp(const void *a1, const void *a2) {
+ const unsigned char *u1 = a1, *u2 = a2;
+ if (*u1 != *u2) {
+ return (*u1 - *u2);
+ }
+ return (*(++u1) - *(++u2));
+}
+
+static isc_result_t
+svcsortkeylist(isc_buffer_t *target, unsigned int used) {
+ isc_region_t region;
+
+ isc_buffer_usedregion(target, &region);
+ isc_region_consume(&region, used);
+ INSIST(region.length > 0U);
+ qsort(region.base, region.length / 2, 2, svckeycmp);
+ /* Reject duplicates. */
+ while (region.length >= 4) {
+ if (region.base[0] == region.base[2] &&
+ region.base[1] == region.base[3])
+ {
+ return (DNS_R_SYNTAX);
+ }
+ isc_region_consume(&region, 2);
+ }
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+svcb_validate(uint16_t key, isc_region_t *region) {
+ size_t i;
+
+ for (i = 0; i < ARRAY_SIZE(sbpr); i++) {
+ if (sbpr[i].value == key) {
+ switch (sbpr[i].encoding) {
+ case sbpr_port:
+ if (region->length != 2) {
+ return (DNS_R_FORMERR);
+ }
+ break;
+ case sbpr_ipv4s:
+ if ((region->length % 4) != 0 ||
+ region->length == 0)
+ {
+ return (DNS_R_FORMERR);
+ }
+ break;
+ case sbpr_ipv6s:
+ if ((region->length % 16) != 0 ||
+ region->length == 0)
+ {
+ return (DNS_R_FORMERR);
+ }
+ break;
+ case sbpr_alpn: {
+ if (region->length == 0) {
+ return (DNS_R_FORMERR);
+ }
+ while (region->length != 0) {
+ size_t l = *region->base + 1;
+ if (l == 1U || l > region->length) {
+ return (DNS_R_FORMERR);
+ }
+ isc_region_consume(region, l);
+ }
+ break;
+ }
+ case sbpr_keylist: {
+ if ((region->length % 2) != 0 ||
+ region->length == 0)
+ {
+ return (DNS_R_FORMERR);
+ }
+ /* In order? */
+ while (region->length >= 4) {
+ if (region->base[0] > region->base[2] ||
+ (region->base[0] ==
+ region->base[2] &&
+ region->base[1] >=
+ region->base[3]))
+ {
+ return (DNS_R_FORMERR);
+ }
+ isc_region_consume(region, 2);
+ }
+ break;
+ }
+ case sbpr_text:
+ case sbpr_base64:
+ break;
+ case sbpr_dohpath:
+ /*
+ * Minimum valid dohpath is "/{?dns}" as
+ * it MUST be relative (leading "/") and
+ * MUST contain "{?dns}".
+ */
+ if (region->length < 7) {
+ return (DNS_R_FORMERR);
+ }
+ /* MUST be relative */
+ if (region->base[0] != '/') {
+ return (DNS_R_FORMERR);
+ }
+ /* MUST be UTF8 */
+ if (!isc_utf8_valid(region->base,
+ region->length))
+ {
+ return (DNS_R_FORMERR);
+ }
+ /* MUST contain "{?dns}" */
+ if (strnstr((char *)region->base, "{?dns}",
+ region->length) == NULL)
+ {
+ return (DNS_R_FORMERR);
+ }
+ break;
+ case sbpr_empty:
+ if (region->length != 0) {
+ return (DNS_R_FORMERR);
+ }
+ break;
+ }
+ }
+ }
+ return (ISC_R_SUCCESS);
+}
+
+/*
+ * Parse keyname from region.
+ */
+static isc_result_t
+svc_keyfromregion(isc_textregion_t *region, char sep, uint16_t *value,
+ isc_buffer_t *target) {
+ char *e = NULL;
+ size_t i;
+ unsigned long ul;
+
+ /* Look for known key names. */
+ for (i = 0; i < ARRAY_SIZE(sbpr); i++) {
+ size_t len = strlen(sbpr[i].name);
+ if (strncasecmp(region->base, sbpr[i].name, len) != 0 ||
+ (region->base[len] != 0 && region->base[len] != sep))
+ {
+ continue;
+ }
+ isc_textregion_consume(region, len);
+ ul = sbpr[i].value;
+ goto finish;
+ }
+ /* Handle keyXXXXX form. */
+ if (strncmp(region->base, "key", 3) != 0) {
+ return (DNS_R_SYNTAX);
+ }
+ isc_textregion_consume(region, 3);
+ /* Disallow [+-]XXXXX which is allowed by strtoul. */
+ if (region->length == 0 || *region->base == '-' || *region->base == '+')
+ {
+ return (DNS_R_SYNTAX);
+ }
+ /* No zero padding. */
+ if (region->length > 1 && *region->base == '0' &&
+ region->base[1] != sep)
+ {
+ return (DNS_R_SYNTAX);
+ }
+ ul = strtoul(region->base, &e, 10);
+ /* Valid number? */
+ if (e == region->base || (*e != sep && *e != 0)) {
+ return (DNS_R_SYNTAX);
+ }
+ if (ul > 0xffff) {
+ return (ISC_R_RANGE);
+ }
+ isc_textregion_consume(region, e - region->base);
+finish:
+ if (sep == ',' && region->length == 1) {
+ return (DNS_R_SYNTAX);
+ }
+ /* Consume separator. */
+ if (region->length != 0) {
+ isc_textregion_consume(region, 1);
+ }
+ RETERR(uint16_tobuffer(ul, target));
+ if (value != NULL) {
+ *value = ul;
+ }
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+svc_fromtext(isc_textregion_t *region, isc_buffer_t *target) {
+ char *e = NULL;
+ char abuf[16];
+ char tbuf[sizeof("aaaa:aaaa:aaaa:aaaa:aaaa:aaaa:255.255.255.255,")];
+ isc_buffer_t sb;
+ isc_region_t keyregion;
+ size_t len;
+ uint16_t key;
+ unsigned int i;
+ unsigned int used;
+ unsigned long ul;
+
+ for (i = 0; i < ARRAY_SIZE(sbpr); i++) {
+ len = strlen(sbpr[i].name);
+ if (strncmp(region->base, sbpr[i].name, len) != 0 ||
+ (region->base[len] != 0 && region->base[len] != '='))
+ {
+ continue;
+ }
+
+ if (region->base[len] == '=') {
+ len++;
+ }
+
+ RETERR(uint16_tobuffer(sbpr[i].value, target));
+ isc_textregion_consume(region, len);
+
+ sb = *target;
+ RETERR(uint16_tobuffer(0, target)); /* length */
+
+ switch (sbpr[i].encoding) {
+ case sbpr_text:
+ case sbpr_dohpath:
+ RETERR(multitxt_fromtext(region, target));
+ break;
+ case sbpr_alpn:
+ RETERR(alpn_fromtxt(region, target));
+ break;
+ case sbpr_port:
+ if (!isdigit((unsigned char)*region->base)) {
+ return (DNS_R_SYNTAX);
+ }
+ ul = strtoul(region->base, &e, 10);
+ if (*e != '\0') {
+ return (DNS_R_SYNTAX);
+ }
+ if (ul > 0xffff) {
+ return (ISC_R_RANGE);
+ }
+ RETERR(uint16_tobuffer(ul, target));
+ break;
+ case sbpr_ipv4s:
+ do {
+ snprintf(tbuf, sizeof(tbuf), "%*s",
+ (int)(region->length), region->base);
+ e = strchr(tbuf, ',');
+ if (e != NULL) {
+ *e++ = 0;
+ isc_textregion_consume(region,
+ e - tbuf);
+ }
+ if (inet_pton(AF_INET, tbuf, abuf) != 1) {
+ return (DNS_R_SYNTAX);
+ }
+ mem_tobuffer(target, abuf, 4);
+ } while (e != NULL);
+ break;
+ case sbpr_ipv6s:
+ do {
+ snprintf(tbuf, sizeof(tbuf), "%*s",
+ (int)(region->length), region->base);
+ e = strchr(tbuf, ',');
+ if (e != NULL) {
+ *e++ = 0;
+ isc_textregion_consume(region,
+ e - tbuf);
+ }
+ if (inet_pton(AF_INET6, tbuf, abuf) != 1) {
+ return (DNS_R_SYNTAX);
+ }
+ mem_tobuffer(target, abuf, 16);
+ } while (e != NULL);
+ break;
+ case sbpr_base64:
+ RETERR(isc_base64_decodestring(region->base, target));
+ break;
+ case sbpr_empty:
+ if (region->length != 0) {
+ return (DNS_R_SYNTAX);
+ }
+ break;
+ case sbpr_keylist:
+ if (region->length == 0) {
+ return (DNS_R_SYNTAX);
+ }
+ used = isc_buffer_usedlength(target);
+ while (region->length != 0) {
+ RETERR(svc_keyfromregion(region, ',', NULL,
+ target));
+ }
+ RETERR(svcsortkeylist(target, used));
+ break;
+ default:
+ UNREACHABLE();
+ }
+
+ len = isc_buffer_usedlength(target) -
+ isc_buffer_usedlength(&sb) - 2;
+ RETERR(uint16_tobuffer(len, &sb)); /* length */
+ switch (sbpr[i].encoding) {
+ case sbpr_dohpath:
+ /*
+ * Apply constraints not applied by multitxt_fromtext.
+ */
+ keyregion.base = isc_buffer_used(&sb);
+ keyregion.length = isc_buffer_usedlength(target) -
+ isc_buffer_usedlength(&sb);
+ RETERR(svcb_validate(sbpr[i].value, &keyregion));
+ break;
+ default:
+ break;
+ }
+ return (ISC_R_SUCCESS);
+ }
+
+ RETERR(svc_keyfromregion(region, '=', &key, target));
+ if (region->length == 0) {
+ RETERR(uint16_tobuffer(0, target)); /* length */
+ /* Sanity check keyXXXXX form. */
+ keyregion.base = isc_buffer_used(target);
+ keyregion.length = 0;
+ return (svcb_validate(key, &keyregion));
+ }
+ sb = *target;
+ RETERR(uint16_tobuffer(0, target)); /* dummy length */
+ RETERR(multitxt_fromtext(region, target));
+ len = isc_buffer_usedlength(target) - isc_buffer_usedlength(&sb) - 2;
+ RETERR(uint16_tobuffer(len, &sb)); /* length */
+ /* Sanity check keyXXXXX form. */
+ keyregion.base = isc_buffer_used(&sb);
+ keyregion.length = len;
+ return (svcb_validate(key, &keyregion));
+}
+
+static const char *
+svcparamkey(unsigned short value, enum encoding *encoding, char *buf,
+ size_t len) {
+ size_t i;
+ int n;
+
+ for (i = 0; i < ARRAY_SIZE(sbpr); i++) {
+ if (sbpr[i].value == value && sbpr[i].initial) {
+ *encoding = sbpr[i].encoding;
+ return (sbpr[i].name);
+ }
+ }
+ n = snprintf(buf, len, "key%u", value);
+ INSIST(n > 0 && (unsigned)n < len);
+ *encoding = sbpr_text;
+ return (buf);
+}
+
+static isc_result_t
+svcsortkeys(isc_buffer_t *target, unsigned int used) {
+ isc_region_t r1, r2, man = { .base = NULL, .length = 0 };
+ unsigned char buf[1024];
+ uint16_t mankey = 0;
+ bool have_alpn = false;
+
+ if (isc_buffer_usedlength(target) == used) {
+ return (ISC_R_SUCCESS);
+ }
+
+ /*
+ * Get the parameters into r1.
+ */
+ isc_buffer_usedregion(target, &r1);
+ isc_region_consume(&r1, used);
+
+ while (1) {
+ uint16_t key1, len1, key2, len2;
+ unsigned char *base1, *base2;
+
+ r2 = r1;
+
+ /*
+ * Get the first parameter.
+ */
+ base1 = r1.base;
+ key1 = uint16_fromregion(&r1);
+ isc_region_consume(&r1, 2);
+ len1 = uint16_fromregion(&r1);
+ isc_region_consume(&r1, 2);
+ isc_region_consume(&r1, len1);
+
+ /*
+ * Was there only one key left?
+ */
+ if (r1.length == 0) {
+ if (mankey != 0) {
+ /* Is this the last mandatory key? */
+ if (key1 != mankey || man.length != 0) {
+ return (DNS_R_INCONSISTENTRR);
+ }
+ } else if (key1 == SVCB_MAN_KEY) {
+ /* Lone mandatory field. */
+ return (DNS_R_DISALLOWED);
+ } else if (key1 == SVCB_NO_DEFAULT_ALPN_KEY &&
+ !have_alpn)
+ {
+ /* Missing required ALPN field. */
+ return (DNS_R_DISALLOWED);
+ }
+ return (ISC_R_SUCCESS);
+ }
+
+ /*
+ * Find the smallest parameter.
+ */
+ while (r1.length != 0) {
+ base2 = r1.base;
+ key2 = uint16_fromregion(&r1);
+ isc_region_consume(&r1, 2);
+ len2 = uint16_fromregion(&r1);
+ isc_region_consume(&r1, 2);
+ isc_region_consume(&r1, len2);
+ if (key2 == key1) {
+ return (DNS_R_DUPLICATE);
+ }
+ if (key2 < key1) {
+ base1 = base2;
+ key1 = key2;
+ len1 = len2;
+ }
+ }
+
+ /*
+ * Do we need to move the smallest parameter to the start?
+ */
+ if (base1 != r2.base) {
+ size_t offset = 0;
+ size_t bytes = len1 + 4;
+ size_t length = base1 - r2.base;
+
+ /*
+ * Move the smallest parameter to the start.
+ */
+ while (bytes > 0) {
+ size_t count;
+
+ if (bytes > sizeof(buf)) {
+ count = sizeof(buf);
+ } else {
+ count = bytes;
+ }
+ memmove(buf, base1, count);
+ memmove(r2.base + offset + count,
+ r2.base + offset, length);
+ memmove(r2.base + offset, buf, count);
+ base1 += count;
+ bytes -= count;
+ offset += count;
+ }
+ }
+
+ /*
+ * Check ALPN is present when NO-DEFAULT-ALPN is set.
+ */
+ if (key1 == SVCB_ALPN_KEY) {
+ have_alpn = true;
+ } else if (key1 == SVCB_NO_DEFAULT_ALPN_KEY && !have_alpn) {
+ /* Missing required ALPN field. */
+ return (DNS_R_DISALLOWED);
+ }
+
+ /*
+ * Check key against mandatory key list.
+ */
+ if (mankey != 0) {
+ if (key1 > mankey) {
+ return (DNS_R_INCONSISTENTRR);
+ }
+ if (key1 == mankey) {
+ if (man.length >= 2) {
+ mankey = uint16_fromregion(&man);
+ isc_region_consume(&man, 2);
+ } else {
+ mankey = 0;
+ }
+ }
+ }
+
+ /*
+ * Is this the mandatory key?
+ */
+ if (key1 == SVCB_MAN_KEY) {
+ man = r2;
+ man.length = len1 + 4;
+ isc_region_consume(&man, 4);
+ if (man.length >= 2) {
+ mankey = uint16_fromregion(&man);
+ isc_region_consume(&man, 2);
+ if (mankey == SVCB_MAN_KEY) {
+ return (DNS_R_DISALLOWED);
+ }
+ } else {
+ return (DNS_R_SYNTAX);
+ }
+ }
+
+ /*
+ * Consume the smallest parameter.
+ */
+ isc_region_consume(&r2, len1 + 4);
+ r1 = r2;
+ }
+}
+
+static isc_result_t
+generic_fromtext_in_svcb(ARGS_FROMTEXT) {
+ isc_token_t token;
+ dns_name_t name;
+ isc_buffer_t buffer;
+ bool alias;
+ bool ok = true;
+ unsigned int used;
+
+ UNUSED(type);
+ UNUSED(rdclass);
+ UNUSED(callbacks);
+
+ /*
+ * SvcPriority.
+ */
+ RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_number,
+ false));
+ if (token.value.as_ulong > 0xffffU) {
+ RETTOK(ISC_R_RANGE);
+ }
+ RETERR(uint16_tobuffer(token.value.as_ulong, target));
+
+ alias = token.value.as_ulong == 0;
+
+ /*
+ * TargetName.
+ */
+ RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_qstring,
+ false));
+ dns_name_init(&name, NULL);
+ buffer_fromregion(&buffer, &token.value.as_region);
+ if (origin == NULL) {
+ origin = dns_rootname;
+ }
+ RETTOK(dns_name_fromtext(&name, &buffer, origin, options, target));
+ if (!alias && (options & DNS_RDATA_CHECKNAMES) != 0) {
+ ok = dns_name_ishostname(&name, false);
+ }
+ if (!ok && (options & DNS_RDATA_CHECKNAMESFAIL) != 0) {
+ RETTOK(DNS_R_BADNAME);
+ }
+ if (!ok && callbacks != NULL) {
+ warn_badname(&name, lexer, callbacks);
+ }
+
+ if (alias) {
+ return (ISC_R_SUCCESS);
+ }
+
+ /*
+ * SvcParams
+ */
+ used = isc_buffer_usedlength(target);
+ while (1) {
+ RETERR(isc_lex_getmastertoken(lexer, &token,
+ isc_tokentype_qvpair, true));
+ if (token.type == isc_tokentype_eol ||
+ token.type == isc_tokentype_eof)
+ {
+ isc_lex_ungettoken(lexer, &token);
+ return (svcsortkeys(target, used));
+ }
+
+ if (token.type != isc_tokentype_string && /* key only */
+ token.type != isc_tokentype_qvpair &&
+ token.type != isc_tokentype_vpair)
+ {
+ RETTOK(DNS_R_SYNTAX);
+ }
+ RETTOK(svc_fromtext(&token.value.as_textregion, target));
+ }
+}
+
+static isc_result_t
+fromtext_in_svcb(ARGS_FROMTEXT) {
+ REQUIRE(type == dns_rdatatype_svcb);
+ REQUIRE(rdclass == dns_rdataclass_in);
+ UNUSED(type);
+ UNUSED(rdclass);
+ UNUSED(callbacks);
+
+ return (generic_fromtext_in_svcb(CALL_FROMTEXT));
+}
+
+static isc_result_t
+generic_totext_in_svcb(ARGS_TOTEXT) {
+ isc_region_t region;
+ dns_name_t name;
+ dns_name_t prefix;
+ bool sub;
+ char buf[sizeof("xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:255.255.255.255")];
+ unsigned short num;
+ int n;
+
+ REQUIRE(rdata->length != 0);
+
+ dns_name_init(&name, NULL);
+ dns_name_init(&prefix, NULL);
+
+ dns_rdata_toregion(rdata, &region);
+
+ /*
+ * SvcPriority.
+ */
+ num = uint16_fromregion(&region);
+ isc_region_consume(&region, 2);
+ n = snprintf(buf, sizeof(buf), "%u ", num);
+ INSIST(n > 0 && (unsigned)n < sizeof(buf));
+ RETERR(str_totext(buf, target));
+
+ /*
+ * TargetName.
+ */
+ dns_name_fromregion(&name, &region);
+ isc_region_consume(&region, name_length(&name));
+ sub = name_prefix(&name, tctx->origin, &prefix);
+ RETERR(dns_name_totext(&prefix, sub, target));
+
+ while (region.length > 0) {
+ isc_region_t r;
+ enum encoding encoding;
+
+ RETERR(str_totext(" ", target));
+
+ INSIST(region.length >= 2);
+ num = uint16_fromregion(&region);
+ isc_region_consume(&region, 2);
+ RETERR(str_totext(svcparamkey(num, &encoding, buf, sizeof(buf)),
+ target));
+
+ INSIST(region.length >= 2);
+ num = uint16_fromregion(&region);
+ isc_region_consume(&region, 2);
+
+ INSIST(region.length >= num);
+ r = region;
+ r.length = num;
+ isc_region_consume(&region, num);
+ if (num == 0) {
+ continue;
+ }
+ if (encoding != sbpr_empty) {
+ RETERR(str_totext("=", target));
+ }
+ switch (encoding) {
+ case sbpr_text:
+ RETERR(multitxt_totext(&r, target));
+ break;
+ case sbpr_port:
+ num = uint16_fromregion(&r);
+ isc_region_consume(&r, 2);
+ n = snprintf(buf, sizeof(buf), "%u", num);
+ INSIST(n > 0 && (unsigned)n < sizeof(buf));
+ RETERR(str_totext(buf, target));
+ INSIST(r.length == 0U);
+ break;
+ case sbpr_ipv4s:
+ while (r.length > 0U) {
+ INSIST(r.length >= 4U);
+ inet_ntop(AF_INET, r.base, buf, sizeof(buf));
+ RETERR(str_totext(buf, target));
+ isc_region_consume(&r, 4);
+ if (r.length != 0U) {
+ RETERR(str_totext(",", target));
+ }
+ }
+ break;
+ case sbpr_ipv6s:
+ while (r.length > 0U) {
+ INSIST(r.length >= 16U);
+ inet_ntop(AF_INET6, r.base, buf, sizeof(buf));
+ RETERR(str_totext(buf, target));
+ isc_region_consume(&r, 16);
+ if (r.length != 0U) {
+ RETERR(str_totext(",", target));
+ }
+ }
+ break;
+ case sbpr_base64:
+ RETERR(isc_base64_totext(&r, 0, "", target));
+ break;
+ case sbpr_alpn:
+ INSIST(r.length != 0U);
+ RETERR(str_totext("\"", target));
+ while (r.length != 0) {
+ commatxt_totext(&r, false, true, target);
+ if (r.length != 0) {
+ RETERR(str_totext(",", target));
+ }
+ }
+ RETERR(str_totext("\"", target));
+ break;
+ case sbpr_empty:
+ INSIST(r.length == 0U);
+ break;
+ case sbpr_keylist:
+ while (r.length > 0) {
+ num = uint16_fromregion(&r);
+ isc_region_consume(&r, 2);
+ RETERR(str_totext(svcparamkey(num, &encoding,
+ buf, sizeof(buf)),
+ target));
+ if (r.length != 0) {
+ RETERR(str_totext(",", target));
+ }
+ }
+ break;
+ default:
+ UNREACHABLE();
+ }
+ }
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+totext_in_svcb(ARGS_TOTEXT) {
+ REQUIRE(rdata->type == dns_rdatatype_svcb);
+ REQUIRE(rdata->rdclass == dns_rdataclass_in);
+ REQUIRE(rdata->length != 0);
+
+ return (generic_totext_in_svcb(CALL_TOTEXT));
+}
+
+static isc_result_t
+generic_fromwire_in_svcb(ARGS_FROMWIRE) {
+ dns_name_t name;
+ isc_region_t region, man = { .base = NULL, .length = 0 };
+ bool alias, first = true, have_alpn = false;
+ uint16_t lastkey = 0, mankey = 0;
+
+ UNUSED(type);
+ UNUSED(rdclass);
+
+ dns_decompress_setmethods(dctx, DNS_COMPRESS_NONE);
+
+ dns_name_init(&name, NULL);
+
+ /*
+ * SvcPriority.
+ */
+ isc_buffer_activeregion(source, &region);
+ if (region.length < 2) {
+ return (ISC_R_UNEXPECTEDEND);
+ }
+ RETERR(mem_tobuffer(target, region.base, 2));
+ alias = uint16_fromregion(&region) == 0;
+ isc_buffer_forward(source, 2);
+
+ /*
+ * TargetName.
+ */
+ RETERR(dns_name_fromwire(&name, source, dctx, options, target));
+
+ if (alias) {
+ return (ISC_R_SUCCESS);
+ }
+
+ /*
+ * SvcParams.
+ */
+ isc_buffer_activeregion(source, &region);
+ while (region.length > 0U) {
+ isc_region_t keyregion;
+ uint16_t key, len;
+
+ /*
+ * SvcParamKey
+ */
+ if (region.length < 2U) {
+ return (ISC_R_UNEXPECTEDEND);
+ }
+ RETERR(mem_tobuffer(target, region.base, 2));
+ key = uint16_fromregion(&region);
+ isc_region_consume(&region, 2);
+
+ /*
+ * Keys must be unique and in order.
+ */
+ if (!first && key <= lastkey) {
+ return (DNS_R_FORMERR);
+ }
+
+ /*
+ * Check mandatory keys.
+ */
+ if (mankey != 0) {
+ /* Missing mandatory key? */
+ if (key > mankey) {
+ return (DNS_R_FORMERR);
+ }
+ if (key == mankey) {
+ /* Get next mandatory key. */
+ if (man.length >= 2) {
+ mankey = uint16_fromregion(&man);
+ isc_region_consume(&man, 2);
+ } else {
+ mankey = 0;
+ }
+ }
+ }
+
+ /*
+ * Check alpn present when no-default-alpn is set.
+ */
+ if (key == SVCB_ALPN_KEY) {
+ have_alpn = true;
+ } else if (key == SVCB_NO_DEFAULT_ALPN_KEY && !have_alpn) {
+ return (DNS_R_FORMERR);
+ }
+
+ first = false;
+ lastkey = key;
+
+ /*
+ * SvcParamValue length.
+ */
+ if (region.length < 2U) {
+ return (ISC_R_UNEXPECTEDEND);
+ }
+ RETERR(mem_tobuffer(target, region.base, 2));
+ len = uint16_fromregion(&region);
+ isc_region_consume(&region, 2);
+
+ /*
+ * SvcParamValue.
+ */
+ if (region.length < len) {
+ return (ISC_R_UNEXPECTEDEND);
+ }
+
+ /*
+ * Remember manatory key.
+ */
+ if (key == SVCB_MAN_KEY) {
+ man = region;
+ man.length = len;
+ /* Get first mandatory key */
+ if (man.length >= 2) {
+ mankey = uint16_fromregion(&man);
+ isc_region_consume(&man, 2);
+ if (mankey == SVCB_MAN_KEY) {
+ return (DNS_R_FORMERR);
+ }
+ } else {
+ return (DNS_R_FORMERR);
+ }
+ }
+ keyregion = region;
+ keyregion.length = len;
+ RETERR(svcb_validate(key, &keyregion));
+ RETERR(mem_tobuffer(target, region.base, len));
+ isc_region_consume(&region, len);
+ isc_buffer_forward(source, len + 4);
+ }
+
+ /*
+ * Do we have an outstanding mandatory key?
+ */
+ if (mankey != 0) {
+ return (DNS_R_FORMERR);
+ }
+
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+fromwire_in_svcb(ARGS_FROMWIRE) {
+ REQUIRE(type == dns_rdatatype_svcb);
+ REQUIRE(rdclass == dns_rdataclass_in);
+
+ return (generic_fromwire_in_svcb(CALL_FROMWIRE));
+}
+
+static isc_result_t
+generic_towire_in_svcb(ARGS_TOWIRE) {
+ dns_name_t name;
+ dns_offsets_t offsets;
+ isc_region_t region;
+
+ REQUIRE(rdata->length != 0);
+
+ dns_compress_setmethods(cctx, DNS_COMPRESS_NONE);
+
+ /*
+ * SvcPriority.
+ */
+ dns_rdata_toregion(rdata, &region);
+ RETERR(mem_tobuffer(target, region.base, 2));
+ isc_region_consume(&region, 2);
+
+ /*
+ * TargetName.
+ */
+ dns_name_init(&name, offsets);
+ dns_name_fromregion(&name, &region);
+ RETERR(dns_name_towire(&name, cctx, target));
+ isc_region_consume(&region, name_length(&name));
+
+ /*
+ * SvcParams.
+ */
+ return (mem_tobuffer(target, region.base, region.length));
+}
+
+static isc_result_t
+towire_in_svcb(ARGS_TOWIRE) {
+ REQUIRE(rdata->type == dns_rdatatype_svcb);
+ REQUIRE(rdata->length != 0);
+
+ return (generic_towire_in_svcb(CALL_TOWIRE));
+}
+
+static int
+compare_in_svcb(ARGS_COMPARE) {
+ isc_region_t region1;
+ isc_region_t region2;
+
+ REQUIRE(rdata1->type == rdata2->type);
+ REQUIRE(rdata1->rdclass == rdata2->rdclass);
+ REQUIRE(rdata1->type == dns_rdatatype_svcb);
+ REQUIRE(rdata1->rdclass == dns_rdataclass_in);
+ REQUIRE(rdata1->length != 0);
+ REQUIRE(rdata2->length != 0);
+
+ dns_rdata_toregion(rdata1, &region1);
+ dns_rdata_toregion(rdata2, &region2);
+
+ return (isc_region_compare(&region1, &region2));
+}
+
+static isc_result_t
+generic_fromstruct_in_svcb(ARGS_FROMSTRUCT) {
+ dns_rdata_in_svcb_t *svcb = source;
+ isc_region_t region;
+
+ REQUIRE(svcb != NULL);
+ REQUIRE(svcb->common.rdtype == type);
+ REQUIRE(svcb->common.rdclass == rdclass);
+
+ UNUSED(type);
+ UNUSED(rdclass);
+
+ RETERR(uint16_tobuffer(svcb->priority, target));
+ dns_name_toregion(&svcb->svcdomain, &region);
+ RETERR(isc_buffer_copyregion(target, &region));
+
+ return (mem_tobuffer(target, svcb->svc, svcb->svclen));
+}
+
+static isc_result_t
+fromstruct_in_svcb(ARGS_FROMSTRUCT) {
+ dns_rdata_in_svcb_t *svcb = source;
+
+ REQUIRE(type == dns_rdatatype_svcb);
+ REQUIRE(rdclass == dns_rdataclass_in);
+ REQUIRE(svcb != NULL);
+ REQUIRE(svcb->common.rdtype == type);
+ REQUIRE(svcb->common.rdclass == rdclass);
+
+ return (generic_fromstruct_in_svcb(CALL_FROMSTRUCT));
+}
+
+static isc_result_t
+generic_tostruct_in_svcb(ARGS_TOSTRUCT) {
+ isc_region_t region;
+ dns_rdata_in_svcb_t *svcb = target;
+ dns_name_t name;
+
+ REQUIRE(svcb != NULL);
+ REQUIRE(rdata->length != 0);
+
+ svcb->common.rdclass = rdata->rdclass;
+ svcb->common.rdtype = rdata->type;
+ ISC_LINK_INIT(&svcb->common, link);
+
+ dns_rdata_toregion(rdata, &region);
+
+ svcb->priority = uint16_fromregion(&region);
+ isc_region_consume(&region, 2);
+
+ dns_name_init(&svcb->svcdomain, NULL);
+ dns_name_init(&name, NULL);
+ dns_name_fromregion(&name, &region);
+ isc_region_consume(&region, name_length(&name));
+
+ name_duporclone(&name, mctx, &svcb->svcdomain);
+ svcb->svclen = region.length;
+ svcb->svc = mem_maybedup(mctx, region.base, region.length);
+
+ if (svcb->svc == NULL) {
+ if (mctx != NULL) {
+ dns_name_free(&svcb->svcdomain, svcb->mctx);
+ }
+ return (ISC_R_NOMEMORY);
+ }
+
+ svcb->offset = 0;
+ svcb->mctx = mctx;
+
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+tostruct_in_svcb(ARGS_TOSTRUCT) {
+ dns_rdata_in_svcb_t *svcb = target;
+
+ REQUIRE(rdata->rdclass == dns_rdataclass_in);
+ REQUIRE(rdata->type == dns_rdatatype_svcb);
+ REQUIRE(svcb != NULL);
+ REQUIRE(rdata->length != 0);
+
+ return (generic_tostruct_in_svcb(CALL_TOSTRUCT));
+}
+
+static void
+generic_freestruct_in_svcb(ARGS_FREESTRUCT) {
+ dns_rdata_in_svcb_t *svcb = source;
+
+ REQUIRE(svcb != NULL);
+
+ if (svcb->mctx == NULL) {
+ return;
+ }
+
+ dns_name_free(&svcb->svcdomain, svcb->mctx);
+ isc_mem_free(svcb->mctx, svcb->svc);
+ svcb->mctx = NULL;
+}
+
+static void
+freestruct_in_svcb(ARGS_FREESTRUCT) {
+ dns_rdata_in_svcb_t *svcb = source;
+
+ REQUIRE(svcb != NULL);
+ REQUIRE(svcb->common.rdclass == dns_rdataclass_in);
+ REQUIRE(svcb->common.rdtype == dns_rdatatype_svcb);
+
+ generic_freestruct_in_svcb(CALL_FREESTRUCT);
+}
+
+static isc_result_t
+generic_additionaldata_in_svcb(ARGS_ADDLDATA) {
+ bool alias, done = false;
+ dns_fixedname_t fixed;
+ dns_name_t name, *fname = NULL;
+ dns_offsets_t offsets;
+ dns_rdataset_t rdataset;
+ isc_region_t region;
+ unsigned int cnames = 0;
+
+ dns_name_init(&name, offsets);
+ dns_rdata_toregion(rdata, &region);
+ alias = uint16_fromregion(&region) == 0;
+ isc_region_consume(&region, 2);
+
+ dns_name_fromregion(&name, &region);
+
+ if (dns_name_equal(&name, dns_rootname)) {
+ /*
+ * "." only means owner name in service form.
+ */
+ if (alias || dns_name_equal(owner, dns_rootname) ||
+ !dns_name_ishostname(owner, false))
+ {
+ return (ISC_R_SUCCESS);
+ }
+ /* Only lookup address records */
+ return ((add)(arg, owner, dns_rdatatype_a, NULL));
+ }
+
+ /*
+ * Follow CNAME chains when processing HTTPS and SVCB records.
+ */
+ dns_rdataset_init(&rdataset);
+ fname = dns_fixedname_initname(&fixed);
+ do {
+ RETERR((add)(arg, &name, dns_rdatatype_cname, &rdataset));
+ if (dns_rdataset_isassociated(&rdataset)) {
+ isc_result_t result;
+ result = dns_rdataset_first(&rdataset);
+ if (result == ISC_R_SUCCESS) {
+ dns_rdata_t current = DNS_RDATA_INIT;
+ dns_rdata_cname_t cname;
+
+ dns_rdataset_current(&rdataset, &current);
+
+ result = dns_rdata_tostruct(&current, &cname,
+ NULL);
+ RUNTIME_CHECK(result == ISC_R_SUCCESS);
+ dns_name_copy(&cname.cname, fname);
+ dns_name_clone(fname, &name);
+ } else {
+ done = true;
+ }
+ dns_rdataset_disassociate(&rdataset);
+ } else {
+ done = true;
+ }
+ /*
+ * Stop following a potentially infinite CNAME chain.
+ */
+ if (!done && cnames++ > MAX_CNAMES) {
+ return (ISC_R_SUCCESS);
+ }
+ } while (!done);
+
+ /*
+ * Look up HTTPS/SVCB records when processing the alias form.
+ */
+ if (alias) {
+ RETERR((add)(arg, &name, rdata->type, &rdataset));
+ /*
+ * Don't return A or AAAA if this is not the last element
+ * in the HTTP / SVCB chain.
+ */
+ if (dns_rdataset_isassociated(&rdataset)) {
+ dns_rdataset_disassociate(&rdataset);
+ return (ISC_R_SUCCESS);
+ }
+ }
+ return ((add)(arg, &name, dns_rdatatype_a, NULL));
+}
+
+static isc_result_t
+additionaldata_in_svcb(ARGS_ADDLDATA) {
+ REQUIRE(rdata->type == dns_rdatatype_svcb);
+ REQUIRE(rdata->rdclass == dns_rdataclass_in);
+
+ return (generic_additionaldata_in_svcb(CALL_ADDLDATA));
+}
+
+static isc_result_t
+digest_in_svcb(ARGS_DIGEST) {
+ isc_region_t region1;
+
+ REQUIRE(rdata->type == dns_rdatatype_svcb);
+ REQUIRE(rdata->rdclass == dns_rdataclass_in);
+
+ dns_rdata_toregion(rdata, &region1);
+ return ((digest)(arg, &region1));
+}
+
+static bool
+checkowner_in_svcb(ARGS_CHECKOWNER) {
+ REQUIRE(type == dns_rdatatype_svcb);
+ REQUIRE(rdclass == dns_rdataclass_in);
+
+ UNUSED(name);
+ UNUSED(type);
+ UNUSED(rdclass);
+ UNUSED(wildcard);
+
+ return (true);
+}
+
+static bool
+generic_checknames_in_svcb(ARGS_CHECKNAMES) {
+ isc_region_t region;
+ dns_name_t name;
+ bool alias;
+
+ UNUSED(owner);
+
+ dns_rdata_toregion(rdata, &region);
+ INSIST(region.length > 1);
+ alias = uint16_fromregion(&region) == 0;
+ isc_region_consume(&region, 2);
+ dns_name_init(&name, NULL);
+ dns_name_fromregion(&name, &region);
+ if (!alias && !dns_name_ishostname(&name, false)) {
+ if (bad != NULL) {
+ dns_name_clone(&name, bad);
+ }
+ return (false);
+ }
+ return (true);
+}
+
+static bool
+checknames_in_svcb(ARGS_CHECKNAMES) {
+ REQUIRE(rdata->type == dns_rdatatype_svcb);
+ REQUIRE(rdata->rdclass == dns_rdataclass_in);
+
+ return (generic_checknames_in_svcb(CALL_CHECKNAMES));
+}
+
+static int
+casecompare_in_svcb(ARGS_COMPARE) {
+ return (compare_in_svcb(rdata1, rdata2));
+}
+
+static isc_result_t
+generic_rdata_in_svcb_first(dns_rdata_in_svcb_t *svcb) {
+ if (svcb->svclen == 0) {
+ return (ISC_R_NOMORE);
+ }
+ svcb->offset = 0;
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+generic_rdata_in_svcb_next(dns_rdata_in_svcb_t *svcb) {
+ isc_region_t region;
+ size_t len;
+
+ if (svcb->offset >= svcb->svclen) {
+ return (ISC_R_NOMORE);
+ }
+
+ region.base = svcb->svc + svcb->offset;
+ region.length = svcb->svclen - svcb->offset;
+ INSIST(region.length >= 4);
+ isc_region_consume(&region, 2);
+ len = uint16_fromregion(&region);
+ INSIST(region.length >= len + 2);
+ svcb->offset += len + 4;
+ return (svcb->offset >= svcb->svclen ? ISC_R_NOMORE : ISC_R_SUCCESS);
+}
+
+static void
+generic_rdata_in_svcb_current(dns_rdata_in_svcb_t *svcb, isc_region_t *region) {
+ size_t len;
+
+ INSIST(svcb->offset <= svcb->svclen);
+
+ region->base = svcb->svc + svcb->offset;
+ region->length = svcb->svclen - svcb->offset;
+ INSIST(region->length >= 4);
+ isc_region_consume(region, 2);
+ len = uint16_fromregion(region);
+ INSIST(region->length >= len + 2);
+ region->base = svcb->svc + svcb->offset;
+ region->length = len + 4;
+}
+
+isc_result_t
+dns_rdata_in_svcb_first(dns_rdata_in_svcb_t *svcb) {
+ REQUIRE(svcb != NULL);
+ REQUIRE(svcb->common.rdtype == dns_rdatatype_svcb);
+ REQUIRE(svcb->common.rdclass == dns_rdataclass_in);
+
+ return (generic_rdata_in_svcb_first(svcb));
+}
+
+isc_result_t
+dns_rdata_in_svcb_next(dns_rdata_in_svcb_t *svcb) {
+ REQUIRE(svcb != NULL);
+ REQUIRE(svcb->common.rdtype == dns_rdatatype_svcb);
+ REQUIRE(svcb->common.rdclass == dns_rdataclass_in);
+
+ return (generic_rdata_in_svcb_next(svcb));
+}
+
+void
+dns_rdata_in_svcb_current(dns_rdata_in_svcb_t *svcb, isc_region_t *region) {
+ REQUIRE(svcb != NULL);
+ REQUIRE(svcb->common.rdtype == dns_rdatatype_svcb);
+ REQUIRE(svcb->common.rdclass == dns_rdataclass_in);
+ REQUIRE(region != NULL);
+
+ generic_rdata_in_svcb_current(svcb, region);
+}
+
+#endif /* RDATA_IN_1_SVCB_64_C */
diff --git a/lib/dns/rdata/in_1/svcb_64.h b/lib/dns/rdata/in_1/svcb_64.h
new file mode 100644
index 0000000..c861716
--- /dev/null
+++ b/lib/dns/rdata/in_1/svcb_64.h
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#pragma once
+
+/*!
+ * \brief Per draft-ietf-dnsop-svcb-https-02
+ */
+
+typedef struct dns_rdata_in_svcb {
+ dns_rdatacommon_t common;
+ isc_mem_t *mctx;
+ uint16_t priority;
+ dns_name_t svcdomain;
+ unsigned char *svc;
+ uint16_t svclen;
+ uint16_t offset;
+} dns_rdata_in_svcb_t;
+
+isc_result_t
+dns_rdata_in_svcb_first(dns_rdata_in_svcb_t *);
+
+isc_result_t
+dns_rdata_in_svcb_next(dns_rdata_in_svcb_t *);
+
+void
+dns_rdata_in_svcb_current(dns_rdata_in_svcb_t *, isc_region_t *);
diff --git a/lib/dns/rdata/in_1/wks_11.c b/lib/dns/rdata/in_1/wks_11.c
new file mode 100644
index 0000000..7252065
--- /dev/null
+++ b/lib/dns/rdata/in_1/wks_11.c
@@ -0,0 +1,404 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#ifndef RDATA_IN_1_WKS_11_C
+#define RDATA_IN_1_WKS_11_C
+
+#include <limits.h>
+#include <stdlib.h>
+
+#include <isc/net.h>
+#include <isc/netdb.h>
+#include <isc/once.h>
+
+#define RRTYPE_WKS_ATTRIBUTES (0)
+
+static isc_mutex_t wks_lock;
+
+static void
+init_lock(void) {
+ isc_mutex_init(&wks_lock);
+}
+
+static bool
+mygetprotobyname(const char *name, long *proto) {
+ struct protoent *pe;
+
+ LOCK(&wks_lock);
+ pe = getprotobyname(name);
+ if (pe != NULL) {
+ *proto = pe->p_proto;
+ }
+ UNLOCK(&wks_lock);
+ return (pe != NULL);
+}
+
+static bool
+mygetservbyname(const char *name, const char *proto, long *port) {
+ struct servent *se;
+
+ LOCK(&wks_lock);
+ se = getservbyname(name, proto);
+ if (se != NULL) {
+ *port = ntohs(se->s_port);
+ }
+ UNLOCK(&wks_lock);
+ return (se != NULL);
+}
+
+static isc_result_t
+fromtext_in_wks(ARGS_FROMTEXT) {
+ static isc_once_t once = ISC_ONCE_INIT;
+ isc_token_t token;
+ isc_region_t region;
+ struct in_addr addr;
+ char *e = NULL;
+ long proto;
+ unsigned char bm[8 * 1024]; /* 64k bits */
+ long port;
+ long maxport = -1;
+ const char *ps = NULL;
+ unsigned int n;
+ char service[32];
+ int i;
+ isc_result_t result;
+
+ REQUIRE(type == dns_rdatatype_wks);
+ REQUIRE(rdclass == dns_rdataclass_in);
+
+ UNUSED(type);
+ UNUSED(origin);
+ UNUSED(options);
+ UNUSED(rdclass);
+ UNUSED(callbacks);
+
+ RUNTIME_CHECK(isc_once_do(&once, init_lock) == ISC_R_SUCCESS);
+
+ /*
+ * IPv4 dotted quad.
+ */
+ CHECK(isc_lex_getmastertoken(lexer, &token, isc_tokentype_string,
+ false));
+
+ isc_buffer_availableregion(target, &region);
+ if (inet_pton(AF_INET, DNS_AS_STR(token), &addr) != 1) {
+ CHECKTOK(DNS_R_BADDOTTEDQUAD);
+ }
+ if (region.length < 4) {
+ return (ISC_R_NOSPACE);
+ }
+ memmove(region.base, &addr, 4);
+ isc_buffer_add(target, 4);
+
+ /*
+ * Protocol.
+ */
+ CHECK(isc_lex_getmastertoken(lexer, &token, isc_tokentype_string,
+ false));
+
+ proto = strtol(DNS_AS_STR(token), &e, 10);
+ if (*e != '\0' && !mygetprotobyname(DNS_AS_STR(token), &proto)) {
+ CHECKTOK(DNS_R_UNKNOWNPROTO);
+ }
+
+ if (proto < 0 || proto > 0xff) {
+ CHECKTOK(ISC_R_RANGE);
+ }
+
+ if (proto == IPPROTO_TCP) {
+ ps = "tcp";
+ } else if (proto == IPPROTO_UDP) {
+ ps = "udp";
+ }
+
+ CHECK(uint8_tobuffer(proto, target));
+
+ memset(bm, 0, sizeof(bm));
+ do {
+ CHECK(isc_lex_getmastertoken(lexer, &token,
+ isc_tokentype_string, true));
+ if (token.type != isc_tokentype_string) {
+ break;
+ }
+
+ /*
+ * Lowercase the service string as some getservbyname() are
+ * case sensitive and the database is usually in lowercase.
+ */
+ strlcpy(service, DNS_AS_STR(token), sizeof(service));
+ for (i = strlen(service) - 1; i >= 0; i--) {
+ if (isupper(service[i] & 0xff)) {
+ service[i] = tolower(service[i] & 0xff);
+ }
+ }
+
+ port = strtol(DNS_AS_STR(token), &e, 10);
+ if (*e != 0 && !mygetservbyname(service, ps, &port) &&
+ !mygetservbyname(DNS_AS_STR(token), ps, &port))
+ {
+ CHECKTOK(DNS_R_UNKNOWNSERVICE);
+ }
+ if (port < 0 || port > 0xffff) {
+ CHECKTOK(ISC_R_RANGE);
+ }
+ if (port > maxport) {
+ maxport = port;
+ }
+ bm[port / 8] |= (0x80 >> (port % 8));
+ } while (1);
+
+ /*
+ * Let upper layer handle eol/eof.
+ */
+ isc_lex_ungettoken(lexer, &token);
+
+ n = (maxport + 8) / 8;
+ result = mem_tobuffer(target, bm, n);
+
+cleanup:
+ return (result);
+}
+
+static isc_result_t
+totext_in_wks(ARGS_TOTEXT) {
+ isc_region_t sr;
+ unsigned short proto;
+ char buf[sizeof("65535")];
+ unsigned int i, j;
+
+ UNUSED(tctx);
+
+ REQUIRE(rdata->type == dns_rdatatype_wks);
+ REQUIRE(rdata->rdclass == dns_rdataclass_in);
+ REQUIRE(rdata->length >= 5);
+
+ dns_rdata_toregion(rdata, &sr);
+ RETERR(inet_totext(AF_INET, tctx->flags, &sr, target));
+ isc_region_consume(&sr, 4);
+
+ proto = uint8_fromregion(&sr);
+ snprintf(buf, sizeof(buf), "%u", proto);
+ RETERR(str_totext(" ", target));
+ RETERR(str_totext(buf, target));
+ isc_region_consume(&sr, 1);
+
+ INSIST(sr.length <= 8 * 1024);
+ for (i = 0; i < sr.length; i++) {
+ if (sr.base[i] != 0) {
+ for (j = 0; j < 8; j++) {
+ if ((sr.base[i] & (0x80 >> j)) != 0) {
+ {
+ snprintf(buf, sizeof(buf), "%u",
+ i * 8 + j);
+ RETERR(str_totext(" ", target));
+ RETERR(str_totext(buf, target));
+ }
+ }
+ }
+ }
+ }
+
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+fromwire_in_wks(ARGS_FROMWIRE) {
+ isc_region_t sr;
+ isc_region_t tr;
+
+ REQUIRE(type == dns_rdatatype_wks);
+ REQUIRE(rdclass == dns_rdataclass_in);
+
+ UNUSED(type);
+ UNUSED(dctx);
+ UNUSED(options);
+ UNUSED(rdclass);
+
+ isc_buffer_activeregion(source, &sr);
+ isc_buffer_availableregion(target, &tr);
+
+ if (sr.length < 5) {
+ return (ISC_R_UNEXPECTEDEND);
+ }
+ if (sr.length > 8 * 1024 + 5) {
+ return (DNS_R_EXTRADATA);
+ }
+ if (sr.length > 5 && sr.base[sr.length - 1] == 0) {
+ return (DNS_R_FORMERR);
+ }
+ if (tr.length < sr.length) {
+ return (ISC_R_NOSPACE);
+ }
+
+ memmove(tr.base, sr.base, sr.length);
+ isc_buffer_add(target, sr.length);
+ isc_buffer_forward(source, sr.length);
+
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+towire_in_wks(ARGS_TOWIRE) {
+ isc_region_t sr;
+
+ UNUSED(cctx);
+
+ REQUIRE(rdata->type == dns_rdatatype_wks);
+ REQUIRE(rdata->rdclass == dns_rdataclass_in);
+ REQUIRE(rdata->length != 0);
+
+ dns_rdata_toregion(rdata, &sr);
+ return (mem_tobuffer(target, sr.base, sr.length));
+}
+
+static int
+compare_in_wks(ARGS_COMPARE) {
+ isc_region_t r1;
+ isc_region_t r2;
+
+ REQUIRE(rdata1->type == rdata2->type);
+ REQUIRE(rdata1->rdclass == rdata2->rdclass);
+ REQUIRE(rdata1->type == dns_rdatatype_wks);
+ REQUIRE(rdata1->rdclass == dns_rdataclass_in);
+ REQUIRE(rdata1->length != 0);
+ REQUIRE(rdata2->length != 0);
+
+ dns_rdata_toregion(rdata1, &r1);
+ dns_rdata_toregion(rdata2, &r2);
+ return (isc_region_compare(&r1, &r2));
+}
+
+static isc_result_t
+fromstruct_in_wks(ARGS_FROMSTRUCT) {
+ dns_rdata_in_wks_t *wks = source;
+ uint32_t a;
+
+ REQUIRE(type == dns_rdatatype_wks);
+ REQUIRE(rdclass == dns_rdataclass_in);
+ REQUIRE(wks != NULL);
+ REQUIRE(wks->common.rdtype == type);
+ REQUIRE(wks->common.rdclass == rdclass);
+ REQUIRE((wks->map != NULL && wks->map_len <= 8 * 1024) ||
+ wks->map_len == 0);
+
+ UNUSED(type);
+ UNUSED(rdclass);
+
+ a = ntohl(wks->in_addr.s_addr);
+ RETERR(uint32_tobuffer(a, target));
+ RETERR(uint8_tobuffer(wks->protocol, target));
+ return (mem_tobuffer(target, wks->map, wks->map_len));
+}
+
+static isc_result_t
+tostruct_in_wks(ARGS_TOSTRUCT) {
+ dns_rdata_in_wks_t *wks = target;
+ uint32_t n;
+ isc_region_t region;
+
+ REQUIRE(wks != NULL);
+ REQUIRE(rdata->type == dns_rdatatype_wks);
+ REQUIRE(rdata->rdclass == dns_rdataclass_in);
+ REQUIRE(rdata->length != 0);
+
+ wks->common.rdclass = rdata->rdclass;
+ wks->common.rdtype = rdata->type;
+ ISC_LINK_INIT(&wks->common, link);
+
+ dns_rdata_toregion(rdata, &region);
+ n = uint32_fromregion(&region);
+ wks->in_addr.s_addr = htonl(n);
+ isc_region_consume(&region, 4);
+ wks->protocol = uint8_fromregion(&region);
+ isc_region_consume(&region, 1);
+ wks->map_len = region.length;
+ wks->map = mem_maybedup(mctx, region.base, region.length);
+ if (wks->map == NULL) {
+ return (ISC_R_NOMEMORY);
+ }
+ wks->mctx = mctx;
+ return (ISC_R_SUCCESS);
+}
+
+static void
+freestruct_in_wks(ARGS_FREESTRUCT) {
+ dns_rdata_in_wks_t *wks = source;
+
+ REQUIRE(wks != NULL);
+ REQUIRE(wks->common.rdtype == dns_rdatatype_wks);
+ REQUIRE(wks->common.rdclass == dns_rdataclass_in);
+
+ if (wks->mctx == NULL) {
+ return;
+ }
+
+ if (wks->map != NULL) {
+ isc_mem_free(wks->mctx, wks->map);
+ }
+ wks->mctx = NULL;
+}
+
+static isc_result_t
+additionaldata_in_wks(ARGS_ADDLDATA) {
+ REQUIRE(rdata->type == dns_rdatatype_wks);
+ REQUIRE(rdata->rdclass == dns_rdataclass_in);
+
+ UNUSED(rdata);
+ UNUSED(owner);
+ UNUSED(add);
+ UNUSED(arg);
+
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+digest_in_wks(ARGS_DIGEST) {
+ isc_region_t r;
+
+ REQUIRE(rdata->type == dns_rdatatype_wks);
+ REQUIRE(rdata->rdclass == dns_rdataclass_in);
+
+ dns_rdata_toregion(rdata, &r);
+
+ return ((digest)(arg, &r));
+}
+
+static bool
+checkowner_in_wks(ARGS_CHECKOWNER) {
+ REQUIRE(type == dns_rdatatype_wks);
+ REQUIRE(rdclass == dns_rdataclass_in);
+
+ UNUSED(type);
+ UNUSED(rdclass);
+
+ return (dns_name_ishostname(name, wildcard));
+}
+
+static bool
+checknames_in_wks(ARGS_CHECKNAMES) {
+ REQUIRE(rdata->type == dns_rdatatype_wks);
+ REQUIRE(rdata->rdclass == dns_rdataclass_in);
+
+ UNUSED(rdata);
+ UNUSED(owner);
+ UNUSED(bad);
+
+ return (true);
+}
+
+static int
+casecompare_in_wks(ARGS_COMPARE) {
+ return (compare_in_wks(rdata1, rdata2));
+}
+
+#endif /* RDATA_IN_1_WKS_11_C */
diff --git a/lib/dns/rdata/in_1/wks_11.h b/lib/dns/rdata/in_1/wks_11.h
new file mode 100644
index 0000000..8863725
--- /dev/null
+++ b/lib/dns/rdata/in_1/wks_11.h
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#pragma once
+
+typedef struct dns_rdata_in_wks {
+ dns_rdatacommon_t common;
+ isc_mem_t *mctx;
+ struct in_addr in_addr;
+ uint16_t protocol;
+ unsigned char *map;
+ uint16_t map_len;
+} dns_rdata_in_wks_t;
diff --git a/lib/dns/rdata/rdatastructpre.h b/lib/dns/rdata/rdatastructpre.h
new file mode 100644
index 0000000..4fe31b1
--- /dev/null
+++ b/lib/dns/rdata/rdatastructpre.h
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#pragma once
+
+#include <isc/lang.h>
+#include <isc/sockaddr.h>
+
+#include <dns/name.h>
+#include <dns/types.h>
+
+ISC_LANG_BEGINDECLS
+
+typedef struct dns_rdatacommon {
+ dns_rdataclass_t rdclass;
+ dns_rdatatype_t rdtype;
+ ISC_LINK(struct dns_rdatacommon) link;
+} dns_rdatacommon_t;
+
+#define DNS_RDATACOMMON_INIT(_data, _rdtype, _rdclass) \
+ do { \
+ (_data)->common.rdtype = (_rdtype); \
+ (_data)->common.rdclass = (_rdclass); \
+ ISC_LINK_INIT(&(_data)->common, link); \
+ } while (0)
diff --git a/lib/dns/rdata/rdatastructsuf.h b/lib/dns/rdata/rdatastructsuf.h
new file mode 100644
index 0000000..c3ec424
--- /dev/null
+++ b/lib/dns/rdata/rdatastructsuf.h
@@ -0,0 +1,14 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+ISC_LANG_ENDDECLS
diff --git a/lib/dns/rdatalist.c b/lib/dns/rdatalist.c
new file mode 100644
index 0000000..a2643c7
--- /dev/null
+++ b/lib/dns/rdatalist.c
@@ -0,0 +1,448 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/*! \file */
+
+#include <stddef.h>
+#include <string.h>
+
+#include <isc/util.h>
+
+#include <dns/name.h>
+#include <dns/nsec3.h>
+#include <dns/rdata.h>
+#include <dns/rdatalist.h>
+#include <dns/rdataset.h>
+
+#include "rdatalist_p.h"
+
+static dns_rdatasetmethods_t methods = {
+ isc__rdatalist_disassociate,
+ isc__rdatalist_first,
+ isc__rdatalist_next,
+ isc__rdatalist_current,
+ isc__rdatalist_clone,
+ isc__rdatalist_count,
+ isc__rdatalist_addnoqname,
+ isc__rdatalist_getnoqname,
+ isc__rdatalist_addclosest,
+ isc__rdatalist_getclosest,
+ NULL, /* settrust */
+ NULL, /* expire */
+ NULL, /* clearprefetch */
+ isc__rdatalist_setownercase,
+ isc__rdatalist_getownercase,
+ NULL /* addglue */
+};
+
+void
+dns_rdatalist_init(dns_rdatalist_t *rdatalist) {
+ REQUIRE(rdatalist != NULL);
+
+ /*
+ * Initialize rdatalist.
+ */
+
+ rdatalist->rdclass = 0;
+ rdatalist->type = 0;
+ rdatalist->covers = 0;
+ rdatalist->ttl = 0;
+ ISC_LIST_INIT(rdatalist->rdata);
+ ISC_LINK_INIT(rdatalist, link);
+ memset(rdatalist->upper, 0xeb, sizeof(rdatalist->upper));
+ /*
+ * Clear upper set bit.
+ */
+ rdatalist->upper[0] &= ~0x01;
+}
+
+isc_result_t
+dns_rdatalist_tordataset(dns_rdatalist_t *rdatalist, dns_rdataset_t *rdataset) {
+ /*
+ * Make 'rdataset' refer to the rdata in 'rdatalist'.
+ */
+
+ REQUIRE(rdatalist != NULL);
+ REQUIRE(DNS_RDATASET_VALID(rdataset));
+ REQUIRE(!dns_rdataset_isassociated(rdataset));
+
+ /* Check if dns_rdatalist_init has was called. */
+ REQUIRE(rdatalist->upper[0] == 0xea);
+
+ rdataset->methods = &methods;
+ rdataset->rdclass = rdatalist->rdclass;
+ rdataset->type = rdatalist->type;
+ rdataset->covers = rdatalist->covers;
+ rdataset->ttl = rdatalist->ttl;
+ rdataset->trust = 0;
+ rdataset->private1 = rdatalist;
+ rdataset->private2 = NULL;
+ rdataset->private3 = NULL;
+ rdataset->privateuint4 = 0;
+ rdataset->private5 = NULL;
+
+ return (ISC_R_SUCCESS);
+}
+
+isc_result_t
+dns_rdatalist_fromrdataset(dns_rdataset_t *rdataset,
+ dns_rdatalist_t **rdatalist) {
+ REQUIRE(rdatalist != NULL && rdataset != NULL);
+ *rdatalist = rdataset->private1;
+
+ return (ISC_R_SUCCESS);
+}
+
+void
+isc__rdatalist_disassociate(dns_rdataset_t *rdataset) {
+ UNUSED(rdataset);
+}
+
+isc_result_t
+isc__rdatalist_first(dns_rdataset_t *rdataset) {
+ dns_rdatalist_t *rdatalist;
+
+ rdatalist = rdataset->private1;
+ rdataset->private2 = ISC_LIST_HEAD(rdatalist->rdata);
+
+ if (rdataset->private2 == NULL) {
+ return (ISC_R_NOMORE);
+ }
+
+ return (ISC_R_SUCCESS);
+}
+
+isc_result_t
+isc__rdatalist_next(dns_rdataset_t *rdataset) {
+ dns_rdata_t *rdata;
+
+ REQUIRE(rdataset != NULL);
+
+ rdata = rdataset->private2;
+ if (rdata == NULL) {
+ return (ISC_R_NOMORE);
+ }
+
+ rdataset->private2 = ISC_LIST_NEXT(rdata, link);
+
+ if (rdataset->private2 == NULL) {
+ return (ISC_R_NOMORE);
+ }
+
+ return (ISC_R_SUCCESS);
+}
+
+void
+isc__rdatalist_current(dns_rdataset_t *rdataset, dns_rdata_t *rdata) {
+ dns_rdata_t *list_rdata;
+
+ REQUIRE(rdataset != NULL);
+
+ list_rdata = rdataset->private2;
+ INSIST(list_rdata != NULL);
+
+ dns_rdata_clone(list_rdata, rdata);
+}
+
+void
+isc__rdatalist_clone(dns_rdataset_t *source, dns_rdataset_t *target) {
+ REQUIRE(source != NULL);
+ REQUIRE(target != NULL);
+
+ *target = *source;
+
+ /*
+ * Reset iterator state.
+ */
+ target->private2 = NULL;
+}
+
+unsigned int
+isc__rdatalist_count(dns_rdataset_t *rdataset) {
+ dns_rdatalist_t *rdatalist;
+ dns_rdata_t *rdata;
+ unsigned int count;
+
+ REQUIRE(rdataset != NULL);
+
+ rdatalist = rdataset->private1;
+
+ count = 0;
+ for (rdata = ISC_LIST_HEAD(rdatalist->rdata); rdata != NULL;
+ rdata = ISC_LIST_NEXT(rdata, link))
+ {
+ count++;
+ }
+
+ return (count);
+}
+
+isc_result_t
+isc__rdatalist_addnoqname(dns_rdataset_t *rdataset, const dns_name_t *name) {
+ dns_rdataset_t *neg = NULL;
+ dns_rdataset_t *negsig = NULL;
+ dns_rdataset_t *rdset;
+ dns_ttl_t ttl;
+
+ REQUIRE(rdataset != NULL);
+
+ for (rdset = ISC_LIST_HEAD(name->list); rdset != NULL;
+ rdset = ISC_LIST_NEXT(rdset, link))
+ {
+ if (rdset->rdclass != rdataset->rdclass) {
+ continue;
+ }
+ if (rdset->type == dns_rdatatype_nsec ||
+ rdset->type == dns_rdatatype_nsec3)
+ {
+ neg = rdset;
+ }
+ }
+ if (neg == NULL) {
+ return (ISC_R_NOTFOUND);
+ }
+
+ for (rdset = ISC_LIST_HEAD(name->list); rdset != NULL;
+ rdset = ISC_LIST_NEXT(rdset, link))
+ {
+ if (rdset->type == dns_rdatatype_rrsig &&
+ rdset->covers == neg->type)
+ {
+ negsig = rdset;
+ }
+ }
+
+ if (negsig == NULL) {
+ return (ISC_R_NOTFOUND);
+ }
+ /*
+ * Minimise ttl.
+ */
+ ttl = rdataset->ttl;
+ if (neg->ttl < ttl) {
+ ttl = neg->ttl;
+ }
+ if (negsig->ttl < ttl) {
+ ttl = negsig->ttl;
+ }
+ rdataset->ttl = neg->ttl = negsig->ttl = ttl;
+ rdataset->attributes |= DNS_RDATASETATTR_NOQNAME;
+ rdataset->private6 = name;
+ return (ISC_R_SUCCESS);
+}
+
+isc_result_t
+isc__rdatalist_getnoqname(dns_rdataset_t *rdataset, dns_name_t *name,
+ dns_rdataset_t *neg, dns_rdataset_t *negsig) {
+ dns_rdataclass_t rdclass;
+ dns_rdataset_t *tneg = NULL;
+ dns_rdataset_t *tnegsig = NULL;
+ const dns_name_t *noqname;
+
+ REQUIRE(rdataset != NULL);
+ REQUIRE((rdataset->attributes & DNS_RDATASETATTR_NOQNAME) != 0);
+
+ rdclass = rdataset->rdclass;
+ noqname = rdataset->private6;
+
+ (void)dns_name_dynamic(noqname); /* Sanity Check. */
+
+ for (rdataset = ISC_LIST_HEAD(noqname->list); rdataset != NULL;
+ rdataset = ISC_LIST_NEXT(rdataset, link))
+ {
+ if (rdataset->rdclass != rdclass) {
+ continue;
+ }
+ if (rdataset->type == dns_rdatatype_nsec ||
+ rdataset->type == dns_rdatatype_nsec3)
+ {
+ tneg = rdataset;
+ }
+ }
+ if (tneg == NULL) {
+ return (ISC_R_NOTFOUND);
+ }
+
+ for (rdataset = ISC_LIST_HEAD(noqname->list); rdataset != NULL;
+ rdataset = ISC_LIST_NEXT(rdataset, link))
+ {
+ if (rdataset->type == dns_rdatatype_rrsig &&
+ rdataset->covers == tneg->type)
+ {
+ tnegsig = rdataset;
+ }
+ }
+ if (tnegsig == NULL) {
+ return (ISC_R_NOTFOUND);
+ }
+
+ dns_name_clone(noqname, name);
+ dns_rdataset_clone(tneg, neg);
+ dns_rdataset_clone(tnegsig, negsig);
+ return (ISC_R_SUCCESS);
+}
+
+isc_result_t
+isc__rdatalist_addclosest(dns_rdataset_t *rdataset, const dns_name_t *name) {
+ dns_rdataset_t *neg = NULL;
+ dns_rdataset_t *negsig = NULL;
+ dns_rdataset_t *rdset;
+ dns_ttl_t ttl;
+
+ REQUIRE(rdataset != NULL);
+
+ for (rdset = ISC_LIST_HEAD(name->list); rdset != NULL;
+ rdset = ISC_LIST_NEXT(rdset, link))
+ {
+ if (rdset->rdclass != rdataset->rdclass) {
+ continue;
+ }
+ if (rdset->type == dns_rdatatype_nsec ||
+ rdset->type == dns_rdatatype_nsec3)
+ {
+ neg = rdset;
+ }
+ }
+ if (neg == NULL) {
+ return (ISC_R_NOTFOUND);
+ }
+
+ for (rdset = ISC_LIST_HEAD(name->list); rdset != NULL;
+ rdset = ISC_LIST_NEXT(rdset, link))
+ {
+ if (rdset->type == dns_rdatatype_rrsig &&
+ rdset->covers == neg->type)
+ {
+ negsig = rdset;
+ }
+ }
+
+ if (negsig == NULL) {
+ return (ISC_R_NOTFOUND);
+ }
+ /*
+ * Minimise ttl.
+ */
+ ttl = rdataset->ttl;
+ if (neg->ttl < ttl) {
+ ttl = neg->ttl;
+ }
+ if (negsig->ttl < ttl) {
+ ttl = negsig->ttl;
+ }
+ rdataset->ttl = neg->ttl = negsig->ttl = ttl;
+ rdataset->attributes |= DNS_RDATASETATTR_CLOSEST;
+ rdataset->private7 = name;
+ return (ISC_R_SUCCESS);
+}
+
+isc_result_t
+isc__rdatalist_getclosest(dns_rdataset_t *rdataset, dns_name_t *name,
+ dns_rdataset_t *neg, dns_rdataset_t *negsig) {
+ dns_rdataclass_t rdclass;
+ dns_rdataset_t *tneg = NULL;
+ dns_rdataset_t *tnegsig = NULL;
+ const dns_name_t *closest;
+
+ REQUIRE(rdataset != NULL);
+ REQUIRE((rdataset->attributes & DNS_RDATASETATTR_CLOSEST) != 0);
+
+ rdclass = rdataset->rdclass;
+ closest = rdataset->private7;
+
+ (void)dns_name_dynamic(closest); /* Sanity Check. */
+
+ for (rdataset = ISC_LIST_HEAD(closest->list); rdataset != NULL;
+ rdataset = ISC_LIST_NEXT(rdataset, link))
+ {
+ if (rdataset->rdclass != rdclass) {
+ continue;
+ }
+ if (rdataset->type == dns_rdatatype_nsec ||
+ rdataset->type == dns_rdatatype_nsec3)
+ {
+ tneg = rdataset;
+ }
+ }
+ if (tneg == NULL) {
+ return (ISC_R_NOTFOUND);
+ }
+
+ for (rdataset = ISC_LIST_HEAD(closest->list); rdataset != NULL;
+ rdataset = ISC_LIST_NEXT(rdataset, link))
+ {
+ if (rdataset->type == dns_rdatatype_rrsig &&
+ rdataset->covers == tneg->type)
+ {
+ tnegsig = rdataset;
+ }
+ }
+ if (tnegsig == NULL) {
+ return (ISC_R_NOTFOUND);
+ }
+
+ dns_name_clone(closest, name);
+ dns_rdataset_clone(tneg, neg);
+ dns_rdataset_clone(tnegsig, negsig);
+ return (ISC_R_SUCCESS);
+}
+
+void
+isc__rdatalist_setownercase(dns_rdataset_t *rdataset, const dns_name_t *name) {
+ dns_rdatalist_t *rdatalist;
+ unsigned int i;
+
+ /*
+ * We do not need to worry about label lengths as they are all
+ * less than or equal to 63.
+ */
+ rdatalist = rdataset->private1;
+ memset(rdatalist->upper, 0, sizeof(rdatalist->upper));
+ for (i = 1; i < name->length; i++) {
+ if (name->ndata[i] >= 0x41 && name->ndata[i] <= 0x5a) {
+ rdatalist->upper[i / 8] |= 1 << (i % 8);
+ /*
+ * Record that upper has been set.
+ */
+ }
+ }
+ /*
+ * Record that upper has been set.
+ */
+ rdatalist->upper[0] |= 0x01;
+}
+
+void
+isc__rdatalist_getownercase(const dns_rdataset_t *rdataset, dns_name_t *name) {
+ dns_rdatalist_t *rdatalist;
+ unsigned int i;
+
+ rdatalist = rdataset->private1;
+ if ((rdatalist->upper[0] & 0x01) == 0) {
+ return;
+ }
+ for (i = 0; i < name->length; i++) {
+ /*
+ * Set the case bit if it does not match the recorded bit.
+ */
+ if (name->ndata[i] >= 0x61 && name->ndata[i] <= 0x7a &&
+ (rdatalist->upper[i / 8] & (1 << (i % 8))) != 0)
+ {
+ name->ndata[i] &= ~0x20; /* clear the lower case bit */
+ } else if (name->ndata[i] >= 0x41 && name->ndata[i] <= 0x5a &&
+ (rdatalist->upper[i / 8] & (1 << (i % 8))) == 0)
+ {
+ name->ndata[i] |= 0x20; /* set the lower case bit */
+ }
+ }
+}
diff --git a/lib/dns/rdatalist_p.h b/lib/dns/rdatalist_p.h
new file mode 100644
index 0000000..f27a084
--- /dev/null
+++ b/lib/dns/rdatalist_p.h
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#pragma once
+
+/*! \file */
+
+#include <isc/result.h>
+
+#include <dns/types.h>
+
+ISC_LANG_BEGINDECLS
+
+void
+isc__rdatalist_disassociate(dns_rdataset_t *rdatasetp);
+
+isc_result_t
+isc__rdatalist_first(dns_rdataset_t *rdataset);
+
+isc_result_t
+isc__rdatalist_next(dns_rdataset_t *rdataset);
+
+void
+isc__rdatalist_current(dns_rdataset_t *rdataset, dns_rdata_t *rdata);
+
+void
+isc__rdatalist_clone(dns_rdataset_t *source, dns_rdataset_t *target);
+
+unsigned int
+isc__rdatalist_count(dns_rdataset_t *rdataset);
+
+isc_result_t
+isc__rdatalist_addnoqname(dns_rdataset_t *rdataset, const dns_name_t *name);
+
+isc_result_t
+isc__rdatalist_getnoqname(dns_rdataset_t *rdataset, dns_name_t *name,
+ dns_rdataset_t *neg, dns_rdataset_t *negsig);
+
+isc_result_t
+isc__rdatalist_addclosest(dns_rdataset_t *rdataset, const dns_name_t *name);
+
+isc_result_t
+isc__rdatalist_getclosest(dns_rdataset_t *rdataset, dns_name_t *name,
+ dns_rdataset_t *neg, dns_rdataset_t *negsig);
+
+void
+isc__rdatalist_setownercase(dns_rdataset_t *rdataset, const dns_name_t *name);
+
+void
+isc__rdatalist_getownercase(const dns_rdataset_t *rdataset, dns_name_t *name);
+
+ISC_LANG_ENDDECLS
diff --git a/lib/dns/rdataset.c b/lib/dns/rdataset.c
new file mode 100644
index 0000000..4d48203
--- /dev/null
+++ b/lib/dns/rdataset.c
@@ -0,0 +1,749 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/*! \file */
+
+#include <inttypes.h>
+#include <stdbool.h>
+#include <stdlib.h>
+
+#include <isc/buffer.h>
+#include <isc/mem.h>
+#include <isc/random.h>
+#include <isc/serial.h>
+#include <isc/util.h>
+
+#include <dns/compress.h>
+#include <dns/fixedname.h>
+#include <dns/name.h>
+#include <dns/ncache.h>
+#include <dns/rdata.h>
+#include <dns/rdataset.h>
+
+static const char *trustnames[] = {
+ "none", "pending-additional",
+ "pending-answer", "additional",
+ "glue", "answer",
+ "authauthority", "authanswer",
+ "secure", "local" /* aka ultimate */
+};
+
+const char *
+dns_trust_totext(dns_trust_t trust) {
+ if (trust >= sizeof(trustnames) / sizeof(*trustnames)) {
+ return ("bad");
+ }
+ return (trustnames[trust]);
+}
+
+void
+dns_rdataset_init(dns_rdataset_t *rdataset) {
+ /*
+ * Make 'rdataset' a valid, disassociated rdataset.
+ */
+
+ REQUIRE(rdataset != NULL);
+
+ rdataset->magic = DNS_RDATASET_MAGIC;
+ rdataset->methods = NULL;
+ ISC_LINK_INIT(rdataset, link);
+ rdataset->rdclass = 0;
+ rdataset->type = 0;
+ rdataset->ttl = 0;
+ rdataset->trust = 0;
+ rdataset->covers = 0;
+ rdataset->attributes = 0;
+ rdataset->count = DNS_RDATASET_COUNT_UNDEFINED;
+ rdataset->private1 = NULL;
+ rdataset->private2 = NULL;
+ rdataset->private3 = NULL;
+ rdataset->privateuint4 = 0;
+ rdataset->private5 = NULL;
+ rdataset->private6 = NULL;
+ rdataset->private7 = NULL;
+ rdataset->resign = 0;
+}
+
+void
+dns_rdataset_invalidate(dns_rdataset_t *rdataset) {
+ /*
+ * Invalidate 'rdataset'.
+ */
+
+ REQUIRE(DNS_RDATASET_VALID(rdataset));
+ REQUIRE(rdataset->methods == NULL);
+
+ rdataset->magic = 0;
+ ISC_LINK_INIT(rdataset, link);
+ rdataset->rdclass = 0;
+ rdataset->type = 0;
+ rdataset->ttl = 0;
+ rdataset->trust = 0;
+ rdataset->covers = 0;
+ rdataset->attributes = 0;
+ rdataset->count = DNS_RDATASET_COUNT_UNDEFINED;
+ rdataset->private1 = NULL;
+ rdataset->private2 = NULL;
+ rdataset->private3 = NULL;
+ rdataset->privateuint4 = 0;
+ rdataset->private5 = NULL;
+}
+
+void
+dns_rdataset_disassociate(dns_rdataset_t *rdataset) {
+ /*
+ * Disassociate 'rdataset' from its rdata, allowing it to be reused.
+ */
+
+ REQUIRE(DNS_RDATASET_VALID(rdataset));
+ REQUIRE(rdataset->methods != NULL);
+
+ (rdataset->methods->disassociate)(rdataset);
+ rdataset->methods = NULL;
+ ISC_LINK_INIT(rdataset, link);
+ rdataset->rdclass = 0;
+ rdataset->type = 0;
+ rdataset->ttl = 0;
+ rdataset->trust = 0;
+ rdataset->covers = 0;
+ rdataset->attributes = 0;
+ rdataset->count = DNS_RDATASET_COUNT_UNDEFINED;
+ rdataset->private1 = NULL;
+ rdataset->private2 = NULL;
+ rdataset->private3 = NULL;
+ rdataset->privateuint4 = 0;
+ rdataset->private5 = NULL;
+ rdataset->private6 = NULL;
+}
+
+bool
+dns_rdataset_isassociated(dns_rdataset_t *rdataset) {
+ /*
+ * Is 'rdataset' associated?
+ */
+
+ REQUIRE(DNS_RDATASET_VALID(rdataset));
+
+ if (rdataset->methods != NULL) {
+ return (true);
+ }
+
+ return (false);
+}
+
+static void
+question_disassociate(dns_rdataset_t *rdataset) {
+ UNUSED(rdataset);
+}
+
+static isc_result_t
+question_cursor(dns_rdataset_t *rdataset) {
+ UNUSED(rdataset);
+
+ return (ISC_R_NOMORE);
+}
+
+static void
+question_current(dns_rdataset_t *rdataset, dns_rdata_t *rdata) {
+ /*
+ * This routine should never be called.
+ */
+ UNUSED(rdataset);
+ UNUSED(rdata);
+
+ REQUIRE(0);
+}
+
+static void
+question_clone(dns_rdataset_t *source, dns_rdataset_t *target) {
+ *target = *source;
+}
+
+static unsigned int
+question_count(dns_rdataset_t *rdataset) {
+ /*
+ * This routine should never be called.
+ */
+ UNUSED(rdataset);
+ REQUIRE(0);
+
+ return (0);
+}
+
+static dns_rdatasetmethods_t question_methods = {
+ question_disassociate,
+ question_cursor,
+ question_cursor,
+ question_current,
+ question_clone,
+ question_count,
+ NULL, /* addnoqname */
+ NULL, /* getnoqname */
+ NULL, /* addclosest */
+ NULL, /* getclosest */
+ NULL, /* settrust */
+ NULL, /* expire */
+ NULL, /* clearprefetch */
+ NULL, /* setownercase */
+ NULL, /* getownercase */
+ NULL /* addglue */
+};
+
+void
+dns_rdataset_makequestion(dns_rdataset_t *rdataset, dns_rdataclass_t rdclass,
+ dns_rdatatype_t type) {
+ /*
+ * Make 'rdataset' a valid, associated, question rdataset, with a
+ * question class of 'rdclass' and type 'type'.
+ */
+
+ REQUIRE(DNS_RDATASET_VALID(rdataset));
+ REQUIRE(rdataset->methods == NULL);
+
+ rdataset->methods = &question_methods;
+ rdataset->rdclass = rdclass;
+ rdataset->type = type;
+ rdataset->attributes |= DNS_RDATASETATTR_QUESTION;
+}
+
+unsigned int
+dns_rdataset_count(dns_rdataset_t *rdataset) {
+ /*
+ * Return the number of records in 'rdataset'.
+ */
+
+ REQUIRE(DNS_RDATASET_VALID(rdataset));
+ REQUIRE(rdataset->methods != NULL);
+
+ return ((rdataset->methods->count)(rdataset));
+}
+
+void
+dns_rdataset_clone(dns_rdataset_t *source, dns_rdataset_t *target) {
+ /*
+ * Make 'target' refer to the same rdataset as 'source'.
+ */
+
+ REQUIRE(DNS_RDATASET_VALID(source));
+ REQUIRE(source->methods != NULL);
+ REQUIRE(DNS_RDATASET_VALID(target));
+ REQUIRE(target->methods == NULL);
+
+ (source->methods->clone)(source, target);
+}
+
+isc_result_t
+dns_rdataset_first(dns_rdataset_t *rdataset) {
+ /*
+ * Move the rdata cursor to the first rdata in the rdataset (if any).
+ */
+
+ REQUIRE(DNS_RDATASET_VALID(rdataset));
+ REQUIRE(rdataset->methods != NULL);
+
+ return ((rdataset->methods->first)(rdataset));
+}
+
+isc_result_t
+dns_rdataset_next(dns_rdataset_t *rdataset) {
+ /*
+ * Move the rdata cursor to the next rdata in the rdataset (if any).
+ */
+
+ REQUIRE(DNS_RDATASET_VALID(rdataset));
+ REQUIRE(rdataset->methods != NULL);
+
+ return ((rdataset->methods->next)(rdataset));
+}
+
+void
+dns_rdataset_current(dns_rdataset_t *rdataset, dns_rdata_t *rdata) {
+ /*
+ * Make 'rdata' refer to the current rdata.
+ */
+
+ REQUIRE(DNS_RDATASET_VALID(rdataset));
+ REQUIRE(rdataset->methods != NULL);
+
+ (rdataset->methods->current)(rdataset, rdata);
+}
+
+#define MAX_SHUFFLE 32
+#define WANT_FIXED(r) (((r)->attributes & DNS_RDATASETATTR_FIXEDORDER) != 0)
+#define WANT_RANDOM(r) (((r)->attributes & DNS_RDATASETATTR_RANDOMIZE) != 0)
+#define WANT_CYCLIC(r) (((r)->attributes & DNS_RDATASETATTR_CYCLIC) != 0)
+
+struct towire_sort {
+ int key;
+ dns_rdata_t *rdata;
+};
+
+static int
+towire_compare(const void *av, const void *bv) {
+ const struct towire_sort *a = (const struct towire_sort *)av;
+ const struct towire_sort *b = (const struct towire_sort *)bv;
+ return (a->key - b->key);
+}
+
+static void
+swap_rdata(dns_rdata_t *in, unsigned int a, unsigned int b) {
+ dns_rdata_t rdata = in[a];
+ in[a] = in[b];
+ in[b] = rdata;
+}
+
+static isc_result_t
+towiresorted(dns_rdataset_t *rdataset, const dns_name_t *owner_name,
+ dns_compress_t *cctx, isc_buffer_t *target,
+ dns_rdatasetorderfunc_t order, const void *order_arg, bool partial,
+ unsigned int options, unsigned int *countp, void **state) {
+ isc_region_t r;
+ isc_result_t result;
+ unsigned int i, count = 0, added;
+ isc_buffer_t savedbuffer, rdlen, rrbuffer;
+ unsigned int headlen;
+ bool question = false;
+ bool shuffle = false, sort = false;
+ bool want_random, want_cyclic;
+ dns_rdata_t in_fixed[MAX_SHUFFLE];
+ dns_rdata_t *in = in_fixed;
+ struct towire_sort out_fixed[MAX_SHUFFLE];
+ struct towire_sort *out = out_fixed;
+ dns_fixedname_t fixed;
+ dns_name_t *name;
+ uint16_t offset;
+
+ UNUSED(state);
+
+ /*
+ * Convert 'rdataset' to wire format, compressing names as specified
+ * in cctx, and storing the result in 'target'.
+ */
+
+ REQUIRE(DNS_RDATASET_VALID(rdataset));
+ REQUIRE(rdataset->methods != NULL);
+ REQUIRE(countp != NULL);
+ REQUIRE(cctx != NULL && cctx->mctx != NULL);
+
+ want_random = WANT_RANDOM(rdataset);
+ want_cyclic = WANT_CYCLIC(rdataset);
+
+ if ((rdataset->attributes & DNS_RDATASETATTR_QUESTION) != 0) {
+ question = true;
+ count = 1;
+ result = dns_rdataset_first(rdataset);
+ INSIST(result == ISC_R_NOMORE);
+ } else if ((rdataset->attributes & DNS_RDATASETATTR_NEGATIVE) != 0) {
+ /*
+ * This is a negative caching rdataset.
+ */
+ unsigned int ncache_opts = 0;
+ if ((options & DNS_RDATASETTOWIRE_OMITDNSSEC) != 0) {
+ ncache_opts |= DNS_NCACHETOWIRE_OMITDNSSEC;
+ }
+ return (dns_ncache_towire(rdataset, cctx, target, ncache_opts,
+ countp));
+ } else {
+ count = (rdataset->methods->count)(rdataset);
+ result = dns_rdataset_first(rdataset);
+ if (result == ISC_R_NOMORE) {
+ return (ISC_R_SUCCESS);
+ }
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+ }
+
+ /*
+ * Do we want to sort and/or shuffle this answer?
+ */
+ if (!question && count > 1 && rdataset->type != dns_rdatatype_rrsig) {
+ if (order != NULL) {
+ sort = true;
+ }
+ if (want_random || want_cyclic) {
+ shuffle = true;
+ }
+ }
+
+ if ((shuffle || sort)) {
+ if (count > MAX_SHUFFLE) {
+ in = isc_mem_get(cctx->mctx, count * sizeof(*in));
+ out = isc_mem_get(cctx->mctx, count * sizeof(*out));
+ if (in == NULL || out == NULL) {
+ shuffle = sort = false;
+ }
+ }
+ }
+
+ if ((shuffle || sort)) {
+ uint32_t seed = 0;
+ unsigned int j = 0;
+
+ /*
+ * First we get handles to all of the rdata.
+ */
+ i = 0;
+ do {
+ INSIST(i < count);
+ dns_rdata_init(&in[i]);
+ dns_rdataset_current(rdataset, &in[i]);
+ i++;
+ result = dns_rdataset_next(rdataset);
+ } while (result == ISC_R_SUCCESS);
+ if (result != ISC_R_NOMORE) {
+ goto cleanup;
+ }
+ INSIST(i == count);
+
+ if (want_random) {
+ seed = isc_random32();
+ }
+
+ if (want_cyclic &&
+ (rdataset->count != DNS_RDATASET_COUNT_UNDEFINED))
+ {
+ j = rdataset->count % count;
+ }
+
+ for (i = 0; i < count; i++) {
+ if (want_random) {
+ swap_rdata(in, j, j + seed % (count - j));
+ }
+
+ out[i].key = (sort) ? (*order)(&in[j], order_arg) : 0;
+ out[i].rdata = &in[j];
+ if (++j == count) {
+ j = 0;
+ }
+ }
+ /*
+ * Sortlist order.
+ */
+ if (sort) {
+ qsort(out, count, sizeof(out[0]), towire_compare);
+ }
+ }
+
+ savedbuffer = *target;
+ i = 0;
+ added = 0;
+
+ name = dns_fixedname_initname(&fixed);
+ dns_name_copy(owner_name, name);
+ dns_rdataset_getownercase(rdataset, name);
+ offset = 0xffff;
+
+ name->attributes |= owner_name->attributes & DNS_NAMEATTR_NOCOMPRESS;
+
+ do {
+ /*
+ * Copy out the name, type, class, ttl.
+ */
+
+ rrbuffer = *target;
+ dns_compress_setmethods(cctx, DNS_COMPRESS_GLOBAL14);
+ result = dns_name_towire2(name, cctx, target, &offset);
+ if (result != ISC_R_SUCCESS) {
+ goto rollback;
+ }
+ headlen = sizeof(dns_rdataclass_t) + sizeof(dns_rdatatype_t);
+ if (!question) {
+ headlen += sizeof(dns_ttl_t) + 2;
+ } /* XXX 2 for rdata len
+ */
+ isc_buffer_availableregion(target, &r);
+ if (r.length < headlen) {
+ result = ISC_R_NOSPACE;
+ goto rollback;
+ }
+ isc_buffer_putuint16(target, rdataset->type);
+ isc_buffer_putuint16(target, rdataset->rdclass);
+ if (!question) {
+ dns_rdata_t rdata = DNS_RDATA_INIT;
+
+ isc_buffer_putuint32(target, rdataset->ttl);
+
+ /*
+ * Save space for rdlen.
+ */
+ rdlen = *target;
+ isc_buffer_add(target, 2);
+
+ /*
+ * Copy out the rdata
+ */
+ if (shuffle || sort) {
+ rdata = *(out[i].rdata);
+ } else {
+ dns_rdata_reset(&rdata);
+ dns_rdataset_current(rdataset, &rdata);
+ }
+ result = dns_rdata_towire(&rdata, cctx, target);
+ if (result != ISC_R_SUCCESS) {
+ goto rollback;
+ }
+ INSIST((target->used >= rdlen.used + 2) &&
+ (target->used - rdlen.used - 2 < 65536));
+ isc_buffer_putuint16(
+ &rdlen,
+ (uint16_t)(target->used - rdlen.used - 2));
+ added++;
+ }
+
+ if (shuffle || sort) {
+ i++;
+ if (i == count) {
+ result = ISC_R_NOMORE;
+ } else {
+ result = ISC_R_SUCCESS;
+ }
+ } else {
+ result = dns_rdataset_next(rdataset);
+ }
+ } while (result == ISC_R_SUCCESS);
+
+ if (result != ISC_R_NOMORE) {
+ goto rollback;
+ }
+
+ *countp += count;
+
+ result = ISC_R_SUCCESS;
+ goto cleanup;
+
+rollback:
+ if (partial && result == ISC_R_NOSPACE) {
+ INSIST(rrbuffer.used < 65536);
+ dns_compress_rollback(cctx, (uint16_t)rrbuffer.used);
+ *countp += added;
+ *target = rrbuffer;
+ goto cleanup;
+ }
+ INSIST(savedbuffer.used < 65536);
+ dns_compress_rollback(cctx, (uint16_t)savedbuffer.used);
+ *countp = 0;
+ *target = savedbuffer;
+
+cleanup:
+ if (out != NULL && out != out_fixed) {
+ isc_mem_put(cctx->mctx, out, count * sizeof(*out));
+ }
+ if (in != NULL && in != in_fixed) {
+ isc_mem_put(cctx->mctx, in, count * sizeof(*in));
+ }
+ return (result);
+}
+
+isc_result_t
+dns_rdataset_towiresorted(dns_rdataset_t *rdataset,
+ const dns_name_t *owner_name, dns_compress_t *cctx,
+ isc_buffer_t *target, dns_rdatasetorderfunc_t order,
+ const void *order_arg, unsigned int options,
+ unsigned int *countp) {
+ return (towiresorted(rdataset, owner_name, cctx, target, order,
+ order_arg, false, options, countp, NULL));
+}
+
+isc_result_t
+dns_rdataset_towirepartial(dns_rdataset_t *rdataset,
+ const dns_name_t *owner_name, dns_compress_t *cctx,
+ isc_buffer_t *target, dns_rdatasetorderfunc_t order,
+ const void *order_arg, unsigned int options,
+ unsigned int *countp, void **state) {
+ REQUIRE(state == NULL); /* XXX remove when implemented */
+ return (towiresorted(rdataset, owner_name, cctx, target, order,
+ order_arg, true, options, countp, state));
+}
+
+isc_result_t
+dns_rdataset_towire(dns_rdataset_t *rdataset, const dns_name_t *owner_name,
+ dns_compress_t *cctx, isc_buffer_t *target,
+ unsigned int options, unsigned int *countp) {
+ return (towiresorted(rdataset, owner_name, cctx, target, NULL, NULL,
+ false, options, countp, NULL));
+}
+
+isc_result_t
+dns_rdataset_additionaldata(dns_rdataset_t *rdataset,
+ const dns_name_t *owner_name,
+ dns_additionaldatafunc_t add, void *arg) {
+ dns_rdata_t rdata = DNS_RDATA_INIT;
+ isc_result_t result;
+
+ /*
+ * For each rdata in rdataset, call 'add' for each name and type in the
+ * rdata which is subject to additional section processing.
+ */
+
+ REQUIRE(DNS_RDATASET_VALID(rdataset));
+ REQUIRE((rdataset->attributes & DNS_RDATASETATTR_QUESTION) == 0);
+
+ result = dns_rdataset_first(rdataset);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+
+ do {
+ dns_rdataset_current(rdataset, &rdata);
+ result = dns_rdata_additionaldata(&rdata, owner_name, add, arg);
+ if (result == ISC_R_SUCCESS) {
+ result = dns_rdataset_next(rdataset);
+ }
+ dns_rdata_reset(&rdata);
+ } while (result == ISC_R_SUCCESS);
+
+ if (result != ISC_R_NOMORE) {
+ return (result);
+ }
+
+ return (ISC_R_SUCCESS);
+}
+
+isc_result_t
+dns_rdataset_addnoqname(dns_rdataset_t *rdataset, dns_name_t *name) {
+ REQUIRE(DNS_RDATASET_VALID(rdataset));
+ REQUIRE(rdataset->methods != NULL);
+ if (rdataset->methods->addnoqname == NULL) {
+ return (ISC_R_NOTIMPLEMENTED);
+ }
+ return ((rdataset->methods->addnoqname)(rdataset, name));
+}
+
+isc_result_t
+dns_rdataset_getnoqname(dns_rdataset_t *rdataset, dns_name_t *name,
+ dns_rdataset_t *neg, dns_rdataset_t *negsig) {
+ REQUIRE(DNS_RDATASET_VALID(rdataset));
+ REQUIRE(rdataset->methods != NULL);
+
+ if (rdataset->methods->getnoqname == NULL) {
+ return (ISC_R_NOTIMPLEMENTED);
+ }
+ return ((rdataset->methods->getnoqname)(rdataset, name, neg, negsig));
+}
+
+isc_result_t
+dns_rdataset_addclosest(dns_rdataset_t *rdataset, const dns_name_t *name) {
+ REQUIRE(DNS_RDATASET_VALID(rdataset));
+ REQUIRE(rdataset->methods != NULL);
+ if (rdataset->methods->addclosest == NULL) {
+ return (ISC_R_NOTIMPLEMENTED);
+ }
+ return ((rdataset->methods->addclosest)(rdataset, name));
+}
+
+isc_result_t
+dns_rdataset_getclosest(dns_rdataset_t *rdataset, dns_name_t *name,
+ dns_rdataset_t *neg, dns_rdataset_t *negsig) {
+ REQUIRE(DNS_RDATASET_VALID(rdataset));
+ REQUIRE(rdataset->methods != NULL);
+
+ if (rdataset->methods->getclosest == NULL) {
+ return (ISC_R_NOTIMPLEMENTED);
+ }
+ return ((rdataset->methods->getclosest)(rdataset, name, neg, negsig));
+}
+
+void
+dns_rdataset_settrust(dns_rdataset_t *rdataset, dns_trust_t trust) {
+ REQUIRE(DNS_RDATASET_VALID(rdataset));
+ REQUIRE(rdataset->methods != NULL);
+
+ if (rdataset->methods->settrust != NULL) {
+ (rdataset->methods->settrust)(rdataset, trust);
+ } else {
+ rdataset->trust = trust;
+ }
+}
+
+void
+dns_rdataset_expire(dns_rdataset_t *rdataset) {
+ REQUIRE(DNS_RDATASET_VALID(rdataset));
+ REQUIRE(rdataset->methods != NULL);
+
+ if (rdataset->methods->expire != NULL) {
+ (rdataset->methods->expire)(rdataset);
+ }
+}
+
+void
+dns_rdataset_clearprefetch(dns_rdataset_t *rdataset) {
+ REQUIRE(DNS_RDATASET_VALID(rdataset));
+ REQUIRE(rdataset->methods != NULL);
+
+ if (rdataset->methods->clearprefetch != NULL) {
+ (rdataset->methods->clearprefetch)(rdataset);
+ }
+}
+
+void
+dns_rdataset_setownercase(dns_rdataset_t *rdataset, const dns_name_t *name) {
+ REQUIRE(DNS_RDATASET_VALID(rdataset));
+ REQUIRE(rdataset->methods != NULL);
+
+ if (rdataset->methods->setownercase != NULL) {
+ (rdataset->methods->setownercase)(rdataset, name);
+ }
+}
+
+void
+dns_rdataset_getownercase(const dns_rdataset_t *rdataset, dns_name_t *name) {
+ REQUIRE(DNS_RDATASET_VALID(rdataset));
+ REQUIRE(rdataset->methods != NULL);
+
+ if (rdataset->methods->getownercase != NULL) {
+ (rdataset->methods->getownercase)(rdataset, name);
+ }
+}
+
+void
+dns_rdataset_trimttl(dns_rdataset_t *rdataset, dns_rdataset_t *sigrdataset,
+ dns_rdata_rrsig_t *rrsig, isc_stdtime_t now,
+ bool acceptexpired) {
+ uint32_t ttl = 0;
+
+ REQUIRE(DNS_RDATASET_VALID(rdataset));
+ REQUIRE(DNS_RDATASET_VALID(sigrdataset));
+ REQUIRE(rrsig != NULL);
+
+ /*
+ * If we accept expired RRsets keep them for no more than 120 seconds.
+ */
+ if (acceptexpired &&
+ (isc_serial_le(rrsig->timeexpire, ((now + 120) & 0xffffffff)) ||
+ isc_serial_le(rrsig->timeexpire, now)))
+ {
+ ttl = 120;
+ } else if (isc_serial_ge(rrsig->timeexpire, now)) {
+ ttl = rrsig->timeexpire - now;
+ }
+
+ ttl = ISC_MIN(ISC_MIN(rdataset->ttl, sigrdataset->ttl),
+ ISC_MIN(rrsig->originalttl, ttl));
+ rdataset->ttl = ttl;
+ sigrdataset->ttl = ttl;
+}
+
+isc_result_t
+dns_rdataset_addglue(dns_rdataset_t *rdataset, dns_dbversion_t *version,
+ dns_message_t *msg) {
+ REQUIRE(DNS_RDATASET_VALID(rdataset));
+ REQUIRE(rdataset->methods != NULL);
+ REQUIRE(rdataset->type == dns_rdatatype_ns);
+
+ if (rdataset->methods->addglue == NULL) {
+ return (ISC_R_NOTIMPLEMENTED);
+ }
+
+ return ((rdataset->methods->addglue)(rdataset, version, msg));
+}
diff --git a/lib/dns/rdatasetiter.c b/lib/dns/rdatasetiter.c
new file mode 100644
index 0000000..8e8159f
--- /dev/null
+++ b/lib/dns/rdatasetiter.c
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/*! \file */
+
+#include <stddef.h>
+
+#include <isc/util.h>
+
+#include <dns/rdataset.h>
+#include <dns/rdatasetiter.h>
+
+void
+dns_rdatasetiter_destroy(dns_rdatasetiter_t **iteratorp) {
+ /*
+ * Destroy '*iteratorp'.
+ */
+
+ REQUIRE(iteratorp != NULL);
+ REQUIRE(DNS_RDATASETITER_VALID(*iteratorp));
+
+ (*iteratorp)->methods->destroy(iteratorp);
+
+ ENSURE(*iteratorp == NULL);
+}
+
+isc_result_t
+dns_rdatasetiter_first(dns_rdatasetiter_t *iterator) {
+ /*
+ * Move the rdataset cursor to the first rdataset at the node (if any).
+ */
+
+ REQUIRE(DNS_RDATASETITER_VALID(iterator));
+
+ return (iterator->methods->first(iterator));
+}
+
+isc_result_t
+dns_rdatasetiter_next(dns_rdatasetiter_t *iterator) {
+ /*
+ * Move the rdataset cursor to the next rdataset at the node (if any).
+ */
+
+ REQUIRE(DNS_RDATASETITER_VALID(iterator));
+
+ return (iterator->methods->next(iterator));
+}
+
+void
+dns_rdatasetiter_current(dns_rdatasetiter_t *iterator,
+ dns_rdataset_t *rdataset) {
+ /*
+ * Return the current rdataset.
+ */
+
+ REQUIRE(DNS_RDATASETITER_VALID(iterator));
+ REQUIRE(DNS_RDATASET_VALID(rdataset));
+ REQUIRE(!dns_rdataset_isassociated(rdataset));
+
+ iterator->methods->current(iterator, rdataset);
+}
diff --git a/lib/dns/rdataslab.c b/lib/dns/rdataslab.c
new file mode 100644
index 0000000..24fdaa8
--- /dev/null
+++ b/lib/dns/rdataslab.c
@@ -0,0 +1,1005 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/*! \file */
+
+#include <stdbool.h>
+#include <stdlib.h>
+
+#include <isc/mem.h>
+#include <isc/region.h>
+#include <isc/result.h>
+#include <isc/string.h> /* Required for HP/UX (and others?) */
+#include <isc/util.h>
+
+#include <dns/rdata.h>
+#include <dns/rdataset.h>
+#include <dns/rdataslab.h>
+
+/*
+ * The rdataslab structure allows iteration to occur in both load order
+ * and DNSSEC order. The structure is as follows:
+ *
+ * header (reservelen bytes)
+ * record count (2 bytes)
+ * offset table (4 x record count bytes in load order)
+ * data records
+ * data length (2 bytes)
+ * order (2 bytes)
+ * meta data (1 byte for RRSIG's)
+ * data (data length bytes)
+ *
+ * If DNS_RDATASET_FIXED is defined to be zero (0) the format of a
+ * rdataslab is as follows:
+ *
+ * header (reservelen bytes)
+ * record count (2 bytes)
+ * data records
+ * data length (2 bytes)
+ * meta data (1 byte for RRSIG's)
+ * data (data length bytes)
+ *
+ * Offsets are from the end of the header.
+ *
+ * Load order traversal is performed by walking the offset table to find
+ * the start of the record (DNS_RDATASET_FIXED = 1).
+ *
+ * DNSSEC order traversal is performed by walking the data records.
+ *
+ * The order is stored with record to allow for efficient reconstruction
+ * of the offset table following a merge or subtraction.
+ *
+ * The iterator methods in rbtdb support both load order and DNSSEC order
+ * iteration.
+ *
+ * WARNING:
+ * rbtdb.c directly interacts with the slab's raw structures. If the
+ * structure changes then rbtdb.c also needs to be updated to reflect
+ * the changes. See the areas tagged with "RDATASLAB".
+ */
+
+struct xrdata {
+ dns_rdata_t rdata;
+ unsigned int order;
+};
+
+/*% Note: the "const void *" are just to make qsort happy. */
+static int
+compare_rdata(const void *p1, const void *p2) {
+ const struct xrdata *x1 = p1;
+ const struct xrdata *x2 = p2;
+ return (dns_rdata_compare(&x1->rdata, &x2->rdata));
+}
+
+#if DNS_RDATASET_FIXED
+static void
+fillin_offsets(unsigned char *offsetbase, unsigned int *offsettable,
+ unsigned length) {
+ unsigned int i, j;
+ unsigned char *raw;
+
+ for (i = 0, j = 0; i < length; i++) {
+ if (offsettable[i] == 0) {
+ continue;
+ }
+
+ /*
+ * Fill in offset table.
+ */
+ raw = &offsetbase[j * 4 + 2];
+ *raw++ = (offsettable[i] & 0xff000000) >> 24;
+ *raw++ = (offsettable[i] & 0xff0000) >> 16;
+ *raw++ = (offsettable[i] & 0xff00) >> 8;
+ *raw = offsettable[i] & 0xff;
+
+ /*
+ * Fill in table index.
+ */
+ raw = offsetbase + offsettable[i] + 2;
+ *raw++ = (j & 0xff00) >> 8;
+ *raw = j++ & 0xff;
+ }
+}
+#endif /* if DNS_RDATASET_FIXED */
+
+isc_result_t
+dns_rdataslab_fromrdataset(dns_rdataset_t *rdataset, isc_mem_t *mctx,
+ isc_region_t *region, unsigned int reservelen) {
+ /*
+ * Use &removed as a sentinel pointer for duplicate
+ * rdata as rdata.data == NULL is valid.
+ */
+ static unsigned char removed;
+ struct xrdata *x;
+ unsigned char *rawbuf;
+#if DNS_RDATASET_FIXED
+ unsigned char *offsetbase;
+#endif /* if DNS_RDATASET_FIXED */
+ unsigned int buflen;
+ isc_result_t result;
+ unsigned int nitems;
+ unsigned int nalloc;
+ unsigned int i;
+#if DNS_RDATASET_FIXED
+ unsigned int *offsettable;
+#endif /* if DNS_RDATASET_FIXED */
+ unsigned int length;
+
+ buflen = reservelen + 2;
+
+ nitems = dns_rdataset_count(rdataset);
+
+ /*
+ * If there are no rdata then we can just need to allocate a header
+ * with zero a record count.
+ */
+ if (nitems == 0) {
+ if (rdataset->type != 0) {
+ return (ISC_R_FAILURE);
+ }
+ rawbuf = isc_mem_get(mctx, buflen);
+ region->base = rawbuf;
+ region->length = buflen;
+ rawbuf += reservelen;
+ *rawbuf++ = 0;
+ *rawbuf = 0;
+ return (ISC_R_SUCCESS);
+ }
+
+ if (nitems > 0xffff) {
+ return (ISC_R_NOSPACE);
+ }
+
+ /*
+ * Remember the original number of items.
+ */
+ nalloc = nitems;
+ x = isc_mem_get(mctx, nalloc * sizeof(struct xrdata));
+
+ /*
+ * Save all of the rdata members into an array.
+ */
+ result = dns_rdataset_first(rdataset);
+ if (result != ISC_R_SUCCESS && result != ISC_R_NOMORE) {
+ goto free_rdatas;
+ }
+ for (i = 0; i < nalloc && result == ISC_R_SUCCESS; i++) {
+ INSIST(result == ISC_R_SUCCESS);
+ dns_rdata_init(&x[i].rdata);
+ dns_rdataset_current(rdataset, &x[i].rdata);
+ INSIST(x[i].rdata.data != &removed);
+#if DNS_RDATASET_FIXED
+ x[i].order = i;
+#endif /* if DNS_RDATASET_FIXED */
+ result = dns_rdataset_next(rdataset);
+ }
+ if (i != nalloc || result != ISC_R_NOMORE) {
+ /*
+ * Somehow we iterated over fewer rdatas than
+ * dns_rdataset_count() said there were or there
+ * were more items than dns_rdataset_count said
+ * there were.
+ */
+ result = ISC_R_FAILURE;
+ goto free_rdatas;
+ }
+
+ /*
+ * Put into DNSSEC order.
+ */
+ if (nalloc > 1U) {
+ qsort(x, nalloc, sizeof(struct xrdata), compare_rdata);
+ }
+
+ /*
+ * Remove duplicates and compute the total storage required.
+ *
+ * If an rdata is not a duplicate, accumulate the storage size
+ * required for the rdata. We do not store the class, type, etc,
+ * just the rdata, so our overhead is 2 bytes for the number of
+ * records, and 8 for each rdata, (length(2), offset(4) and order(2))
+ * and then the rdata itself.
+ */
+ for (i = 1; i < nalloc; i++) {
+ if (compare_rdata(&x[i - 1].rdata, &x[i].rdata) == 0) {
+ x[i - 1].rdata.data = &removed;
+#if DNS_RDATASET_FIXED
+ /*
+ * Preserve the least order so A, B, A -> A, B
+ * after duplicate removal.
+ */
+ if (x[i - 1].order < x[i].order) {
+ x[i].order = x[i - 1].order;
+ }
+#endif /* if DNS_RDATASET_FIXED */
+ nitems--;
+ } else {
+#if DNS_RDATASET_FIXED
+ buflen += (8 + x[i - 1].rdata.length);
+#else /* if DNS_RDATASET_FIXED */
+ buflen += (2 + x[i - 1].rdata.length);
+#endif /* if DNS_RDATASET_FIXED */
+ /*
+ * Provide space to store the per RR meta data.
+ */
+ if (rdataset->type == dns_rdatatype_rrsig) {
+ buflen++;
+ }
+ }
+ }
+
+ /*
+ * Don't forget the last item!
+ */
+#if DNS_RDATASET_FIXED
+ buflen += (8 + x[i - 1].rdata.length);
+#else /* if DNS_RDATASET_FIXED */
+ buflen += (2 + x[i - 1].rdata.length);
+#endif /* if DNS_RDATASET_FIXED */
+ /*
+ * Provide space to store the per RR meta data.
+ */
+ if (rdataset->type == dns_rdatatype_rrsig) {
+ buflen++;
+ }
+
+ /*
+ * Ensure that singleton types are actually singletons.
+ */
+ if (nitems > 1 && dns_rdatatype_issingleton(rdataset->type)) {
+ /*
+ * We have a singleton type, but there's more than one
+ * RR in the rdataset.
+ */
+ result = DNS_R_SINGLETON;
+ goto free_rdatas;
+ }
+
+ /*
+ * Allocate the memory, set up a buffer, start copying in
+ * data.
+ */
+ rawbuf = isc_mem_get(mctx, buflen);
+
+#if DNS_RDATASET_FIXED
+ /* Allocate temporary offset table. */
+ offsettable = isc_mem_get(mctx, nalloc * sizeof(unsigned int));
+ memset(offsettable, 0, nalloc * sizeof(unsigned int));
+#endif /* if DNS_RDATASET_FIXED */
+
+ region->base = rawbuf;
+ region->length = buflen;
+
+ memset(rawbuf, 0, buflen);
+ rawbuf += reservelen;
+
+#if DNS_RDATASET_FIXED
+ offsetbase = rawbuf;
+#endif /* if DNS_RDATASET_FIXED */
+
+ *rawbuf++ = (nitems & 0xff00) >> 8;
+ *rawbuf++ = (nitems & 0x00ff);
+
+#if DNS_RDATASET_FIXED
+ /* Skip load order table. Filled in later. */
+ rawbuf += nitems * 4;
+#endif /* if DNS_RDATASET_FIXED */
+
+ for (i = 0; i < nalloc; i++) {
+ if (x[i].rdata.data == &removed) {
+ continue;
+ }
+#if DNS_RDATASET_FIXED
+ offsettable[x[i].order] = rawbuf - offsetbase;
+#endif /* if DNS_RDATASET_FIXED */
+ length = x[i].rdata.length;
+ if (rdataset->type == dns_rdatatype_rrsig) {
+ length++;
+ }
+ INSIST(length <= 0xffff);
+ *rawbuf++ = (length & 0xff00) >> 8;
+ *rawbuf++ = (length & 0x00ff);
+#if DNS_RDATASET_FIXED
+ rawbuf += 2; /* filled in later */
+#endif /* if DNS_RDATASET_FIXED */
+ /*
+ * Store the per RR meta data.
+ */
+ if (rdataset->type == dns_rdatatype_rrsig) {
+ *rawbuf++ = (x[i].rdata.flags & DNS_RDATA_OFFLINE)
+ ? DNS_RDATASLAB_OFFLINE
+ : 0;
+ }
+ memmove(rawbuf, x[i].rdata.data, x[i].rdata.length);
+ rawbuf += x[i].rdata.length;
+ }
+
+#if DNS_RDATASET_FIXED
+ fillin_offsets(offsetbase, offsettable, nalloc);
+ isc_mem_put(mctx, offsettable, nalloc * sizeof(unsigned int));
+#endif /* if DNS_RDATASET_FIXED */
+
+ result = ISC_R_SUCCESS;
+
+free_rdatas:
+ isc_mem_put(mctx, x, nalloc * sizeof(struct xrdata));
+ return (result);
+}
+
+unsigned int
+dns_rdataslab_size(unsigned char *slab, unsigned int reservelen) {
+ unsigned int count, length;
+ unsigned char *current;
+
+ REQUIRE(slab != NULL);
+
+ current = slab + reservelen;
+ count = *current++ * 256;
+ count += *current++;
+#if DNS_RDATASET_FIXED
+ current += (4 * count);
+#endif /* if DNS_RDATASET_FIXED */
+ while (count > 0) {
+ count--;
+ length = *current++ * 256;
+ length += *current++;
+#if DNS_RDATASET_FIXED
+ current += length + 2;
+#else /* if DNS_RDATASET_FIXED */
+ current += length;
+#endif /* if DNS_RDATASET_FIXED */
+ }
+
+ return ((unsigned int)(current - slab));
+}
+
+unsigned int
+dns_rdataslab_rdatasize(unsigned char *slab, unsigned int reservelen) {
+ unsigned int count, length, rdatalen = 0;
+ unsigned char *current;
+
+ REQUIRE(slab != NULL);
+
+ current = slab + reservelen;
+ count = *current++ * 256;
+ count += *current++;
+#if DNS_RDATASET_FIXED
+ current += (4 * count);
+#endif /* if DNS_RDATASET_FIXED */
+ while (count > 0) {
+ count--;
+ length = *current++ * 256;
+ length += *current++;
+ rdatalen += length;
+#if DNS_RDATASET_FIXED
+ current += length + 2;
+#else /* if DNS_RDATASET_FIXED */
+ current += length;
+#endif /* if DNS_RDATASET_FIXED */
+ }
+
+ return (rdatalen);
+}
+
+unsigned int
+dns_rdataslab_count(unsigned char *slab, unsigned int reservelen) {
+ unsigned int count;
+ unsigned char *current;
+
+ REQUIRE(slab != NULL);
+
+ current = slab + reservelen;
+ count = *current++ * 256;
+ count += *current++;
+ return (count);
+}
+
+/*
+ * Make the dns_rdata_t 'rdata' refer to the slab item
+ * beginning at '*current', which is part of a slab of type
+ * 'type' and class 'rdclass', and advance '*current' to
+ * point to the next item in the slab.
+ */
+static void
+rdata_from_slab(unsigned char **current, dns_rdataclass_t rdclass,
+ dns_rdatatype_t type, dns_rdata_t *rdata) {
+ unsigned char *tcurrent = *current;
+ isc_region_t region;
+ unsigned int length;
+ bool offline = false;
+
+ length = *tcurrent++ * 256;
+ length += *tcurrent++;
+
+ if (type == dns_rdatatype_rrsig) {
+ if ((*tcurrent & DNS_RDATASLAB_OFFLINE) != 0) {
+ offline = true;
+ }
+ length--;
+ tcurrent++;
+ }
+ region.length = length;
+#if DNS_RDATASET_FIXED
+ tcurrent += 2;
+#endif /* if DNS_RDATASET_FIXED */
+ region.base = tcurrent;
+ tcurrent += region.length;
+ dns_rdata_fromregion(rdata, rdclass, type, &region);
+ if (offline) {
+ rdata->flags |= DNS_RDATA_OFFLINE;
+ }
+ *current = tcurrent;
+}
+
+/*
+ * Return true iff 'slab' (slab data of type 'type' and class 'rdclass')
+ * contains an rdata identical to 'rdata'. This does case insensitive
+ * comparisons per DNSSEC.
+ */
+static bool
+rdata_in_slab(unsigned char *slab, unsigned int reservelen,
+ dns_rdataclass_t rdclass, dns_rdatatype_t type,
+ dns_rdata_t *rdata) {
+ unsigned int count, i;
+ unsigned char *current;
+ dns_rdata_t trdata = DNS_RDATA_INIT;
+ int n;
+
+ current = slab + reservelen;
+ count = *current++ * 256;
+ count += *current++;
+
+#if DNS_RDATASET_FIXED
+ current += (4 * count);
+#endif /* if DNS_RDATASET_FIXED */
+
+ for (i = 0; i < count; i++) {
+ rdata_from_slab(&current, rdclass, type, &trdata);
+
+ n = dns_rdata_compare(&trdata, rdata);
+ if (n == 0) {
+ return (true);
+ }
+ if (n > 0) { /* In DNSSEC order. */
+ break;
+ }
+ dns_rdata_reset(&trdata);
+ }
+ return (false);
+}
+
+isc_result_t
+dns_rdataslab_merge(unsigned char *oslab, unsigned char *nslab,
+ unsigned int reservelen, isc_mem_t *mctx,
+ dns_rdataclass_t rdclass, dns_rdatatype_t type,
+ unsigned int flags, unsigned char **tslabp) {
+ unsigned char *ocurrent, *ostart, *ncurrent, *tstart, *tcurrent, *data;
+ unsigned int ocount, ncount, count, olength, tlength, tcount, length;
+ dns_rdata_t ordata = DNS_RDATA_INIT;
+ dns_rdata_t nrdata = DNS_RDATA_INIT;
+ bool added_something = false;
+ unsigned int oadded = 0;
+ unsigned int nadded = 0;
+ unsigned int nncount = 0;
+#if DNS_RDATASET_FIXED
+ unsigned int oncount;
+ unsigned int norder = 0;
+ unsigned int oorder = 0;
+ unsigned char *offsetbase;
+ unsigned int *offsettable;
+#endif /* if DNS_RDATASET_FIXED */
+
+ /*
+ * XXX Need parameter to allow "delete rdatasets in nslab" merge,
+ * or perhaps another merge routine for this purpose.
+ */
+
+ REQUIRE(tslabp != NULL && *tslabp == NULL);
+ REQUIRE(oslab != NULL && nslab != NULL);
+
+ ocurrent = oslab + reservelen;
+ ocount = *ocurrent++ * 256;
+ ocount += *ocurrent++;
+#if DNS_RDATASET_FIXED
+ ocurrent += (4 * ocount);
+#endif /* if DNS_RDATASET_FIXED */
+ ostart = ocurrent;
+ ncurrent = nslab + reservelen;
+ ncount = *ncurrent++ * 256;
+ ncount += *ncurrent++;
+#if DNS_RDATASET_FIXED
+ ncurrent += (4 * ncount);
+#endif /* if DNS_RDATASET_FIXED */
+ INSIST(ocount > 0 && ncount > 0);
+
+#if DNS_RDATASET_FIXED
+ oncount = ncount;
+#endif /* if DNS_RDATASET_FIXED */
+
+ /*
+ * Yes, this is inefficient!
+ */
+
+ /*
+ * Figure out the length of the old slab's data.
+ */
+ olength = 0;
+ for (count = 0; count < ocount; count++) {
+ length = *ocurrent++ * 256;
+ length += *ocurrent++;
+#if DNS_RDATASET_FIXED
+ olength += length + 8;
+ ocurrent += length + 2;
+#else /* if DNS_RDATASET_FIXED */
+ olength += length + 2;
+ ocurrent += length;
+#endif /* if DNS_RDATASET_FIXED */
+ }
+
+ /*
+ * Start figuring out the target length and count.
+ */
+ tlength = reservelen + 2 + olength;
+ tcount = ocount;
+
+ /*
+ * Add in the length of rdata in the new slab that aren't in
+ * the old slab.
+ */
+ do {
+ dns_rdata_init(&nrdata);
+ rdata_from_slab(&ncurrent, rdclass, type, &nrdata);
+ if (!rdata_in_slab(oslab, reservelen, rdclass, type, &nrdata)) {
+ /*
+ * This rdata isn't in the old slab.
+ */
+#if DNS_RDATASET_FIXED
+ tlength += nrdata.length + 8;
+#else /* if DNS_RDATASET_FIXED */
+ tlength += nrdata.length + 2;
+#endif /* if DNS_RDATASET_FIXED */
+ if (type == dns_rdatatype_rrsig) {
+ tlength++;
+ }
+ tcount++;
+ nncount++;
+ added_something = true;
+ }
+ ncount--;
+ } while (ncount > 0);
+ ncount = nncount;
+
+ if (((flags & DNS_RDATASLAB_EXACT) != 0) && (tcount != ncount + ocount))
+ {
+ return (DNS_R_NOTEXACT);
+ }
+
+ if (!added_something && (flags & DNS_RDATASLAB_FORCE) == 0) {
+ return (DNS_R_UNCHANGED);
+ }
+
+ /*
+ * Ensure that singleton types are actually singletons.
+ */
+ if (tcount > 1 && dns_rdatatype_issingleton(type)) {
+ /*
+ * We have a singleton type, but there's more than one
+ * RR in the rdataset.
+ */
+ return (DNS_R_SINGLETON);
+ }
+
+ if (tcount > 0xffff) {
+ return (ISC_R_NOSPACE);
+ }
+
+ /*
+ * Copy the reserved area from the new slab.
+ */
+ tstart = isc_mem_get(mctx, tlength);
+ memmove(tstart, nslab, reservelen);
+ tcurrent = tstart + reservelen;
+#if DNS_RDATASET_FIXED
+ offsetbase = tcurrent;
+#endif /* if DNS_RDATASET_FIXED */
+
+ /*
+ * Write the new count.
+ */
+ *tcurrent++ = (tcount & 0xff00) >> 8;
+ *tcurrent++ = (tcount & 0x00ff);
+
+#if DNS_RDATASET_FIXED
+ /*
+ * Skip offset table.
+ */
+ tcurrent += (tcount * 4);
+
+ offsettable = isc_mem_get(mctx,
+ (ocount + oncount) * sizeof(unsigned int));
+ memset(offsettable, 0, (ocount + oncount) * sizeof(unsigned int));
+#endif /* if DNS_RDATASET_FIXED */
+
+ /*
+ * Merge the two slabs.
+ */
+ ocurrent = ostart;
+ INSIST(ocount != 0);
+#if DNS_RDATASET_FIXED
+ oorder = ocurrent[2] * 256 + ocurrent[3];
+ INSIST(oorder < ocount);
+#endif /* if DNS_RDATASET_FIXED */
+ rdata_from_slab(&ocurrent, rdclass, type, &ordata);
+
+ ncurrent = nslab + reservelen + 2;
+#if DNS_RDATASET_FIXED
+ ncurrent += (4 * oncount);
+#endif /* if DNS_RDATASET_FIXED */
+
+ if (ncount > 0) {
+ do {
+ dns_rdata_reset(&nrdata);
+#if DNS_RDATASET_FIXED
+ norder = ncurrent[2] * 256 + ncurrent[3];
+
+ INSIST(norder < oncount);
+#endif /* if DNS_RDATASET_FIXED */
+ rdata_from_slab(&ncurrent, rdclass, type, &nrdata);
+ } while (rdata_in_slab(oslab, reservelen, rdclass, type,
+ &nrdata));
+ }
+
+ while (oadded < ocount || nadded < ncount) {
+ bool fromold;
+ if (oadded == ocount) {
+ fromold = false;
+ } else if (nadded == ncount) {
+ fromold = true;
+ } else {
+ fromold = (dns_rdata_compare(&ordata, &nrdata) < 0);
+ }
+ if (fromold) {
+#if DNS_RDATASET_FIXED
+ offsettable[oorder] = tcurrent - offsetbase;
+#endif /* if DNS_RDATASET_FIXED */
+ length = ordata.length;
+ data = ordata.data;
+ if (type == dns_rdatatype_rrsig) {
+ length++;
+ data--;
+ }
+ *tcurrent++ = (length & 0xff00) >> 8;
+ *tcurrent++ = (length & 0x00ff);
+#if DNS_RDATASET_FIXED
+ tcurrent += 2; /* fill in later */
+#endif /* if DNS_RDATASET_FIXED */
+ memmove(tcurrent, data, length);
+ tcurrent += length;
+ oadded++;
+ if (oadded < ocount) {
+ dns_rdata_reset(&ordata);
+#if DNS_RDATASET_FIXED
+ oorder = ocurrent[2] * 256 + ocurrent[3];
+ INSIST(oorder < ocount);
+#endif /* if DNS_RDATASET_FIXED */
+ rdata_from_slab(&ocurrent, rdclass, type,
+ &ordata);
+ }
+ } else {
+#if DNS_RDATASET_FIXED
+ offsettable[ocount + norder] = tcurrent - offsetbase;
+#endif /* if DNS_RDATASET_FIXED */
+ length = nrdata.length;
+ data = nrdata.data;
+ if (type == dns_rdatatype_rrsig) {
+ length++;
+ data--;
+ }
+ *tcurrent++ = (length & 0xff00) >> 8;
+ *tcurrent++ = (length & 0x00ff);
+#if DNS_RDATASET_FIXED
+ tcurrent += 2; /* fill in later */
+#endif /* if DNS_RDATASET_FIXED */
+ memmove(tcurrent, data, length);
+ tcurrent += length;
+ nadded++;
+ if (nadded < ncount) {
+ do {
+ dns_rdata_reset(&nrdata);
+#if DNS_RDATASET_FIXED
+ norder = ncurrent[2] * 256 +
+ ncurrent[3];
+ INSIST(norder < oncount);
+#endif /* if DNS_RDATASET_FIXED */
+ rdata_from_slab(&ncurrent, rdclass,
+ type, &nrdata);
+ } while (rdata_in_slab(oslab, reservelen,
+ rdclass, type, &nrdata));
+ }
+ }
+ }
+
+#if DNS_RDATASET_FIXED
+ fillin_offsets(offsetbase, offsettable, ocount + oncount);
+
+ isc_mem_put(mctx, offsettable,
+ (ocount + oncount) * sizeof(unsigned int));
+#endif /* if DNS_RDATASET_FIXED */
+
+ INSIST(tcurrent == tstart + tlength);
+
+ *tslabp = tstart;
+
+ return (ISC_R_SUCCESS);
+}
+
+isc_result_t
+dns_rdataslab_subtract(unsigned char *mslab, unsigned char *sslab,
+ unsigned int reservelen, isc_mem_t *mctx,
+ dns_rdataclass_t rdclass, dns_rdatatype_t type,
+ unsigned int flags, unsigned char **tslabp) {
+ unsigned char *mcurrent, *sstart, *scurrent, *tstart, *tcurrent;
+ unsigned int mcount, scount, rcount, count, tlength, tcount, i;
+ dns_rdata_t srdata = DNS_RDATA_INIT;
+ dns_rdata_t mrdata = DNS_RDATA_INIT;
+#if DNS_RDATASET_FIXED
+ unsigned char *offsetbase;
+ unsigned int *offsettable;
+ unsigned int order;
+#endif /* if DNS_RDATASET_FIXED */
+
+ REQUIRE(tslabp != NULL && *tslabp == NULL);
+ REQUIRE(mslab != NULL && sslab != NULL);
+
+ mcurrent = mslab + reservelen;
+ mcount = *mcurrent++ * 256;
+ mcount += *mcurrent++;
+ scurrent = sslab + reservelen;
+ scount = *scurrent++ * 256;
+ scount += *scurrent++;
+ INSIST(mcount > 0 && scount > 0);
+
+ /*
+ * Yes, this is inefficient!
+ */
+
+ /*
+ * Start figuring out the target length and count.
+ */
+ tlength = reservelen + 2;
+ tcount = 0;
+ rcount = 0;
+
+#if DNS_RDATASET_FIXED
+ mcurrent += 4 * mcount;
+ scurrent += 4 * scount;
+#endif /* if DNS_RDATASET_FIXED */
+ sstart = scurrent;
+
+ /*
+ * Add in the length of rdata in the mslab that aren't in
+ * the sslab.
+ */
+ for (i = 0; i < mcount; i++) {
+ unsigned char *mrdatabegin = mcurrent;
+ rdata_from_slab(&mcurrent, rdclass, type, &mrdata);
+ scurrent = sstart;
+ for (count = 0; count < scount; count++) {
+ dns_rdata_reset(&srdata);
+ rdata_from_slab(&scurrent, rdclass, type, &srdata);
+ if (dns_rdata_compare(&mrdata, &srdata) == 0) {
+ break;
+ }
+ }
+ if (count == scount) {
+ /*
+ * This rdata isn't in the sslab, and thus isn't
+ * being subtracted.
+ */
+ tlength += (unsigned int)(mcurrent - mrdatabegin);
+ tcount++;
+ } else {
+ rcount++;
+ }
+ dns_rdata_reset(&mrdata);
+ }
+
+#if DNS_RDATASET_FIXED
+ tlength += (4 * tcount);
+#endif /* if DNS_RDATASET_FIXED */
+
+ /*
+ * Check that all the records originally existed. The numeric
+ * check only works as rdataslabs do not contain duplicates.
+ */
+ if (((flags & DNS_RDATASLAB_EXACT) != 0) && (rcount != scount)) {
+ return (DNS_R_NOTEXACT);
+ }
+
+ /*
+ * Don't continue if the new rdataslab would be empty.
+ */
+ if (tcount == 0) {
+ return (DNS_R_NXRRSET);
+ }
+
+ /*
+ * If nothing is going to change, we can stop.
+ */
+ if (rcount == 0) {
+ return (DNS_R_UNCHANGED);
+ }
+
+ /*
+ * Copy the reserved area from the mslab.
+ */
+ tstart = isc_mem_get(mctx, tlength);
+ memmove(tstart, mslab, reservelen);
+ tcurrent = tstart + reservelen;
+#if DNS_RDATASET_FIXED
+ offsetbase = tcurrent;
+
+ offsettable = isc_mem_get(mctx, mcount * sizeof(unsigned int));
+ memset(offsettable, 0, mcount * sizeof(unsigned int));
+#endif /* if DNS_RDATASET_FIXED */
+
+ /*
+ * Write the new count.
+ */
+ *tcurrent++ = (tcount & 0xff00) >> 8;
+ *tcurrent++ = (tcount & 0x00ff);
+
+#if DNS_RDATASET_FIXED
+ tcurrent += (4 * tcount);
+#endif /* if DNS_RDATASET_FIXED */
+
+ /*
+ * Copy the parts of mslab not in sslab.
+ */
+ mcurrent = mslab + reservelen;
+ mcount = *mcurrent++ * 256;
+ mcount += *mcurrent++;
+#if DNS_RDATASET_FIXED
+ mcurrent += (4 * mcount);
+#endif /* if DNS_RDATASET_FIXED */
+ for (i = 0; i < mcount; i++) {
+ unsigned char *mrdatabegin = mcurrent;
+#if DNS_RDATASET_FIXED
+ order = mcurrent[2] * 256 + mcurrent[3];
+ INSIST(order < mcount);
+#endif /* if DNS_RDATASET_FIXED */
+ rdata_from_slab(&mcurrent, rdclass, type, &mrdata);
+ scurrent = sstart;
+ for (count = 0; count < scount; count++) {
+ dns_rdata_reset(&srdata);
+ rdata_from_slab(&scurrent, rdclass, type, &srdata);
+ if (dns_rdata_compare(&mrdata, &srdata) == 0) {
+ break;
+ }
+ }
+ if (count == scount) {
+ /*
+ * This rdata isn't in the sslab, and thus should be
+ * copied to the tslab.
+ */
+ unsigned int length;
+ length = (unsigned int)(mcurrent - mrdatabegin);
+#if DNS_RDATASET_FIXED
+ offsettable[order] = tcurrent - offsetbase;
+#endif /* if DNS_RDATASET_FIXED */
+ memmove(tcurrent, mrdatabegin, length);
+ tcurrent += length;
+ }
+ dns_rdata_reset(&mrdata);
+ }
+
+#if DNS_RDATASET_FIXED
+ fillin_offsets(offsetbase, offsettable, mcount);
+
+ isc_mem_put(mctx, offsettable, mcount * sizeof(unsigned int));
+#endif /* if DNS_RDATASET_FIXED */
+
+ INSIST(tcurrent == tstart + tlength);
+
+ *tslabp = tstart;
+
+ return (ISC_R_SUCCESS);
+}
+
+bool
+dns_rdataslab_equal(unsigned char *slab1, unsigned char *slab2,
+ unsigned int reservelen) {
+ unsigned char *current1, *current2;
+ unsigned int count1, count2;
+ unsigned int length1, length2;
+
+ current1 = slab1 + reservelen;
+ count1 = *current1++ * 256;
+ count1 += *current1++;
+
+ current2 = slab2 + reservelen;
+ count2 = *current2++ * 256;
+ count2 += *current2++;
+
+ if (count1 != count2) {
+ return (false);
+ }
+
+#if DNS_RDATASET_FIXED
+ current1 += (4 * count1);
+ current2 += (4 * count2);
+#endif /* if DNS_RDATASET_FIXED */
+
+ while (count1 > 0) {
+ length1 = *current1++ * 256;
+ length1 += *current1++;
+
+ length2 = *current2++ * 256;
+ length2 += *current2++;
+
+#if DNS_RDATASET_FIXED
+ current1 += 2;
+ current2 += 2;
+#endif /* if DNS_RDATASET_FIXED */
+
+ if (length1 != length2 ||
+ memcmp(current1, current2, length1) != 0)
+ {
+ return (false);
+ }
+
+ current1 += length1;
+ current2 += length1;
+
+ count1--;
+ }
+ return (true);
+}
+
+bool
+dns_rdataslab_equalx(unsigned char *slab1, unsigned char *slab2,
+ unsigned int reservelen, dns_rdataclass_t rdclass,
+ dns_rdatatype_t type) {
+ unsigned char *current1, *current2;
+ unsigned int count1, count2;
+ dns_rdata_t rdata1 = DNS_RDATA_INIT;
+ dns_rdata_t rdata2 = DNS_RDATA_INIT;
+
+ current1 = slab1 + reservelen;
+ count1 = *current1++ * 256;
+ count1 += *current1++;
+
+ current2 = slab2 + reservelen;
+ count2 = *current2++ * 256;
+ count2 += *current2++;
+
+ if (count1 != count2) {
+ return (false);
+ }
+
+#if DNS_RDATASET_FIXED
+ current1 += (4 * count1);
+ current2 += (4 * count2);
+#endif /* if DNS_RDATASET_FIXED */
+
+ while (count1-- > 0) {
+ rdata_from_slab(&current1, rdclass, type, &rdata1);
+ rdata_from_slab(&current2, rdclass, type, &rdata2);
+ if (dns_rdata_compare(&rdata1, &rdata2) != 0) {
+ return (false);
+ }
+ dns_rdata_reset(&rdata1);
+ dns_rdata_reset(&rdata2);
+ }
+ return (true);
+}
diff --git a/lib/dns/request.c b/lib/dns/request.c
new file mode 100644
index 0000000..b2b7b11
--- /dev/null
+++ b/lib/dns/request.c
@@ -0,0 +1,1216 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/*! \file */
+
+#include <inttypes.h>
+#include <stdbool.h>
+
+#include <isc/magic.h>
+#include <isc/mem.h>
+#include <isc/netmgr.h>
+#include <isc/result.h>
+#include <isc/task.h>
+#include <isc/thread.h>
+#include <isc/util.h>
+
+#include <dns/acl.h>
+#include <dns/compress.h>
+#include <dns/dispatch.h>
+#include <dns/events.h>
+#include <dns/log.h>
+#include <dns/message.h>
+#include <dns/rdata.h>
+#include <dns/rdatastruct.h>
+#include <dns/request.h>
+#include <dns/tsig.h>
+
+#define REQUESTMGR_MAGIC ISC_MAGIC('R', 'q', 'u', 'M')
+#define VALID_REQUESTMGR(mgr) ISC_MAGIC_VALID(mgr, REQUESTMGR_MAGIC)
+
+#define REQUEST_MAGIC ISC_MAGIC('R', 'q', 'u', '!')
+#define VALID_REQUEST(request) ISC_MAGIC_VALID(request, REQUEST_MAGIC)
+
+typedef ISC_LIST(dns_request_t) dns_requestlist_t;
+
+#define DNS_REQUEST_NLOCKS 7
+
+struct dns_requestmgr {
+ unsigned int magic;
+ isc_refcount_t references;
+
+ isc_mutex_t lock;
+ isc_mem_t *mctx;
+
+ /* locked */
+ isc_taskmgr_t *taskmgr;
+ dns_dispatchmgr_t *dispatchmgr;
+ dns_dispatch_t *dispatchv4;
+ dns_dispatch_t *dispatchv6;
+ atomic_bool exiting;
+ isc_eventlist_t whenshutdown;
+ unsigned int hash;
+ isc_mutex_t locks[DNS_REQUEST_NLOCKS];
+ dns_requestlist_t requests;
+};
+
+struct dns_request {
+ unsigned int magic;
+ isc_refcount_t references;
+
+ unsigned int hash;
+ isc_mem_t *mctx;
+ int32_t flags;
+ ISC_LINK(dns_request_t) link;
+ isc_buffer_t *query;
+ isc_buffer_t *answer;
+ dns_requestevent_t *event;
+ dns_dispatch_t *dispatch;
+ dns_dispentry_t *dispentry;
+ dns_requestmgr_t *requestmgr;
+ isc_buffer_t *tsig;
+ dns_tsigkey_t *tsigkey;
+ isc_sockaddr_t destaddr;
+ unsigned int timeout;
+ unsigned int udpcount;
+};
+
+#define DNS_REQUEST_F_CONNECTING 0x0001
+#define DNS_REQUEST_F_SENDING 0x0002
+#define DNS_REQUEST_F_CANCELED 0x0004
+#define DNS_REQUEST_F_TCP 0x0010
+
+#define DNS_REQUEST_CANCELED(r) (((r)->flags & DNS_REQUEST_F_CANCELED) != 0)
+#define DNS_REQUEST_CONNECTING(r) (((r)->flags & DNS_REQUEST_F_CONNECTING) != 0)
+#define DNS_REQUEST_SENDING(r) (((r)->flags & DNS_REQUEST_F_SENDING) != 0)
+
+/***
+ *** Forward
+ ***/
+
+static void
+mgr_destroy(dns_requestmgr_t *requestmgr);
+static unsigned int
+mgr_gethash(dns_requestmgr_t *requestmgr);
+static void
+send_shutdown_events(dns_requestmgr_t *requestmgr);
+
+static isc_result_t
+req_render(dns_message_t *message, isc_buffer_t **buffer, unsigned int options,
+ isc_mem_t *mctx);
+static void
+req_response(isc_result_t result, isc_region_t *region, void *arg);
+static void
+req_senddone(isc_result_t eresult, isc_region_t *region, void *arg);
+static void
+req_sendevent(dns_request_t *request, isc_result_t result);
+static void
+req_connected(isc_result_t eresult, isc_region_t *region, void *arg);
+static void
+req_attach(dns_request_t *source, dns_request_t **targetp);
+static void
+req_detach(dns_request_t **requestp);
+static void
+req_destroy(dns_request_t *request);
+static void
+req_log(int level, const char *fmt, ...) ISC_FORMAT_PRINTF(2, 3);
+void
+request_cancel(dns_request_t *request);
+
+/***
+ *** Public
+ ***/
+
+isc_result_t
+dns_requestmgr_create(isc_mem_t *mctx, isc_taskmgr_t *taskmgr,
+ dns_dispatchmgr_t *dispatchmgr,
+ dns_dispatch_t *dispatchv4, dns_dispatch_t *dispatchv6,
+ dns_requestmgr_t **requestmgrp) {
+ dns_requestmgr_t *requestmgr;
+ int i;
+
+ req_log(ISC_LOG_DEBUG(3), "dns_requestmgr_create");
+
+ REQUIRE(requestmgrp != NULL && *requestmgrp == NULL);
+ REQUIRE(taskmgr != NULL);
+ REQUIRE(dispatchmgr != NULL);
+
+ requestmgr = isc_mem_get(mctx, sizeof(*requestmgr));
+ *requestmgr = (dns_requestmgr_t){ 0 };
+
+ isc_taskmgr_attach(taskmgr, &requestmgr->taskmgr);
+ dns_dispatchmgr_attach(dispatchmgr, &requestmgr->dispatchmgr);
+ isc_mutex_init(&requestmgr->lock);
+
+ for (i = 0; i < DNS_REQUEST_NLOCKS; i++) {
+ isc_mutex_init(&requestmgr->locks[i]);
+ }
+ if (dispatchv4 != NULL) {
+ dns_dispatch_attach(dispatchv4, &requestmgr->dispatchv4);
+ }
+ if (dispatchv6 != NULL) {
+ dns_dispatch_attach(dispatchv6, &requestmgr->dispatchv6);
+ }
+ isc_mem_attach(mctx, &requestmgr->mctx);
+
+ isc_refcount_init(&requestmgr->references, 1);
+
+ ISC_LIST_INIT(requestmgr->whenshutdown);
+ ISC_LIST_INIT(requestmgr->requests);
+
+ atomic_init(&requestmgr->exiting, false);
+
+ requestmgr->magic = REQUESTMGR_MAGIC;
+
+ req_log(ISC_LOG_DEBUG(3), "dns_requestmgr_create: %p", requestmgr);
+
+ *requestmgrp = requestmgr;
+ return (ISC_R_SUCCESS);
+}
+
+void
+dns_requestmgr_whenshutdown(dns_requestmgr_t *requestmgr, isc_task_t *task,
+ isc_event_t **eventp) {
+ isc_task_t *tclone;
+ isc_event_t *event;
+
+ req_log(ISC_LOG_DEBUG(3), "dns_requestmgr_whenshutdown");
+
+ REQUIRE(VALID_REQUESTMGR(requestmgr));
+ REQUIRE(eventp != NULL);
+
+ event = *eventp;
+ *eventp = NULL;
+
+ LOCK(&requestmgr->lock);
+
+ if (atomic_load_acquire(&requestmgr->exiting)) {
+ /*
+ * We're already shutdown. Send the event.
+ */
+ event->ev_sender = requestmgr;
+ isc_task_send(task, &event);
+ } else {
+ tclone = NULL;
+ isc_task_attach(task, &tclone);
+ event->ev_sender = tclone;
+ ISC_LIST_APPEND(requestmgr->whenshutdown, event, ev_link);
+ }
+ UNLOCK(&requestmgr->lock);
+}
+
+void
+dns_requestmgr_shutdown(dns_requestmgr_t *requestmgr) {
+ dns_request_t *request;
+
+ REQUIRE(VALID_REQUESTMGR(requestmgr));
+
+ req_log(ISC_LOG_DEBUG(3), "dns_requestmgr_shutdown: %p", requestmgr);
+
+ if (!atomic_compare_exchange_strong(&requestmgr->exiting,
+ &(bool){ false }, true))
+ {
+ return;
+ }
+
+ LOCK(&requestmgr->lock);
+ for (request = ISC_LIST_HEAD(requestmgr->requests); request != NULL;
+ request = ISC_LIST_NEXT(request, link))
+ {
+ dns_request_cancel(request);
+ }
+
+ if (ISC_LIST_EMPTY(requestmgr->requests)) {
+ send_shutdown_events(requestmgr);
+ }
+
+ UNLOCK(&requestmgr->lock);
+}
+
+void
+dns_requestmgr_attach(dns_requestmgr_t *source, dns_requestmgr_t **targetp) {
+ uint_fast32_t ref;
+
+ REQUIRE(VALID_REQUESTMGR(source));
+ REQUIRE(targetp != NULL && *targetp == NULL);
+
+ REQUIRE(!atomic_load_acquire(&source->exiting));
+
+ ref = isc_refcount_increment(&source->references);
+
+ req_log(ISC_LOG_DEBUG(3),
+ "dns_requestmgr_attach: %p: references = %" PRIuFAST32, source,
+ ref + 1);
+
+ *targetp = source;
+}
+
+void
+dns_requestmgr_detach(dns_requestmgr_t **requestmgrp) {
+ dns_requestmgr_t *requestmgr = NULL;
+ uint_fast32_t ref;
+
+ REQUIRE(requestmgrp != NULL && VALID_REQUESTMGR(*requestmgrp));
+
+ requestmgr = *requestmgrp;
+ *requestmgrp = NULL;
+
+ ref = isc_refcount_decrement(&requestmgr->references);
+
+ req_log(ISC_LOG_DEBUG(3),
+ "dns_requestmgr_detach: %p: references = %" PRIuFAST32,
+ requestmgr, ref - 1);
+
+ if (ref == 1) {
+ INSIST(ISC_LIST_EMPTY(requestmgr->requests));
+ mgr_destroy(requestmgr);
+ }
+}
+
+/* FIXME */
+static void
+send_shutdown_events(dns_requestmgr_t *requestmgr) {
+ isc_event_t *event, *next_event;
+ isc_task_t *etask;
+
+ req_log(ISC_LOG_DEBUG(3), "send_shutdown_events: %p", requestmgr);
+
+ /*
+ * Caller must be holding the manager lock.
+ */
+ for (event = ISC_LIST_HEAD(requestmgr->whenshutdown); event != NULL;
+ event = next_event)
+ {
+ next_event = ISC_LIST_NEXT(event, ev_link);
+ ISC_LIST_UNLINK(requestmgr->whenshutdown, event, ev_link);
+ etask = event->ev_sender;
+ event->ev_sender = requestmgr;
+ isc_task_sendanddetach(&etask, &event);
+ }
+}
+
+static void
+mgr_destroy(dns_requestmgr_t *requestmgr) {
+ int i;
+
+ req_log(ISC_LOG_DEBUG(3), "mgr_destroy");
+
+ isc_refcount_destroy(&requestmgr->references);
+
+ isc_mutex_destroy(&requestmgr->lock);
+ for (i = 0; i < DNS_REQUEST_NLOCKS; i++) {
+ isc_mutex_destroy(&requestmgr->locks[i]);
+ }
+ if (requestmgr->dispatchv4 != NULL) {
+ dns_dispatch_detach(&requestmgr->dispatchv4);
+ }
+ if (requestmgr->dispatchv6 != NULL) {
+ dns_dispatch_detach(&requestmgr->dispatchv6);
+ }
+ if (requestmgr->dispatchmgr != NULL) {
+ dns_dispatchmgr_detach(&requestmgr->dispatchmgr);
+ }
+ if (requestmgr->taskmgr != NULL) {
+ isc_taskmgr_detach(&requestmgr->taskmgr);
+ }
+ requestmgr->magic = 0;
+ isc_mem_putanddetach(&requestmgr->mctx, requestmgr,
+ sizeof(*requestmgr));
+}
+
+static unsigned int
+mgr_gethash(dns_requestmgr_t *requestmgr) {
+ req_log(ISC_LOG_DEBUG(3), "mgr_gethash");
+ /*
+ * Locked by caller.
+ */
+ requestmgr->hash++;
+ return (requestmgr->hash % DNS_REQUEST_NLOCKS);
+}
+
+static void
+req_send(dns_request_t *request) {
+ isc_region_t r;
+
+ req_log(ISC_LOG_DEBUG(3), "req_send: request %p", request);
+
+ REQUIRE(VALID_REQUEST(request));
+
+ isc_buffer_usedregion(request->query, &r);
+
+ request->flags |= DNS_REQUEST_F_SENDING;
+
+ /* detached in req_senddone() */
+ req_attach(request, &(dns_request_t *){ NULL });
+ dns_dispatch_send(request->dispentry, &r);
+}
+
+static isc_result_t
+new_request(isc_mem_t *mctx, dns_request_t **requestp) {
+ dns_request_t *request = NULL;
+
+ request = isc_mem_get(mctx, sizeof(*request));
+ *request = (dns_request_t){ 0 };
+ ISC_LINK_INIT(request, link);
+
+ isc_refcount_init(&request->references, 1);
+ isc_mem_attach(mctx, &request->mctx);
+
+ request->magic = REQUEST_MAGIC;
+ *requestp = request;
+ return (ISC_R_SUCCESS);
+}
+
+static bool
+isblackholed(dns_dispatchmgr_t *dispatchmgr, const isc_sockaddr_t *destaddr) {
+ dns_acl_t *blackhole;
+ isc_netaddr_t netaddr;
+ char netaddrstr[ISC_NETADDR_FORMATSIZE];
+ int match;
+ isc_result_t result;
+
+ blackhole = dns_dispatchmgr_getblackhole(dispatchmgr);
+ if (blackhole == NULL) {
+ return (false);
+ }
+
+ isc_netaddr_fromsockaddr(&netaddr, destaddr);
+ result = dns_acl_match(&netaddr, NULL, blackhole, NULL, &match, NULL);
+ if (result != ISC_R_SUCCESS || match <= 0) {
+ return (false);
+ }
+
+ isc_netaddr_format(&netaddr, netaddrstr, sizeof(netaddrstr));
+ req_log(ISC_LOG_DEBUG(10), "blackholed address %s", netaddrstr);
+
+ return (true);
+}
+
+static isc_result_t
+tcp_dispatch(bool newtcp, dns_requestmgr_t *requestmgr,
+ const isc_sockaddr_t *srcaddr, const isc_sockaddr_t *destaddr,
+ dns_dispatch_t **dispatchp) {
+ isc_result_t result;
+
+ if (!newtcp) {
+ result = dns_dispatch_gettcp(requestmgr->dispatchmgr, destaddr,
+ srcaddr, dispatchp);
+ if (result == ISC_R_SUCCESS) {
+ char peer[ISC_SOCKADDR_FORMATSIZE];
+
+ isc_sockaddr_format(destaddr, peer, sizeof(peer));
+ req_log(ISC_LOG_DEBUG(1),
+ "attached to TCP connection to %s", peer);
+ return (result);
+ }
+ }
+
+ result = dns_dispatch_createtcp(requestmgr->dispatchmgr, srcaddr,
+ destaddr, dispatchp);
+ return (result);
+}
+
+static isc_result_t
+udp_dispatch(dns_requestmgr_t *requestmgr, const isc_sockaddr_t *srcaddr,
+ const isc_sockaddr_t *destaddr, dns_dispatch_t **dispatchp) {
+ dns_dispatch_t *disp = NULL;
+
+ if (srcaddr == NULL) {
+ switch (isc_sockaddr_pf(destaddr)) {
+ case PF_INET:
+ disp = requestmgr->dispatchv4;
+ break;
+
+ case PF_INET6:
+ disp = requestmgr->dispatchv6;
+ break;
+
+ default:
+ return (ISC_R_NOTIMPLEMENTED);
+ }
+ if (disp == NULL) {
+ return (ISC_R_FAMILYNOSUPPORT);
+ }
+ dns_dispatch_attach(disp, dispatchp);
+ return (ISC_R_SUCCESS);
+ }
+
+ return (dns_dispatch_createudp(requestmgr->dispatchmgr, srcaddr,
+ dispatchp));
+}
+
+static isc_result_t
+get_dispatch(bool tcp, bool newtcp, dns_requestmgr_t *requestmgr,
+ const isc_sockaddr_t *srcaddr, const isc_sockaddr_t *destaddr,
+ dns_dispatch_t **dispatchp) {
+ isc_result_t result;
+
+ if (tcp) {
+ result = tcp_dispatch(newtcp, requestmgr, srcaddr, destaddr,
+ dispatchp);
+ } else {
+ result = udp_dispatch(requestmgr, srcaddr, destaddr, dispatchp);
+ }
+ return (result);
+}
+
+isc_result_t
+dns_request_createraw(dns_requestmgr_t *requestmgr, isc_buffer_t *msgbuf,
+ const isc_sockaddr_t *srcaddr,
+ const isc_sockaddr_t *destaddr, unsigned int options,
+ unsigned int timeout, unsigned int udptimeout,
+ unsigned int udpretries, isc_task_t *task,
+ isc_taskaction_t action, void *arg,
+ dns_request_t **requestp) {
+ dns_request_t *request = NULL;
+ isc_result_t result;
+ isc_mem_t *mctx = NULL;
+ dns_messageid_t id;
+ bool tcp = false;
+ bool newtcp = false;
+ isc_region_t r;
+ unsigned int dispopt = 0;
+
+ REQUIRE(VALID_REQUESTMGR(requestmgr));
+ REQUIRE(msgbuf != NULL);
+ REQUIRE(destaddr != NULL);
+ REQUIRE(task != NULL);
+ REQUIRE(action != NULL);
+ REQUIRE(requestp != NULL && *requestp == NULL);
+ REQUIRE(timeout > 0);
+ REQUIRE(udpretries != UINT_MAX);
+
+ if (srcaddr != NULL) {
+ REQUIRE(isc_sockaddr_pf(srcaddr) == isc_sockaddr_pf(destaddr));
+ }
+
+ mctx = requestmgr->mctx;
+
+ req_log(ISC_LOG_DEBUG(3), "dns_request_createraw");
+
+ if (atomic_load_acquire(&requestmgr->exiting)) {
+ return (ISC_R_SHUTTINGDOWN);
+ }
+
+ if (isblackholed(requestmgr->dispatchmgr, destaddr)) {
+ return (DNS_R_BLACKHOLED);
+ }
+
+ /* detached in dns_request_destroy() */
+ result = new_request(mctx, &request);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+
+ request->udpcount = udpretries + 1;
+
+ request->event = (dns_requestevent_t *)isc_event_allocate(
+ mctx, task, DNS_EVENT_REQUESTDONE, action, arg,
+ sizeof(dns_requestevent_t));
+ isc_task_attach(task, &(isc_task_t *){ NULL });
+ request->event->ev_sender = task;
+ request->event->request = request;
+ request->event->result = ISC_R_FAILURE;
+
+ isc_buffer_usedregion(msgbuf, &r);
+ if (r.length < DNS_MESSAGE_HEADERLEN || r.length > 65535) {
+ result = DNS_R_FORMERR;
+ goto cleanup;
+ }
+
+ if ((options & DNS_REQUESTOPT_TCP) != 0 || r.length > 512) {
+ tcp = true;
+ request->timeout = timeout * 1000;
+ } else {
+ if (udptimeout == 0) {
+ udptimeout = timeout / request->udpcount;
+ }
+ if (udptimeout == 0) {
+ udptimeout = 1;
+ }
+ request->timeout = udptimeout * 1000;
+ }
+
+ isc_buffer_allocate(mctx, &request->query, r.length + (tcp ? 2 : 0));
+ result = isc_buffer_copyregion(request->query, &r);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+
+ /* detached in req_connected() */
+ req_attach(request, &(dns_request_t *){ NULL });
+
+again:
+
+ result = get_dispatch(tcp, newtcp, requestmgr, srcaddr, destaddr,
+ &request->dispatch);
+ if (result != ISC_R_SUCCESS) {
+ goto detach;
+ }
+
+ if ((options & DNS_REQUESTOPT_FIXEDID) != 0) {
+ id = (r.base[0] << 8) | r.base[1];
+ dispopt |= DNS_DISPATCHOPT_FIXEDID;
+ }
+
+ result = dns_dispatch_add(request->dispatch, dispopt, request->timeout,
+ destaddr, req_connected, req_senddone,
+ req_response, request, &id,
+ &request->dispentry);
+ if (result != ISC_R_SUCCESS) {
+ if ((options & DNS_REQUESTOPT_FIXEDID) != 0 && !newtcp) {
+ newtcp = true;
+ dns_dispatch_detach(&request->dispatch);
+ goto again;
+ }
+
+ goto detach;
+ }
+
+ /* Add message ID. */
+ isc_buffer_usedregion(request->query, &r);
+ r.base[0] = (id >> 8) & 0xff;
+ r.base[1] = id & 0xff;
+
+ LOCK(&requestmgr->lock);
+ dns_requestmgr_attach(requestmgr, &request->requestmgr);
+ request->hash = mgr_gethash(requestmgr);
+ ISC_LIST_APPEND(requestmgr->requests, request, link);
+ UNLOCK(&requestmgr->lock);
+
+ request->destaddr = *destaddr;
+
+ request->flags |= DNS_REQUEST_F_CONNECTING;
+ if (tcp) {
+ request->flags |= DNS_REQUEST_F_TCP;
+ }
+
+ result = dns_dispatch_connect(request->dispentry);
+ if (result != ISC_R_SUCCESS) {
+ goto unlink;
+ }
+
+ req_log(ISC_LOG_DEBUG(3), "dns_request_createraw: request %p", request);
+ *requestp = request;
+ return (ISC_R_SUCCESS);
+
+unlink:
+ LOCK(&requestmgr->lock);
+ ISC_LIST_UNLINK(requestmgr->requests, request, link);
+ UNLOCK(&requestmgr->lock);
+
+detach:
+ /* connect failed, detach here */
+ req_detach(&(dns_request_t *){ request });
+
+cleanup:
+ isc_task_detach(&(isc_task_t *){ task });
+ /* final detach to shut down request */
+ req_detach(&request);
+ req_log(ISC_LOG_DEBUG(3), "dns_request_createraw: failed %s",
+ isc_result_totext(result));
+ return (result);
+}
+
+isc_result_t
+dns_request_create(dns_requestmgr_t *requestmgr, dns_message_t *message,
+ const isc_sockaddr_t *srcaddr,
+ const isc_sockaddr_t *destaddr, unsigned int options,
+ dns_tsigkey_t *key, unsigned int timeout,
+ unsigned int udptimeout, unsigned int udpretries,
+ isc_task_t *task, isc_taskaction_t action, void *arg,
+ dns_request_t **requestp) {
+ dns_request_t *request = NULL;
+ isc_result_t result;
+ isc_mem_t *mctx = NULL;
+ dns_messageid_t id;
+ bool tcp = false;
+ bool connected = false;
+
+ REQUIRE(VALID_REQUESTMGR(requestmgr));
+ REQUIRE(message != NULL);
+ REQUIRE(destaddr != NULL);
+ REQUIRE(task != NULL);
+ REQUIRE(action != NULL);
+ REQUIRE(requestp != NULL && *requestp == NULL);
+ REQUIRE(timeout > 0);
+ REQUIRE(udpretries != UINT_MAX);
+
+ mctx = requestmgr->mctx;
+
+ req_log(ISC_LOG_DEBUG(3), "dns_request_create");
+
+ if (atomic_load_acquire(&requestmgr->exiting)) {
+ return (ISC_R_SHUTTINGDOWN);
+ }
+
+ if (srcaddr != NULL &&
+ isc_sockaddr_pf(srcaddr) != isc_sockaddr_pf(destaddr))
+ {
+ return (ISC_R_FAMILYMISMATCH);
+ }
+
+ if (isblackholed(requestmgr->dispatchmgr, destaddr)) {
+ return (DNS_R_BLACKHOLED);
+ }
+
+ /* detached in dns_request_destroy() */
+ result = new_request(mctx, &request);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+
+ request->udpcount = udpretries + 1;
+
+ request->event = (dns_requestevent_t *)isc_event_allocate(
+ mctx, task, DNS_EVENT_REQUESTDONE, action, arg,
+ sizeof(dns_requestevent_t));
+ isc_task_attach(task, &(isc_task_t *){ NULL });
+ request->event->ev_sender = task;
+ request->event->request = request;
+ request->event->result = ISC_R_FAILURE;
+
+ if (key != NULL) {
+ dns_tsigkey_attach(key, &request->tsigkey);
+ }
+
+ result = dns_message_settsigkey(message, request->tsigkey);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+
+ if ((options & DNS_REQUESTOPT_TCP) != 0) {
+ tcp = true;
+ request->timeout = timeout * 1000;
+ } else {
+ if (udptimeout == 0) {
+ udptimeout = timeout / request->udpcount;
+ }
+ if (udptimeout == 0) {
+ udptimeout = 1;
+ }
+ request->timeout = udptimeout * 1000;
+ }
+
+ /* detached in req_connected() */
+ req_attach(request, &(dns_request_t *){ NULL });
+
+again:
+ result = get_dispatch(tcp, false, requestmgr, srcaddr, destaddr,
+ &request->dispatch);
+ if (result != ISC_R_SUCCESS) {
+ goto detach;
+ }
+
+ result = dns_dispatch_add(
+ request->dispatch, 0, request->timeout, destaddr, req_connected,
+ req_senddone, req_response, request, &id, &request->dispentry);
+ if (result != ISC_R_SUCCESS) {
+ goto detach;
+ }
+
+ message->id = id;
+ result = req_render(message, &request->query, options, mctx);
+ if (result == DNS_R_USETCP && !tcp) {
+ /*
+ * Try again using TCP.
+ */
+ dns_message_renderreset(message);
+ dns_dispatch_done(&request->dispentry);
+ dns_dispatch_detach(&request->dispatch);
+ options |= DNS_REQUESTOPT_TCP;
+ tcp = true;
+ goto again;
+ }
+ if (result != ISC_R_SUCCESS) {
+ goto detach;
+ }
+
+ result = dns_message_getquerytsig(message, mctx, &request->tsig);
+ if (result != ISC_R_SUCCESS) {
+ goto detach;
+ }
+
+ LOCK(&requestmgr->lock);
+ dns_requestmgr_attach(requestmgr, &request->requestmgr);
+ request->hash = mgr_gethash(requestmgr);
+ ISC_LIST_APPEND(requestmgr->requests, request, link);
+ UNLOCK(&requestmgr->lock);
+
+ request->destaddr = *destaddr;
+ if (tcp && connected) {
+ req_send(request);
+
+ /* no need to call req_connected(), detach here */
+ req_detach(&(dns_request_t *){ request });
+ } else {
+ request->flags |= DNS_REQUEST_F_CONNECTING;
+ if (tcp) {
+ request->flags |= DNS_REQUEST_F_TCP;
+ }
+
+ result = dns_dispatch_connect(request->dispentry);
+ if (result != ISC_R_SUCCESS) {
+ goto unlink;
+ }
+ }
+
+ req_log(ISC_LOG_DEBUG(3), "dns_request_create: request %p", request);
+ *requestp = request;
+ return (ISC_R_SUCCESS);
+
+unlink:
+ LOCK(&requestmgr->lock);
+ ISC_LIST_UNLINK(requestmgr->requests, request, link);
+ UNLOCK(&requestmgr->lock);
+
+detach:
+ /* connect failed, detach here */
+ req_detach(&(dns_request_t *){ request });
+
+cleanup:
+ isc_task_detach(&(isc_task_t *){ task });
+ /* final detach to shut down request */
+ req_detach(&request);
+ req_log(ISC_LOG_DEBUG(3), "dns_request_create: failed %s",
+ isc_result_totext(result));
+ return (result);
+}
+
+static isc_result_t
+req_render(dns_message_t *message, isc_buffer_t **bufferp, unsigned int options,
+ isc_mem_t *mctx) {
+ isc_buffer_t *buf1 = NULL;
+ isc_buffer_t *buf2 = NULL;
+ isc_result_t result;
+ isc_region_t r;
+ dns_compress_t cctx;
+ bool cleanup_cctx = false;
+
+ REQUIRE(bufferp != NULL && *bufferp == NULL);
+
+ req_log(ISC_LOG_DEBUG(3), "request_render");
+
+ /*
+ * Create buffer able to hold largest possible message.
+ */
+ isc_buffer_allocate(mctx, &buf1, 65535);
+
+ result = dns_compress_init(&cctx, -1, mctx);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+ cleanup_cctx = true;
+
+ if ((options & DNS_REQUESTOPT_CASE) != 0) {
+ dns_compress_setsensitive(&cctx, true);
+ }
+
+ /*
+ * Render message.
+ */
+ result = dns_message_renderbegin(message, &cctx, buf1);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+ result = dns_message_rendersection(message, DNS_SECTION_QUESTION, 0);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+ result = dns_message_rendersection(message, DNS_SECTION_ANSWER, 0);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+ result = dns_message_rendersection(message, DNS_SECTION_AUTHORITY, 0);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+ result = dns_message_rendersection(message, DNS_SECTION_ADDITIONAL, 0);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+ result = dns_message_renderend(message);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+
+ dns_compress_invalidate(&cctx);
+ cleanup_cctx = false;
+
+ /*
+ * Copy rendered message to exact sized buffer.
+ */
+ isc_buffer_usedregion(buf1, &r);
+ if ((options & DNS_REQUESTOPT_TCP) == 0 && r.length > 512) {
+ result = DNS_R_USETCP;
+ goto cleanup;
+ }
+ isc_buffer_allocate(mctx, &buf2, r.length);
+ result = isc_buffer_copyregion(buf2, &r);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+
+ /*
+ * Cleanup and return.
+ */
+ isc_buffer_free(&buf1);
+ *bufferp = buf2;
+ return (ISC_R_SUCCESS);
+
+cleanup:
+ dns_message_renderreset(message);
+ if (buf1 != NULL) {
+ isc_buffer_free(&buf1);
+ }
+ if (buf2 != NULL) {
+ isc_buffer_free(&buf2);
+ }
+ if (cleanup_cctx) {
+ dns_compress_invalidate(&cctx);
+ }
+ return (result);
+}
+
+void
+request_cancel(dns_request_t *request) {
+ if (!DNS_REQUEST_CANCELED(request)) {
+ req_log(ISC_LOG_DEBUG(3), "request_cancel: request %p",
+ request);
+
+ request->flags |= DNS_REQUEST_F_CANCELED;
+ request->flags &= ~DNS_REQUEST_F_CONNECTING;
+
+ if (request->dispentry != NULL) {
+ dns_dispatch_done(&request->dispentry);
+ }
+
+ dns_dispatch_detach(&request->dispatch);
+ }
+}
+
+void
+dns_request_cancel(dns_request_t *request) {
+ REQUIRE(VALID_REQUEST(request));
+
+ req_log(ISC_LOG_DEBUG(3), "dns_request_cancel: request %p", request);
+ LOCK(&request->requestmgr->locks[request->hash]);
+ request_cancel(request);
+ req_sendevent(request, ISC_R_CANCELED);
+ UNLOCK(&request->requestmgr->locks[request->hash]);
+}
+
+isc_result_t
+dns_request_getresponse(dns_request_t *request, dns_message_t *message,
+ unsigned int options) {
+ isc_result_t result;
+
+ REQUIRE(VALID_REQUEST(request));
+ REQUIRE(request->answer != NULL);
+
+ req_log(ISC_LOG_DEBUG(3), "dns_request_getresponse: request %p",
+ request);
+
+ result = dns_message_setquerytsig(message, request->tsig);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+ result = dns_message_settsigkey(message, request->tsigkey);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+ result = dns_message_parse(message, request->answer, options);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+ if (request->tsigkey != NULL) {
+ result = dns_tsig_verify(request->answer, message, NULL, NULL);
+ }
+ return (result);
+}
+
+isc_buffer_t *
+dns_request_getanswer(dns_request_t *request) {
+ REQUIRE(VALID_REQUEST(request));
+
+ return (request->answer);
+}
+
+bool
+dns_request_usedtcp(dns_request_t *request) {
+ REQUIRE(VALID_REQUEST(request));
+
+ return ((request->flags & DNS_REQUEST_F_TCP) != 0);
+}
+
+void
+dns_request_destroy(dns_request_t **requestp) {
+ dns_request_t *request;
+
+ REQUIRE(requestp != NULL && VALID_REQUEST(*requestp));
+
+ request = *requestp;
+ *requestp = NULL;
+
+ req_log(ISC_LOG_DEBUG(3), "dns_request_destroy: request %p", request);
+
+ LOCK(&request->requestmgr->lock);
+ LOCK(&request->requestmgr->locks[request->hash]);
+ ISC_LIST_UNLINK(request->requestmgr->requests, request, link);
+ UNLOCK(&request->requestmgr->locks[request->hash]);
+ UNLOCK(&request->requestmgr->lock);
+
+ /*
+ * These should have been cleaned up before the completion
+ * event was sent.
+ */
+ INSIST(request->dispentry == NULL);
+ INSIST(request->dispatch == NULL);
+
+ /* final detach to shut down request */
+ req_detach(&request);
+}
+
+static void
+req_connected(isc_result_t eresult, isc_region_t *region, void *arg) {
+ dns_request_t *request = (dns_request_t *)arg;
+
+ UNUSED(region);
+
+ req_log(ISC_LOG_DEBUG(3), "req_connected: request %p: %s", request,
+ isc_result_totext(eresult));
+
+ REQUIRE(VALID_REQUEST(request));
+ REQUIRE(DNS_REQUEST_CONNECTING(request) ||
+ DNS_REQUEST_CANCELED(request));
+
+ LOCK(&request->requestmgr->locks[request->hash]);
+ request->flags &= ~DNS_REQUEST_F_CONNECTING;
+
+ if (eresult == ISC_R_TIMEDOUT) {
+ dns_dispatch_done(&request->dispentry);
+ dns_dispatch_detach(&request->dispatch);
+ req_sendevent(request, eresult);
+ } else if (DNS_REQUEST_CANCELED(request)) {
+ req_sendevent(request, ISC_R_CANCELED);
+ } else if (eresult == ISC_R_SUCCESS) {
+ req_send(request);
+ } else {
+ request_cancel(request);
+ req_sendevent(request, ISC_R_CANCELED);
+ }
+ UNLOCK(&request->requestmgr->locks[request->hash]);
+
+ /* attached in dns_request_create/_createraw() */
+ req_detach(&(dns_request_t *){ request });
+}
+
+static void
+req_senddone(isc_result_t eresult, isc_region_t *region, void *arg) {
+ dns_request_t *request = (dns_request_t *)arg;
+
+ REQUIRE(VALID_REQUEST(request));
+ REQUIRE(DNS_REQUEST_SENDING(request));
+
+ UNUSED(region);
+
+ req_log(ISC_LOG_DEBUG(3), "req_senddone: request %p", request);
+
+ LOCK(&request->requestmgr->locks[request->hash]);
+ request->flags &= ~DNS_REQUEST_F_SENDING;
+
+ if (DNS_REQUEST_CANCELED(request)) {
+ if (eresult == ISC_R_TIMEDOUT) {
+ req_sendevent(request, eresult);
+ } else {
+ req_sendevent(request, ISC_R_CANCELED);
+ }
+ } else if (eresult != ISC_R_SUCCESS) {
+ request_cancel(request);
+ req_sendevent(request, ISC_R_CANCELED);
+ }
+
+ UNLOCK(&request->requestmgr->locks[request->hash]);
+
+ /* attached in req_send() */
+ req_detach(&request);
+}
+
+static void
+req_response(isc_result_t result, isc_region_t *region, void *arg) {
+ dns_request_t *request = (dns_request_t *)arg;
+
+ if (result == ISC_R_CANCELED) {
+ return;
+ }
+
+ req_log(ISC_LOG_DEBUG(3), "req_response: request %p: %s", request,
+ isc_result_totext(result));
+
+ REQUIRE(VALID_REQUEST(request));
+
+ if (result == ISC_R_TIMEDOUT) {
+ LOCK(&request->requestmgr->locks[request->hash]);
+ if (request->udpcount > 1 &&
+ (request->flags & DNS_REQUEST_F_TCP) == 0)
+ {
+ request->udpcount -= 1;
+ dns_dispatch_resume(request->dispentry,
+ request->timeout);
+ if (!DNS_REQUEST_SENDING(request)) {
+ req_send(request);
+ }
+ UNLOCK(&request->requestmgr->locks[request->hash]);
+ return;
+ }
+
+ /* The lock is unlocked below */
+ goto done;
+ }
+
+ LOCK(&request->requestmgr->locks[request->hash]);
+
+ if (result != ISC_R_SUCCESS) {
+ goto done;
+ }
+
+ /*
+ * Copy region to request.
+ */
+ isc_buffer_allocate(request->mctx, &request->answer, region->length);
+ result = isc_buffer_copyregion(request->answer, region);
+ if (result != ISC_R_SUCCESS) {
+ isc_buffer_free(&request->answer);
+ }
+
+done:
+ /*
+ * Cleanup.
+ */
+ if (request->dispentry != NULL) {
+ dns_dispatch_done(&request->dispentry);
+ }
+ request_cancel(request);
+
+ /*
+ * Send completion event.
+ */
+ req_sendevent(request, result);
+ UNLOCK(&request->requestmgr->locks[request->hash]);
+}
+
+static void
+req_sendevent(dns_request_t *request, isc_result_t result) {
+ isc_task_t *task = NULL;
+
+ REQUIRE(VALID_REQUEST(request));
+
+ if (request->event == NULL) {
+ return;
+ }
+
+ req_log(ISC_LOG_DEBUG(3), "req_sendevent: request %p", request);
+
+ /*
+ * Lock held by caller.
+ */
+ task = request->event->ev_sender;
+ request->event->ev_sender = request;
+ request->event->result = result;
+
+ isc_task_sendanddetach(&task, (isc_event_t **)&request->event);
+}
+
+static void
+req_attach(dns_request_t *source, dns_request_t **targetp) {
+ REQUIRE(VALID_REQUEST(source));
+ REQUIRE(targetp != NULL && *targetp == NULL);
+
+ isc_refcount_increment(&source->references);
+
+ *targetp = source;
+}
+
+static void
+req_detach(dns_request_t **requestp) {
+ dns_request_t *request = NULL;
+ uint_fast32_t ref;
+
+ REQUIRE(requestp != NULL && VALID_REQUEST(*requestp));
+
+ request = *requestp;
+ *requestp = NULL;
+
+ ref = isc_refcount_decrement(&request->references);
+
+ if (request->requestmgr != NULL &&
+ atomic_load_acquire(&request->requestmgr->exiting))
+ {
+ /* We are shutting down and this was last request */
+ LOCK(&request->requestmgr->lock);
+ if (ISC_LIST_EMPTY(request->requestmgr->requests)) {
+ send_shutdown_events(request->requestmgr);
+ }
+ UNLOCK(&request->requestmgr->lock);
+ }
+
+ if (ref == 1) {
+ req_destroy(request);
+ }
+}
+
+static void
+req_destroy(dns_request_t *request) {
+ REQUIRE(VALID_REQUEST(request));
+
+ req_log(ISC_LOG_DEBUG(3), "req_destroy: request %p", request);
+
+ isc_refcount_destroy(&request->references);
+
+ request->magic = 0;
+ if (request->query != NULL) {
+ isc_buffer_free(&request->query);
+ }
+ if (request->answer != NULL) {
+ isc_buffer_free(&request->answer);
+ }
+ if (request->event != NULL) {
+ isc_event_free((isc_event_t **)&request->event);
+ }
+ if (request->dispentry != NULL) {
+ dns_dispatch_done(&request->dispentry);
+ }
+ if (request->dispatch != NULL) {
+ dns_dispatch_detach(&request->dispatch);
+ }
+ if (request->tsig != NULL) {
+ isc_buffer_free(&request->tsig);
+ }
+ if (request->tsigkey != NULL) {
+ dns_tsigkey_detach(&request->tsigkey);
+ }
+ if (request->requestmgr != NULL) {
+ dns_requestmgr_detach(&request->requestmgr);
+ }
+ isc_mem_putanddetach(&request->mctx, request, sizeof(*request));
+}
+
+static void
+req_log(int level, const char *fmt, ...) {
+ va_list ap;
+
+ va_start(ap, fmt);
+ isc_log_vwrite(dns_lctx, DNS_LOGCATEGORY_GENERAL, DNS_LOGMODULE_REQUEST,
+ level, fmt, ap);
+ va_end(ap);
+}
diff --git a/lib/dns/resolver.c b/lib/dns/resolver.c
new file mode 100644
index 0000000..66bb1ac
--- /dev/null
+++ b/lib/dns/resolver.c
@@ -0,0 +1,11753 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/*! \file */
+
+#include <ctype.h>
+#include <inttypes.h>
+#include <stdbool.h>
+
+#include <isc/atomic.h>
+#include <isc/counter.h>
+#include <isc/log.h>
+#include <isc/print.h>
+#include <isc/random.h>
+#include <isc/refcount.h>
+#include <isc/result.h>
+#include <isc/siphash.h>
+#include <isc/stats.h>
+#include <isc/string.h>
+#include <isc/task.h>
+#include <isc/time.h>
+#include <isc/timer.h>
+#include <isc/util.h>
+
+#include <dns/acl.h>
+#include <dns/adb.h>
+#include <dns/badcache.h>
+#include <dns/cache.h>
+#include <dns/db.h>
+#include <dns/dispatch.h>
+#include <dns/dnstap.h>
+#include <dns/ds.h>
+#include <dns/edns.h>
+#include <dns/events.h>
+#include <dns/forward.h>
+#include <dns/keytable.h>
+#include <dns/log.h>
+#include <dns/message.h>
+#include <dns/ncache.h>
+#include <dns/nsec.h>
+#include <dns/nsec3.h>
+#include <dns/opcode.h>
+#include <dns/peer.h>
+#include <dns/rbt.h>
+#include <dns/rcode.h>
+#include <dns/rdata.h>
+#include <dns/rdataclass.h>
+#include <dns/rdatalist.h>
+#include <dns/rdataset.h>
+#include <dns/rdatastruct.h>
+#include <dns/rdatatype.h>
+#include <dns/resolver.h>
+#include <dns/rootns.h>
+#include <dns/stats.h>
+#include <dns/tsig.h>
+#include <dns/validator.h>
+#include <dns/zone.h>
+
+/* Detailed logging of fctx attach/detach */
+#ifndef FCTX_TRACE
+#undef FCTX_TRACE
+#endif
+
+#ifdef WANT_QUERYTRACE
+#define RTRACE(m) \
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_RESOLVER, \
+ DNS_LOGMODULE_RESOLVER, ISC_LOG_DEBUG(3), "res %p: %s", \
+ res, (m))
+#define RRTRACE(r, m) \
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_RESOLVER, \
+ DNS_LOGMODULE_RESOLVER, ISC_LOG_DEBUG(3), "res %p: %s", \
+ (r), (m))
+#define FCTXTRACE(m) \
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_RESOLVER, \
+ DNS_LOGMODULE_RESOLVER, ISC_LOG_DEBUG(3), \
+ "fctx %p(%s): %s", fctx, fctx->info, (m))
+#define FCTXTRACE2(m1, m2) \
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_RESOLVER, \
+ DNS_LOGMODULE_RESOLVER, ISC_LOG_DEBUG(3), \
+ "fctx %p(%s): %s %s", fctx, fctx->info, (m1), (m2))
+#define FCTXTRACE3(m, res) \
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_RESOLVER, \
+ DNS_LOGMODULE_RESOLVER, ISC_LOG_DEBUG(3), \
+ "fctx %p(%s): [result: %s] %s", fctx, fctx->info, \
+ isc_result_totext(res), (m))
+#define FCTXTRACE4(m1, m2, res) \
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_RESOLVER, \
+ DNS_LOGMODULE_RESOLVER, ISC_LOG_DEBUG(3), \
+ "fctx %p(%s): [result: %s] %s %s", fctx, fctx->info, \
+ isc_result_totext(res), (m1), (m2))
+#define FCTXTRACE5(m1, m2, v) \
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_RESOLVER, \
+ DNS_LOGMODULE_RESOLVER, ISC_LOG_DEBUG(3), \
+ "fctx %p(%s): %s %s%u", fctx, fctx->info, (m1), (m2), \
+ (v))
+#define FTRACE(m) \
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_RESOLVER, \
+ DNS_LOGMODULE_RESOLVER, ISC_LOG_DEBUG(3), \
+ "fetch %p (fctx %p(%s)): %s", fetch, fetch->private, \
+ fetch->private->info, (m))
+#define QTRACE(m) \
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_RESOLVER, \
+ DNS_LOGMODULE_RESOLVER, ISC_LOG_DEBUG(3), \
+ "resquery %p (fctx %p(%s)): %s", query, query->fctx, \
+ query->fctx->info, (m))
+#else /* ifdef WANT_QUERYTRACE */
+#define RTRACE(m) \
+ do { \
+ UNUSED(m); \
+ } while (0)
+#define RRTRACE(r, m) \
+ do { \
+ UNUSED(r); \
+ UNUSED(m); \
+ } while (0)
+#define FCTXTRACE(m) \
+ do { \
+ UNUSED(fctx); \
+ UNUSED(m); \
+ } while (0)
+#define FCTXTRACE2(m1, m2) \
+ do { \
+ UNUSED(fctx); \
+ UNUSED(m1); \
+ UNUSED(m2); \
+ } while (0)
+#define FCTXTRACE3(m1, res) \
+ do { \
+ UNUSED(fctx); \
+ UNUSED(m1); \
+ UNUSED(res); \
+ } while (0)
+#define FCTXTRACE4(m1, m2, res) \
+ do { \
+ UNUSED(fctx); \
+ UNUSED(m1); \
+ UNUSED(m2); \
+ UNUSED(res); \
+ } while (0)
+#define FCTXTRACE5(m1, m2, v) \
+ do { \
+ UNUSED(fctx); \
+ UNUSED(m1); \
+ UNUSED(m2); \
+ UNUSED(v); \
+ } while (0)
+#define FTRACE(m) \
+ do { \
+ UNUSED(m); \
+ } while (0)
+#define QTRACE(m) \
+ do { \
+ UNUSED(m); \
+ } while (0)
+#endif /* WANT_QUERYTRACE */
+
+/*
+ * Add or remove an extra fctx reference without setting or clearing
+ * the pointer.
+ */
+#define fctx_addref(f) fctx_attach((f), &(fetchctx_t *){ NULL })
+#define fctx_unref(f) fctx_detach(&(fetchctx_t *){ (f) })
+
+/*
+ * The maximum time we will wait for a single query.
+ */
+#define MAX_SINGLE_QUERY_TIMEOUT 9000U
+#define MAX_SINGLE_QUERY_TIMEOUT_US (MAX_SINGLE_QUERY_TIMEOUT * US_PER_MS)
+
+/*
+ * We need to allow a individual query time to complete / timeout.
+ */
+#define MINIMUM_QUERY_TIMEOUT (MAX_SINGLE_QUERY_TIMEOUT + 1000U)
+
+/* The default time in seconds for the whole query to live. */
+#ifndef DEFAULT_QUERY_TIMEOUT
+#define DEFAULT_QUERY_TIMEOUT MINIMUM_QUERY_TIMEOUT
+#endif /* ifndef DEFAULT_QUERY_TIMEOUT */
+
+/* The maximum time in seconds for the whole query to live. */
+#ifndef MAXIMUM_QUERY_TIMEOUT
+#define MAXIMUM_QUERY_TIMEOUT 30000
+#endif /* ifndef MAXIMUM_QUERY_TIMEOUT */
+
+/* The default maximum number of recursions to follow before giving up. */
+#ifndef DEFAULT_RECURSION_DEPTH
+#define DEFAULT_RECURSION_DEPTH 7
+#endif /* ifndef DEFAULT_RECURSION_DEPTH */
+
+/* The default maximum number of iterative queries to allow before giving up. */
+#ifndef DEFAULT_MAX_QUERIES
+#define DEFAULT_MAX_QUERIES 100
+#endif /* ifndef DEFAULT_MAX_QUERIES */
+
+/*
+ * After NS_FAIL_LIMIT attempts to fetch a name server address,
+ * if the number of addresses in the NS RRset exceeds NS_RR_LIMIT,
+ * stop trying to fetch, in order to avoid wasting resources.
+ */
+#define NS_FAIL_LIMIT 4
+#define NS_RR_LIMIT 5
+/*
+ * IP address lookups are performed for at most NS_PROCESSING_LIMIT NS RRs in
+ * any NS RRset encountered, to avoid excessive resource use while processing
+ * large delegations.
+ */
+#define NS_PROCESSING_LIMIT 20
+
+STATIC_ASSERT(NS_PROCESSING_LIMIT > NS_RR_LIMIT,
+ "The maximum number of NS RRs processed for each delegation "
+ "(NS_PROCESSING_LIMIT) must be larger than the large delegation "
+ "threshold (NS_RR_LIMIT).");
+
+/* Hash table for zone counters */
+#ifndef RES_DOMAIN_HASH_BITS
+#define RES_DOMAIN_HASH_BITS 12
+#endif /* ifndef RES_DOMAIN_HASH_BITS */
+#define RES_NOBUCKET 0xffffffff
+
+#define GOLDEN_RATIO_32 0x61C88647
+
+#define HASHSIZE(bits) (UINT64_C(1) << (bits))
+
+#define RES_DOMAIN_MAX_BITS 32
+#define RES_DOMAIN_OVERCOMMIT 3
+
+#define RES_DOMAIN_NEXTTABLE(hindex) ((hindex == 0) ? 1 : 0)
+
+static uint32_t
+hash_32(uint32_t val, unsigned int bits) {
+ REQUIRE(bits <= RES_DOMAIN_MAX_BITS);
+ /* High bits are more random. */
+ return (val * GOLDEN_RATIO_32 >> (32 - bits));
+}
+
+/*%
+ * Maximum EDNS0 input packet size.
+ */
+#define RECV_BUFFER_SIZE 4096 /* XXXRTH Constant. */
+
+/*%
+ * Default EDNS0 buffer size
+ */
+#define DEFAULT_EDNS_BUFSIZE 1232
+
+/*%
+ * This defines the maximum number of timeouts we will permit before we
+ * disable EDNS0 on the query.
+ */
+#define MAX_EDNS0_TIMEOUTS 3
+
+#define DNS_RESOLVER_BADCACHESIZE 1021
+#define DNS_RESOLVER_BADCACHETTL(fctx) \
+ (((fctx)->res->lame_ttl > 30) ? (fctx)->res->lame_ttl : 30)
+
+typedef struct fetchctx fetchctx_t;
+
+typedef struct query {
+ /* Locked by task event serialization. */
+ unsigned int magic;
+ isc_refcount_t references;
+ fetchctx_t *fctx;
+ dns_message_t *rmessage;
+ isc_mem_t *mctx;
+ dns_dispatchmgr_t *dispatchmgr;
+ dns_dispatch_t *dispatch;
+ dns_adbaddrinfo_t *addrinfo;
+ isc_time_t start;
+ dns_messageid_t id;
+ dns_dispentry_t *dispentry;
+ ISC_LINK(struct query) link;
+ isc_buffer_t buffer;
+ isc_buffer_t *tsig;
+ dns_tsigkey_t *tsigkey;
+ int ednsversion;
+ unsigned int options;
+ unsigned int attributes;
+ unsigned int udpsize;
+ unsigned char data[512];
+} resquery_t;
+
+struct tried {
+ isc_sockaddr_t addr;
+ unsigned int count;
+ ISC_LINK(struct tried) link;
+};
+
+#define QUERY_MAGIC ISC_MAGIC('Q', '!', '!', '!')
+#define VALID_QUERY(query) ISC_MAGIC_VALID(query, QUERY_MAGIC)
+
+#define RESQUERY_ATTR_CANCELED 0x02
+
+#define RESQUERY_CONNECTING(q) ((q)->connects > 0)
+#define RESQUERY_CANCELED(q) (((q)->attributes & RESQUERY_ATTR_CANCELED) != 0)
+#define RESQUERY_SENDING(q) ((q)->sends > 0)
+
+typedef enum {
+ fetchstate_init = 0, /*%< Start event has not run yet. */
+ fetchstate_active,
+ fetchstate_done /*%< FETCHDONE events posted. */
+} fetchstate_t;
+
+typedef enum {
+ badns_unreachable = 0,
+ badns_response,
+ badns_validation,
+ badns_forwarder,
+} badnstype_t;
+
+struct fetchctx {
+ /*% Not locked. */
+ unsigned int magic;
+ dns_resolver_t *res;
+ dns_fixedname_t fname;
+ dns_name_t *name;
+ dns_rdatatype_t type;
+ unsigned int options;
+ unsigned int bucketnum;
+ unsigned int dbucketnum;
+ char *info;
+ isc_mem_t *mctx;
+ isc_stdtime_t now;
+ isc_task_t *task;
+
+ /* Atomic */
+ isc_refcount_t references;
+
+ /*% Locked by appropriate bucket lock. */
+ fetchstate_t state;
+ atomic_bool want_shutdown;
+ bool cloned;
+ bool spilled;
+ isc_event_t control_event;
+ ISC_LINK(struct fetchctx) link;
+ ISC_LIST(dns_fetchevent_t) events;
+
+ /*% Locked by task event serialization. */
+ dns_fixedname_t dfname;
+ dns_name_t *domain;
+ dns_rdataset_t nameservers;
+ atomic_uint_fast32_t attributes;
+ isc_timer_t *timer;
+ isc_time_t expires;
+ isc_time_t expires_try_stale;
+ isc_time_t next_timeout;
+ isc_time_t final;
+ isc_interval_t interval;
+ dns_message_t *qmessage;
+ ISC_LIST(resquery_t) queries;
+ dns_adbfindlist_t finds;
+ dns_adbfind_t *find;
+ /*
+ * altfinds are names and/or addresses of dual stack servers that
+ * should be used when iterative resolution to a server is not
+ * possible because the address family of that server is not usable.
+ */
+ dns_adbfindlist_t altfinds;
+ dns_adbfind_t *altfind;
+ dns_adbaddrinfolist_t forwaddrs;
+ dns_adbaddrinfolist_t altaddrs;
+ dns_forwarderlist_t forwarders;
+ dns_fwdpolicy_t fwdpolicy;
+ isc_sockaddrlist_t bad;
+ ISC_LIST(struct tried) edns;
+ isc_sockaddrlist_t bad_edns;
+ dns_validator_t *validator;
+ ISC_LIST(dns_validator_t) validators;
+ dns_db_t *cache;
+ dns_adb_t *adb;
+ bool ns_ttl_ok;
+ uint32_t ns_ttl;
+ isc_counter_t *qc;
+ bool minimized;
+ unsigned int qmin_labels;
+ isc_result_t qmin_warning;
+ bool ip6arpaskip;
+ bool forwarding;
+ dns_fixedname_t qminfname;
+ dns_name_t *qminname;
+ dns_rdatatype_t qmintype;
+ dns_fetch_t *qminfetch;
+ dns_rdataset_t qminrrset;
+ dns_fixedname_t qmindcfname;
+ dns_name_t *qmindcname;
+ dns_fixedname_t fwdfname;
+ dns_name_t *fwdname;
+
+ /*%
+ * The number of events we're waiting for.
+ */
+ atomic_uint_fast32_t pending; /* Bucket lock. */
+
+ /*%
+ * The number of times we've "restarted" the current
+ * nameserver set. This acts as a failsafe to prevent
+ * us from pounding constantly on a particular set of
+ * servers that, for whatever reason, are not giving
+ * us useful responses, but are responding in such a
+ * way that they are not marked "bad".
+ */
+ unsigned int restarts;
+
+ /*%
+ * The number of timeouts that have occurred since we
+ * last successfully received a response packet. This
+ * is used for EDNS0 black hole detection.
+ */
+ unsigned int timeouts;
+
+ /*%
+ * Look aside state for DS lookups.
+ */
+ dns_fixedname_t nsfname;
+ dns_name_t *nsname;
+
+ dns_fetch_t *nsfetch;
+ dns_rdataset_t nsrrset;
+
+ /*%
+ * Number of queries that reference this context.
+ */
+ atomic_uint_fast32_t nqueries; /* Bucket lock. */
+
+ /*%
+ * Random numbers to use for mixing up server addresses.
+ */
+ uint32_t rand_buf;
+ uint32_t rand_bits;
+
+ /*%
+ * Fetch-local statistics for detailed logging.
+ */
+ isc_result_t result; /*%< fetch result */
+ isc_result_t vresult; /*%< validation result */
+ int exitline;
+ isc_time_t start;
+ uint64_t duration;
+ bool logged;
+ unsigned int querysent;
+ unsigned int referrals;
+ unsigned int lamecount;
+ unsigned int quotacount;
+ unsigned int neterr;
+ unsigned int badresp;
+ unsigned int adberr;
+ unsigned int findfail;
+ unsigned int valfail;
+ bool timeout;
+ dns_adbaddrinfo_t *addrinfo;
+ unsigned int depth;
+ char clientstr[ISC_SOCKADDR_FORMATSIZE];
+};
+
+#define FCTX_MAGIC ISC_MAGIC('F', '!', '!', '!')
+#define VALID_FCTX(fctx) ISC_MAGIC_VALID(fctx, FCTX_MAGIC)
+
+#define FCTX_ATTR_HAVEANSWER 0x0001
+#define FCTX_ATTR_GLUING 0x0002
+#define FCTX_ATTR_ADDRWAIT 0x0004
+#define FCTX_ATTR_SHUTTINGDOWN 0x0008 /* Bucket lock */
+#define FCTX_ATTR_WANTCACHE 0x0010
+#define FCTX_ATTR_WANTNCACHE 0x0020
+#define FCTX_ATTR_NEEDEDNS0 0x0040
+#define FCTX_ATTR_TRIEDFIND 0x0080
+#define FCTX_ATTR_TRIEDALT 0x0100
+
+#define HAVE_ANSWER(f) \
+ ((atomic_load_acquire(&(f)->attributes) & FCTX_ATTR_HAVEANSWER) != 0)
+#define GLUING(f) \
+ ((atomic_load_acquire(&(f)->attributes) & FCTX_ATTR_GLUING) != 0)
+#define ADDRWAIT(f) \
+ ((atomic_load_acquire(&(f)->attributes) & FCTX_ATTR_ADDRWAIT) != 0)
+#define SHUTTINGDOWN(f) \
+ ((atomic_load_acquire(&(f)->attributes) & FCTX_ATTR_SHUTTINGDOWN) != 0)
+#define WANTCACHE(f) \
+ ((atomic_load_acquire(&(f)->attributes) & FCTX_ATTR_WANTCACHE) != 0)
+#define WANTNCACHE(f) \
+ ((atomic_load_acquire(&(f)->attributes) & FCTX_ATTR_WANTNCACHE) != 0)
+#define NEEDEDNS0(f) \
+ ((atomic_load_acquire(&(f)->attributes) & FCTX_ATTR_NEEDEDNS0) != 0)
+#define TRIEDFIND(f) \
+ ((atomic_load_acquire(&(f)->attributes) & FCTX_ATTR_TRIEDFIND) != 0)
+#define TRIEDALT(f) \
+ ((atomic_load_acquire(&(f)->attributes) & FCTX_ATTR_TRIEDALT) != 0)
+
+#define FCTX_ATTR_SET(f, a) atomic_fetch_or_release(&(f)->attributes, (a))
+#define FCTX_ATTR_CLR(f, a) atomic_fetch_and_release(&(f)->attributes, ~(a))
+
+typedef struct {
+ dns_adbaddrinfo_t *addrinfo;
+ fetchctx_t *fctx;
+ dns_message_t *message;
+} dns_valarg_t;
+
+struct dns_fetch {
+ unsigned int magic;
+ isc_mem_t *mctx;
+ dns_resolver_t *res;
+ fetchctx_t *private;
+};
+
+#define DNS_FETCH_MAGIC ISC_MAGIC('F', 't', 'c', 'h')
+#define DNS_FETCH_VALID(fetch) ISC_MAGIC_VALID(fetch, DNS_FETCH_MAGIC)
+
+typedef struct fctxbucket {
+ isc_task_t *task;
+ isc_mutex_t lock;
+ ISC_LIST(fetchctx_t) fctxs;
+ atomic_bool exiting;
+} fctxbucket_t;
+
+typedef struct fctxcount fctxcount_t;
+struct fctxcount {
+ dns_fixedname_t dfname;
+ dns_name_t *domain;
+ uint32_t count;
+ uint32_t allowed;
+ uint32_t dropped;
+ isc_stdtime_t logged;
+ ISC_LINK(fctxcount_t) link;
+};
+
+typedef struct zonebucket {
+ isc_mutex_t lock;
+ ISC_LIST(fctxcount_t) list;
+} zonebucket_t;
+
+typedef struct alternate {
+ bool isaddress;
+ union {
+ isc_sockaddr_t addr;
+ struct {
+ dns_name_t name;
+ in_port_t port;
+ } _n;
+ } _u;
+ ISC_LINK(struct alternate) link;
+} alternate_t;
+
+struct dns_resolver {
+ /* Unlocked. */
+ unsigned int magic;
+ isc_mem_t *mctx;
+ isc_mutex_t lock;
+ isc_mutex_t primelock;
+ dns_rdataclass_t rdclass;
+ isc_nm_t *nm;
+ isc_timermgr_t *timermgr;
+ isc_taskmgr_t *taskmgr;
+ dns_view_t *view;
+ bool frozen;
+ unsigned int options;
+ dns_dispatchmgr_t *dispatchmgr;
+ dns_dispatchset_t *dispatches4;
+ dns_dispatchset_t *dispatches6;
+ unsigned int nbuckets;
+ fctxbucket_t *buckets;
+ uint8_t dhashbits;
+ zonebucket_t *dbuckets;
+ uint32_t lame_ttl;
+ ISC_LIST(alternate_t) alternates;
+ uint16_t udpsize;
+ dns_rbt_t *algorithms;
+ dns_rbt_t *digests;
+ dns_rbt_t *mustbesecure;
+ unsigned int spillatmax;
+ unsigned int spillatmin;
+ isc_timer_t *spillattimer;
+ bool zero_no_soa_ttl;
+ unsigned int query_timeout;
+ unsigned int maxdepth;
+ unsigned int maxqueries;
+ isc_result_t quotaresp[2];
+
+ /* Additions for serve-stale feature. */
+ unsigned int retryinterval; /* in milliseconds */
+ unsigned int nonbackofftries;
+
+ /* Atomic */
+ isc_refcount_t references;
+ atomic_uint_fast32_t zspill; /* fetches-per-zone */
+ atomic_bool exiting;
+ atomic_bool priming;
+
+ /* Locked by lock. */
+ isc_eventlist_t whenshutdown;
+ isc_refcount_t activebuckets;
+ unsigned int spillat; /* clients-per-query */
+
+ dns_badcache_t *badcache; /* Bad cache. */
+
+ /* Locked by primelock. */
+ dns_fetch_t *primefetch;
+
+ /* Atomic. */
+ atomic_uint_fast32_t nfctx;
+};
+
+#define RES_MAGIC ISC_MAGIC('R', 'e', 's', '!')
+#define VALID_RESOLVER(res) ISC_MAGIC_VALID(res, RES_MAGIC)
+
+/*%
+ * Private addrinfo flags.
+ */
+enum {
+ FCTX_ADDRINFO_MARK = 1 << 0,
+ FCTX_ADDRINFO_FORWARDER = 1 << 1,
+ FCTX_ADDRINFO_EDNSOK = 1 << 2,
+ FCTX_ADDRINFO_NOCOOKIE = 1 << 3,
+ FCTX_ADDRINFO_BADCOOKIE = 1 << 4,
+ FCTX_ADDRINFO_DUALSTACK = 1 << 5,
+ FCTX_ADDRINFO_NOEDNS0 = 1 << 6,
+};
+
+#define UNMARKED(a) (((a)->flags & FCTX_ADDRINFO_MARK) == 0)
+#define ISFORWARDER(a) (((a)->flags & FCTX_ADDRINFO_FORWARDER) != 0)
+#define NOCOOKIE(a) (((a)->flags & FCTX_ADDRINFO_NOCOOKIE) != 0)
+#define EDNSOK(a) (((a)->flags & FCTX_ADDRINFO_EDNSOK) != 0)
+#define BADCOOKIE(a) (((a)->flags & FCTX_ADDRINFO_BADCOOKIE) != 0)
+#define ISDUALSTACK(a) (((a)->flags & FCTX_ADDRINFO_DUALSTACK) != 0)
+
+#define NXDOMAIN(r) (((r)->attributes & DNS_RDATASETATTR_NXDOMAIN) != 0)
+#define NEGATIVE(r) (((r)->attributes & DNS_RDATASETATTR_NEGATIVE) != 0)
+
+#define NXDOMAIN_RESULT(r) \
+ ((r) == DNS_R_NXDOMAIN || (r) == DNS_R_NCACHENXDOMAIN)
+#define NXRRSET_RESULT(r) \
+ ((r) == DNS_R_NCACHENXRRSET || (r) == DNS_R_NXRRSET || \
+ (r) == DNS_R_HINTNXRRSET)
+
+#ifdef ENABLE_AFL
+bool dns_fuzzing_resolver = false;
+void
+dns_resolver_setfuzzing(void) {
+ dns_fuzzing_resolver = true;
+}
+#endif /* ifdef ENABLE_AFL */
+
+static unsigned char ip6_arpa_data[] = "\003IP6\004ARPA";
+static unsigned char ip6_arpa_offsets[] = { 0, 4, 9 };
+static const dns_name_t ip6_arpa = DNS_NAME_INITABSOLUTE(ip6_arpa_data,
+ ip6_arpa_offsets);
+
+static void
+destroy(dns_resolver_t *res);
+static isc_result_t
+resquery_send(resquery_t *query);
+static void
+resquery_response(isc_result_t eresult, isc_region_t *region, void *arg);
+static void
+resquery_connected(isc_result_t eresult, isc_region_t *region, void *arg);
+static void
+fctx_try(fetchctx_t *fctx, bool retrying, bool badcache);
+static void
+fctx_shutdown(fetchctx_t *fctx);
+static void
+fctx_minimize_qname(fetchctx_t *fctx);
+static void
+fctx_destroy(fetchctx_t *fctx, bool exiting);
+static void
+send_shutdown_events(dns_resolver_t *res);
+static isc_result_t
+ncache_adderesult(dns_message_t *message, dns_db_t *cache, dns_dbnode_t *node,
+ dns_rdatatype_t covers, isc_stdtime_t now, dns_ttl_t minttl,
+ dns_ttl_t maxttl, bool optout, bool secure,
+ dns_rdataset_t *ardataset, isc_result_t *eresultp);
+static void
+validated(isc_task_t *task, isc_event_t *event);
+static void
+maybe_cancel_validators(fetchctx_t *fctx, bool locked);
+static void
+add_bad(fetchctx_t *fctx, dns_message_t *rmessage, dns_adbaddrinfo_t *addrinfo,
+ isc_result_t reason, badnstype_t badtype);
+static isc_result_t
+findnoqname(fetchctx_t *fctx, dns_message_t *message, dns_name_t *name,
+ dns_rdatatype_t type, dns_name_t **noqname);
+
+#define fctx_attach(fctx, fctxp) \
+ fctx__attach(fctx, fctxp, __FILE__, __LINE__, __func__)
+#define fctx_detach(fctxp) fctx__detach(fctxp, __FILE__, __LINE__, __func__)
+#define fctx_done_detach(fctxp, result) \
+ fctx__done_detach(fctxp, result, __FILE__, __LINE__, __func__);
+
+static void
+fctx__attach(fetchctx_t *fctx, fetchctx_t **fctxp, const char *file,
+ unsigned int line, const char *func);
+static void
+fctx__detach(fetchctx_t **fctxp, const char *file, unsigned int line,
+ const char *func);
+
+static void
+fctx__done_detach(fetchctx_t **fctxp, isc_result_t result, const char *file,
+ unsigned int line, const char *func);
+
+static void
+resume_qmin(isc_task_t *task, isc_event_t *event);
+
+/*%
+ * The structure and functions defined below implement the resolver
+ * query (resquery) response handling logic.
+ *
+ * When a resolver query is sent and a response is received, the
+ * resquery_response() event handler is run, which calls the rctx_*()
+ * functions. The respctx_t structure maintains state from function
+ * to function.
+ *
+ * The call flow is described below:
+ *
+ * 1. resquery_response():
+ * - Initialize a respctx_t structure (rctx_respinit()).
+ * - Check for dispatcher failure (rctx_dispfail()).
+ * - Parse the response (rctx_parse()).
+ * - Log the response (rctx_logpacket()).
+ * - Check the parsed response for an OPT record and handle
+ * EDNS (rctx_opt(), rctx_edns()).
+ * - Check for a bad or lame server (rctx_badserver(), rctx_lameserver()).
+ * - Handle delegation-only zones (rctx_delonly_zone()).
+ * - If RCODE and ANCOUNT suggest this is a positive answer, and
+ * if so, call rctx_answer(): go to step 2.
+ * - If RCODE and NSCOUNT suggest this is a negative answer or a
+ * referral, call rctx_answer_none(): go to step 4.
+ * - Check the additional section for data that should be cached
+ * (rctx_additional()).
+ * - Clean up and finish by calling rctx_done(): go to step 5.
+ *
+ * 2. rctx_answer():
+ * - If the answer appears to be positive, call rctx_answer_positive():
+ * go to step 3.
+ * - If the response is a malformed delegation (with glue or NS records
+ * in the answer section), call rctx_answer_none(): go to step 4.
+ *
+ * 3. rctx_answer_positive():
+ * - Initialize the portions of respctx_t needed for processing an answer
+ * (rctx_answer_init()).
+ * - Scan the answer section to find records that are responsive to the
+ * query (rctx_answer_scan()).
+ * - For whichever type of response was found, call a separate routine
+ * to handle it: matching QNAME/QTYPE (rctx_answer_match()),
+ * CNAME (rctx_answer_cname()), covering DNAME (rctx_answer_dname()),
+ * or any records returned in response to a query of type ANY
+ * (rctx_answer_any()).
+ * - Scan the authority section for NS or other records that may be
+ * included with a positive answer (rctx_authority_scan()).
+ *
+ * 4. rctx_answer_none():
+ * - Determine whether this is an NXDOMAIN, NXRRSET, or referral.
+ * - If referral, set up the resolver to follow the delegation
+ * (rctx_referral()).
+ * - If NXDOMAIN/NXRRSET, scan the authority section for NS and SOA
+ * records included with a negative response (rctx_authority_negative()),
+ * then for DNSSEC proof of nonexistence (rctx_authority_dnssec()).
+ *
+ * 5. rctx_done():
+ * - Set up chasing of DS records if needed (rctx_chaseds()).
+ * - If the response wasn't intended for us, wait for another response
+ * from the dispatcher (rctx_next()).
+ * - If there is a problem with the responding server, set up another
+ * query to a different server (rctx_nextserver()).
+ * - If there is a problem that might be temporary or dependent on
+ * EDNS options, set up another query to the same server with changed
+ * options (rctx_resend()).
+ * - Shut down the fetch context.
+ */
+
+typedef struct respctx {
+ resquery_t *query;
+ fetchctx_t *fctx;
+ isc_result_t result;
+ isc_buffer_t buffer;
+ unsigned int retryopts; /* updated options to pass to
+ * fctx_query() when resending */
+
+ dns_rdatatype_t type; /* type being sought (set to
+ * ANY if qtype was SIG or RRSIG) */
+ bool aa; /* authoritative answer? */
+ dns_trust_t trust; /* answer trust level */
+ bool chaining; /* CNAME/DNAME processing? */
+ bool next_server; /* give up, try the next server
+ * */
+
+ badnstype_t broken_type; /* type of name server problem
+ * */
+ isc_result_t broken_server;
+
+ bool get_nameservers; /* get a new NS rrset at
+ * zone cut? */
+ bool resend; /* resend this query? */
+ bool nextitem; /* invalid response; keep
+ * listening for the correct one */
+ bool truncated; /* response was truncated */
+ bool no_response; /* no response was received */
+ bool glue_in_answer; /* glue may be in the answer
+ * section */
+ bool ns_in_answer; /* NS may be in the answer
+ * section */
+ bool negative; /* is this a negative response? */
+
+ isc_stdtime_t now; /* time info */
+ isc_time_t tnow;
+ isc_time_t *finish;
+
+ unsigned int dname_labels;
+ unsigned int domain_labels; /* range of permissible number
+ * of
+ * labels in a DNAME */
+
+ dns_name_t *aname; /* answer name */
+ dns_rdataset_t *ardataset; /* answer rdataset */
+
+ dns_name_t *cname; /* CNAME name */
+ dns_rdataset_t *crdataset; /* CNAME rdataset */
+
+ dns_name_t *dname; /* DNAME name */
+ dns_rdataset_t *drdataset; /* DNAME rdataset */
+
+ dns_name_t *ns_name; /* NS name */
+ dns_rdataset_t *ns_rdataset; /* NS rdataset */
+
+ dns_name_t *soa_name; /* SOA name in a negative answer */
+ dns_name_t *ds_name; /* DS name in a negative answer */
+
+ dns_name_t *found_name; /* invalid name in negative
+ * response */
+ dns_rdatatype_t found_type; /* invalid type in negative
+ * response */
+
+ dns_rdataset_t *opt; /* OPT rdataset */
+} respctx_t;
+
+static void
+rctx_respinit(resquery_t *query, fetchctx_t *fctx, isc_result_t result,
+ isc_region_t *region, respctx_t *rctx);
+
+static void
+rctx_answer_init(respctx_t *rctx);
+
+static void
+rctx_answer_scan(respctx_t *rctx);
+
+static void
+rctx_authority_positive(respctx_t *rctx);
+
+static isc_result_t
+rctx_answer_any(respctx_t *rctx);
+
+static isc_result_t
+rctx_answer_match(respctx_t *rctx);
+
+static isc_result_t
+rctx_answer_cname(respctx_t *rctx);
+
+static isc_result_t
+rctx_answer_dname(respctx_t *rctx);
+
+static isc_result_t
+rctx_answer_positive(respctx_t *rctx);
+
+static isc_result_t
+rctx_authority_negative(respctx_t *rctx);
+
+static isc_result_t
+rctx_authority_dnssec(respctx_t *rctx);
+
+static void
+rctx_additional(respctx_t *rctx);
+
+static isc_result_t
+rctx_referral(respctx_t *rctx);
+
+static isc_result_t
+rctx_answer_none(respctx_t *rctx);
+
+static void
+rctx_nextserver(respctx_t *rctx, dns_message_t *message,
+ dns_adbaddrinfo_t *addrinfo, isc_result_t result);
+
+static void
+rctx_resend(respctx_t *rctx, dns_adbaddrinfo_t *addrinfo);
+
+static isc_result_t
+rctx_next(respctx_t *rctx);
+
+static void
+rctx_chaseds(respctx_t *rctx, dns_message_t *message,
+ dns_adbaddrinfo_t *addrinfo, isc_result_t result);
+
+static void
+rctx_done(respctx_t *rctx, isc_result_t result);
+
+static void
+rctx_logpacket(respctx_t *rctx);
+
+static void
+rctx_opt(respctx_t *rctx);
+
+static void
+rctx_edns(respctx_t *rctx);
+
+static isc_result_t
+rctx_parse(respctx_t *rctx);
+
+static isc_result_t
+rctx_badserver(respctx_t *rctx, isc_result_t result);
+
+static isc_result_t
+rctx_answer(respctx_t *rctx);
+
+static isc_result_t
+rctx_lameserver(respctx_t *rctx);
+
+static isc_result_t
+rctx_dispfail(respctx_t *rctx);
+
+static isc_result_t
+rctx_timedout(respctx_t *rctx);
+
+static void
+rctx_delonly_zone(respctx_t *rctx);
+
+static void
+rctx_ncache(respctx_t *rctx);
+
+/*%
+ * Increment resolver-related statistics counters.
+ */
+static void
+inc_stats(dns_resolver_t *res, isc_statscounter_t counter) {
+ if (res->view->resstats != NULL) {
+ isc_stats_increment(res->view->resstats, counter);
+ }
+}
+
+static void
+dec_stats(dns_resolver_t *res, isc_statscounter_t counter) {
+ if (res->view->resstats != NULL) {
+ isc_stats_decrement(res->view->resstats, counter);
+ }
+}
+
+static isc_result_t
+valcreate(fetchctx_t *fctx, dns_message_t *message, dns_adbaddrinfo_t *addrinfo,
+ dns_name_t *name, dns_rdatatype_t type, dns_rdataset_t *rdataset,
+ dns_rdataset_t *sigrdataset, unsigned int valoptions,
+ isc_task_t *task) {
+ dns_validator_t *validator = NULL;
+ dns_valarg_t *valarg;
+ isc_result_t result;
+
+ valarg = isc_mem_get(fctx->mctx, sizeof(*valarg));
+
+ *valarg = (dns_valarg_t){
+ .addrinfo = addrinfo,
+ };
+
+ fctx_attach(fctx, &valarg->fctx);
+ dns_message_attach(message, &valarg->message);
+
+ if (!ISC_LIST_EMPTY(fctx->validators)) {
+ valoptions |= DNS_VALIDATOR_DEFER;
+ } else {
+ valoptions &= ~DNS_VALIDATOR_DEFER;
+ }
+
+ result = dns_validator_create(fctx->res->view, name, type, rdataset,
+ sigrdataset, message, valoptions, task,
+ validated, valarg, &validator);
+ RUNTIME_CHECK(result == ISC_R_SUCCESS);
+ if (result == ISC_R_SUCCESS) {
+ inc_stats(fctx->res, dns_resstatscounter_val);
+ if ((valoptions & DNS_VALIDATOR_DEFER) == 0) {
+ INSIST(fctx->validator == NULL);
+ fctx->validator = validator;
+ }
+ ISC_LIST_APPEND(fctx->validators, validator, link);
+ } else {
+ dns_message_detach(&valarg->message);
+ fctx_detach(&valarg->fctx);
+ isc_mem_put(fctx->mctx, valarg, sizeof(*valarg));
+ }
+ return (result);
+}
+
+static bool
+rrsig_fromchildzone(fetchctx_t *fctx, dns_rdataset_t *rdataset) {
+ dns_namereln_t namereln;
+ dns_rdata_rrsig_t rrsig;
+ dns_rdata_t rdata = DNS_RDATA_INIT;
+ int order;
+ isc_result_t result;
+ unsigned int labels;
+
+ for (result = dns_rdataset_first(rdataset); result == ISC_R_SUCCESS;
+ result = dns_rdataset_next(rdataset))
+ {
+ dns_rdataset_current(rdataset, &rdata);
+ result = dns_rdata_tostruct(&rdata, &rrsig, NULL);
+ RUNTIME_CHECK(result == ISC_R_SUCCESS);
+ namereln = dns_name_fullcompare(&rrsig.signer, fctx->domain,
+ &order, &labels);
+ if (namereln == dns_namereln_subdomain) {
+ return (true);
+ }
+ dns_rdata_reset(&rdata);
+ }
+ return (false);
+}
+
+static bool
+fix_mustbedelegationornxdomain(dns_message_t *message, fetchctx_t *fctx) {
+ dns_name_t *name;
+ dns_name_t *domain = fctx->domain;
+ dns_rdataset_t *rdataset;
+ dns_rdatatype_t type;
+ isc_result_t result;
+ bool keep_auth = false;
+
+ if (message->rcode == dns_rcode_nxdomain) {
+ return (false);
+ }
+
+ /*
+ * A DS RRset can appear anywhere in a zone, even for a delegation-only
+ * zone. So a response to an explicit query for this type should be
+ * excluded from delegation-only fixup.
+ *
+ * SOA, NS, and DNSKEY can only exist at a zone apex, so a positive
+ * response to a query for these types can never violate the
+ * delegation-only assumption: if the query name is below a
+ * zone cut, the response should normally be a referral, which should
+ * be accepted; if the query name is below a zone cut but the server
+ * happens to have authority for the zone of the query name, the
+ * response is a (non-referral) answer. But this does not violate
+ * delegation-only because the query name must be in a different zone
+ * due to the "apex-only" nature of these types. Note that if the
+ * remote server happens to have authority for a child zone of a
+ * delegation-only zone, we may still incorrectly "fix" the response
+ * with NXDOMAIN for queries for other types. Unfortunately it's
+ * generally impossible to differentiate this case from violation of
+ * the delegation-only assumption. Once the resolver learns the
+ * correct zone cut, possibly via a separate query for an "apex-only"
+ * type, queries for other types will be resolved correctly.
+ *
+ * A query for type ANY will be accepted if it hits an exceptional
+ * type above in the answer section as it should be from a child
+ * zone.
+ *
+ * Also accept answers with RRSIG records from the child zone.
+ * Direct queries for RRSIG records should not be answered from
+ * the parent zone.
+ */
+
+ if (message->counts[DNS_SECTION_ANSWER] != 0 &&
+ (fctx->type == dns_rdatatype_ns || fctx->type == dns_rdatatype_ds ||
+ fctx->type == dns_rdatatype_soa ||
+ fctx->type == dns_rdatatype_any ||
+ fctx->type == dns_rdatatype_rrsig ||
+ fctx->type == dns_rdatatype_dnskey))
+ {
+ result = dns_message_firstname(message, DNS_SECTION_ANSWER);
+ while (result == ISC_R_SUCCESS) {
+ name = NULL;
+ dns_message_currentname(message, DNS_SECTION_ANSWER,
+ &name);
+ for (rdataset = ISC_LIST_HEAD(name->list);
+ rdataset != NULL;
+ rdataset = ISC_LIST_NEXT(rdataset, link))
+ {
+ if (!dns_name_equal(name, fctx->name)) {
+ continue;
+ }
+ type = rdataset->type;
+ /*
+ * RRsig from child?
+ */
+ if (type == dns_rdatatype_rrsig &&
+ rrsig_fromchildzone(fctx, rdataset))
+ {
+ return (false);
+ }
+ /*
+ * Direct query for apex records or DS.
+ */
+ if (fctx->type == type &&
+ (type == dns_rdatatype_ds ||
+ type == dns_rdatatype_ns ||
+ type == dns_rdatatype_soa ||
+ type == dns_rdatatype_dnskey))
+ {
+ return (false);
+ }
+ /*
+ * Indirect query for apex records or DS.
+ */
+ if (fctx->type == dns_rdatatype_any &&
+ (type == dns_rdatatype_ns ||
+ type == dns_rdatatype_ds ||
+ type == dns_rdatatype_soa ||
+ type == dns_rdatatype_dnskey))
+ {
+ return (false);
+ }
+ }
+ result = dns_message_nextname(message,
+ DNS_SECTION_ANSWER);
+ }
+ }
+
+ /*
+ * A NODATA response to a DS query?
+ */
+ if (fctx->type == dns_rdatatype_ds &&
+ message->counts[DNS_SECTION_ANSWER] == 0)
+ {
+ return (false);
+ }
+
+ /* Look for referral or indication of answer from child zone? */
+ if (message->counts[DNS_SECTION_AUTHORITY] == 0) {
+ goto munge;
+ }
+
+ result = dns_message_firstname(message, DNS_SECTION_AUTHORITY);
+ while (result == ISC_R_SUCCESS) {
+ name = NULL;
+ dns_message_currentname(message, DNS_SECTION_AUTHORITY, &name);
+ for (rdataset = ISC_LIST_HEAD(name->list); rdataset != NULL;
+ rdataset = ISC_LIST_NEXT(rdataset, link))
+ {
+ type = rdataset->type;
+ if (type == dns_rdatatype_soa &&
+ dns_name_equal(name, domain))
+ {
+ keep_auth = true;
+ }
+
+ if (type != dns_rdatatype_ns &&
+ type != dns_rdatatype_soa &&
+ type != dns_rdatatype_rrsig)
+ {
+ continue;
+ }
+
+ if (type == dns_rdatatype_rrsig) {
+ if (rrsig_fromchildzone(fctx, rdataset)) {
+ return (false);
+ } else {
+ continue;
+ }
+ }
+
+ /* NS or SOA records. */
+ if (dns_name_equal(name, domain)) {
+ /*
+ * If a query for ANY causes a negative
+ * response, we can be sure that this is
+ * an empty node. For other type of queries
+ * we cannot differentiate an empty node
+ * from a node that just doesn't have that
+ * type of record. We only accept the former
+ * case.
+ */
+ if (message->counts[DNS_SECTION_ANSWER] == 0 &&
+ fctx->type == dns_rdatatype_any)
+ {
+ return (false);
+ }
+ } else if (dns_name_issubdomain(name, domain)) {
+ /* Referral or answer from child zone. */
+ return (false);
+ }
+ }
+ result = dns_message_nextname(message, DNS_SECTION_AUTHORITY);
+ }
+
+munge:
+ message->rcode = dns_rcode_nxdomain;
+ message->counts[DNS_SECTION_ANSWER] = 0;
+ if (!keep_auth) {
+ message->counts[DNS_SECTION_AUTHORITY] = 0;
+ }
+ message->counts[DNS_SECTION_ADDITIONAL] = 0;
+ return (true);
+}
+
+static void
+resquery_destroy(resquery_t *query) {
+ fetchctx_t *fctx = query->fctx;
+ dns_resolver_t *res = fctx->res;
+ unsigned int bucket = fctx->bucketnum;
+
+ if (ISC_LINK_LINKED(query, link)) {
+ ISC_LIST_UNLINK(fctx->queries, query, link);
+ }
+
+ if (query->tsig != NULL) {
+ isc_buffer_free(&query->tsig);
+ }
+
+ if (query->tsigkey != NULL) {
+ dns_tsigkey_detach(&query->tsigkey);
+ }
+
+ if (query->dispentry != NULL) {
+ dns_dispatch_done(&query->dispentry);
+ }
+
+ if (query->dispatch != NULL) {
+ dns_dispatch_detach(&query->dispatch);
+ }
+
+ isc_refcount_destroy(&query->references);
+
+ LOCK(&res->buckets[bucket].lock);
+ atomic_fetch_sub_release(&fctx->nqueries, 1);
+ UNLOCK(&res->buckets[bucket].lock);
+ fctx_detach(&query->fctx);
+
+ if (query->rmessage != NULL) {
+ dns_message_detach(&query->rmessage);
+ }
+
+ query->magic = 0;
+ isc_mem_put(query->mctx, query, sizeof(*query));
+}
+
+static void
+resquery_attach(resquery_t *source, resquery_t **targetp) {
+ REQUIRE(VALID_QUERY(source));
+ REQUIRE(targetp != NULL && *targetp == NULL);
+
+ isc_refcount_increment(&source->references);
+
+ *targetp = source;
+}
+
+static void
+resquery_detach(resquery_t **queryp) {
+ uint_fast32_t ref;
+ resquery_t *query = NULL;
+
+ REQUIRE(queryp != NULL && VALID_QUERY(*queryp));
+
+ query = *queryp;
+ *queryp = NULL;
+
+ ref = isc_refcount_decrement(&query->references);
+ if (ref == 1) {
+ resquery_destroy(query);
+ }
+}
+
+/*%
+ * Update EDNS statistics for a server after not getting a response to a UDP
+ * query sent to it.
+ */
+static void
+update_edns_stats(resquery_t *query) {
+ fetchctx_t *fctx = query->fctx;
+
+ if ((query->options & DNS_FETCHOPT_TCP) != 0) {
+ return;
+ }
+
+ if ((query->options & DNS_FETCHOPT_NOEDNS0) == 0) {
+ dns_adb_ednsto(fctx->adb, query->addrinfo);
+ } else {
+ dns_adb_timeout(fctx->adb, query->addrinfo);
+ }
+}
+
+/*
+ * Start the maximum lifetime timer for the fetch. This will
+ * trigger if, for example, some ADB or validator dependency
+ * loop occurs and causes a fetch to hang.
+ */
+static isc_result_t
+fctx_starttimer(fetchctx_t *fctx) {
+ return (isc_timer_reset(fctx->timer, isc_timertype_once, &fctx->final,
+ NULL, true));
+}
+
+static void
+fctx_stoptimer(fetchctx_t *fctx) {
+ isc_result_t result;
+
+ /*
+ * We don't return a result if resetting the timer to inactive fails
+ * since there's nothing to be done about it. Resetting to inactive
+ * should never fail anyway, since the code as currently written
+ * cannot fail in that case.
+ */
+ result = isc_timer_reset(fctx->timer, isc_timertype_inactive, NULL,
+ NULL, true);
+ if (result != ISC_R_SUCCESS) {
+ UNEXPECTED_ERROR("isc_timer_reset(): %s",
+ isc_result_totext(result));
+ }
+}
+
+static void
+fctx_cancelquery(resquery_t **queryp, isc_time_t *finish, bool no_response,
+ bool age_untried) {
+ resquery_t *query = NULL;
+ fetchctx_t *fctx = NULL;
+ unsigned int rtt, rttms;
+ unsigned int factor;
+ dns_adbfind_t *find = NULL;
+ dns_adbaddrinfo_t *addrinfo;
+ isc_stdtime_t now;
+
+ REQUIRE(queryp != NULL);
+
+ query = *queryp;
+ fctx = query->fctx;
+
+ if (RESQUERY_CANCELED(query)) {
+ return;
+ }
+
+ FCTXTRACE("cancelquery");
+
+ query->attributes |= RESQUERY_ATTR_CANCELED;
+
+ /*
+ * Should we update the RTT?
+ */
+ if (finish != NULL || no_response) {
+ if (finish != NULL) {
+ /*
+ * We have both the start and finish times for this
+ * packet, so we can compute a real RTT.
+ */
+ rtt = (unsigned int)isc_time_microdiff(finish,
+ &query->start);
+ factor = DNS_ADB_RTTADJDEFAULT;
+
+ rttms = rtt / US_PER_MS;
+ if (rttms < DNS_RESOLVER_QRYRTTCLASS0) {
+ inc_stats(fctx->res,
+ dns_resstatscounter_queryrtt0);
+ } else if (rttms < DNS_RESOLVER_QRYRTTCLASS1) {
+ inc_stats(fctx->res,
+ dns_resstatscounter_queryrtt1);
+ } else if (rttms < DNS_RESOLVER_QRYRTTCLASS2) {
+ inc_stats(fctx->res,
+ dns_resstatscounter_queryrtt2);
+ } else if (rttms < DNS_RESOLVER_QRYRTTCLASS3) {
+ inc_stats(fctx->res,
+ dns_resstatscounter_queryrtt3);
+ } else if (rttms < DNS_RESOLVER_QRYRTTCLASS4) {
+ inc_stats(fctx->res,
+ dns_resstatscounter_queryrtt4);
+ } else {
+ inc_stats(fctx->res,
+ dns_resstatscounter_queryrtt5);
+ }
+ } else {
+ uint32_t value;
+ uint32_t mask;
+
+ update_edns_stats(query);
+
+ /*
+ * If "forward first;" is used and a forwarder timed
+ * out, do not attempt to query it again in this fetch
+ * context.
+ */
+ if (fctx->fwdpolicy == dns_fwdpolicy_first &&
+ ISFORWARDER(query->addrinfo))
+ {
+ add_bad(fctx, query->rmessage, query->addrinfo,
+ ISC_R_TIMEDOUT, badns_forwarder);
+ }
+
+ /*
+ * We don't have an RTT for this query. Maybe the
+ * packet was lost, or maybe this server is very
+ * slow. We don't know. Increase the RTT.
+ */
+ INSIST(no_response);
+ value = isc_random32();
+ if (query->addrinfo->srtt > 800000) {
+ mask = 0x3fff;
+ } else if (query->addrinfo->srtt > 400000) {
+ mask = 0x7fff;
+ } else if (query->addrinfo->srtt > 200000) {
+ mask = 0xffff;
+ } else if (query->addrinfo->srtt > 100000) {
+ mask = 0x1ffff;
+ } else if (query->addrinfo->srtt > 50000) {
+ mask = 0x3ffff;
+ } else if (query->addrinfo->srtt > 25000) {
+ mask = 0x7ffff;
+ } else {
+ mask = 0xfffff;
+ }
+
+ /*
+ * Don't adjust timeout on EDNS queries unless we have
+ * seen a EDNS response.
+ */
+ if ((query->options & DNS_FETCHOPT_NOEDNS0) == 0 &&
+ !EDNSOK(query->addrinfo))
+ {
+ mask >>= 2;
+ }
+
+ rtt = query->addrinfo->srtt + (value & mask);
+ if (rtt > MAX_SINGLE_QUERY_TIMEOUT_US) {
+ rtt = MAX_SINGLE_QUERY_TIMEOUT_US;
+ }
+
+ /*
+ * Replace the current RTT with our value.
+ */
+ factor = DNS_ADB_RTTADJREPLACE;
+ }
+
+ dns_adb_adjustsrtt(fctx->adb, query->addrinfo, rtt, factor);
+ }
+
+ if ((query->options & DNS_FETCHOPT_TCP) == 0) {
+ /* Inform the ADB that we're ending a UDP fetch */
+ dns_adb_endudpfetch(fctx->adb, query->addrinfo);
+ }
+
+ /*
+ * Age RTTs of servers not tried.
+ */
+ isc_stdtime_get(&now);
+ if (finish != NULL || age_untried) {
+ for (addrinfo = ISC_LIST_HEAD(fctx->forwaddrs);
+ addrinfo != NULL;
+ addrinfo = ISC_LIST_NEXT(addrinfo, publink))
+ {
+ if (UNMARKED(addrinfo)) {
+ dns_adb_agesrtt(fctx->adb, addrinfo, now);
+ }
+ }
+ }
+
+ if ((finish != NULL || age_untried) && TRIEDFIND(fctx)) {
+ for (find = ISC_LIST_HEAD(fctx->finds); find != NULL;
+ find = ISC_LIST_NEXT(find, publink))
+ {
+ for (addrinfo = ISC_LIST_HEAD(find->list);
+ addrinfo != NULL;
+ addrinfo = ISC_LIST_NEXT(addrinfo, publink))
+ {
+ if (UNMARKED(addrinfo)) {
+ dns_adb_agesrtt(fctx->adb, addrinfo,
+ now);
+ }
+ }
+ }
+ }
+
+ if ((finish != NULL || age_untried) && TRIEDALT(fctx)) {
+ for (addrinfo = ISC_LIST_HEAD(fctx->altaddrs); addrinfo != NULL;
+ addrinfo = ISC_LIST_NEXT(addrinfo, publink))
+ {
+ if (UNMARKED(addrinfo)) {
+ dns_adb_agesrtt(fctx->adb, addrinfo, now);
+ }
+ }
+ for (find = ISC_LIST_HEAD(fctx->altfinds); find != NULL;
+ find = ISC_LIST_NEXT(find, publink))
+ {
+ for (addrinfo = ISC_LIST_HEAD(find->list);
+ addrinfo != NULL;
+ addrinfo = ISC_LIST_NEXT(addrinfo, publink))
+ {
+ if (UNMARKED(addrinfo)) {
+ dns_adb_agesrtt(fctx->adb, addrinfo,
+ now);
+ }
+ }
+ }
+ }
+
+ /*
+ * Check for any outstanding dispatch responses and if they
+ * exist, cancel them.
+ */
+ if (query->dispentry != NULL) {
+ dns_dispatch_done(&query->dispentry);
+ }
+
+ LOCK(&fctx->res->buckets[fctx->bucketnum].lock);
+ if (ISC_LINK_LINKED(query, link)) {
+ ISC_LIST_UNLINK(fctx->queries, query, link);
+ }
+ UNLOCK(&fctx->res->buckets[fctx->bucketnum].lock);
+
+ resquery_detach(queryp);
+}
+
+static void
+fctx_cleanup(fetchctx_t *fctx) {
+ dns_adbfind_t *find = NULL, *next_find = NULL;
+ dns_adbaddrinfo_t *addr = NULL, *next_addr = NULL;
+
+ REQUIRE(ISC_LIST_EMPTY(fctx->queries));
+
+ for (find = ISC_LIST_HEAD(fctx->finds); find != NULL; find = next_find)
+ {
+ next_find = ISC_LIST_NEXT(find, publink);
+ ISC_LIST_UNLINK(fctx->finds, find, publink);
+ dns_adb_destroyfind(&find);
+ fctx_unref(fctx);
+ }
+ fctx->find = NULL;
+
+ for (find = ISC_LIST_HEAD(fctx->altfinds); find != NULL;
+ find = next_find)
+ {
+ next_find = ISC_LIST_NEXT(find, publink);
+ ISC_LIST_UNLINK(fctx->altfinds, find, publink);
+ dns_adb_destroyfind(&find);
+ fctx_unref(fctx);
+ }
+ fctx->altfind = NULL;
+
+ for (addr = ISC_LIST_HEAD(fctx->forwaddrs); addr != NULL;
+ addr = next_addr)
+ {
+ next_addr = ISC_LIST_NEXT(addr, publink);
+ ISC_LIST_UNLINK(fctx->forwaddrs, addr, publink);
+ dns_adb_freeaddrinfo(fctx->adb, &addr);
+ }
+
+ for (addr = ISC_LIST_HEAD(fctx->altaddrs); addr != NULL;
+ addr = next_addr)
+ {
+ next_addr = ISC_LIST_NEXT(addr, publink);
+ ISC_LIST_UNLINK(fctx->altaddrs, addr, publink);
+ dns_adb_freeaddrinfo(fctx->adb, &addr);
+ }
+}
+
+static void
+fctx_cancelqueries(fetchctx_t *fctx, bool no_response, bool age_untried) {
+ resquery_t *query = NULL, *next_query = NULL;
+ ISC_LIST(resquery_t) queries;
+
+ FCTXTRACE("cancelqueries");
+
+ ISC_LIST_INIT(queries);
+
+ /*
+ * Move the queries to a local list so we can cancel
+ * them without holding the lock.
+ */
+ LOCK(&fctx->res->buckets[fctx->bucketnum].lock);
+ ISC_LIST_MOVE(queries, fctx->queries);
+ UNLOCK(&fctx->res->buckets[fctx->bucketnum].lock);
+
+ for (query = ISC_LIST_HEAD(queries); query != NULL; query = next_query)
+ {
+ next_query = ISC_LIST_NEXT(query, link);
+
+ /*
+ * Note that we have to unlink the query here,
+ * because if it's still linked in fctx_cancelquery(),
+ * then it will try to unlink it from fctx->queries.
+ */
+ ISC_LIST_UNLINK(queries, query, link);
+ fctx_cancelquery(&query, NULL, no_response, age_untried);
+ }
+}
+
+static void
+fcount_logspill(fetchctx_t *fctx, fctxcount_t *counter, bool final) {
+ char dbuf[DNS_NAME_FORMATSIZE];
+ isc_stdtime_t now;
+
+ if (!isc_log_wouldlog(dns_lctx, ISC_LOG_INFO)) {
+ return;
+ }
+
+ /* Do not log a message if there were no dropped fetches. */
+ if (counter->dropped == 0) {
+ return;
+ }
+
+ /* Do not log the cumulative message if the previous log is recent. */
+ isc_stdtime_get(&now);
+ if (!final && counter->logged > now - 60) {
+ return;
+ }
+
+ dns_name_format(fctx->domain, dbuf, sizeof(dbuf));
+
+ if (!final) {
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_SPILL,
+ DNS_LOGMODULE_RESOLVER, ISC_LOG_INFO,
+ "too many simultaneous fetches for %s "
+ "(allowed %d spilled %d)",
+ dbuf, counter->allowed, counter->dropped);
+ } else {
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_SPILL,
+ DNS_LOGMODULE_RESOLVER, ISC_LOG_INFO,
+ "fetch counters for %s now being discarded "
+ "(allowed %d spilled %d; cumulative since "
+ "initial trigger event)",
+ dbuf, counter->allowed, counter->dropped);
+ }
+
+ counter->logged = now;
+}
+
+static isc_result_t
+fcount_incr(fetchctx_t *fctx, bool force) {
+ isc_result_t result = ISC_R_SUCCESS;
+ zonebucket_t *dbucket = NULL;
+ fctxcount_t *counter = NULL;
+ uint32_t hashval;
+ uint32_t dbucketnum;
+
+ REQUIRE(fctx != NULL);
+ REQUIRE(fctx->res != NULL);
+
+ INSIST(fctx->dbucketnum == RES_NOBUCKET);
+ hashval = dns_name_fullhash(fctx->domain, false);
+ dbucketnum = hash_32(hashval, fctx->res->dhashbits);
+
+ dbucket = &fctx->res->dbuckets[dbucketnum];
+
+ LOCK(&dbucket->lock);
+ for (counter = ISC_LIST_HEAD(dbucket->list); counter != NULL;
+ counter = ISC_LIST_NEXT(counter, link))
+ {
+ if (dns_name_equal(counter->domain, fctx->domain)) {
+ break;
+ }
+ }
+
+ if (counter == NULL) {
+ counter = isc_mem_get(fctx->res->mctx, sizeof(*counter));
+ *counter = (fctxcount_t){
+ .count = 1,
+ .allowed = 1,
+ };
+
+ counter->domain = dns_fixedname_initname(&counter->dfname);
+ ISC_LINK_INIT(counter, link);
+ dns_name_copy(fctx->domain, counter->domain);
+ ISC_LIST_APPEND(dbucket->list, counter, link);
+ } else {
+ uint_fast32_t spill = atomic_load_acquire(&fctx->res->zspill);
+ if (!force && spill != 0 && counter->count >= spill) {
+ counter->dropped++;
+ fcount_logspill(fctx, counter, false);
+ result = ISC_R_QUOTA;
+ } else {
+ counter->count++;
+ counter->allowed++;
+ }
+ }
+ UNLOCK(&dbucket->lock);
+
+ if (result == ISC_R_SUCCESS) {
+ fctx->dbucketnum = dbucketnum;
+ }
+
+ return (result);
+}
+
+static void
+fcount_decr(fetchctx_t *fctx) {
+ zonebucket_t *dbucket = NULL;
+ fctxcount_t *counter = NULL;
+
+ REQUIRE(fctx != NULL);
+
+ if (fctx->dbucketnum == RES_NOBUCKET) {
+ return;
+ }
+
+ dbucket = &fctx->res->dbuckets[fctx->dbucketnum];
+
+ LOCK(&dbucket->lock);
+ for (counter = ISC_LIST_HEAD(dbucket->list); counter != NULL;
+ counter = ISC_LIST_NEXT(counter, link))
+ {
+ if (dns_name_equal(counter->domain, fctx->domain)) {
+ break;
+ }
+ }
+
+ if (counter != NULL) {
+ INSIST(counter->count != 0);
+ counter->count--;
+ fctx->dbucketnum = RES_NOBUCKET;
+
+ if (counter->count == 0) {
+ fcount_logspill(fctx, counter, true);
+ ISC_LIST_UNLINK(dbucket->list, counter, link);
+ isc_mem_put(fctx->res->mctx, counter, sizeof(*counter));
+ }
+ }
+
+ UNLOCK(&dbucket->lock);
+}
+
+static void
+fctx_sendevents(fetchctx_t *fctx, isc_result_t result, int line) {
+ dns_fetchevent_t *event, *next_event;
+ isc_task_t *task;
+ unsigned int count = 0;
+ isc_interval_t i;
+ bool logit = false;
+ isc_time_t now;
+ unsigned int old_spillat;
+ unsigned int new_spillat = 0; /* initialized to silence
+ * compiler warnings */
+
+ /*
+ * Caller must be holding the appropriate bucket lock.
+ */
+ REQUIRE(fctx->state == fetchstate_done);
+
+ FCTXTRACE("sendevents");
+
+ /*
+ * Keep some record of fetch result for logging later (if required).
+ */
+ fctx->result = result;
+ fctx->exitline = line;
+ TIME_NOW(&now);
+ fctx->duration = isc_time_microdiff(&now, &fctx->start);
+
+ for (event = ISC_LIST_HEAD(fctx->events); event != NULL;
+ event = next_event)
+ {
+ next_event = ISC_LIST_NEXT(event, ev_link);
+ ISC_LIST_UNLINK(fctx->events, event, ev_link);
+
+ /*
+ * Only the regular fetch events should be counted for the
+ * clients-per-query limit, in case if there are multiple events
+ * registered for a single client.
+ */
+ if (event->ev_type == DNS_EVENT_FETCHDONE) {
+ count++;
+ }
+
+ if (event->ev_type == DNS_EVENT_TRYSTALE) {
+ /*
+ * Not applicable to TRY STALE events, this function is
+ * called when the fetch has either completed or timed
+ * out due to resolver-query-timeout being reached.
+ */
+ isc_task_detach((isc_task_t **)&event->ev_sender);
+ isc_event_free((isc_event_t **)&event);
+ continue;
+ }
+ task = event->ev_sender;
+ event->ev_sender = fctx;
+ event->vresult = fctx->vresult;
+ if (!HAVE_ANSWER(fctx)) {
+ event->result = result;
+ }
+
+ INSIST(event->result != ISC_R_SUCCESS ||
+ dns_rdataset_isassociated(event->rdataset) ||
+ fctx->type == dns_rdatatype_any ||
+ fctx->type == dns_rdatatype_rrsig ||
+ fctx->type == dns_rdatatype_sig);
+
+ /*
+ * Negative results must be indicated in event->result.
+ */
+ if (dns_rdataset_isassociated(event->rdataset) &&
+ NEGATIVE(event->rdataset))
+ {
+ INSIST(event->result == DNS_R_NCACHENXDOMAIN ||
+ event->result == DNS_R_NCACHENXRRSET);
+ }
+
+ FCTXTRACE("event");
+ isc_task_sendanddetach(&task, ISC_EVENT_PTR(&event));
+ }
+
+ if (HAVE_ANSWER(fctx) && fctx->spilled &&
+ (count < fctx->res->spillatmax || fctx->res->spillatmax == 0))
+ {
+ LOCK(&fctx->res->lock);
+ if (count == fctx->res->spillat &&
+ !atomic_load_acquire(&fctx->res->exiting))
+ {
+ old_spillat = fctx->res->spillat;
+ fctx->res->spillat += 5;
+ if (fctx->res->spillat > fctx->res->spillatmax &&
+ fctx->res->spillatmax != 0)
+ {
+ fctx->res->spillat = fctx->res->spillatmax;
+ }
+ new_spillat = fctx->res->spillat;
+ if (new_spillat != old_spillat) {
+ logit = true;
+ }
+ isc_interval_set(&i, 20 * 60, 0);
+ result = isc_timer_reset(fctx->res->spillattimer,
+ isc_timertype_ticker, NULL, &i,
+ true);
+ RUNTIME_CHECK(result == ISC_R_SUCCESS);
+ }
+ UNLOCK(&fctx->res->lock);
+ if (logit) {
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_RESOLVER,
+ DNS_LOGMODULE_RESOLVER, ISC_LOG_NOTICE,
+ "clients-per-query increased to %u",
+ new_spillat);
+ }
+ }
+}
+
+static void
+fctx__done_detach(fetchctx_t **fctxp, isc_result_t result, const char *file,
+ unsigned int line, const char *func) {
+ fetchctx_t *fctx = NULL;
+ dns_resolver_t *res = NULL;
+ bool no_response = false;
+ bool age_untried = false;
+
+ REQUIRE(fctxp != NULL && VALID_FCTX(*fctxp));
+
+ fctx = *fctxp;
+ res = fctx->res;
+
+ FCTXTRACE("done");
+
+#ifdef FCTX_TRACE
+ fprintf(stderr, "%s:%s:%u:%s(%p, %p): %s\n", func, file, line, __func__,
+ fctx, fctxp, isc_result_totext(result));
+#else
+ UNUSED(file);
+ UNUSED(line);
+ UNUSED(func);
+#endif
+
+ LOCK(&res->buckets[fctx->bucketnum].lock);
+ INSIST(fctx->state != fetchstate_done);
+ fctx->state = fetchstate_done;
+ UNLOCK(&res->buckets[fctx->bucketnum].lock);
+
+ if (result == ISC_R_SUCCESS) {
+ if (fctx->qmin_warning != ISC_R_SUCCESS) {
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_LAME_SERVERS,
+ DNS_LOGMODULE_RESOLVER, ISC_LOG_INFO,
+ "success resolving '%s' "
+ "after disabling qname minimization due "
+ "to '%s'",
+ fctx->info,
+ isc_result_totext(fctx->qmin_warning));
+ }
+
+ /*
+ * A success result indicates we got a response to a
+ * query. That query should be canceled already. If
+ * there still are any outstanding queries attached to the
+ * same fctx, then those have *not* gotten a response,
+ * so we set 'no_response' to true here: that way, when
+ * we run fctx_cancelqueries() below, the SRTTs will
+ * be adjusted.
+ */
+ no_response = true;
+ } else if (result == ISC_R_TIMEDOUT) {
+ age_untried = true;
+ }
+
+ fctx->qmin_warning = ISC_R_SUCCESS;
+
+ fctx_cancelqueries(fctx, no_response, age_untried);
+ fctx_stoptimer(fctx);
+
+ LOCK(&res->buckets[fctx->bucketnum].lock);
+ FCTX_ATTR_CLR(fctx, FCTX_ATTR_ADDRWAIT);
+ fctx_sendevents(fctx, result, line);
+ fctx_shutdown(fctx);
+ UNLOCK(&res->buckets[fctx->bucketnum].lock);
+
+ fctx_detach(fctxp);
+}
+
+static void
+resquery_senddone(isc_result_t eresult, isc_region_t *region, void *arg) {
+ resquery_t *query = (resquery_t *)arg;
+ resquery_t *copy = query;
+ fetchctx_t *fctx = NULL;
+
+ QTRACE("senddone");
+
+ UNUSED(region);
+
+ fctx = query->fctx;
+
+ if (RESQUERY_CANCELED(query)) {
+ goto detach;
+ }
+
+ /*
+ * See the note in resquery_connected() about reference
+ * counting on error conditions.
+ */
+ switch (eresult) {
+ case ISC_R_SUCCESS:
+ case ISC_R_CANCELED:
+ case ISC_R_SHUTTINGDOWN:
+ break;
+
+ case ISC_R_HOSTUNREACH:
+ case ISC_R_NETUNREACH:
+ case ISC_R_NOPERM:
+ case ISC_R_ADDRNOTAVAIL:
+ case ISC_R_CONNREFUSED:
+ /* No route to remote. */
+ FCTXTRACE3("query canceled in resquery_senddone(): "
+ "no route to host; no response",
+ eresult);
+ add_bad(fctx, query->rmessage, query->addrinfo, eresult,
+ badns_unreachable);
+ fctx_cancelquery(&copy, NULL, true, false);
+ FCTX_ATTR_CLR(fctx, FCTX_ATTR_ADDRWAIT);
+ fctx_try(fctx, true, false);
+ break;
+
+ default:
+ FCTXTRACE3("query canceled in resquery_senddone() "
+ "due to unexpected result; responding",
+ eresult);
+ fctx_cancelquery(&copy, NULL, false, false);
+ fctx_done_detach(&fctx, eresult);
+ break;
+ }
+
+detach:
+ resquery_detach(&query);
+}
+
+static isc_result_t
+fctx_addopt(dns_message_t *message, unsigned int version, uint16_t udpsize,
+ dns_ednsopt_t *ednsopts, size_t count) {
+ dns_rdataset_t *rdataset = NULL;
+ isc_result_t result;
+
+ result = dns_message_buildopt(message, &rdataset, version, udpsize,
+ DNS_MESSAGEEXTFLAG_DO, ednsopts, count);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+ return (dns_message_setopt(message, rdataset));
+}
+
+static void
+fctx_setretryinterval(fetchctx_t *fctx, unsigned int rtt) {
+ unsigned int seconds, us;
+ uint64_t limit;
+ isc_time_t now;
+
+ /*
+ * Has this fetch already expired?
+ */
+ isc_time_now(&now);
+ limit = isc_time_microdiff(&fctx->expires, &now);
+ if (limit < US_PER_MS) {
+ FCTXTRACE("fetch already expired");
+ isc_interval_set(&fctx->interval, 0, 0);
+ return;
+ }
+
+ us = fctx->res->retryinterval * US_PER_MS;
+
+ /*
+ * Exponential backoff after the first few tries.
+ */
+ if (fctx->restarts > fctx->res->nonbackofftries) {
+ int shift = fctx->restarts - fctx->res->nonbackofftries;
+ if (shift > 6) {
+ shift = 6;
+ }
+ us <<= shift;
+ }
+
+ /*
+ * Add a fudge factor to the expected rtt based on the current
+ * estimate.
+ */
+ if (rtt < 50000) {
+ rtt += 50000;
+ } else if (rtt < 100000) {
+ rtt += 100000;
+ } else {
+ rtt += 200000;
+ }
+
+ /*
+ * Always wait for at least the expected rtt.
+ */
+ if (us < rtt) {
+ us = rtt;
+ }
+
+ /*
+ * But don't wait past the stale timeout (if any), the final
+ * expiration of the fetch, or for more than 10 seconds total.
+ */
+ if ((fctx->options & DNS_FETCHOPT_TRYSTALE_ONTIMEOUT) != 0) {
+ uint64_t stale = isc_time_microdiff(&fctx->expires_try_stale,
+ &now);
+ if (stale >= US_PER_MS && us > stale) {
+ FCTXTRACE("setting stale timeout");
+ us = stale;
+ }
+ }
+ if (us > limit) {
+ us = limit;
+ }
+ if (us > MAX_SINGLE_QUERY_TIMEOUT_US) {
+ us = MAX_SINGLE_QUERY_TIMEOUT_US;
+ }
+
+ seconds = us / US_PER_SEC;
+ us -= seconds * US_PER_SEC;
+ isc_interval_set(&fctx->interval, seconds, us * NS_PER_US);
+ isc_time_nowplusinterval(&fctx->next_timeout, &fctx->interval);
+}
+
+static isc_result_t
+resquery_timeout(resquery_t *query) {
+ fetchctx_t *fctx = query->fctx;
+ dns_fetchevent_t *event = NULL, *next = NULL;
+ uint64_t timeleft;
+ isc_time_t now;
+
+ FCTXTRACE("timeout");
+
+ /*
+ * If not configured for serve-stale, do nothing.
+ */
+ if ((fctx->options & DNS_FETCHOPT_TRYSTALE_ONTIMEOUT) == 0) {
+ return (ISC_R_SUCCESS);
+ }
+
+ /*
+ * If we haven't reached the serve-stale timeout, do nothing.
+ * (Note that netmgr timeouts have millisecond accuracy, so
+ * anything less than 1000 microseconds is close enough to zero.)
+ */
+ isc_time_now(&now);
+ timeleft = isc_time_microdiff(&fctx->expires_try_stale, &now);
+ if (timeleft >= US_PER_MS) {
+ return (ISC_R_SUCCESS);
+ }
+
+ /*
+ * Send the TRYSTALE events.
+ */
+ LOCK(&fctx->res->buckets[fctx->bucketnum].lock);
+ for (event = ISC_LIST_HEAD(fctx->events); event != NULL; event = next) {
+ isc_task_t *sender = NULL;
+
+ next = ISC_LIST_NEXT(event, ev_link);
+ if (event->ev_type != DNS_EVENT_TRYSTALE) {
+ continue;
+ }
+
+ ISC_LIST_UNLINK(fctx->events, event, ev_link);
+ sender = event->ev_sender;
+ event->vresult = ISC_R_TIMEDOUT;
+ event->result = ISC_R_TIMEDOUT;
+ isc_task_sendanddetach(&sender, ISC_EVENT_PTR(&event));
+ }
+ UNLOCK(&fctx->res->buckets[fctx->bucketnum].lock);
+
+ /*
+ * If the next timeout is more than 1ms in the future,
+ * resume waiting.
+ */
+ timeleft = isc_time_microdiff(&fctx->next_timeout, &now);
+ if (timeleft >= US_PER_MS) {
+ dns_dispatch_resume(query->dispentry, (timeleft / US_PER_MS));
+ return (ISC_R_COMPLETE);
+ }
+
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+fctx_query(fetchctx_t *fctx, dns_adbaddrinfo_t *addrinfo,
+ unsigned int options) {
+ isc_result_t result;
+ dns_resolver_t *res = NULL;
+ resquery_t *query = NULL;
+ isc_sockaddr_t addr;
+ bool have_addr = false;
+ unsigned int srtt;
+
+ FCTXTRACE("query");
+
+ res = fctx->res;
+
+ srtt = addrinfo->srtt;
+
+ /*
+ * Allow an additional second for the kernel to resend the SYN
+ * (or SYN without ECN in the case of stupid firewalls blocking
+ * ECN negotiation) over the current RTT estimate.
+ */
+ if ((options & DNS_FETCHOPT_TCP) != 0) {
+ srtt += US_PER_SEC;
+ }
+
+ /*
+ * A forwarder needs to make multiple queries. Give it at least
+ * a second to do these in.
+ */
+ if (ISFORWARDER(addrinfo) && srtt < US_PER_SEC) {
+ srtt = US_PER_SEC;
+ }
+
+ fctx_setretryinterval(fctx, srtt);
+ if (isc_interval_iszero(&fctx->interval)) {
+ FCTXTRACE("fetch expired");
+ return (ISC_R_TIMEDOUT);
+ }
+
+ INSIST(ISC_LIST_EMPTY(fctx->validators));
+
+ query = isc_mem_get(fctx->mctx, sizeof(*query));
+ *query = (resquery_t){ .mctx = fctx->mctx,
+ .options = options,
+ .addrinfo = addrinfo,
+ .dispatchmgr = res->dispatchmgr };
+
+ isc_refcount_init(&query->references, 1);
+
+ /*
+ * Note that the caller MUST guarantee that 'addrinfo' will
+ * remain valid until this query is canceled.
+ */
+
+ dns_message_create(fctx->mctx, DNS_MESSAGE_INTENTPARSE,
+ &query->rmessage);
+ TIME_NOW(&query->start);
+
+ /*
+ * If this is a TCP query, then we need to make a socket and
+ * a dispatch for it here. Otherwise we use the resolver's
+ * shared dispatch.
+ */
+ if (res->view->peers != NULL) {
+ dns_peer_t *peer = NULL;
+ isc_netaddr_t dstip;
+ bool usetcp = false;
+ isc_netaddr_fromsockaddr(&dstip, &addrinfo->sockaddr);
+ result = dns_peerlist_peerbyaddr(res->view->peers, &dstip,
+ &peer);
+ if (result == ISC_R_SUCCESS) {
+ result = dns_peer_getquerysource(peer, &addr);
+ if (result == ISC_R_SUCCESS) {
+ have_addr = true;
+ }
+ result = dns_peer_getforcetcp(peer, &usetcp);
+ if (result == ISC_R_SUCCESS && usetcp) {
+ query->options |= DNS_FETCHOPT_TCP;
+ }
+ }
+ }
+
+ if ((query->options & DNS_FETCHOPT_TCP) != 0) {
+ int pf;
+
+ pf = isc_sockaddr_pf(&addrinfo->sockaddr);
+ if (!have_addr) {
+ switch (pf) {
+ case PF_INET:
+ result = dns_dispatch_getlocaladdress(
+ res->dispatches4->dispatches[0], &addr);
+ break;
+ case PF_INET6:
+ result = dns_dispatch_getlocaladdress(
+ res->dispatches6->dispatches[0], &addr);
+ break;
+ default:
+ result = ISC_R_NOTIMPLEMENTED;
+ break;
+ }
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup_query;
+ }
+ }
+ isc_sockaddr_setport(&addr, 0);
+
+ result = dns_dispatch_createtcp(res->dispatchmgr, &addr,
+ &addrinfo->sockaddr,
+ &query->dispatch);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup_query;
+ }
+
+ FCTXTRACE("connecting via TCP");
+ } else {
+ if (have_addr) {
+ result = dns_dispatch_createudp(res->dispatchmgr, &addr,
+ &query->dispatch);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup_query;
+ }
+ } else {
+ switch (isc_sockaddr_pf(&addrinfo->sockaddr)) {
+ case PF_INET:
+ dns_dispatch_attach(
+ dns_resolver_dispatchv4(res),
+ &query->dispatch);
+ break;
+ case PF_INET6:
+ dns_dispatch_attach(
+ dns_resolver_dispatchv6(res),
+ &query->dispatch);
+ break;
+ default:
+ result = ISC_R_NOTIMPLEMENTED;
+ goto cleanup_query;
+ }
+ }
+
+ /*
+ * We should always have a valid dispatcher here. If we
+ * don't support a protocol family, then its dispatcher
+ * will be NULL, but we shouldn't be finding addresses
+ * for protocol types we don't support, so the
+ * dispatcher we found should never be NULL.
+ */
+ INSIST(query->dispatch != NULL);
+ }
+
+ fctx_attach(fctx, &query->fctx);
+ ISC_LINK_INIT(query, link);
+ query->magic = QUERY_MAGIC;
+
+ if ((query->options & DNS_FETCHOPT_TCP) == 0) {
+ if (dns_adbentry_overquota(addrinfo->entry)) {
+ result = ISC_R_QUOTA;
+ goto cleanup_dispatch;
+ }
+
+ /* Inform the ADB that we're starting a UDP fetch */
+ dns_adb_beginudpfetch(fctx->adb, addrinfo);
+ }
+
+ LOCK(&res->buckets[fctx->bucketnum].lock);
+ ISC_LIST_APPEND(fctx->queries, query, link);
+ atomic_fetch_add_relaxed(&fctx->nqueries, 1);
+ UNLOCK(&res->buckets[fctx->bucketnum].lock);
+
+ /* Set up the dispatch and set the query ID */
+ result = dns_dispatch_add(
+ query->dispatch, 0, isc_interval_ms(&fctx->interval),
+ &query->addrinfo->sockaddr, resquery_connected,
+ resquery_senddone, resquery_response, query, &query->id,
+ &query->dispentry);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup_udpfetch;
+ }
+
+ /* Connect the socket */
+ resquery_attach(query, &(resquery_t *){ NULL });
+ result = dns_dispatch_connect(query->dispentry);
+
+ RUNTIME_CHECK(result == ISC_R_SUCCESS);
+
+ return (result);
+
+cleanup_udpfetch:
+ if (!RESQUERY_CANCELED(query)) {
+ if ((query->options & DNS_FETCHOPT_TCP) == 0) {
+ /* Inform the ADB that we're ending a UDP fetch */
+ dns_adb_endudpfetch(fctx->adb, addrinfo);
+ }
+ }
+
+cleanup_dispatch:
+ fctx_detach(&query->fctx);
+
+ if (query->dispatch != NULL) {
+ dns_dispatch_detach(&query->dispatch);
+ }
+
+cleanup_query:
+ LOCK(&res->buckets[fctx->bucketnum].lock);
+ if (ISC_LINK_LINKED(query, link)) {
+ atomic_fetch_sub_release(&fctx->nqueries, 1);
+ ISC_LIST_UNLINK(fctx->queries, query, link);
+ }
+ UNLOCK(&res->buckets[fctx->bucketnum].lock);
+
+ query->magic = 0;
+ dns_message_detach(&query->rmessage);
+ isc_mem_put(fctx->mctx, query, sizeof(*query));
+
+ return (result);
+}
+
+static bool
+bad_edns(fetchctx_t *fctx, isc_sockaddr_t *address) {
+ isc_sockaddr_t *sa;
+
+ for (sa = ISC_LIST_HEAD(fctx->bad_edns); sa != NULL;
+ sa = ISC_LIST_NEXT(sa, link))
+ {
+ if (isc_sockaddr_equal(sa, address)) {
+ return (true);
+ }
+ }
+
+ return (false);
+}
+
+static void
+add_bad_edns(fetchctx_t *fctx, isc_sockaddr_t *address) {
+ isc_sockaddr_t *sa;
+
+#ifdef ENABLE_AFL
+ if (dns_fuzzing_resolver) {
+ return;
+ }
+#endif /* ifdef ENABLE_AFL */
+ if (bad_edns(fctx, address)) {
+ return;
+ }
+
+ sa = isc_mem_get(fctx->mctx, sizeof(*sa));
+
+ *sa = *address;
+ ISC_LIST_INITANDAPPEND(fctx->bad_edns, sa, link);
+}
+
+static struct tried *
+triededns(fetchctx_t *fctx, isc_sockaddr_t *address) {
+ struct tried *tried;
+
+ for (tried = ISC_LIST_HEAD(fctx->edns); tried != NULL;
+ tried = ISC_LIST_NEXT(tried, link))
+ {
+ if (isc_sockaddr_equal(&tried->addr, address)) {
+ return (tried);
+ }
+ }
+
+ return (NULL);
+}
+
+static void
+add_triededns(fetchctx_t *fctx, isc_sockaddr_t *address) {
+ struct tried *tried;
+
+ tried = triededns(fctx, address);
+ if (tried != NULL) {
+ tried->count++;
+ return;
+ }
+
+ tried = isc_mem_get(fctx->mctx, sizeof(*tried));
+
+ tried->addr = *address;
+ tried->count = 1;
+ ISC_LIST_INITANDAPPEND(fctx->edns, tried, link);
+}
+
+static size_t
+addr2buf(void *buf, const size_t bufsize, const isc_sockaddr_t *sockaddr) {
+ isc_netaddr_t netaddr;
+ isc_netaddr_fromsockaddr(&netaddr, sockaddr);
+ switch (netaddr.family) {
+ case AF_INET:
+ INSIST(bufsize >= 4);
+ memmove(buf, &netaddr.type.in, 4);
+ return (4);
+ case AF_INET6:
+ INSIST(bufsize >= 16);
+ memmove(buf, &netaddr.type.in6, 16);
+ return (16);
+ default:
+ UNREACHABLE();
+ }
+ return (0);
+}
+
+static size_t
+add_serveraddr(uint8_t *buf, const size_t bufsize, const resquery_t *query) {
+ return (addr2buf(buf, bufsize, &query->addrinfo->sockaddr));
+}
+
+/*
+ * Client cookie is 8 octets.
+ * Server cookie is [8..32] octets.
+ */
+#define CLIENT_COOKIE_SIZE 8U
+#define COOKIE_BUFFER_SIZE (8U + 32U)
+
+static void
+compute_cc(const resquery_t *query, uint8_t *cookie, const size_t len) {
+ INSIST(len >= CLIENT_COOKIE_SIZE);
+ STATIC_ASSERT(sizeof(query->fctx->res->view->secret) >=
+ ISC_SIPHASH24_KEY_LENGTH,
+ "The view->secret size can't fit SipHash 2-4 key "
+ "length");
+
+ uint8_t buf[16] ISC_NONSTRING = { 0 };
+ size_t buflen = add_serveraddr(buf, sizeof(buf), query);
+
+ uint8_t digest[ISC_SIPHASH24_TAG_LENGTH] ISC_NONSTRING = { 0 };
+ isc_siphash24(query->fctx->res->view->secret, buf, buflen, digest);
+ memmove(cookie, digest, CLIENT_COOKIE_SIZE);
+}
+
+static isc_result_t
+issecuredomain(dns_view_t *view, const dns_name_t *name, dns_rdatatype_t type,
+ isc_stdtime_t now, bool checknta, bool *ntap, bool *issecure) {
+ dns_name_t suffix;
+ unsigned int labels;
+
+ /*
+ * For DS variants we need to check fom the parent domain,
+ * since there may be a negative trust anchor for the name,
+ * while the enclosing domain where the DS record lives is
+ * under a secure entry point.
+ */
+ labels = dns_name_countlabels(name);
+ if (dns_rdatatype_atparent(type) && labels > 1) {
+ dns_name_init(&suffix, NULL);
+ dns_name_getlabelsequence(name, 1, labels - 1, &suffix);
+ name = &suffix;
+ }
+
+ return (dns_view_issecuredomain(view, name, now, checknta, ntap,
+ issecure));
+}
+
+static isc_result_t
+resquery_send(resquery_t *query) {
+ isc_result_t result;
+ fetchctx_t *fctx = query->fctx;
+ dns_resolver_t *res = fctx->res;
+ isc_buffer_t buffer;
+ dns_name_t *qname = NULL;
+ dns_rdataset_t *qrdataset = NULL;
+ isc_region_t r;
+ isc_netaddr_t ipaddr;
+ dns_tsigkey_t *tsigkey = NULL;
+ dns_peer_t *peer = NULL;
+ dns_compress_t cctx;
+ bool cleanup_cctx = false;
+ bool useedns;
+ bool secure_domain;
+ bool tcp = ((query->options & DNS_FETCHOPT_TCP) != 0);
+ dns_ednsopt_t ednsopts[DNS_EDNSOPTIONS];
+ unsigned ednsopt = 0;
+ uint16_t hint = 0, udpsize = 0; /* No EDNS */
+#ifdef HAVE_DNSTAP
+ isc_sockaddr_t localaddr, *la = NULL;
+ unsigned char zone[DNS_NAME_MAXWIRE];
+ dns_dtmsgtype_t dtmsgtype;
+ isc_region_t zr;
+ isc_buffer_t zb;
+#endif /* HAVE_DNSTAP */
+
+ QTRACE("send");
+
+ if (atomic_load_acquire(&res->exiting)) {
+ FCTXTRACE("resquery_send: resolver shutting down");
+ return (ISC_R_SHUTTINGDOWN);
+ }
+
+ result = dns_message_gettempname(fctx->qmessage, &qname);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup_temps;
+ }
+ result = dns_message_gettemprdataset(fctx->qmessage, &qrdataset);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup_temps;
+ }
+
+ fctx->qmessage->opcode = dns_opcode_query;
+
+ /*
+ * Set up question.
+ */
+ dns_name_clone(fctx->name, qname);
+ dns_rdataset_makequestion(qrdataset, res->rdclass, fctx->type);
+ ISC_LIST_APPEND(qname->list, qrdataset, link);
+ dns_message_addname(fctx->qmessage, qname, DNS_SECTION_QUESTION);
+ qname = NULL;
+ qrdataset = NULL;
+
+ /*
+ * Set RD if the client has requested that we do a recursive
+ * query, or if we're sending to a forwarder.
+ */
+ if ((query->options & DNS_FETCHOPT_RECURSIVE) != 0 ||
+ ISFORWARDER(query->addrinfo))
+ {
+ fctx->qmessage->flags |= DNS_MESSAGEFLAG_RD;
+ }
+
+ /*
+ * Set CD if the client says not to validate, or if the
+ * question is under a secure entry point and this is a
+ * recursive/forward query -- unless the client said not to.
+ */
+ if ((query->options & DNS_FETCHOPT_NOCDFLAG) != 0) {
+ /* Do nothing */
+ } else if ((query->options & DNS_FETCHOPT_NOVALIDATE) != 0) {
+ fctx->qmessage->flags |= DNS_MESSAGEFLAG_CD;
+ } else if (res->view->enablevalidation &&
+ ((fctx->qmessage->flags & DNS_MESSAGEFLAG_RD) != 0))
+ {
+ bool checknta = ((query->options & DNS_FETCHOPT_NONTA) == 0);
+ bool ntacovered = false;
+ result = issecuredomain(res->view, fctx->name, fctx->type,
+ isc_time_seconds(&query->start),
+ checknta, &ntacovered, &secure_domain);
+ if (result != ISC_R_SUCCESS) {
+ secure_domain = false;
+ }
+ if (secure_domain ||
+ (ISFORWARDER(query->addrinfo) && ntacovered))
+ {
+ fctx->qmessage->flags |= DNS_MESSAGEFLAG_CD;
+ }
+ }
+
+ /*
+ * We don't have to set opcode because it defaults to query.
+ */
+ fctx->qmessage->id = query->id;
+
+ /*
+ * Convert the question to wire format.
+ */
+ result = dns_compress_init(&cctx, -1, fctx->res->mctx);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup_message;
+ }
+ cleanup_cctx = true;
+
+ isc_buffer_init(&buffer, query->data, sizeof(query->data));
+ result = dns_message_renderbegin(fctx->qmessage, &cctx, &buffer);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup_message;
+ }
+
+ result = dns_message_rendersection(fctx->qmessage, DNS_SECTION_QUESTION,
+ 0);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup_message;
+ }
+
+ isc_netaddr_fromsockaddr(&ipaddr, &query->addrinfo->sockaddr);
+ (void)dns_peerlist_peerbyaddr(fctx->res->view->peers, &ipaddr, &peer);
+
+ /*
+ * The ADB does not know about servers with "edns no". Check
+ * this, and then inform the ADB for future use.
+ */
+ if ((query->addrinfo->flags & FCTX_ADDRINFO_NOEDNS0) == 0 &&
+ peer != NULL &&
+ dns_peer_getsupportedns(peer, &useedns) == ISC_R_SUCCESS &&
+ !useedns)
+ {
+ query->options |= DNS_FETCHOPT_NOEDNS0;
+ dns_adb_changeflags(fctx->adb, query->addrinfo,
+ FCTX_ADDRINFO_NOEDNS0,
+ FCTX_ADDRINFO_NOEDNS0);
+ }
+
+ /* Sync NOEDNS0 flag in addrinfo->flags and options now. */
+ if ((query->addrinfo->flags & FCTX_ADDRINFO_NOEDNS0) != 0) {
+ query->options |= DNS_FETCHOPT_NOEDNS0;
+ }
+
+ if (fctx->timeout && (query->options & DNS_FETCHOPT_NOEDNS0) == 0) {
+ isc_sockaddr_t *sockaddr = &query->addrinfo->sockaddr;
+ struct tried *tried;
+
+ /*
+ * If this is the first timeout for this server in this
+ * fetch context, try setting EDNS UDP buffer size to
+ * the largest UDP response size we have seen from this
+ * server so far.
+ *
+ * If this server has already timed out twice or more in
+ * this fetch context, force TCP.
+ */
+ if ((tried = triededns(fctx, sockaddr)) != NULL) {
+ if (tried->count == 1U) {
+ hint = dns_adb_getudpsize(fctx->adb,
+ query->addrinfo);
+ } else if (tried->count >= 2U) {
+ if ((query->options & DNS_FETCHOPT_TCP) == 0) {
+ /*
+ * Inform the ADB that we're ending a
+ * UDP fetch, and turn the query into
+ * a TCP query.
+ */
+ dns_adb_endudpfetch(fctx->adb,
+ query->addrinfo);
+ query->options |= DNS_FETCHOPT_TCP;
+ }
+ }
+ }
+ }
+ fctx->timeout = false;
+
+ /*
+ * Use EDNS0, unless the caller doesn't want it, or we know that
+ * the remote server doesn't like it.
+ */
+ if ((query->options & DNS_FETCHOPT_NOEDNS0) == 0) {
+ if ((query->addrinfo->flags & FCTX_ADDRINFO_NOEDNS0) == 0) {
+ uint16_t peerudpsize = 0;
+ unsigned int version = DNS_EDNS_VERSION;
+ unsigned int flags = query->addrinfo->flags;
+ bool reqnsid = res->view->requestnsid;
+ bool sendcookie = res->view->sendcookie;
+ bool tcpkeepalive = false;
+ unsigned char cookie[COOKIE_BUFFER_SIZE];
+ uint16_t padding = 0;
+
+ /*
+ * Set the default UDP size to what was
+ * configured as 'edns-buffer-size'
+ */
+ udpsize = res->udpsize;
+
+ /*
+ * This server timed out for the first time in
+ * this fetch context and we received a response
+ * from it before (either in this fetch context
+ * or in a different one). Set 'udpsize' to the
+ * size of the largest UDP response we have
+ * received from this server so far.
+ */
+ if (hint != 0U) {
+ udpsize = hint;
+ }
+
+ /*
+ * If a fixed EDNS UDP buffer size is configured
+ * for this server, make sure we obey that.
+ */
+ if (peer != NULL) {
+ (void)dns_peer_getudpsize(peer, &peerudpsize);
+ if (peerudpsize != 0) {
+ udpsize = peerudpsize;
+ }
+ }
+
+ if ((flags & DNS_FETCHOPT_EDNSVERSIONSET) != 0) {
+ version = flags & DNS_FETCHOPT_EDNSVERSIONMASK;
+ version >>= DNS_FETCHOPT_EDNSVERSIONSHIFT;
+ }
+
+ /* Request NSID/COOKIE/VERSION for current peer?
+ */
+ if (peer != NULL) {
+ uint8_t ednsversion;
+ (void)dns_peer_getrequestnsid(peer, &reqnsid);
+ (void)dns_peer_getsendcookie(peer, &sendcookie);
+ result = dns_peer_getednsversion(peer,
+ &ednsversion);
+ if (result == ISC_R_SUCCESS &&
+ ednsversion < version)
+ {
+ version = ednsversion;
+ }
+ }
+ if (NOCOOKIE(query->addrinfo)) {
+ sendcookie = false;
+ }
+ if (reqnsid) {
+ INSIST(ednsopt < DNS_EDNSOPTIONS);
+ ednsopts[ednsopt].code = DNS_OPT_NSID;
+ ednsopts[ednsopt].length = 0;
+ ednsopts[ednsopt].value = NULL;
+ ednsopt++;
+ }
+ if (sendcookie) {
+ INSIST(ednsopt < DNS_EDNSOPTIONS);
+ ednsopts[ednsopt].code = DNS_OPT_COOKIE;
+ ednsopts[ednsopt].length =
+ (uint16_t)dns_adb_getcookie(
+ fctx->adb, query->addrinfo,
+ cookie, sizeof(cookie));
+ if (ednsopts[ednsopt].length != 0) {
+ ednsopts[ednsopt].value = cookie;
+ inc_stats(
+ fctx->res,
+ dns_resstatscounter_cookieout);
+ } else {
+ compute_cc(query, cookie,
+ CLIENT_COOKIE_SIZE);
+ ednsopts[ednsopt].value = cookie;
+ ednsopts[ednsopt].length =
+ CLIENT_COOKIE_SIZE;
+ inc_stats(
+ fctx->res,
+ dns_resstatscounter_cookienew);
+ }
+ ednsopt++;
+ }
+
+ /* Add TCP keepalive option if appropriate */
+ if ((peer != NULL) && tcp) {
+ (void)dns_peer_gettcpkeepalive(peer,
+ &tcpkeepalive);
+ }
+ if (tcpkeepalive) {
+ INSIST(ednsopt < DNS_EDNSOPTIONS);
+ ednsopts[ednsopt].code = DNS_OPT_TCP_KEEPALIVE;
+ ednsopts[ednsopt].length = 0;
+ ednsopts[ednsopt].value = NULL;
+ ednsopt++;
+ }
+
+ /* Add PAD for current peer? Require TCP for now
+ */
+ if ((peer != NULL) && tcp) {
+ (void)dns_peer_getpadding(peer, &padding);
+ }
+ if (padding != 0) {
+ INSIST(ednsopt < DNS_EDNSOPTIONS);
+ ednsopts[ednsopt].code = DNS_OPT_PAD;
+ ednsopts[ednsopt].length = 0;
+ ednsopt++;
+ dns_message_setpadding(fctx->qmessage, padding);
+ }
+
+ query->ednsversion = version;
+ result = fctx_addopt(fctx->qmessage, version, udpsize,
+ ednsopts, ednsopt);
+ if (reqnsid && result == ISC_R_SUCCESS) {
+ query->options |= DNS_FETCHOPT_WANTNSID;
+ } else if (result != ISC_R_SUCCESS) {
+ /*
+ * We couldn't add the OPT, but we'll
+ * press on. We're not using EDNS0, so
+ * set the NOEDNS0 bit.
+ */
+ query->options |= DNS_FETCHOPT_NOEDNS0;
+ query->ednsversion = -1;
+ udpsize = 0;
+ }
+ } else {
+ /*
+ * We know this server doesn't like EDNS0, so we
+ * won't use it. Set the NOEDNS0 bit since
+ * we're not using EDNS0.
+ */
+ query->options |= DNS_FETCHOPT_NOEDNS0;
+ query->ednsversion = -1;
+ }
+ } else {
+ query->ednsversion = -1;
+ }
+
+ /*
+ * Record the UDP EDNS size chosen.
+ */
+ query->udpsize = udpsize;
+
+ /*
+ * If we need EDNS0 to do this query and aren't using it, we
+ * lose.
+ */
+ if (NEEDEDNS0(fctx) && (query->options & DNS_FETCHOPT_NOEDNS0) != 0) {
+ result = DNS_R_SERVFAIL;
+ goto cleanup_message;
+ }
+
+ add_triededns(fctx, &query->addrinfo->sockaddr);
+
+ /*
+ * Clear CD if EDNS is not in use.
+ */
+ if ((query->options & DNS_FETCHOPT_NOEDNS0) != 0) {
+ fctx->qmessage->flags &= ~DNS_MESSAGEFLAG_CD;
+ }
+
+ /*
+ * Add TSIG record tailored to the current recipient.
+ */
+ result = dns_view_getpeertsig(fctx->res->view, &ipaddr, &tsigkey);
+ if (result != ISC_R_SUCCESS && result != ISC_R_NOTFOUND) {
+ goto cleanup_message;
+ }
+
+ if (tsigkey != NULL) {
+ result = dns_message_settsigkey(fctx->qmessage, tsigkey);
+ dns_tsigkey_detach(&tsigkey);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup_message;
+ }
+ }
+
+ result = dns_message_rendersection(fctx->qmessage,
+ DNS_SECTION_ADDITIONAL, 0);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup_message;
+ }
+
+ result = dns_message_renderend(fctx->qmessage);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup_message;
+ }
+
+#ifdef HAVE_DNSTAP
+ memset(&zr, 0, sizeof(zr));
+ isc_buffer_init(&zb, zone, sizeof(zone));
+ dns_compress_setmethods(&cctx, DNS_COMPRESS_NONE);
+ result = dns_name_towire(fctx->domain, &cctx, &zb);
+ if (result == ISC_R_SUCCESS) {
+ isc_buffer_usedregion(&zb, &zr);
+ }
+#endif /* HAVE_DNSTAP */
+
+ dns_compress_invalidate(&cctx);
+ cleanup_cctx = false;
+
+ if (dns_message_gettsigkey(fctx->qmessage) != NULL) {
+ dns_tsigkey_attach(dns_message_gettsigkey(fctx->qmessage),
+ &query->tsigkey);
+ result = dns_message_getquerytsig(
+ fctx->qmessage, fctx->res->mctx, &query->tsig);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup_message;
+ }
+ }
+
+ /*
+ * Log the outgoing packet.
+ */
+ dns_message_logfmtpacket(
+ fctx->qmessage, "sending packet to", &query->addrinfo->sockaddr,
+ DNS_LOGCATEGORY_RESOLVER, DNS_LOGMODULE_PACKETS,
+ &dns_master_style_comment, ISC_LOG_DEBUG(11), fctx->res->mctx);
+
+ /*
+ * We're now done with the query message.
+ */
+ dns_message_reset(fctx->qmessage, DNS_MESSAGE_INTENTRENDER);
+
+ isc_buffer_usedregion(&buffer, &r);
+
+ resquery_attach(query, &(resquery_t *){ NULL });
+ dns_dispatch_send(query->dispentry, &r);
+
+ QTRACE("sent");
+
+#ifdef HAVE_DNSTAP
+ /*
+ * Log the outgoing query via dnstap.
+ */
+ if ((fctx->qmessage->flags & DNS_MESSAGEFLAG_RD) != 0) {
+ dtmsgtype = DNS_DTTYPE_FQ;
+ } else {
+ dtmsgtype = DNS_DTTYPE_RQ;
+ }
+
+ result = dns_dispentry_getlocaladdress(query->dispentry, &localaddr);
+ if (result == ISC_R_SUCCESS) {
+ la = &localaddr;
+ }
+
+ dns_dt_send(fctx->res->view, dtmsgtype, la, &query->addrinfo->sockaddr,
+ tcp, &zr, &query->start, NULL, &buffer);
+#endif /* HAVE_DNSTAP */
+
+ return (ISC_R_SUCCESS);
+
+cleanup_message:
+ if (cleanup_cctx) {
+ dns_compress_invalidate(&cctx);
+ }
+
+ dns_message_reset(fctx->qmessage, DNS_MESSAGE_INTENTRENDER);
+
+ /*
+ * Stop the dispatcher from listening.
+ */
+ dns_dispatch_done(&query->dispentry);
+
+cleanup_temps:
+ if (qname != NULL) {
+ dns_message_puttempname(fctx->qmessage, &qname);
+ }
+ if (qrdataset != NULL) {
+ dns_message_puttemprdataset(fctx->qmessage, &qrdataset);
+ }
+
+ return (result);
+}
+
+static void
+resquery_connected(isc_result_t eresult, isc_region_t *region, void *arg) {
+ resquery_t *query = (resquery_t *)arg;
+ resquery_t *copy = query;
+ isc_result_t result;
+ fetchctx_t *fctx = NULL;
+ dns_resolver_t *res = NULL;
+ int pf;
+
+ REQUIRE(VALID_QUERY(query));
+
+ QTRACE("connected");
+
+ UNUSED(region);
+
+ fctx = query->fctx;
+ res = fctx->res;
+
+ if (RESQUERY_CANCELED(query)) {
+ goto detach;
+ }
+
+ if (atomic_load_acquire(&fctx->res->exiting)) {
+ eresult = ISC_R_SHUTTINGDOWN;
+ }
+
+ /*
+ * The reference counting of resquery objects is complex:
+ *
+ * 1. attached in fctx_query()
+ * 2. attached prior to dns_dispatch_connect(), detached in
+ * resquery_connected()
+ * 3. attached prior to dns_dispatch_send(), detached in
+ * resquery_senddone()
+ * 4. finally detached in fctx_cancelquery()
+ *
+ * On error conditions, it's necessary to call fctx_cancelquery()
+ * from resquery_connected() or _senddone(), detaching twice
+ * within the same function. To make it clear that's what's
+ * happening, we cancel-and-detach 'copy' and detach 'query',
+ * which are both pointing to the same object.
+ */
+ switch (eresult) {
+ case ISC_R_SUCCESS:
+ /*
+ * We are connected. Send the query.
+ */
+
+ result = resquery_send(query);
+ if (result != ISC_R_SUCCESS) {
+ FCTXTRACE("query canceled: resquery_send() failed; "
+ "responding");
+
+ fctx_cancelquery(&copy, NULL, false, false);
+ fctx_done_detach(&fctx, result);
+ break;
+ }
+
+ fctx->querysent++;
+
+ pf = isc_sockaddr_pf(&query->addrinfo->sockaddr);
+ if (pf == PF_INET) {
+ inc_stats(res, dns_resstatscounter_queryv4);
+ } else {
+ inc_stats(res, dns_resstatscounter_queryv6);
+ }
+ if (res->view->resquerystats != NULL) {
+ dns_rdatatypestats_increment(res->view->resquerystats,
+ fctx->type);
+ }
+ break;
+
+ case ISC_R_CANCELED:
+ case ISC_R_SHUTTINGDOWN:
+ FCTXTRACE3("shutdown in resquery_connected()", eresult);
+ fctx_cancelquery(&copy, NULL, true, false);
+ fctx_done_detach(&fctx, eresult);
+ break;
+
+ case ISC_R_NETUNREACH:
+ case ISC_R_HOSTUNREACH:
+ case ISC_R_CONNREFUSED:
+ case ISC_R_NOPERM:
+ case ISC_R_ADDRNOTAVAIL:
+ case ISC_R_CONNECTIONRESET:
+ case ISC_R_TIMEDOUT:
+ /*
+ * Do not query this server again in this fetch context.
+ */
+ FCTXTRACE3("query failed in resquery_connected(): "
+ "no response",
+ eresult);
+ add_bad(fctx, query->rmessage, query->addrinfo, eresult,
+ badns_unreachable);
+ fctx_cancelquery(&copy, NULL, true, false);
+
+ FCTX_ATTR_CLR(fctx, FCTX_ATTR_ADDRWAIT);
+ fctx_try(fctx, true, false);
+ break;
+
+ default:
+ FCTXTRACE3("query canceled in resquery_connected() "
+ "due to unexpected result; responding",
+ eresult);
+
+ fctx_cancelquery(&copy, NULL, false, false);
+ fctx_done_detach(&fctx, eresult);
+ break;
+ }
+
+detach:
+ resquery_detach(&query);
+}
+
+static void
+fctx_finddone(isc_task_t *task, isc_event_t *event) {
+ fetchctx_t *fctx = event->ev_arg;
+ dns_adbfind_t *find = event->ev_sender;
+ dns_resolver_t *res;
+ bool want_try = false;
+ bool want_done = false;
+ unsigned int bucketnum;
+ uint_fast32_t pending;
+
+ REQUIRE(VALID_FCTX(fctx));
+ res = fctx->res;
+
+ UNUSED(task);
+
+ FCTXTRACE("finddone");
+
+ bucketnum = fctx->bucketnum;
+ LOCK(&res->buckets[bucketnum].lock);
+
+ pending = atomic_fetch_sub_release(&fctx->pending, 1);
+ INSIST(pending > 0);
+
+ if (ADDRWAIT(fctx)) {
+ /*
+ * The fetch is waiting for a name to be found.
+ */
+ INSIST(!SHUTTINGDOWN(fctx));
+ if (event->ev_type == DNS_EVENT_ADBMOREADDRESSES) {
+ FCTX_ATTR_CLR(fctx, FCTX_ATTR_ADDRWAIT);
+ want_try = true;
+ } else {
+ fctx->findfail++;
+ if (atomic_load_acquire(&fctx->pending) == 0) {
+ /*
+ * We've got nothing else to wait for
+ * and don't know the answer. There's
+ * nothing to do but fail the fctx.
+ */
+ FCTX_ATTR_CLR(fctx, FCTX_ATTR_ADDRWAIT);
+ want_done = true;
+ }
+ }
+ }
+
+ isc_event_free(&event);
+ UNLOCK(&res->buckets[bucketnum].lock);
+
+ dns_adb_destroyfind(&find);
+
+ if (want_done) {
+ FCTXTRACE("fetch failed in finddone(); return "
+ "ISC_R_FAILURE");
+
+ /* Detach the extra reference from findname(). */
+ fctx_unref(fctx);
+ fctx_done_detach(&fctx, ISC_R_FAILURE);
+ } else if (want_try) {
+ fctx_try(fctx, true, false);
+ fctx_detach(&fctx);
+ } else {
+ fctx_detach(&fctx);
+ }
+}
+
+static bool
+bad_server(fetchctx_t *fctx, isc_sockaddr_t *address) {
+ isc_sockaddr_t *sa;
+
+ for (sa = ISC_LIST_HEAD(fctx->bad); sa != NULL;
+ sa = ISC_LIST_NEXT(sa, link))
+ {
+ if (isc_sockaddr_equal(sa, address)) {
+ return (true);
+ }
+ }
+
+ return (false);
+}
+
+static bool
+mark_bad(fetchctx_t *fctx) {
+ dns_adbfind_t *curr;
+ dns_adbaddrinfo_t *addrinfo;
+ bool all_bad = true;
+
+#ifdef ENABLE_AFL
+ if (dns_fuzzing_resolver) {
+ return (false);
+ }
+#endif /* ifdef ENABLE_AFL */
+
+ /*
+ * Mark all known bad servers, so we don't try to talk to them
+ * again.
+ */
+
+ /*
+ * Mark any bad nameservers.
+ */
+ for (curr = ISC_LIST_HEAD(fctx->finds); curr != NULL;
+ curr = ISC_LIST_NEXT(curr, publink))
+ {
+ for (addrinfo = ISC_LIST_HEAD(curr->list); addrinfo != NULL;
+ addrinfo = ISC_LIST_NEXT(addrinfo, publink))
+ {
+ if (bad_server(fctx, &addrinfo->sockaddr)) {
+ addrinfo->flags |= FCTX_ADDRINFO_MARK;
+ } else {
+ all_bad = false;
+ }
+ }
+ }
+
+ /*
+ * Mark any bad forwarders.
+ */
+ for (addrinfo = ISC_LIST_HEAD(fctx->forwaddrs); addrinfo != NULL;
+ addrinfo = ISC_LIST_NEXT(addrinfo, publink))
+ {
+ if (bad_server(fctx, &addrinfo->sockaddr)) {
+ addrinfo->flags |= FCTX_ADDRINFO_MARK;
+ } else {
+ all_bad = false;
+ }
+ }
+
+ /*
+ * Mark any bad alternates.
+ */
+ for (curr = ISC_LIST_HEAD(fctx->altfinds); curr != NULL;
+ curr = ISC_LIST_NEXT(curr, publink))
+ {
+ for (addrinfo = ISC_LIST_HEAD(curr->list); addrinfo != NULL;
+ addrinfo = ISC_LIST_NEXT(addrinfo, publink))
+ {
+ if (bad_server(fctx, &addrinfo->sockaddr)) {
+ addrinfo->flags |= FCTX_ADDRINFO_MARK;
+ } else {
+ all_bad = false;
+ }
+ }
+ }
+
+ for (addrinfo = ISC_LIST_HEAD(fctx->altaddrs); addrinfo != NULL;
+ addrinfo = ISC_LIST_NEXT(addrinfo, publink))
+ {
+ if (bad_server(fctx, &addrinfo->sockaddr)) {
+ addrinfo->flags |= FCTX_ADDRINFO_MARK;
+ } else {
+ all_bad = false;
+ }
+ }
+
+ return (all_bad);
+}
+
+static void
+add_bad(fetchctx_t *fctx, dns_message_t *rmessage, dns_adbaddrinfo_t *addrinfo,
+ isc_result_t reason, badnstype_t badtype) {
+ char namebuf[DNS_NAME_FORMATSIZE];
+ char addrbuf[ISC_SOCKADDR_FORMATSIZE];
+ char classbuf[64];
+ char typebuf[64];
+ char code[64];
+ isc_buffer_t b;
+ isc_sockaddr_t *sa;
+ const char *spc = "";
+ isc_sockaddr_t *address = &addrinfo->sockaddr;
+
+#ifdef ENABLE_AFL
+ if (dns_fuzzing_resolver) {
+ return;
+ }
+#endif /* ifdef ENABLE_AFL */
+
+ if (reason == DNS_R_LAME) {
+ fctx->lamecount++;
+ } else {
+ switch (badtype) {
+ case badns_unreachable:
+ fctx->neterr++;
+ break;
+ case badns_response:
+ fctx->badresp++;
+ break;
+ case badns_validation:
+ break; /* counted as 'valfail' */
+ case badns_forwarder:
+ /*
+ * We were called to prevent the given forwarder
+ * from being used again for this fetch context.
+ */
+ break;
+ }
+ }
+
+ if (bad_server(fctx, address)) {
+ /*
+ * We already know this server is bad.
+ */
+ return;
+ }
+
+ FCTXTRACE("add_bad");
+
+ sa = isc_mem_get(fctx->mctx, sizeof(*sa));
+ *sa = *address;
+ ISC_LIST_INITANDAPPEND(fctx->bad, sa, link);
+
+ if (reason == DNS_R_LAME) { /* already logged */
+ return;
+ }
+
+ if (reason == DNS_R_UNEXPECTEDRCODE &&
+ rmessage->rcode == dns_rcode_servfail && ISFORWARDER(addrinfo))
+ {
+ return;
+ }
+
+ if (reason == DNS_R_UNEXPECTEDRCODE) {
+ isc_buffer_init(&b, code, sizeof(code) - 1);
+ dns_rcode_totext(rmessage->rcode, &b);
+ code[isc_buffer_usedlength(&b)] = '\0';
+ spc = " ";
+ } else if (reason == DNS_R_UNEXPECTEDOPCODE) {
+ isc_buffer_init(&b, code, sizeof(code) - 1);
+ dns_opcode_totext((dns_opcode_t)rmessage->opcode, &b);
+ code[isc_buffer_usedlength(&b)] = '\0';
+ spc = " ";
+ } else {
+ code[0] = '\0';
+ }
+ dns_name_format(fctx->name, namebuf, sizeof(namebuf));
+ dns_rdatatype_format(fctx->type, typebuf, sizeof(typebuf));
+ dns_rdataclass_format(fctx->res->rdclass, classbuf, sizeof(classbuf));
+ isc_sockaddr_format(address, addrbuf, sizeof(addrbuf));
+ isc_log_write(
+ dns_lctx, DNS_LOGCATEGORY_LAME_SERVERS, DNS_LOGMODULE_RESOLVER,
+ ISC_LOG_INFO, "%s%s%s resolving '%s/%s/%s': %s", code, spc,
+ isc_result_totext(reason), namebuf, typebuf, classbuf, addrbuf);
+}
+
+/*
+ * Sort addrinfo list by RTT.
+ */
+static void
+sort_adbfind(dns_adbfind_t *find, unsigned int bias) {
+ dns_adbaddrinfo_t *best, *curr;
+ dns_adbaddrinfolist_t sorted;
+ unsigned int best_srtt, curr_srtt;
+
+ /* Lame N^2 bubble sort. */
+ ISC_LIST_INIT(sorted);
+ while (!ISC_LIST_EMPTY(find->list)) {
+ best = ISC_LIST_HEAD(find->list);
+ best_srtt = best->srtt;
+ if (isc_sockaddr_pf(&best->sockaddr) != AF_INET6) {
+ best_srtt += bias;
+ }
+ curr = ISC_LIST_NEXT(best, publink);
+ while (curr != NULL) {
+ curr_srtt = curr->srtt;
+ if (isc_sockaddr_pf(&curr->sockaddr) != AF_INET6) {
+ curr_srtt += bias;
+ }
+ if (curr_srtt < best_srtt) {
+ best = curr;
+ best_srtt = curr_srtt;
+ }
+ curr = ISC_LIST_NEXT(curr, publink);
+ }
+ ISC_LIST_UNLINK(find->list, best, publink);
+ ISC_LIST_APPEND(sorted, best, publink);
+ }
+ find->list = sorted;
+}
+
+/*
+ * Sort a list of finds by server RTT.
+ */
+static void
+sort_finds(dns_adbfindlist_t *findlist, unsigned int bias) {
+ dns_adbfind_t *best, *curr;
+ dns_adbfindlist_t sorted;
+ dns_adbaddrinfo_t *addrinfo, *bestaddrinfo;
+ unsigned int best_srtt, curr_srtt;
+
+ /* Sort each find's addrinfo list by SRTT. */
+ for (curr = ISC_LIST_HEAD(*findlist); curr != NULL;
+ curr = ISC_LIST_NEXT(curr, publink))
+ {
+ sort_adbfind(curr, bias);
+ }
+
+ /* Lame N^2 bubble sort. */
+ ISC_LIST_INIT(sorted);
+ while (!ISC_LIST_EMPTY(*findlist)) {
+ best = ISC_LIST_HEAD(*findlist);
+ bestaddrinfo = ISC_LIST_HEAD(best->list);
+ INSIST(bestaddrinfo != NULL);
+ best_srtt = bestaddrinfo->srtt;
+ if (isc_sockaddr_pf(&bestaddrinfo->sockaddr) != AF_INET6) {
+ best_srtt += bias;
+ }
+ curr = ISC_LIST_NEXT(best, publink);
+ while (curr != NULL) {
+ addrinfo = ISC_LIST_HEAD(curr->list);
+ INSIST(addrinfo != NULL);
+ curr_srtt = addrinfo->srtt;
+ if (isc_sockaddr_pf(&addrinfo->sockaddr) != AF_INET6) {
+ curr_srtt += bias;
+ }
+ if (curr_srtt < best_srtt) {
+ best = curr;
+ best_srtt = curr_srtt;
+ }
+ curr = ISC_LIST_NEXT(curr, publink);
+ }
+ ISC_LIST_UNLINK(*findlist, best, publink);
+ ISC_LIST_APPEND(sorted, best, publink);
+ }
+ *findlist = sorted;
+}
+
+/*
+ * Return true iff the ADB find has a pending fetch for 'type'. This is
+ * used to find out whether we're in a loop, where a fetch is waiting for a
+ * find which is waiting for that same fetch.
+ *
+ * Note: This could be done with either an equivalence check (e.g.,
+ * query_pending == DNS_ADBFIND_INET) or with a bit check, as below. If
+ * we checked for equivalence, that would mean we could only detect a loop
+ * when there is exactly one pending fetch, and we're it. If there were
+ * pending fetches for *both* address families, then a loop would be
+ * undetected.
+ *
+ * However, using a bit check means that in theory, an ADB find might be
+ * aborted that could have succeeded, if the other fetch had returned an
+ * answer.
+ *
+ * Since there's a good chance the server is broken and won't answer either
+ * query, and since an ADB find with two pending fetches is a very rare
+ * occurrance anyway, we regard this theoretical SERVFAIL as the lesser
+ * evil.
+ */
+static bool
+waiting_for(dns_adbfind_t *find, dns_rdatatype_t type) {
+ switch (type) {
+ case dns_rdatatype_a:
+ return ((find->query_pending & DNS_ADBFIND_INET) != 0);
+ case dns_rdatatype_aaaa:
+ return ((find->query_pending & DNS_ADBFIND_INET6) != 0);
+ default:
+ return (false);
+ }
+}
+
+static void
+findname(fetchctx_t *fctx, const dns_name_t *name, in_port_t port,
+ unsigned int options, unsigned int flags, isc_stdtime_t now,
+ bool *overquota, bool *need_alternate, unsigned int *no_addresses) {
+ dns_adbaddrinfo_t *ai = NULL;
+ dns_adbfind_t *find = NULL;
+ dns_resolver_t *res = fctx->res;
+ bool unshared = ((fctx->options & DNS_FETCHOPT_UNSHARED) != 0);
+ isc_result_t result;
+
+ FCTXTRACE("FINDNAME");
+
+ /*
+ * If this name is a subdomain of the query domain, tell
+ * the ADB to start looking using zone/hint data. This keeps us
+ * from getting stuck if the nameserver is beneath the zone cut
+ * and we don't know its address (e.g. because the A record has
+ * expired).
+ */
+ if (dns_name_issubdomain(name, fctx->domain)) {
+ options |= DNS_ADBFIND_STARTATZONE;
+ }
+ options |= DNS_ADBFIND_GLUEOK;
+ options |= DNS_ADBFIND_HINTOK;
+
+ /*
+ * See what we know about this address.
+ */
+ fctx_addref(fctx);
+ result = dns_adb_createfind(
+ fctx->adb, res->buckets[fctx->bucketnum].task, fctx_finddone,
+ fctx, name, fctx->name, fctx->type, options, now, NULL,
+ res->view->dstport, fctx->depth + 1, fctx->qc, &find);
+
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_RESOLVER,
+ DNS_LOGMODULE_RESOLVER, ISC_LOG_DEBUG(3),
+ "fctx %p(%s): createfind for %s - %s", fctx, fctx->info,
+ fctx->clientstr, isc_result_totext(result));
+
+ if (result != ISC_R_SUCCESS) {
+ if (result == DNS_R_ALIAS) {
+ char namebuf[DNS_NAME_FORMATSIZE];
+
+ /*
+ * XXXRTH Follow the CNAME/DNAME chain?
+ */
+ dns_adb_destroyfind(&find);
+ fctx->adberr++;
+ dns_name_format(name, namebuf, sizeof(namebuf));
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_CNAME,
+ DNS_LOGMODULE_RESOLVER, ISC_LOG_INFO,
+ "skipping nameserver '%s' because it "
+ "is a CNAME, while resolving '%s'",
+ namebuf, fctx->info);
+ }
+ fctx_detach(&fctx);
+ return;
+ }
+
+ if (!ISC_LIST_EMPTY(find->list)) {
+ /*
+ * We have at least some of the addresses for the
+ * name.
+ */
+ INSIST((find->options & DNS_ADBFIND_WANTEVENT) == 0);
+ if (flags != 0 || port != 0) {
+ for (ai = ISC_LIST_HEAD(find->list); ai != NULL;
+ ai = ISC_LIST_NEXT(ai, publink))
+ {
+ ai->flags |= flags;
+ if (port != 0) {
+ isc_sockaddr_setport(&ai->sockaddr,
+ port);
+ }
+ }
+ }
+ if ((flags & FCTX_ADDRINFO_DUALSTACK) != 0) {
+ ISC_LIST_APPEND(fctx->altfinds, find, publink);
+ } else {
+ ISC_LIST_APPEND(fctx->finds, find, publink);
+ }
+ return;
+ }
+
+ /*
+ * We don't know any of the addresses for this name.
+ *
+ * The find may be waiting on a resolver fetch for a server
+ * address. We need to make sure it isn't waiting on *this*
+ * fetch, because if it is, we won't be answering it and it
+ * won't be answering us.
+ */
+ if (waiting_for(find, fctx->type) && dns_name_equal(name, fctx->name)) {
+ fctx->adberr++;
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_RESOLVER,
+ DNS_LOGMODULE_RESOLVER, ISC_LOG_INFO,
+ "loop detected resolving '%s'", fctx->info);
+
+ if ((find->options & DNS_ADBFIND_WANTEVENT) != 0) {
+ atomic_fetch_add_relaxed(&fctx->pending, 1);
+ dns_adb_cancelfind(find);
+ } else {
+ dns_adb_destroyfind(&find);
+ fctx_detach(&fctx);
+ }
+ return;
+ }
+
+ /*
+ * We may be waiting for another fetch to complete, and
+ * we'll get an event later when the find has what it needs.
+ */
+ if ((find->options & DNS_ADBFIND_WANTEVENT) != 0) {
+ atomic_fetch_add_relaxed(&fctx->pending, 1);
+
+ /*
+ * Bootstrap.
+ */
+ if (need_alternate != NULL && !*need_alternate && unshared &&
+ ((res->dispatches4 == NULL &&
+ find->result_v6 != DNS_R_NXDOMAIN) ||
+ (res->dispatches6 == NULL &&
+ find->result_v4 != DNS_R_NXDOMAIN)))
+ {
+ *need_alternate = true;
+ }
+ if (no_addresses != NULL) {
+ (*no_addresses)++;
+ }
+ return;
+ }
+
+ /*
+ * No addresses and no pending events: the find failed.
+ */
+ if ((find->options & DNS_ADBFIND_OVERQUOTA) != 0) {
+ if (overquota != NULL) {
+ *overquota = true;
+ }
+ fctx->quotacount++; /* quota exceeded */
+ } else if ((find->options & DNS_ADBFIND_LAMEPRUNED) != 0) {
+ fctx->lamecount++; /* cached lame server */
+ } else {
+ fctx->adberr++; /* unreachable server, etc. */
+ }
+
+ /*
+ * If we know there are no addresses for the family we are using then
+ * try to add an alternative server.
+ */
+ if (need_alternate != NULL && !*need_alternate &&
+ ((res->dispatches4 == NULL && find->result_v6 == DNS_R_NXRRSET) ||
+ (res->dispatches6 == NULL && find->result_v4 == DNS_R_NXRRSET)))
+ {
+ *need_alternate = true;
+ }
+ dns_adb_destroyfind(&find);
+ fctx_detach(&fctx);
+}
+
+static bool
+isstrictsubdomain(const dns_name_t *name1, const dns_name_t *name2) {
+ int order;
+ unsigned int nlabels;
+ dns_namereln_t namereln;
+
+ namereln = dns_name_fullcompare(name1, name2, &order, &nlabels);
+ return (namereln == dns_namereln_subdomain);
+}
+
+static isc_result_t
+fctx_getaddresses(fetchctx_t *fctx, bool badcache) {
+ dns_rdata_t rdata = DNS_RDATA_INIT;
+ isc_result_t result;
+ dns_resolver_t *res;
+ isc_stdtime_t now;
+ unsigned int stdoptions = 0;
+ dns_forwarder_t *fwd;
+ dns_adbaddrinfo_t *ai;
+ bool all_bad;
+ dns_rdata_ns_t ns;
+ bool need_alternate = false;
+ bool all_spilled = true;
+ unsigned int no_addresses = 0;
+ unsigned int ns_processed = 0;
+
+ FCTXTRACE5("getaddresses", "fctx->depth=", fctx->depth);
+
+ /*
+ * Don't pound on remote servers. (Failsafe!)
+ */
+ fctx->restarts++;
+ if (fctx->restarts > 100) {
+ FCTXTRACE("too many restarts");
+ return (DNS_R_SERVFAIL);
+ }
+
+ res = fctx->res;
+
+ if (fctx->depth > res->maxdepth) {
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_RESOLVER,
+ DNS_LOGMODULE_RESOLVER, ISC_LOG_DEBUG(3),
+ "too much NS indirection resolving '%s' "
+ "(depth=%u, maxdepth=%u)",
+ fctx->info, fctx->depth, res->maxdepth);
+ return (DNS_R_SERVFAIL);
+ }
+
+ /*
+ * Forwarders.
+ */
+
+ INSIST(ISC_LIST_EMPTY(fctx->forwaddrs));
+ INSIST(ISC_LIST_EMPTY(fctx->altaddrs));
+
+ /*
+ * If we have DNS_FETCHOPT_NOFORWARD set and forwarding policy
+ * allows us to not forward - skip forwarders and go straight
+ * to NSes. This is currently used to make sure that priming
+ * query gets root servers' IP addresses in ADDITIONAL section.
+ */
+ if ((fctx->options & DNS_FETCHOPT_NOFORWARD) != 0 &&
+ (fctx->fwdpolicy != dns_fwdpolicy_only))
+ {
+ goto normal_nses;
+ }
+
+ /*
+ * If this fctx has forwarders, use them; otherwise use any
+ * selective forwarders specified in the view; otherwise use the
+ * resolver's forwarders (if any).
+ */
+ fwd = ISC_LIST_HEAD(fctx->forwarders);
+ if (fwd == NULL) {
+ dns_forwarders_t *forwarders = NULL;
+ dns_name_t *name = fctx->name;
+ dns_name_t suffix;
+ unsigned int labels;
+ dns_fixedname_t fixed;
+ dns_name_t *domain;
+
+ /*
+ * DS records are found in the parent server.
+ * Strip label to get the correct forwarder (if any).
+ */
+ if (dns_rdatatype_atparent(fctx->type) &&
+ dns_name_countlabels(name) > 1)
+ {
+ dns_name_init(&suffix, NULL);
+ labels = dns_name_countlabels(name);
+ dns_name_getlabelsequence(name, 1, labels - 1, &suffix);
+ name = &suffix;
+ }
+
+ domain = dns_fixedname_initname(&fixed);
+ result = dns_fwdtable_find(res->view->fwdtable, name, domain,
+ &forwarders);
+ if (result == ISC_R_SUCCESS || result == DNS_R_PARTIALMATCH) {
+ fwd = ISC_LIST_HEAD(forwarders->fwdrs);
+ fctx->fwdpolicy = forwarders->fwdpolicy;
+ dns_name_copy(domain, fctx->fwdname);
+ if (fctx->fwdpolicy == dns_fwdpolicy_only &&
+ isstrictsubdomain(domain, fctx->domain))
+ {
+ fcount_decr(fctx);
+ dns_name_copy(domain, fctx->domain);
+ result = fcount_incr(fctx, true);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+ }
+ }
+ }
+
+ while (fwd != NULL) {
+ if ((isc_sockaddr_pf(&fwd->addr) == AF_INET &&
+ res->dispatches4 == NULL) ||
+ (isc_sockaddr_pf(&fwd->addr) == AF_INET6 &&
+ res->dispatches6 == NULL))
+ {
+ fwd = ISC_LIST_NEXT(fwd, link);
+ continue;
+ }
+ ai = NULL;
+ result = dns_adb_findaddrinfo(fctx->adb, &fwd->addr, &ai, 0);
+ if (result == ISC_R_SUCCESS) {
+ dns_adbaddrinfo_t *cur;
+ ai->flags |= FCTX_ADDRINFO_FORWARDER;
+ cur = ISC_LIST_HEAD(fctx->forwaddrs);
+ while (cur != NULL && cur->srtt < ai->srtt) {
+ cur = ISC_LIST_NEXT(cur, publink);
+ }
+ if (cur != NULL) {
+ ISC_LIST_INSERTBEFORE(fctx->forwaddrs, cur, ai,
+ publink);
+ } else {
+ ISC_LIST_APPEND(fctx->forwaddrs, ai, publink);
+ }
+ }
+ fwd = ISC_LIST_NEXT(fwd, link);
+ }
+
+ /*
+ * If the forwarding policy is "only", we don't need the
+ * addresses of the nameservers.
+ */
+ if (fctx->fwdpolicy == dns_fwdpolicy_only) {
+ goto out;
+ }
+
+ /*
+ * Normal nameservers.
+ */
+normal_nses:
+ stdoptions = DNS_ADBFIND_WANTEVENT | DNS_ADBFIND_EMPTYEVENT;
+ if (fctx->restarts == 1) {
+ /*
+ * To avoid sending out a flood of queries likely to
+ * result in NXRRSET, we suppress fetches for address
+ * families we don't have the first time through,
+ * provided that we have addresses in some family we
+ * can use.
+ *
+ * We don't want to set this option all the time, since
+ * if fctx->restarts > 1, we've clearly been having
+ * trouble with the addresses we had, so getting more
+ * could help.
+ */
+ stdoptions |= DNS_ADBFIND_AVOIDFETCHES;
+ }
+ if (res->dispatches4 != NULL) {
+ stdoptions |= DNS_ADBFIND_INET;
+ }
+ if (res->dispatches6 != NULL) {
+ stdoptions |= DNS_ADBFIND_INET6;
+ }
+
+ if ((stdoptions & DNS_ADBFIND_ADDRESSMASK) == 0) {
+ return (DNS_R_SERVFAIL);
+ }
+
+ isc_stdtime_get(&now);
+
+ INSIST(ISC_LIST_EMPTY(fctx->finds));
+ INSIST(ISC_LIST_EMPTY(fctx->altfinds));
+
+ for (result = dns_rdataset_first(&fctx->nameservers);
+ result == ISC_R_SUCCESS;
+ result = dns_rdataset_next(&fctx->nameservers))
+ {
+ bool overquota = false;
+
+ dns_rdataset_current(&fctx->nameservers, &rdata);
+ /*
+ * Extract the name from the NS record.
+ */
+ result = dns_rdata_tostruct(&rdata, &ns, NULL);
+ if (result != ISC_R_SUCCESS) {
+ continue;
+ }
+
+ if (no_addresses > NS_FAIL_LIMIT &&
+ dns_rdataset_count(&fctx->nameservers) > NS_RR_LIMIT)
+ {
+ stdoptions |= DNS_ADBFIND_NOFETCH;
+ }
+ findname(fctx, &ns.name, 0, stdoptions, 0, now, &overquota,
+ &need_alternate, &no_addresses);
+
+ if (!overquota) {
+ all_spilled = false;
+ }
+
+ dns_rdata_reset(&rdata);
+ dns_rdata_freestruct(&ns);
+
+ if (++ns_processed >= NS_PROCESSING_LIMIT) {
+ result = ISC_R_NOMORE;
+ break;
+ }
+ }
+ if (result != ISC_R_NOMORE) {
+ return (result);
+ }
+
+ /*
+ * Do we need to use 6 to 4?
+ */
+ if (need_alternate) {
+ int family;
+ alternate_t *a;
+ family = (res->dispatches6 != NULL) ? AF_INET6 : AF_INET;
+ for (a = ISC_LIST_HEAD(res->alternates); a != NULL;
+ a = ISC_LIST_NEXT(a, link))
+ {
+ if (!a->isaddress) {
+ findname(fctx, &a->_u._n.name, a->_u._n.port,
+ stdoptions, FCTX_ADDRINFO_DUALSTACK,
+ now, NULL, NULL, NULL);
+ continue;
+ }
+ if (isc_sockaddr_pf(&a->_u.addr) != family) {
+ continue;
+ }
+ ai = NULL;
+ result = dns_adb_findaddrinfo(fctx->adb, &a->_u.addr,
+ &ai, 0);
+ if (result == ISC_R_SUCCESS) {
+ dns_adbaddrinfo_t *cur;
+ ai->flags |= FCTX_ADDRINFO_FORWARDER;
+ ai->flags |= FCTX_ADDRINFO_DUALSTACK;
+ cur = ISC_LIST_HEAD(fctx->altaddrs);
+ while (cur != NULL && cur->srtt < ai->srtt) {
+ cur = ISC_LIST_NEXT(cur, publink);
+ }
+ if (cur != NULL) {
+ ISC_LIST_INSERTBEFORE(fctx->altaddrs,
+ cur, ai, publink);
+ } else {
+ ISC_LIST_APPEND(fctx->altaddrs, ai,
+ publink);
+ }
+ }
+ }
+ }
+
+out:
+ /*
+ * Mark all known bad servers.
+ */
+ all_bad = mark_bad(fctx);
+
+ /*
+ * How are we doing?
+ */
+ if (all_bad) {
+ /*
+ * We've got no addresses.
+ */
+ if (atomic_load_acquire(&fctx->pending) > 0) {
+ /*
+ * We're fetching the addresses, but don't have
+ * any yet. Tell the caller to wait for an
+ * answer.
+ */
+ result = DNS_R_WAIT;
+ } else {
+ isc_time_t expire;
+ isc_interval_t i;
+ /*
+ * We've lost completely. We don't know any
+ * addresses, and the ADB has told us it can't
+ * get them.
+ */
+ FCTXTRACE("no addresses");
+ isc_interval_set(&i, DNS_RESOLVER_BADCACHETTL(fctx), 0);
+ result = isc_time_nowplusinterval(&expire, &i);
+ if (badcache &&
+ (fctx->type == dns_rdatatype_dnskey ||
+ fctx->type == dns_rdatatype_ds) &&
+ result == ISC_R_SUCCESS)
+ {
+ dns_resolver_addbadcache(res, fctx->name,
+ fctx->type, &expire);
+ }
+
+ result = ISC_R_FAILURE;
+
+ /*
+ * If all of the addresses found were over the
+ * fetches-per-server quota, return the
+ * configured response.
+ */
+ if (all_spilled) {
+ result = res->quotaresp[dns_quotatype_server];
+ inc_stats(res, dns_resstatscounter_serverquota);
+ }
+ }
+ } else {
+ /*
+ * We've found some addresses. We might still be
+ * looking for more addresses.
+ */
+ sort_finds(&fctx->finds, res->view->v6bias);
+ sort_finds(&fctx->altfinds, 0);
+ result = ISC_R_SUCCESS;
+ }
+
+ return (result);
+}
+
+static void
+possibly_mark(fetchctx_t *fctx, dns_adbaddrinfo_t *addr) {
+ isc_netaddr_t na;
+ char buf[ISC_NETADDR_FORMATSIZE];
+ isc_sockaddr_t *sa;
+ bool aborted = false;
+ bool bogus;
+ dns_acl_t *blackhole;
+ isc_netaddr_t ipaddr;
+ dns_peer_t *peer = NULL;
+ dns_resolver_t *res;
+ const char *msg = NULL;
+
+ sa = &addr->sockaddr;
+
+ res = fctx->res;
+ isc_netaddr_fromsockaddr(&ipaddr, sa);
+ blackhole = dns_dispatchmgr_getblackhole(res->dispatchmgr);
+ (void)dns_peerlist_peerbyaddr(res->view->peers, &ipaddr, &peer);
+
+ if (blackhole != NULL) {
+ int match;
+
+ if ((dns_acl_match(&ipaddr, NULL, blackhole, res->view->aclenv,
+ &match, NULL) == ISC_R_SUCCESS) &&
+ match > 0)
+ {
+ aborted = true;
+ }
+ }
+
+ if (peer != NULL && dns_peer_getbogus(peer, &bogus) == ISC_R_SUCCESS &&
+ bogus)
+ {
+ aborted = true;
+ }
+
+ if (aborted) {
+ addr->flags |= FCTX_ADDRINFO_MARK;
+ msg = "ignoring blackholed / bogus server: ";
+ } else if (isc_sockaddr_isnetzero(sa)) {
+ addr->flags |= FCTX_ADDRINFO_MARK;
+ msg = "ignoring net zero address: ";
+ } else if (isc_sockaddr_ismulticast(sa)) {
+ addr->flags |= FCTX_ADDRINFO_MARK;
+ msg = "ignoring multicast address: ";
+ } else if (isc_sockaddr_isexperimental(sa)) {
+ addr->flags |= FCTX_ADDRINFO_MARK;
+ msg = "ignoring experimental address: ";
+ } else if (sa->type.sa.sa_family != AF_INET6) {
+ return;
+ } else if (IN6_IS_ADDR_V4MAPPED(&sa->type.sin6.sin6_addr)) {
+ addr->flags |= FCTX_ADDRINFO_MARK;
+ msg = "ignoring IPv6 mapped IPV4 address: ";
+ } else if (IN6_IS_ADDR_V4COMPAT(&sa->type.sin6.sin6_addr)) {
+ addr->flags |= FCTX_ADDRINFO_MARK;
+ msg = "ignoring IPv6 compatibility IPV4 address: ";
+ } else {
+ return;
+ }
+
+ if (isc_log_wouldlog(dns_lctx, ISC_LOG_DEBUG(3))) {
+ isc_netaddr_fromsockaddr(&na, sa);
+ isc_netaddr_format(&na, buf, sizeof(buf));
+ FCTXTRACE2(msg, buf);
+ }
+}
+
+static dns_adbaddrinfo_t *
+fctx_nextaddress(fetchctx_t *fctx) {
+ dns_adbfind_t *find, *start;
+ dns_adbaddrinfo_t *addrinfo;
+ dns_adbaddrinfo_t *faddrinfo;
+
+ /*
+ * Return the next untried address, if any.
+ */
+
+ /*
+ * Find the first unmarked forwarder (if any).
+ */
+ for (addrinfo = ISC_LIST_HEAD(fctx->forwaddrs); addrinfo != NULL;
+ addrinfo = ISC_LIST_NEXT(addrinfo, publink))
+ {
+ if (!UNMARKED(addrinfo)) {
+ continue;
+ }
+ possibly_mark(fctx, addrinfo);
+ if (UNMARKED(addrinfo)) {
+ addrinfo->flags |= FCTX_ADDRINFO_MARK;
+ fctx->find = NULL;
+ fctx->forwarding = true;
+
+ /*
+ * QNAME minimization is disabled when
+ * forwarding, and has to remain disabled if
+ * we switch back to normal recursion; otherwise
+ * forwarding could leave us in an inconsistent
+ * state.
+ */
+ fctx->minimized = false;
+ return (addrinfo);
+ }
+ }
+
+ /*
+ * No forwarders. Move to the next find.
+ */
+ fctx->forwarding = false;
+ FCTX_ATTR_SET(fctx, FCTX_ATTR_TRIEDFIND);
+
+ find = fctx->find;
+ if (find == NULL) {
+ find = ISC_LIST_HEAD(fctx->finds);
+ } else {
+ find = ISC_LIST_NEXT(find, publink);
+ if (find == NULL) {
+ find = ISC_LIST_HEAD(fctx->finds);
+ }
+ }
+
+ /*
+ * Find the first unmarked addrinfo.
+ */
+ addrinfo = NULL;
+ if (find != NULL) {
+ start = find;
+ do {
+ for (addrinfo = ISC_LIST_HEAD(find->list);
+ addrinfo != NULL;
+ addrinfo = ISC_LIST_NEXT(addrinfo, publink))
+ {
+ if (!UNMARKED(addrinfo)) {
+ continue;
+ }
+ possibly_mark(fctx, addrinfo);
+ if (UNMARKED(addrinfo)) {
+ addrinfo->flags |= FCTX_ADDRINFO_MARK;
+ break;
+ }
+ }
+ if (addrinfo != NULL) {
+ break;
+ }
+ find = ISC_LIST_NEXT(find, publink);
+ if (find == NULL) {
+ find = ISC_LIST_HEAD(fctx->finds);
+ }
+ } while (find != start);
+ }
+
+ fctx->find = find;
+ if (addrinfo != NULL) {
+ return (addrinfo);
+ }
+
+ /*
+ * No nameservers left. Try alternates.
+ */
+
+ FCTX_ATTR_SET(fctx, FCTX_ATTR_TRIEDALT);
+
+ find = fctx->altfind;
+ if (find == NULL) {
+ find = ISC_LIST_HEAD(fctx->altfinds);
+ } else {
+ find = ISC_LIST_NEXT(find, publink);
+ if (find == NULL) {
+ find = ISC_LIST_HEAD(fctx->altfinds);
+ }
+ }
+
+ /*
+ * Find the first unmarked addrinfo.
+ */
+ addrinfo = NULL;
+ if (find != NULL) {
+ start = find;
+ do {
+ for (addrinfo = ISC_LIST_HEAD(find->list);
+ addrinfo != NULL;
+ addrinfo = ISC_LIST_NEXT(addrinfo, publink))
+ {
+ if (!UNMARKED(addrinfo)) {
+ continue;
+ }
+ possibly_mark(fctx, addrinfo);
+ if (UNMARKED(addrinfo)) {
+ addrinfo->flags |= FCTX_ADDRINFO_MARK;
+ break;
+ }
+ }
+ if (addrinfo != NULL) {
+ break;
+ }
+ find = ISC_LIST_NEXT(find, publink);
+ if (find == NULL) {
+ find = ISC_LIST_HEAD(fctx->altfinds);
+ }
+ } while (find != start);
+ }
+
+ faddrinfo = addrinfo;
+
+ /*
+ * See if we have a better alternate server by address.
+ */
+
+ for (addrinfo = ISC_LIST_HEAD(fctx->altaddrs); addrinfo != NULL;
+ addrinfo = ISC_LIST_NEXT(addrinfo, publink))
+ {
+ if (!UNMARKED(addrinfo)) {
+ continue;
+ }
+ possibly_mark(fctx, addrinfo);
+ if (UNMARKED(addrinfo) &&
+ (faddrinfo == NULL || addrinfo->srtt < faddrinfo->srtt))
+ {
+ if (faddrinfo != NULL) {
+ faddrinfo->flags &= ~FCTX_ADDRINFO_MARK;
+ }
+ addrinfo->flags |= FCTX_ADDRINFO_MARK;
+ break;
+ }
+ }
+
+ if (addrinfo == NULL) {
+ addrinfo = faddrinfo;
+ fctx->altfind = find;
+ }
+
+ return (addrinfo);
+}
+
+static void
+fctx_try(fetchctx_t *fctx, bool retrying, bool badcache) {
+ isc_result_t result;
+ dns_adbaddrinfo_t *addrinfo = NULL;
+ dns_resolver_t *res;
+ isc_task_t *task;
+ unsigned int bucketnum;
+
+ FCTXTRACE5("try", "fctx->qc=", isc_counter_used(fctx->qc));
+
+ REQUIRE(!ADDRWAIT(fctx));
+
+ res = fctx->res;
+ bucketnum = fctx->bucketnum;
+
+ /* We've already exceeded maximum query count */
+ if (isc_counter_used(fctx->qc) > res->maxqueries) {
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_RESOLVER,
+ DNS_LOGMODULE_RESOLVER, ISC_LOG_DEBUG(3),
+ "exceeded max queries resolving '%s' "
+ "(querycount=%u, maxqueries=%u)",
+ fctx->info, isc_counter_used(fctx->qc),
+ res->maxqueries);
+ fctx_done_detach(&fctx, DNS_R_SERVFAIL);
+ return;
+ }
+
+ addrinfo = fctx_nextaddress(fctx);
+
+ /* Try to find an address that isn't over quota */
+ while (addrinfo != NULL && dns_adbentry_overquota(addrinfo->entry)) {
+ addrinfo = fctx_nextaddress(fctx);
+ }
+
+ if (addrinfo == NULL) {
+ /* We have no more addresses. Start over. */
+ fctx_cancelqueries(fctx, true, false);
+ fctx_cleanup(fctx);
+ result = fctx_getaddresses(fctx, badcache);
+ if (result == DNS_R_WAIT) {
+ /*
+ * Sleep waiting for addresses.
+ */
+ FCTXTRACE("addrwait");
+ FCTX_ATTR_SET(fctx, FCTX_ATTR_ADDRWAIT);
+ return;
+ } else if (result != ISC_R_SUCCESS) {
+ /*
+ * Something bad happened.
+ */
+ fctx_done_detach(&fctx, result);
+ return;
+ }
+
+ addrinfo = fctx_nextaddress(fctx);
+
+ while (addrinfo != NULL &&
+ dns_adbentry_overquota(addrinfo->entry))
+ {
+ addrinfo = fctx_nextaddress(fctx);
+ }
+
+ /*
+ * While we may have addresses from the ADB, they
+ * might be bad ones. In this case, return SERVFAIL.
+ */
+ if (addrinfo == NULL) {
+ fctx_done_detach(&fctx, DNS_R_SERVFAIL);
+ return;
+ }
+ }
+ /*
+ * We're minimizing and we're not yet at the final NS -
+ * we need to launch a query for NS for 'upper' domain
+ */
+ if (fctx->minimized && !fctx->forwarding) {
+ unsigned int options = fctx->options;
+ /*
+ * Also clear DNS_FETCHOPT_TRYSTALE_ONTIMEOUT here,
+ * otherwise every query minimization step will activate
+ * the try-stale timer again.
+ */
+ options &= ~(DNS_FETCHOPT_QMINIMIZE |
+ DNS_FETCHOPT_TRYSTALE_ONTIMEOUT);
+
+ /*
+ * Is another QNAME minimization fetch still running?
+ */
+ if (fctx->qminfetch != NULL) {
+ bool validfctx = (DNS_FETCH_VALID(fctx->qminfetch) &&
+ VALID_FCTX(fctx->qminfetch->private));
+ char namebuf[DNS_NAME_FORMATSIZE];
+ char typebuf[DNS_RDATATYPE_FORMATSIZE];
+
+ dns_name_format(fctx->qminname, namebuf,
+ sizeof(namebuf));
+ dns_rdatatype_format(fctx->qmintype, typebuf,
+ sizeof(typebuf));
+
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_RESOLVER,
+ DNS_LOGMODULE_RESOLVER, ISC_LOG_ERROR,
+ "fctx %p(%s): attempting QNAME "
+ "minimization fetch for %s/%s but "
+ "fetch %p(%s) still running",
+ fctx, fctx->info, namebuf, typebuf,
+ fctx->qminfetch,
+ validfctx ? fctx->qminfetch->private->info
+ : "<invalid>");
+ fctx_done_detach(&fctx, DNS_R_SERVFAIL);
+ return;
+ }
+
+ /*
+ * Turn on NOFOLLOW in relaxed mode so that QNAME minimisation
+ * doesn't cause additional queries to resolve the target of the
+ * QNAME minimisation request when a referral is returned. This
+ * will also reduce the impact of mis-matched NS RRsets where
+ * the child's NS RRset is garbage. If a delegation is
+ * discovered DNS_R_DELEGATION will be returned to resume_qmin.
+ */
+ if ((options & DNS_FETCHOPT_QMIN_STRICT) == 0) {
+ options |= DNS_FETCHOPT_NOFOLLOW;
+ }
+ fctx_addref(fctx);
+ task = res->buckets[bucketnum].task;
+ result = dns_resolver_createfetch(
+ fctx->res, fctx->qminname, fctx->qmintype, fctx->domain,
+ &fctx->nameservers, NULL, NULL, 0, options, 0, fctx->qc,
+ task, resume_qmin, fctx, &fctx->qminrrset, NULL,
+ &fctx->qminfetch);
+ if (result != ISC_R_SUCCESS) {
+ fctx_unref(fctx);
+ fctx_done_detach(&fctx, DNS_R_SERVFAIL);
+ }
+ return;
+ }
+
+ result = isc_counter_increment(fctx->qc);
+ if (result != ISC_R_SUCCESS) {
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_RESOLVER,
+ DNS_LOGMODULE_RESOLVER, ISC_LOG_DEBUG(3),
+ "exceeded max queries resolving '%s'",
+ fctx->info);
+ fctx_done_detach(&fctx, DNS_R_SERVFAIL);
+ return;
+ }
+
+ result = fctx_query(fctx, addrinfo, fctx->options);
+ if (result != ISC_R_SUCCESS) {
+ fctx_done_detach(&fctx, result);
+ } else if (retrying) {
+ inc_stats(res, dns_resstatscounter_retry);
+ }
+}
+
+static void
+resume_qmin(isc_task_t *task, isc_event_t *event) {
+ dns_fetchevent_t *fevent = NULL;
+ dns_resolver_t *res = NULL;
+ fetchctx_t *fctx = NULL;
+ isc_result_t result;
+ unsigned int bucketnum;
+ unsigned int findoptions = 0;
+ dns_name_t *fname = NULL, *dcname = NULL;
+ dns_fixedname_t ffixed, dcfixed;
+
+ UNUSED(task);
+
+ REQUIRE(event->ev_type == DNS_EVENT_FETCHDONE);
+ fevent = (dns_fetchevent_t *)event;
+ fctx = event->ev_arg;
+ REQUIRE(VALID_FCTX(fctx));
+ res = fctx->res;
+
+ FCTXTRACE("resume_qmin");
+
+ fname = dns_fixedname_initname(&ffixed);
+ dcname = dns_fixedname_initname(&dcfixed);
+
+ if (fevent->node != NULL) {
+ dns_db_detachnode(fevent->db, &fevent->node);
+ }
+ if (fevent->db != NULL) {
+ dns_db_detach(&fevent->db);
+ }
+
+ bucketnum = fctx->bucketnum;
+
+ if (dns_rdataset_isassociated(fevent->rdataset)) {
+ dns_rdataset_disassociate(fevent->rdataset);
+ }
+
+ result = fevent->result;
+ isc_event_free(&event);
+
+ dns_resolver_destroyfetch(&fctx->qminfetch);
+
+ LOCK(&res->buckets[bucketnum].lock);
+ if (SHUTTINGDOWN(fctx)) {
+ maybe_cancel_validators(fctx, true);
+ UNLOCK(&res->buckets[bucketnum].lock);
+ fctx_detach(&fctx);
+ return;
+ }
+ UNLOCK(&res->buckets[bucketnum].lock);
+
+ switch (result) {
+ case ISC_R_SHUTTINGDOWN:
+ case ISC_R_CANCELED:
+ goto cleanup;
+ case DNS_R_NXDOMAIN:
+ case DNS_R_NCACHENXDOMAIN:
+ case DNS_R_FORMERR:
+ case DNS_R_REMOTEFORMERR:
+ case ISC_R_FAILURE:
+ if ((fctx->options & DNS_FETCHOPT_QMIN_STRICT) == 0) {
+ fctx->qmin_labels = DNS_MAX_LABELS + 1;
+ /*
+ * We store the result. If we succeed in the end
+ * we'll issue a warning that the server is
+ * broken.
+ */
+ fctx->qmin_warning = result;
+ } else {
+ goto cleanup;
+ }
+ break;
+ default:
+ /*
+ * When DNS_FETCHOPT_NOFOLLOW is set and a delegation
+ * was discovered, DNS_R_DELEGATION is returned and is
+ * processed here.
+ */
+ break;
+ }
+
+ if (dns_rdataset_isassociated(&fctx->nameservers)) {
+ dns_rdataset_disassociate(&fctx->nameservers);
+ }
+
+ if (dns_rdatatype_atparent(fctx->type)) {
+ findoptions |= DNS_DBFIND_NOEXACT;
+ }
+ result = dns_view_findzonecut(res->view, fctx->name, fname, dcname,
+ fctx->now, findoptions, true, true,
+ &fctx->nameservers, NULL);
+
+ /*
+ * DNS_R_NXDOMAIN here means we have not loaded the root zone
+ * mirror yet - but DNS_R_NXDOMAIN is not a valid return value
+ * when doing recursion, we need to patch it.
+ */
+ if (result == DNS_R_NXDOMAIN) {
+ result = DNS_R_SERVFAIL;
+ }
+
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+ fcount_decr(fctx);
+
+ dns_name_copy(fname, fctx->domain);
+
+ result = fcount_incr(fctx, false);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+
+ dns_name_copy(dcname, fctx->qmindcname);
+ fctx->ns_ttl = fctx->nameservers.ttl;
+ fctx->ns_ttl_ok = true;
+
+ fctx_minimize_qname(fctx);
+
+ if (!fctx->minimized) {
+ /*
+ * We have finished minimizing, but fctx->finds was
+ * filled at the beginning of the run - now we need to
+ * clear it before sending the final query to use proper
+ * nameservers.
+ */
+ fctx_cancelqueries(fctx, false, false);
+ fctx_cleanup(fctx);
+ }
+
+ fctx_try(fctx, true, false);
+ fctx_detach(&fctx);
+ return;
+
+cleanup:
+ /* Detach the extra reference from fctx_try() */
+ fctx_unref(fctx);
+ fctx_done_detach(&fctx, result);
+}
+
+static void
+fctx_destroy(fetchctx_t *fctx, bool exiting) {
+ dns_resolver_t *res = NULL;
+ isc_sockaddr_t *sa = NULL, *next_sa = NULL;
+ struct tried *tried = NULL;
+ unsigned int bucketnum;
+ bool bucket_empty = false;
+ uint_fast32_t nfctx;
+
+ REQUIRE(VALID_FCTX(fctx));
+ REQUIRE(ISC_LIST_EMPTY(fctx->events));
+ REQUIRE(ISC_LIST_EMPTY(fctx->queries));
+ REQUIRE(ISC_LIST_EMPTY(fctx->finds));
+ REQUIRE(ISC_LIST_EMPTY(fctx->altfinds));
+ REQUIRE(atomic_load_acquire(&fctx->pending) == 0);
+ REQUIRE(ISC_LIST_EMPTY(fctx->validators));
+
+ FCTXTRACE("destroy");
+
+ fctx->magic = 0;
+
+ res = fctx->res;
+ bucketnum = fctx->bucketnum;
+
+ LOCK(&res->buckets[bucketnum].lock);
+ REQUIRE(fctx->state != fetchstate_active);
+
+ ISC_LIST_UNLINK(res->buckets[bucketnum].fctxs, fctx, link);
+
+ nfctx = atomic_fetch_sub_release(&res->nfctx, 1);
+ INSIST(nfctx > 0);
+
+ dec_stats(res, dns_resstatscounter_nfetch);
+
+ if (atomic_load_acquire(&res->buckets[bucketnum].exiting) &&
+ ISC_LIST_EMPTY(res->buckets[bucketnum].fctxs))
+ {
+ bucket_empty = true;
+ }
+ UNLOCK(&res->buckets[bucketnum].lock);
+
+ if (bucket_empty && exiting &&
+ isc_refcount_decrement(&res->activebuckets) == 1)
+ {
+ send_shutdown_events(res);
+ }
+
+ isc_refcount_destroy(&fctx->references);
+
+ /*
+ * Free bad.
+ */
+ for (sa = ISC_LIST_HEAD(fctx->bad); sa != NULL; sa = next_sa) {
+ next_sa = ISC_LIST_NEXT(sa, link);
+ ISC_LIST_UNLINK(fctx->bad, sa, link);
+ isc_mem_put(fctx->mctx, sa, sizeof(*sa));
+ }
+
+ for (tried = ISC_LIST_HEAD(fctx->edns); tried != NULL;
+ tried = ISC_LIST_HEAD(fctx->edns))
+ {
+ ISC_LIST_UNLINK(fctx->edns, tried, link);
+ isc_mem_put(fctx->mctx, tried, sizeof(*tried));
+ }
+
+ for (sa = ISC_LIST_HEAD(fctx->bad_edns); sa != NULL; sa = next_sa) {
+ next_sa = ISC_LIST_NEXT(sa, link);
+ ISC_LIST_UNLINK(fctx->bad_edns, sa, link);
+ isc_mem_put(fctx->mctx, sa, sizeof(*sa));
+ }
+
+ isc_counter_detach(&fctx->qc);
+ fcount_decr(fctx);
+ dns_message_detach(&fctx->qmessage);
+ if (dns_rdataset_isassociated(&fctx->nameservers)) {
+ dns_rdataset_disassociate(&fctx->nameservers);
+ }
+ dns_db_detach(&fctx->cache);
+ dns_adb_detach(&fctx->adb);
+
+ isc_timer_destroy(&fctx->timer);
+
+ dns_resolver_detach(&fctx->res);
+
+ isc_mem_free(fctx->mctx, fctx->info);
+ isc_mem_putanddetach(&fctx->mctx, fctx, sizeof(*fctx));
+}
+
+static void
+fctx_shutdown(fetchctx_t *fctx) {
+ isc_event_t *cevent = NULL;
+
+ FCTXTRACE("shutdown");
+
+ /*
+ * Start the shutdown process for fctx, if it isn't already
+ * under way.
+ */
+ if (!atomic_compare_exchange_strong_acq_rel(&fctx->want_shutdown,
+ &(bool){ false }, true))
+ {
+ FCTXTRACE("already shut down");
+ return;
+ }
+
+ /*
+ * Unless we're still initializing (in which case the
+ * control event is still outstanding), we need to post
+ * the control event to tell the fetch we want it to
+ * exit.
+ */
+ if (fctx->state != fetchstate_init) {
+ FCTXTRACE("posting control event");
+ cevent = &fctx->control_event;
+ isc_task_sendto(fctx->res->buckets[fctx->bucketnum].task,
+ &cevent, fctx->bucketnum);
+ }
+}
+
+static void
+fctx_doshutdown(isc_task_t *task, isc_event_t *event) {
+ fetchctx_t *fctx = event->ev_arg;
+ dns_resolver_t *res = NULL;
+ unsigned int bucketnum;
+ dns_validator_t *validator = NULL;
+
+ REQUIRE(VALID_FCTX(fctx));
+
+ UNUSED(task);
+
+ res = fctx->res;
+ bucketnum = fctx->bucketnum;
+
+ FCTXTRACE("doshutdown");
+
+ /*
+ * An fctx that is shutting down is no longer in ADDRWAIT mode.
+ */
+ FCTX_ATTR_CLR(fctx, FCTX_ATTR_ADDRWAIT);
+
+ /*
+ * Cancel all pending validators. Note that this must be done
+ * without the bucket lock held, since that could cause
+ * deadlock.
+ */
+ validator = ISC_LIST_HEAD(fctx->validators);
+ while (validator != NULL) {
+ dns_validator_cancel(validator);
+ validator = ISC_LIST_NEXT(validator, link);
+ }
+
+ if (fctx->nsfetch != NULL) {
+ dns_resolver_cancelfetch(fctx->nsfetch);
+ }
+
+ if (fctx->qminfetch != NULL) {
+ dns_resolver_cancelfetch(fctx->qminfetch);
+ }
+
+ /*
+ * Shut down anything still running on behalf of this
+ * fetch, and clean up finds and addresses. To avoid deadlock
+ * with the ADB, we must do this before we lock the bucket lock.
+ * Increment the fctx references to avoid a race.
+ */
+ fctx_cancelqueries(fctx, false, false);
+ fctx_cleanup(fctx);
+
+ LOCK(&res->buckets[bucketnum].lock);
+
+ FCTX_ATTR_SET(fctx, FCTX_ATTR_SHUTTINGDOWN);
+
+ INSIST(fctx->state != fetchstate_init);
+ INSIST(atomic_load_acquire(&fctx->want_shutdown));
+
+ if (fctx->state == fetchstate_active) {
+ fctx->state = fetchstate_done;
+
+ fctx_sendevents(fctx, ISC_R_CANCELED, __LINE__);
+
+ /* Detach the extra ref from dns_resolver_createfetch(). */
+ fctx_unref(fctx);
+ }
+
+ UNLOCK(&res->buckets[bucketnum].lock);
+
+ fctx_detach(&fctx);
+}
+
+static void
+fctx_start(isc_task_t *task, isc_event_t *event) {
+ fetchctx_t *fctx = event->ev_arg;
+ dns_resolver_t *res = NULL;
+ unsigned int bucketnum;
+ isc_result_t result;
+
+ REQUIRE(VALID_FCTX(fctx));
+
+ UNUSED(task);
+
+ res = fctx->res;
+ bucketnum = fctx->bucketnum;
+
+ FCTXTRACE("start");
+
+ LOCK(&res->buckets[bucketnum].lock);
+
+ INSIST(fctx->state == fetchstate_init);
+ if (atomic_load_acquire(&fctx->want_shutdown)) {
+ /*
+ * We haven't started this fctx yet, but we've been
+ * requested to shut it down. Since we haven't started,
+ * we INSIST that we have no pending ADB finds or
+ * validations.
+ */
+ INSIST(atomic_load_acquire(&fctx->pending) == 0);
+ INSIST(atomic_load_acquire(&fctx->nqueries) == 0);
+ INSIST(ISC_LIST_EMPTY(fctx->validators));
+ UNLOCK(&res->buckets[bucketnum].lock);
+
+ FCTX_ATTR_SET(fctx, FCTX_ATTR_SHUTTINGDOWN);
+
+ /* Detach the extra ref from dns_resolver_createfetch(). */
+ fctx_unref(fctx);
+ fctx_done_detach(&fctx, ISC_R_SHUTTINGDOWN);
+ return;
+ }
+
+ /*
+ * Normal fctx startup.
+ */
+ fctx->state = fetchstate_active;
+
+ /*
+ * Reset the control event for later use in shutting
+ * down the fctx.
+ */
+ ISC_EVENT_INIT(event, sizeof(*event), 0, NULL, DNS_EVENT_FETCHCONTROL,
+ fctx_doshutdown, fctx, NULL, NULL, NULL);
+
+ UNLOCK(&res->buckets[bucketnum].lock);
+
+ result = fctx_starttimer(fctx);
+ if (result != ISC_R_SUCCESS) {
+ fctx_done_detach(&fctx, result);
+ } else {
+ fctx_try(fctx, false, false);
+ }
+}
+
+/*
+ * Fetch Creation, Joining, and Cancellation.
+ */
+
+static void
+fctx_add_event(fetchctx_t *fctx, isc_task_t *task, const isc_sockaddr_t *client,
+ dns_messageid_t id, isc_taskaction_t action, void *arg,
+ dns_rdataset_t *rdataset, dns_rdataset_t *sigrdataset,
+ dns_fetch_t *fetch, isc_eventtype_t event_type) {
+ dns_fetchevent_t *event = NULL;
+
+ FCTXTRACE("addevent");
+
+ /*
+ * We store the task we're going to send this event to in the
+ * sender field. We'll make the fetch the sender when we
+ * actually send the event.
+ */
+ isc_task_attach(task, &(isc_task_t *){ NULL });
+ event = (dns_fetchevent_t *)isc_event_allocate(
+ fctx->res->mctx, task, event_type, action, arg, sizeof(*event));
+ event->result = DNS_R_SERVFAIL;
+ event->qtype = fctx->type;
+ event->db = NULL;
+ event->node = NULL;
+ event->rdataset = rdataset;
+ event->sigrdataset = sigrdataset;
+ event->fetch = fetch;
+ event->client = client;
+ event->id = id;
+ event->foundname = dns_fixedname_initname(&event->fname);
+
+ /*
+ * Store the sigrdataset in the first event in case it is needed
+ * by any of the events.
+ */
+ if (event->sigrdataset != NULL) {
+ ISC_LIST_PREPEND(fctx->events, event, ev_link);
+ } else {
+ ISC_LIST_APPEND(fctx->events, event, ev_link);
+ }
+}
+
+static isc_result_t
+fctx_join(fetchctx_t *fctx, isc_task_t *task, const isc_sockaddr_t *client,
+ dns_messageid_t id, isc_taskaction_t action, void *arg,
+ dns_rdataset_t *rdataset, dns_rdataset_t *sigrdataset,
+ dns_fetch_t *fetch) {
+ FCTXTRACE("join");
+
+ fctx_add_event(fctx, task, client, id, action, arg, rdataset,
+ sigrdataset, fetch, DNS_EVENT_FETCHDONE);
+
+ fetch->magic = DNS_FETCH_MAGIC;
+ fctx_attach(fctx, &fetch->private);
+
+ return (ISC_R_SUCCESS);
+}
+
+static void
+log_ns_ttl(fetchctx_t *fctx, const char *where) {
+ char namebuf[DNS_NAME_FORMATSIZE];
+ char domainbuf[DNS_NAME_FORMATSIZE];
+
+ dns_name_format(fctx->name, namebuf, sizeof(namebuf));
+ dns_name_format(fctx->domain, domainbuf, sizeof(domainbuf));
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_RESOLVER,
+ DNS_LOGMODULE_RESOLVER, ISC_LOG_DEBUG(10),
+ "log_ns_ttl: fctx %p: %s: %s (in '%s'?): %u %u", fctx,
+ where, namebuf, domainbuf, fctx->ns_ttl_ok, fctx->ns_ttl);
+}
+
+static void
+fctx_expired(isc_task_t *task, isc_event_t *event) {
+ fetchctx_t *fctx = event->ev_arg;
+
+ REQUIRE(VALID_FCTX(fctx));
+
+ UNUSED(task);
+
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_RESOLVER,
+ DNS_LOGMODULE_RESOLVER, ISC_LOG_INFO,
+ "shut down hung fetch while resolving '%s'", fctx->info);
+ LOCK(&fctx->res->buckets[fctx->bucketnum].lock);
+ fctx_shutdown(fctx);
+ UNLOCK(&fctx->res->buckets[fctx->bucketnum].lock);
+ isc_event_free(&event);
+}
+
+static isc_result_t
+fctx_create(dns_resolver_t *res, isc_task_t *task, const dns_name_t *name,
+ dns_rdatatype_t type, const dns_name_t *domain,
+ dns_rdataset_t *nameservers, const isc_sockaddr_t *client,
+ unsigned int options, unsigned int bucketnum, unsigned int depth,
+ isc_counter_t *qc, fetchctx_t **fctxp) {
+ fetchctx_t *fctx = NULL;
+ isc_result_t result;
+ isc_result_t iresult;
+ isc_interval_t interval;
+ unsigned int findoptions = 0;
+ char buf[DNS_NAME_FORMATSIZE + DNS_RDATATYPE_FORMATSIZE + 1];
+ uint_fast32_t nfctx;
+ size_t p;
+
+ /*
+ * Caller must be holding the lock for bucket number
+ * 'bucketnum'.
+ */
+ REQUIRE(fctxp != NULL && *fctxp == NULL);
+
+ fctx = isc_mem_get(res->mctx, sizeof(*fctx));
+ *fctx = (fetchctx_t){
+ .type = type,
+ .qmintype = type,
+ .options = options,
+ .task = task,
+ .bucketnum = bucketnum,
+ .dbucketnum = RES_NOBUCKET,
+ .state = fetchstate_init,
+ .depth = depth,
+ .qmin_labels = 1,
+ .fwdpolicy = dns_fwdpolicy_none,
+ .result = ISC_R_FAILURE,
+ .exitline = -1, /* sentinel */
+ };
+
+ dns_resolver_attach(res, &fctx->res);
+
+ if (qc != NULL) {
+ isc_counter_attach(qc, &fctx->qc);
+ } else {
+ result = isc_counter_create(res->mctx, res->maxqueries,
+ &fctx->qc);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup_fetch;
+ }
+ }
+
+ /*
+ * Make fctx->info point to a copy of a formatted string
+ * "name/type". FCTXTRACE won't work until this is done.
+ */
+ dns_name_format(name, buf, sizeof(buf));
+ p = strlcat(buf, "/", sizeof(buf));
+ INSIST(p + DNS_RDATATYPE_FORMATSIZE < sizeof(buf));
+ dns_rdatatype_format(type, buf + p, sizeof(buf) - p);
+ fctx->info = isc_mem_strdup(res->mctx, buf);
+
+ FCTXTRACE("create");
+
+ isc_refcount_init(&fctx->references, 1);
+
+ ISC_LIST_INIT(fctx->queries);
+ ISC_LIST_INIT(fctx->finds);
+ ISC_LIST_INIT(fctx->altfinds);
+ ISC_LIST_INIT(fctx->forwaddrs);
+ ISC_LIST_INIT(fctx->altaddrs);
+ ISC_LIST_INIT(fctx->forwarders);
+ ISC_LIST_INIT(fctx->bad);
+ ISC_LIST_INIT(fctx->edns);
+ ISC_LIST_INIT(fctx->bad_edns);
+ ISC_LIST_INIT(fctx->validators);
+
+ atomic_init(&fctx->attributes, 0);
+
+ fctx->name = dns_fixedname_initname(&fctx->fname);
+ fctx->nsname = dns_fixedname_initname(&fctx->nsfname);
+ fctx->domain = dns_fixedname_initname(&fctx->dfname);
+ fctx->qminname = dns_fixedname_initname(&fctx->qminfname);
+ fctx->qmindcname = dns_fixedname_initname(&fctx->qmindcfname);
+ fctx->fwdname = dns_fixedname_initname(&fctx->fwdfname);
+
+ dns_name_copy(name, fctx->name);
+ dns_name_copy(name, fctx->qminname);
+
+ dns_rdataset_init(&fctx->nameservers);
+ dns_rdataset_init(&fctx->qminrrset);
+ dns_rdataset_init(&fctx->nsrrset);
+
+ TIME_NOW(&fctx->start);
+ fctx->now = (isc_stdtime_t)fctx->start.seconds;
+
+ if (client != NULL) {
+ isc_sockaddr_format(client, fctx->clientstr,
+ sizeof(fctx->clientstr));
+ } else {
+ strlcpy(fctx->clientstr, "<unknown>", sizeof(fctx->clientstr));
+ }
+
+ if (domain == NULL) {
+ dns_forwarders_t *forwarders = NULL;
+ dns_fixedname_t fixed;
+ dns_name_t *fname = dns_fixedname_initname(&fixed);
+ unsigned int labels;
+ const dns_name_t *fwdname = name;
+ dns_name_t suffix;
+
+ /*
+ * DS records are found in the parent server. Strip one
+ * leading label from the name (to be used in finding
+ * the forwarder).
+ */
+ if (dns_rdatatype_atparent(fctx->type) &&
+ dns_name_countlabels(name) > 1)
+ {
+ dns_name_init(&suffix, NULL);
+ labels = dns_name_countlabels(name);
+ dns_name_getlabelsequence(name, 1, labels - 1, &suffix);
+ fwdname = &suffix;
+ }
+
+ /* Find the forwarder for this name. */
+ result = dns_fwdtable_find(fctx->res->view->fwdtable, fwdname,
+ fname, &forwarders);
+ if (result == ISC_R_SUCCESS || result == DNS_R_PARTIALMATCH) {
+ fctx->fwdpolicy = forwarders->fwdpolicy;
+ dns_name_copy(fname, fctx->fwdname);
+ }
+
+ if (fctx->fwdpolicy != dns_fwdpolicy_only) {
+ dns_fixedname_t dcfixed;
+ dns_name_t *dcname = dns_fixedname_initname(&dcfixed);
+
+ /*
+ * The caller didn't supply a query domain and
+ * nameservers, and we're not in forward-only
+ * mode, so find the best nameservers to use.
+ */
+ if (dns_rdatatype_atparent(fctx->type)) {
+ findoptions |= DNS_DBFIND_NOEXACT;
+ }
+ result = dns_view_findzonecut(res->view, name, fname,
+ dcname, fctx->now,
+ findoptions, true, true,
+ &fctx->nameservers, NULL);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup_nameservers;
+ }
+
+ dns_name_copy(fname, fctx->domain);
+ dns_name_copy(dcname, fctx->qmindcname);
+ fctx->ns_ttl = fctx->nameservers.ttl;
+ fctx->ns_ttl_ok = true;
+ } else {
+ /*
+ * We're in forward-only mode. Set the query
+ * domain.
+ */
+ dns_name_copy(fname, fctx->domain);
+ dns_name_copy(fname, fctx->qmindcname);
+ /*
+ * Disable query minimization
+ */
+ options &= ~DNS_FETCHOPT_QMINIMIZE;
+ }
+ } else {
+ dns_name_copy(domain, fctx->domain);
+ dns_name_copy(domain, fctx->qmindcname);
+ dns_rdataset_clone(nameservers, &fctx->nameservers);
+ fctx->ns_ttl = fctx->nameservers.ttl;
+ fctx->ns_ttl_ok = true;
+ }
+
+ /*
+ * Are there too many simultaneous queries for this domain?
+ */
+ result = fcount_incr(fctx, false);
+ if (result != ISC_R_SUCCESS) {
+ result = fctx->res->quotaresp[dns_quotatype_zone];
+ inc_stats(res, dns_resstatscounter_zonequota);
+ goto cleanup_nameservers;
+ }
+
+ log_ns_ttl(fctx, "fctx_create");
+
+ if (!dns_name_issubdomain(fctx->name, fctx->domain)) {
+ dns_name_format(fctx->domain, buf, sizeof(buf));
+ UNEXPECTED_ERROR("'%s' is not subdomain of '%s'", fctx->info,
+ buf);
+ result = ISC_R_UNEXPECTED;
+ goto cleanup_fcount;
+ }
+
+ dns_message_create(res->mctx, DNS_MESSAGE_INTENTRENDER,
+ &fctx->qmessage);
+
+ /*
+ * Compute an expiration time for the entire fetch.
+ */
+ isc_interval_set(&interval, res->query_timeout / 1000,
+ res->query_timeout % 1000 * 1000000);
+ iresult = isc_time_nowplusinterval(&fctx->expires, &interval);
+ if (iresult != ISC_R_SUCCESS) {
+ UNEXPECTED_ERROR("isc_time_nowplusinterval: %s",
+ isc_result_totext(iresult));
+ result = ISC_R_UNEXPECTED;
+ goto cleanup_qmessage;
+ }
+
+ /*
+ * As a backstop, we also set a timer to stop the fetch
+ * if in-band netmgr timeouts don't work. It will fire two
+ * seconds after the fetch should have finished. (This
+ * should be enough of a gap to avoid the timer firing
+ * while a response is being processed normally.)
+ */
+ isc_interval_set(&interval, 2, 0);
+ iresult = isc_time_add(&fctx->expires, &interval, &fctx->final);
+ if (iresult != ISC_R_SUCCESS) {
+ UNEXPECTED_ERROR("isc_time_add: %s",
+ isc_result_totext(iresult));
+ result = ISC_R_UNEXPECTED;
+ goto cleanup_qmessage;
+ }
+
+ /*
+ * Create an inactive timer to enforce maximum query
+ * lifetime. It will be made active when the fetch is
+ * started.
+ */
+ iresult = isc_timer_create(res->timermgr, isc_timertype_inactive, NULL,
+ NULL, res->buckets[bucketnum].task,
+ fctx_expired, fctx, &fctx->timer);
+ if (iresult != ISC_R_SUCCESS) {
+ UNEXPECTED_ERROR("isc_timer_create: %s",
+ isc_result_totext(iresult));
+ result = ISC_R_UNEXPECTED;
+ goto cleanup_qmessage;
+ }
+
+ /*
+ * Default retry interval initialization. We set the interval
+ * now mostly so it won't be uninitialized. It will be set to
+ * the correct value before a query is issued.
+ */
+ isc_interval_set(&fctx->interval, 2, 0);
+
+ /*
+ * If stale answers are enabled, compute an expiration time
+ * after which stale data will be served, if the target RRset is
+ * available in cache.
+ */
+ if ((options & DNS_FETCHOPT_TRYSTALE_ONTIMEOUT) != 0) {
+ INSIST(res->view->staleanswerclienttimeout <=
+ (res->query_timeout - 1000));
+ isc_interval_set(
+ &interval, res->view->staleanswerclienttimeout / 1000,
+ res->view->staleanswerclienttimeout % 1000 * 1000000);
+ iresult = isc_time_nowplusinterval(&fctx->expires_try_stale,
+ &interval);
+ if (iresult != ISC_R_SUCCESS) {
+ UNEXPECTED_ERROR("isc_time_nowplusinterval: %s",
+ isc_result_totext(iresult));
+ result = ISC_R_UNEXPECTED;
+ goto cleanup_timer;
+ }
+ }
+
+ /*
+ * Attach to the view's cache and adb.
+ */
+ dns_db_attach(res->view->cachedb, &fctx->cache);
+ dns_adb_attach(res->view->adb, &fctx->adb);
+ isc_mem_attach(res->mctx, &fctx->mctx);
+
+ ISC_LIST_INIT(fctx->events);
+ ISC_LINK_INIT(fctx, link);
+ fctx->magic = FCTX_MAGIC;
+
+ /*
+ * If qname minimization is enabled we need to trim
+ * the name in fctx to proper length.
+ */
+ if ((options & DNS_FETCHOPT_QMINIMIZE) != 0) {
+ fctx->ip6arpaskip = (options & DNS_FETCHOPT_QMIN_SKIP_IP6A) !=
+ 0 &&
+ dns_name_issubdomain(fctx->name, &ip6_arpa);
+ fctx_minimize_qname(fctx);
+ }
+
+ ISC_LIST_APPEND(res->buckets[bucketnum].fctxs, fctx, link);
+
+ nfctx = atomic_fetch_add_relaxed(&res->nfctx, 1);
+ INSIST(nfctx < UINT32_MAX);
+
+ inc_stats(res, dns_resstatscounter_nfetch);
+
+ *fctxp = fctx;
+
+ return (ISC_R_SUCCESS);
+
+cleanup_timer:
+ isc_timer_destroy(&fctx->timer);
+
+cleanup_qmessage:
+ dns_message_detach(&fctx->qmessage);
+
+cleanup_fcount:
+ fcount_decr(fctx);
+
+cleanup_nameservers:
+ if (dns_rdataset_isassociated(&fctx->nameservers)) {
+ dns_rdataset_disassociate(&fctx->nameservers);
+ }
+ isc_mem_free(res->mctx, fctx->info);
+ isc_counter_detach(&fctx->qc);
+
+cleanup_fetch:
+ dns_resolver_detach(&fctx->res);
+ isc_mem_put(res->mctx, fctx, sizeof(*fctx));
+
+ return (result);
+}
+
+/*
+ * Handle Responses
+ */
+static bool
+is_lame(fetchctx_t *fctx, dns_message_t *message) {
+ dns_name_t *name;
+ dns_rdataset_t *rdataset;
+ isc_result_t result;
+
+ if (message->rcode != dns_rcode_noerror &&
+ message->rcode != dns_rcode_yxdomain &&
+ message->rcode != dns_rcode_nxdomain)
+ {
+ return (false);
+ }
+
+ if (message->counts[DNS_SECTION_ANSWER] != 0) {
+ return (false);
+ }
+
+ if (message->counts[DNS_SECTION_AUTHORITY] == 0) {
+ return (false);
+ }
+
+ result = dns_message_firstname(message, DNS_SECTION_AUTHORITY);
+ while (result == ISC_R_SUCCESS) {
+ name = NULL;
+ dns_message_currentname(message, DNS_SECTION_AUTHORITY, &name);
+ for (rdataset = ISC_LIST_HEAD(name->list); rdataset != NULL;
+ rdataset = ISC_LIST_NEXT(rdataset, link))
+ {
+ dns_namereln_t namereln;
+ int order;
+ unsigned int labels;
+ if (rdataset->type != dns_rdatatype_ns) {
+ continue;
+ }
+ namereln = dns_name_fullcompare(name, fctx->domain,
+ &order, &labels);
+ if (namereln == dns_namereln_equal &&
+ (message->flags & DNS_MESSAGEFLAG_AA) != 0)
+ {
+ return (false);
+ }
+ if (namereln == dns_namereln_subdomain) {
+ return (false);
+ }
+ return (true);
+ }
+ result = dns_message_nextname(message, DNS_SECTION_AUTHORITY);
+ }
+
+ return (false);
+}
+
+static void
+log_lame(fetchctx_t *fctx, dns_adbaddrinfo_t *addrinfo) {
+ char namebuf[DNS_NAME_FORMATSIZE];
+ char domainbuf[DNS_NAME_FORMATSIZE];
+ char addrbuf[ISC_SOCKADDR_FORMATSIZE];
+
+ dns_name_format(fctx->name, namebuf, sizeof(namebuf));
+ dns_name_format(fctx->domain, domainbuf, sizeof(domainbuf));
+ isc_sockaddr_format(&addrinfo->sockaddr, addrbuf, sizeof(addrbuf));
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_LAME_SERVERS,
+ DNS_LOGMODULE_RESOLVER, ISC_LOG_INFO,
+ "lame server resolving '%s' (in '%s'?): %s", namebuf,
+ domainbuf, addrbuf);
+}
+
+static void
+log_formerr(fetchctx_t *fctx, const char *format, ...) {
+ char nsbuf[ISC_SOCKADDR_FORMATSIZE];
+ char msgbuf[2048];
+ va_list args;
+
+ va_start(args, format);
+ vsnprintf(msgbuf, sizeof(msgbuf), format, args);
+ va_end(args);
+
+ isc_sockaddr_format(&fctx->addrinfo->sockaddr, nsbuf, sizeof(nsbuf));
+
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_RESOLVER,
+ DNS_LOGMODULE_RESOLVER, ISC_LOG_NOTICE,
+ "DNS format error from %s resolving %s for %s: %s", nsbuf,
+ fctx->info, fctx->clientstr, msgbuf);
+}
+
+static isc_result_t
+same_question(fetchctx_t *fctx, dns_message_t *message) {
+ isc_result_t result;
+ dns_name_t *name = NULL;
+ dns_rdataset_t *rdataset = NULL;
+
+ /*
+ * Caller must be holding the fctx lock.
+ */
+
+ /*
+ * XXXRTH Currently we support only one question.
+ */
+ if (message->counts[DNS_SECTION_QUESTION] == 0) {
+ if ((message->flags & DNS_MESSAGEFLAG_TC) != 0) {
+ /*
+ * If TC=1 and the question section is empty, we
+ * accept the reply message as a truncated
+ * answer, to be retried over TCP.
+ *
+ * It is really a FORMERR condition, but this is
+ * a workaround to accept replies from some
+ * implementations.
+ *
+ * Because the question section matching is not
+ * performed, the worst that could happen is
+ * that an attacker who gets past the ID and
+ * source port checks can force the use of
+ * TCP. This is considered an acceptable risk.
+ */
+ log_formerr(fctx, "empty question section, "
+ "accepting it anyway as TC=1");
+ return (ISC_R_SUCCESS);
+ } else {
+ log_formerr(fctx, "empty question section");
+ return (DNS_R_FORMERR);
+ }
+ } else if (message->counts[DNS_SECTION_QUESTION] > 1) {
+ log_formerr(fctx, "too many questions");
+ return (DNS_R_FORMERR);
+ }
+
+ result = dns_message_firstname(message, DNS_SECTION_QUESTION);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+
+ dns_message_currentname(message, DNS_SECTION_QUESTION, &name);
+ rdataset = ISC_LIST_HEAD(name->list);
+ INSIST(rdataset != NULL);
+ INSIST(ISC_LIST_NEXT(rdataset, link) == NULL);
+
+ if (fctx->type != rdataset->type ||
+ fctx->res->rdclass != rdataset->rdclass ||
+ !dns_name_equal(fctx->name, name))
+ {
+ char namebuf[DNS_NAME_FORMATSIZE];
+ char classbuf[DNS_RDATACLASS_FORMATSIZE];
+ char typebuf[DNS_RDATATYPE_FORMATSIZE];
+
+ dns_name_format(name, namebuf, sizeof(namebuf));
+ dns_rdataclass_format(rdataset->rdclass, classbuf,
+ sizeof(classbuf));
+ dns_rdatatype_format(rdataset->type, typebuf, sizeof(typebuf));
+ log_formerr(fctx, "question section mismatch: got %s/%s/%s",
+ namebuf, classbuf, typebuf);
+ return (DNS_R_FORMERR);
+ }
+
+ return (ISC_R_SUCCESS);
+}
+
+static void
+clone_results(fetchctx_t *fctx) {
+ dns_fetchevent_t *event = NULL, *hevent = NULL;
+
+ FCTXTRACE("clone_results");
+
+ /*
+ * Set up any other events to have the same data as the first
+ * event.
+ *
+ * Caller must be holding the appropriate lock.
+ */
+
+ fctx->cloned = true;
+
+ for (event = ISC_LIST_HEAD(fctx->events); event != NULL;
+ event = ISC_LIST_NEXT(event, ev_link))
+ {
+ /* This is the the head event; keep a pointer and move
+ * on */
+ if (hevent == NULL) {
+ hevent = ISC_LIST_HEAD(fctx->events);
+ continue;
+ }
+
+ if (event->ev_type == DNS_EVENT_TRYSTALE) {
+ /*
+ * We don't need to clone resulting data to this
+ * type of event, as its associated callback is
+ * only called when stale-answer-client-timeout
+ * triggers, and the logic in there doesn't
+ * expect any result as input, as it will itself
+ * lookup for stale data in cache to use as
+ * result, if any is available.
+ *
+ * Also, if we reached this point, then the
+ * whole fetch context is done, it will cancel
+ * timers, process associated callbacks of type
+ * DNS_EVENT_FETCHDONE, and silently remove/free
+ * events of type DNS_EVENT_TRYSTALE.
+ */
+ continue;
+ }
+
+ event->result = hevent->result;
+ dns_name_copy(hevent->foundname, event->foundname);
+ dns_db_attach(hevent->db, &event->db);
+ dns_db_attachnode(hevent->db, hevent->node, &event->node);
+
+ INSIST(hevent->rdataset != NULL);
+ INSIST(event->rdataset != NULL);
+ if (dns_rdataset_isassociated(hevent->rdataset)) {
+ dns_rdataset_clone(hevent->rdataset, event->rdataset);
+ }
+
+ INSIST(!(hevent->sigrdataset == NULL &&
+ event->sigrdataset != NULL));
+ if (hevent->sigrdataset != NULL &&
+ dns_rdataset_isassociated(hevent->sigrdataset) &&
+ event->sigrdataset != NULL)
+ {
+ dns_rdataset_clone(hevent->sigrdataset,
+ event->sigrdataset);
+ }
+ }
+}
+
+#define CACHE(r) (((r)->attributes & DNS_RDATASETATTR_CACHE) != 0)
+#define ANSWER(r) (((r)->attributes & DNS_RDATASETATTR_ANSWER) != 0)
+#define ANSWERSIG(r) (((r)->attributes & DNS_RDATASETATTR_ANSWERSIG) != 0)
+#define EXTERNAL(r) (((r)->attributes & DNS_RDATASETATTR_EXTERNAL) != 0)
+#define CHAINING(r) (((r)->attributes & DNS_RDATASETATTR_CHAINING) != 0)
+#define CHASE(r) (((r)->attributes & DNS_RDATASETATTR_CHASE) != 0)
+#define CHECKNAMES(r) (((r)->attributes & DNS_RDATASETATTR_CHECKNAMES) != 0)
+
+/*
+ * Cancel validators associated with '*fctx' if it is ready to be
+ * destroyed (i.e., no queries waiting for it and no pending ADB finds).
+ *
+ * Requires:
+ * '*fctx' is shutting down.
+ */
+static void
+maybe_cancel_validators(fetchctx_t *fctx, bool locked) {
+ unsigned int bucketnum;
+ dns_resolver_t *res = fctx->res;
+ dns_validator_t *validator, *next_validator;
+
+ bucketnum = fctx->bucketnum;
+ if (!locked) {
+ LOCK(&res->buckets[bucketnum].lock);
+ }
+
+ REQUIRE(SHUTTINGDOWN(fctx));
+
+ if (atomic_load_acquire(&fctx->pending) != 0 ||
+ atomic_load_acquire(&fctx->nqueries) != 0)
+ {
+ goto unlock;
+ }
+
+ for (validator = ISC_LIST_HEAD(fctx->validators); validator != NULL;
+ validator = next_validator)
+ {
+ next_validator = ISC_LIST_NEXT(validator, link);
+ dns_validator_cancel(validator);
+ }
+unlock:
+ if (!locked) {
+ UNLOCK(&res->buckets[bucketnum].lock);
+ }
+}
+
+/*
+ * typemap with just RRSIG(46) and NSEC(47) bits set.
+ *
+ * Bitmap calculation from dns_nsec_setbit:
+ *
+ * 46 47
+ * shift = 7 - (type % 8); 0 1
+ * mask = 1 << shift; 0x02 0x01
+ * array[type / 8] |= mask;
+ *
+ * Window (0), bitmap length (6), and bitmap.
+ */
+static const unsigned char minimal_typemap[] = { 0, 6, 0, 0, 0, 0, 0, 0x03 };
+
+static bool
+is_minimal_nsec(dns_rdataset_t *nsecset) {
+ dns_rdataset_t rdataset;
+ isc_result_t result;
+
+ dns_rdataset_init(&rdataset);
+ dns_rdataset_clone(nsecset, &rdataset);
+
+ for (result = dns_rdataset_first(&rdataset); result == ISC_R_SUCCESS;
+ result = dns_rdataset_next(&rdataset))
+ {
+ dns_rdata_t rdata = DNS_RDATA_INIT;
+ dns_rdata_nsec_t nsec;
+ dns_rdataset_current(&rdataset, &rdata);
+ result = dns_rdata_tostruct(&rdata, &nsec, NULL);
+ RUNTIME_CHECK(result == ISC_R_SUCCESS);
+ if (nsec.len == sizeof(minimal_typemap) &&
+ memcmp(nsec.typebits, minimal_typemap, nsec.len) == 0)
+ {
+ dns_rdataset_disassociate(&rdataset);
+ return (true);
+ }
+ }
+ dns_rdataset_disassociate(&rdataset);
+ return (false);
+}
+
+/*
+ * If there is a SOA record in the type map then there must be a DNSKEY.
+ */
+static bool
+check_soa_and_dnskey(dns_rdataset_t *nsecset) {
+ dns_rdataset_t rdataset;
+ isc_result_t result;
+
+ dns_rdataset_init(&rdataset);
+ dns_rdataset_clone(nsecset, &rdataset);
+
+ for (result = dns_rdataset_first(&rdataset); result == ISC_R_SUCCESS;
+ result = dns_rdataset_next(&rdataset))
+ {
+ dns_rdata_t rdata = DNS_RDATA_INIT;
+ dns_rdataset_current(&rdataset, &rdata);
+ if (dns_nsec_typepresent(&rdata, dns_rdatatype_soa) &&
+ (!dns_nsec_typepresent(&rdata, dns_rdatatype_dnskey) ||
+ !dns_nsec_typepresent(&rdata, dns_rdatatype_ns)))
+ {
+ dns_rdataset_disassociate(&rdataset);
+ return (false);
+ }
+ }
+ dns_rdataset_disassociate(&rdataset);
+ return (true);
+}
+
+/*
+ * Look for NSEC next name that starts with the label '\000'.
+ */
+static bool
+has_000_label(dns_rdataset_t *nsecset) {
+ dns_rdataset_t rdataset;
+ isc_result_t result;
+
+ dns_rdataset_init(&rdataset);
+ dns_rdataset_clone(nsecset, &rdataset);
+
+ for (result = dns_rdataset_first(&rdataset); result == ISC_R_SUCCESS;
+ result = dns_rdataset_next(&rdataset))
+ {
+ dns_rdata_t rdata = DNS_RDATA_INIT;
+ dns_rdataset_current(&rdataset, &rdata);
+ if (rdata.length > 1 && rdata.data[0] == 1 &&
+ rdata.data[1] == 0)
+ {
+ dns_rdataset_disassociate(&rdataset);
+ return (true);
+ }
+ }
+ dns_rdataset_disassociate(&rdataset);
+ return (false);
+}
+
+/*
+ * The validator has finished.
+ */
+static void
+validated(isc_task_t *task, isc_event_t *event) {
+ dns_adbaddrinfo_t *addrinfo = NULL;
+ dns_dbnode_t *node = NULL;
+ dns_dbnode_t *nsnode = NULL;
+ dns_fetchevent_t *hevent = NULL;
+ dns_name_t *name = NULL;
+ dns_rdataset_t *ardataset = NULL;
+ dns_rdataset_t *asigrdataset = NULL;
+ dns_rdataset_t *rdataset = NULL;
+ dns_rdataset_t *sigrdataset = NULL;
+ dns_resolver_t *res = NULL;
+ dns_valarg_t *valarg = NULL;
+ dns_validatorevent_t *vevent = NULL;
+ fetchctx_t *fctx = NULL;
+ bool chaining;
+ bool negative;
+ bool sentresponse;
+ isc_result_t eresult = ISC_R_SUCCESS;
+ isc_result_t result = ISC_R_SUCCESS;
+ isc_stdtime_t now;
+ uint32_t ttl;
+ unsigned options;
+ uint32_t bucketnum;
+ dns_fixedname_t fwild;
+ dns_name_t *wild = NULL;
+ dns_message_t *message = NULL;
+
+ UNUSED(task); /* for now */
+
+ REQUIRE(event->ev_type == DNS_EVENT_VALIDATORDONE);
+ valarg = event->ev_arg;
+
+ REQUIRE(VALID_FCTX(valarg->fctx));
+ REQUIRE(!ISC_LIST_EMPTY(valarg->fctx->validators));
+
+ fctx = valarg->fctx;
+ valarg->fctx = NULL;
+
+ FCTXTRACE("received validation completion event");
+
+ res = fctx->res;
+ addrinfo = valarg->addrinfo;
+
+ message = valarg->message;
+ valarg->message = NULL;
+
+ vevent = (dns_validatorevent_t *)event;
+ fctx->vresult = vevent->result;
+
+ bucketnum = fctx->bucketnum;
+ LOCK(&res->buckets[bucketnum].lock);
+ ISC_LIST_UNLINK(fctx->validators, vevent->validator, link);
+ fctx->validator = NULL;
+ UNLOCK(&res->buckets[bucketnum].lock);
+
+ /*
+ * Destroy the validator early so that we can
+ * destroy the fctx if necessary. Save the wildcard name.
+ */
+ if (vevent->proofs[DNS_VALIDATOR_NOQNAMEPROOF] != NULL) {
+ wild = dns_fixedname_initname(&fwild);
+ dns_name_copy(dns_fixedname_name(&vevent->validator->wild),
+ wild);
+ }
+ dns_validator_destroy(&vevent->validator);
+ isc_mem_put(fctx->mctx, valarg, sizeof(*valarg));
+
+ negative = (vevent->rdataset == NULL);
+
+ LOCK(&res->buckets[bucketnum].lock);
+ sentresponse = ((fctx->options & DNS_FETCHOPT_NOVALIDATE) != 0);
+
+ /*
+ * If shutting down, ignore the results. Check to see if we're
+ * done waiting for validator completions and ADB pending
+ * events; if so, destroy the fctx.
+ */
+ if (SHUTTINGDOWN(fctx) && !sentresponse) {
+ UNLOCK(&res->buckets[bucketnum].lock);
+ fctx_detach(&fctx);
+ goto cleanup_event;
+ }
+
+ isc_stdtime_get(&now);
+
+ /*
+ * If chaining, we need to make sure that the right result code
+ * is returned, and that the rdatasets are bound.
+ */
+ if (vevent->result == ISC_R_SUCCESS && !negative &&
+ vevent->rdataset != NULL && CHAINING(vevent->rdataset))
+ {
+ if (vevent->rdataset->type == dns_rdatatype_cname) {
+ eresult = DNS_R_CNAME;
+ } else {
+ INSIST(vevent->rdataset->type == dns_rdatatype_dname);
+ eresult = DNS_R_DNAME;
+ }
+ chaining = true;
+ } else {
+ chaining = false;
+ }
+
+ /*
+ * Either we're not shutting down, or we are shutting down but
+ * want to cache the result anyway (if this was a validation
+ * started by a query with cd set)
+ */
+
+ hevent = ISC_LIST_HEAD(fctx->events);
+ if (hevent != NULL) {
+ if (!negative && !chaining &&
+ (fctx->type == dns_rdatatype_any ||
+ fctx->type == dns_rdatatype_rrsig ||
+ fctx->type == dns_rdatatype_sig))
+ {
+ /*
+ * Don't bind rdatasets; the caller
+ * will iterate the node.
+ */
+ } else {
+ ardataset = hevent->rdataset;
+ asigrdataset = hevent->sigrdataset;
+ }
+ }
+
+ if (vevent->result != ISC_R_SUCCESS) {
+ FCTXTRACE("validation failed");
+ inc_stats(res, dns_resstatscounter_valfail);
+ fctx->valfail++;
+ fctx->vresult = vevent->result;
+ if (fctx->vresult != DNS_R_BROKENCHAIN) {
+ result = ISC_R_NOTFOUND;
+ if (vevent->rdataset != NULL) {
+ result = dns_db_findnode(
+ fctx->cache, vevent->name, true, &node);
+ }
+ if (result == ISC_R_SUCCESS) {
+ (void)dns_db_deleterdataset(fctx->cache, node,
+ NULL, vevent->type,
+ 0);
+ }
+ if (result == ISC_R_SUCCESS &&
+ vevent->sigrdataset != NULL)
+ {
+ (void)dns_db_deleterdataset(
+ fctx->cache, node, NULL,
+ dns_rdatatype_rrsig, vevent->type);
+ }
+ if (result == ISC_R_SUCCESS) {
+ dns_db_detachnode(fctx->cache, &node);
+ }
+ }
+ if (fctx->vresult == DNS_R_BROKENCHAIN && !negative) {
+ /*
+ * Cache the data as pending for later
+ * validation.
+ */
+ result = ISC_R_NOTFOUND;
+ if (vevent->rdataset != NULL) {
+ result = dns_db_findnode(
+ fctx->cache, vevent->name, true, &node);
+ }
+ if (result == ISC_R_SUCCESS) {
+ (void)dns_db_addrdataset(
+ fctx->cache, node, NULL, now,
+ vevent->rdataset, 0, NULL);
+ }
+ if (result == ISC_R_SUCCESS &&
+ vevent->sigrdataset != NULL)
+ {
+ (void)dns_db_addrdataset(
+ fctx->cache, node, NULL, now,
+ vevent->sigrdataset, 0, NULL);
+ }
+ if (result == ISC_R_SUCCESS) {
+ dns_db_detachnode(fctx->cache, &node);
+ }
+ }
+ result = fctx->vresult;
+ add_bad(fctx, message, addrinfo, result, badns_validation);
+ dns_message_detach(&message);
+ isc_event_free(&event);
+
+ UNLOCK(&res->buckets[bucketnum].lock);
+ INSIST(fctx->validator == NULL);
+
+ fctx->validator = ISC_LIST_HEAD(fctx->validators);
+ if (fctx->validator != NULL) {
+ dns_validator_send(fctx->validator);
+ fctx_detach(&fctx);
+ } else if (sentresponse) {
+ /* Detach the extra ref that was set in valcreate() */
+ fctx_unref(fctx);
+ fctx_done_detach(&fctx, result); /* Locks bucket */
+ } else if (result == DNS_R_BROKENCHAIN) {
+ isc_result_t tresult;
+ isc_time_t expire;
+ isc_interval_t i;
+
+ isc_interval_set(&i, DNS_RESOLVER_BADCACHETTL(fctx), 0);
+ tresult = isc_time_nowplusinterval(&expire, &i);
+ if (negative &&
+ (fctx->type == dns_rdatatype_dnskey ||
+ fctx->type == dns_rdatatype_ds) &&
+ tresult == ISC_R_SUCCESS)
+ {
+ dns_resolver_addbadcache(res, fctx->name,
+ fctx->type, &expire);
+ }
+
+ /* Detach the extra ref that was set in valcreate() */
+ fctx_unref(fctx);
+ fctx_done_detach(&fctx, result); /* Locks bucket */
+ } else {
+ fctx_try(fctx, true, true); /* Locks bucket */
+ fctx_detach(&fctx);
+ }
+ return;
+ }
+
+ if (negative) {
+ dns_rdatatype_t covers;
+ FCTXTRACE("nonexistence validation OK");
+
+ inc_stats(res, dns_resstatscounter_valnegsuccess);
+
+ /*
+ * Cache DS NXDOMAIN separately to other types.
+ */
+ if (message->rcode == dns_rcode_nxdomain &&
+ fctx->type != dns_rdatatype_ds)
+ {
+ covers = dns_rdatatype_any;
+ } else {
+ covers = fctx->type;
+ }
+
+ result = dns_db_findnode(fctx->cache, vevent->name, true,
+ &node);
+ if (result != ISC_R_SUCCESS) {
+ goto noanswer_response;
+ }
+
+ /*
+ * If we are asking for a SOA record set the cache time
+ * to zero to facilitate locating the containing zone of
+ * a arbitrary zone.
+ */
+ ttl = res->view->maxncachettl;
+ if (fctx->type == dns_rdatatype_soa &&
+ covers == dns_rdatatype_any && res->zero_no_soa_ttl)
+ {
+ ttl = 0;
+ }
+
+ result = ncache_adderesult(message, fctx->cache, node, covers,
+ now, fctx->res->view->minncachettl,
+ ttl, vevent->optout, vevent->secure,
+ ardataset, &eresult);
+ if (result != ISC_R_SUCCESS) {
+ goto noanswer_response;
+ }
+ goto answer_response;
+ } else {
+ inc_stats(res, dns_resstatscounter_valsuccess);
+ }
+
+ FCTXTRACE("validation OK");
+
+ if (vevent->proofs[DNS_VALIDATOR_NOQNAMEPROOF] != NULL) {
+ result = dns_rdataset_addnoqname(
+ vevent->rdataset,
+ vevent->proofs[DNS_VALIDATOR_NOQNAMEPROOF]);
+ RUNTIME_CHECK(result == ISC_R_SUCCESS);
+ INSIST(vevent->sigrdataset != NULL);
+ vevent->sigrdataset->ttl = vevent->rdataset->ttl;
+ if (vevent->proofs[DNS_VALIDATOR_CLOSESTENCLOSER] != NULL) {
+ result = dns_rdataset_addclosest(
+ vevent->rdataset,
+ vevent->proofs[DNS_VALIDATOR_CLOSESTENCLOSER]);
+ RUNTIME_CHECK(result == ISC_R_SUCCESS);
+ }
+ } else if (vevent->rdataset->trust == dns_trust_answer &&
+ vevent->rdataset->type != dns_rdatatype_rrsig)
+ {
+ isc_result_t tresult;
+ dns_name_t *noqname = NULL;
+ tresult = findnoqname(fctx, message, vevent->name,
+ vevent->rdataset->type, &noqname);
+ if (tresult == ISC_R_SUCCESS && noqname != NULL) {
+ tresult = dns_rdataset_addnoqname(vevent->rdataset,
+ noqname);
+ RUNTIME_CHECK(tresult == ISC_R_SUCCESS);
+ }
+ }
+
+ /*
+ * The data was already cached as pending data.
+ * Re-cache it as secure and bind the cached
+ * rdatasets to the first event on the fetch
+ * event list.
+ */
+ result = dns_db_findnode(fctx->cache, vevent->name, true, &node);
+ if (result != ISC_R_SUCCESS) {
+ goto noanswer_response;
+ }
+
+ options = 0;
+ if ((fctx->options & DNS_FETCHOPT_PREFETCH) != 0) {
+ options = DNS_DBADD_PREFETCH;
+ }
+ result = dns_db_addrdataset(fctx->cache, node, NULL, now,
+ vevent->rdataset, options, ardataset);
+ if (result != ISC_R_SUCCESS && result != DNS_R_UNCHANGED) {
+ goto noanswer_response;
+ }
+ if (ardataset != NULL && NEGATIVE(ardataset)) {
+ if (NXDOMAIN(ardataset)) {
+ eresult = DNS_R_NCACHENXDOMAIN;
+ } else {
+ eresult = DNS_R_NCACHENXRRSET;
+ }
+ } else if (vevent->sigrdataset != NULL) {
+ result = dns_db_addrdataset(fctx->cache, node, NULL, now,
+ vevent->sigrdataset, options,
+ asigrdataset);
+ if (result != ISC_R_SUCCESS && result != DNS_R_UNCHANGED) {
+ goto noanswer_response;
+ }
+ }
+
+ if (sentresponse) {
+ /*
+ * If we only deferred the destroy because we wanted to
+ * cache the data, destroy now.
+ */
+ dns_db_detachnode(fctx->cache, &node);
+ if (SHUTTINGDOWN(fctx)) {
+ maybe_cancel_validators(fctx, true);
+ }
+ UNLOCK(&res->buckets[bucketnum].lock);
+ fctx_detach(&fctx);
+ goto cleanup_event;
+ }
+
+ if (!ISC_LIST_EMPTY(fctx->validators)) {
+ INSIST(!negative);
+ INSIST(fctx->type == dns_rdatatype_any ||
+ fctx->type == dns_rdatatype_rrsig ||
+ fctx->type == dns_rdatatype_sig);
+ /*
+ * Don't send a response yet - we have
+ * more rdatasets that still need to
+ * be validated.
+ */
+ dns_db_detachnode(fctx->cache, &node);
+ UNLOCK(&res->buckets[bucketnum].lock);
+ dns_validator_send(ISC_LIST_HEAD(fctx->validators));
+ fctx_detach(&fctx);
+ goto cleanup_event;
+ }
+
+answer_response:
+
+ /*
+ * Cache any SOA/NS/NSEC records that happened to be validated.
+ */
+ result = dns_message_firstname(message, DNS_SECTION_AUTHORITY);
+ while (result == ISC_R_SUCCESS) {
+ name = NULL;
+ dns_message_currentname(message, DNS_SECTION_AUTHORITY, &name);
+ for (rdataset = ISC_LIST_HEAD(name->list); rdataset != NULL;
+ rdataset = ISC_LIST_NEXT(rdataset, link))
+ {
+ if ((rdataset->type != dns_rdatatype_ns &&
+ rdataset->type != dns_rdatatype_soa &&
+ rdataset->type != dns_rdatatype_nsec) ||
+ rdataset->trust != dns_trust_secure)
+ {
+ continue;
+ }
+ for (sigrdataset = ISC_LIST_HEAD(name->list);
+ sigrdataset != NULL;
+ sigrdataset = ISC_LIST_NEXT(sigrdataset, link))
+ {
+ if (sigrdataset->type != dns_rdatatype_rrsig ||
+ sigrdataset->covers != rdataset->type)
+ {
+ continue;
+ }
+ break;
+ }
+ if (sigrdataset == NULL ||
+ sigrdataset->trust != dns_trust_secure)
+ {
+ continue;
+ }
+
+ /*
+ * Don't cache NSEC if missing NSEC or RRSIG types.
+ */
+ if (rdataset->type == dns_rdatatype_nsec &&
+ !dns_nsec_requiredtypespresent(rdataset))
+ {
+ continue;
+ }
+
+ /*
+ * Don't cache "white lies" but do cache
+ * "black lies".
+ */
+ if (rdataset->type == dns_rdatatype_nsec &&
+ !dns_name_equal(fctx->name, name) &&
+ is_minimal_nsec(rdataset))
+ {
+ continue;
+ }
+
+ /*
+ * Check SOA and DNSKEY consistency.
+ */
+ if (rdataset->type == dns_rdatatype_nsec &&
+ !check_soa_and_dnskey(rdataset))
+ {
+ continue;
+ }
+
+ /*
+ * Look for \000 label in next name.
+ */
+ if (rdataset->type == dns_rdatatype_nsec &&
+ has_000_label(rdataset))
+ {
+ continue;
+ }
+
+ result = dns_db_findnode(fctx->cache, name, true,
+ &nsnode);
+ if (result != ISC_R_SUCCESS) {
+ continue;
+ }
+
+ result = dns_db_addrdataset(fctx->cache, nsnode, NULL,
+ now, rdataset, 0, NULL);
+ if (result == ISC_R_SUCCESS) {
+ result = dns_db_addrdataset(
+ fctx->cache, nsnode, NULL, now,
+ sigrdataset, 0, NULL);
+ }
+ dns_db_detachnode(fctx->cache, &nsnode);
+ if (result != ISC_R_SUCCESS) {
+ continue;
+ }
+ }
+ result = dns_message_nextname(message, DNS_SECTION_AUTHORITY);
+ }
+
+ /*
+ * Add the wild card entry.
+ */
+ if (vevent->proofs[DNS_VALIDATOR_NOQNAMEPROOF] != NULL &&
+ vevent->rdataset != NULL &&
+ dns_rdataset_isassociated(vevent->rdataset) &&
+ vevent->rdataset->trust == dns_trust_secure &&
+ vevent->sigrdataset != NULL &&
+ dns_rdataset_isassociated(vevent->sigrdataset) &&
+ vevent->sigrdataset->trust == dns_trust_secure && wild != NULL)
+ {
+ dns_dbnode_t *wnode = NULL;
+
+ result = dns_db_findnode(fctx->cache, wild, true, &wnode);
+ if (result == ISC_R_SUCCESS) {
+ result = dns_db_addrdataset(fctx->cache, wnode, NULL,
+ now, vevent->rdataset, 0,
+ NULL);
+ }
+ if (result == ISC_R_SUCCESS) {
+ (void)dns_db_addrdataset(fctx->cache, wnode, NULL, now,
+ vevent->sigrdataset, 0, NULL);
+ }
+ if (wnode != NULL) {
+ dns_db_detachnode(fctx->cache, &wnode);
+ }
+ }
+
+ result = ISC_R_SUCCESS;
+
+ /*
+ * Respond with an answer, positive or negative,
+ * as opposed to an error. 'node' must be non-NULL.
+ */
+
+ FCTX_ATTR_SET(fctx, FCTX_ATTR_HAVEANSWER);
+
+ if (hevent != NULL) {
+ /*
+ * Negative results must be indicated in event->result.
+ */
+ INSIST(hevent->rdataset != NULL);
+ if (dns_rdataset_isassociated(hevent->rdataset) &&
+ NEGATIVE(hevent->rdataset))
+ {
+ INSIST(eresult == DNS_R_NCACHENXDOMAIN ||
+ eresult == DNS_R_NCACHENXRRSET);
+ }
+
+ hevent->result = eresult;
+ dns_name_copy(vevent->name, hevent->foundname);
+ dns_db_attach(fctx->cache, &hevent->db);
+ dns_db_transfernode(fctx->cache, &node, &hevent->node);
+ clone_results(fctx);
+ }
+
+noanswer_response:
+ if (node != NULL) {
+ dns_db_detachnode(fctx->cache, &node);
+ }
+
+ UNLOCK(&res->buckets[bucketnum].lock);
+ /* Detach the extra reference that was set in valcreate() */
+ fctx_unref(fctx);
+ fctx_done_detach(&fctx, result); /* Locks bucket. */
+
+cleanup_event:
+ INSIST(node == NULL);
+ dns_message_detach(&message);
+ isc_event_free(&event);
+}
+
+static void
+fctx_log(void *arg, int level, const char *fmt, ...) {
+ char msgbuf[2048];
+ va_list args;
+ fetchctx_t *fctx = arg;
+
+ va_start(args, fmt);
+ vsnprintf(msgbuf, sizeof(msgbuf), fmt, args);
+ va_end(args);
+
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_RESOLVER,
+ DNS_LOGMODULE_RESOLVER, level, "fctx %p(%s): %s", fctx,
+ fctx->info, msgbuf);
+}
+
+static isc_result_t
+findnoqname(fetchctx_t *fctx, dns_message_t *message, dns_name_t *name,
+ dns_rdatatype_t type, dns_name_t **noqnamep) {
+ dns_rdataset_t *nrdataset, *next, *sigrdataset;
+ dns_rdata_rrsig_t rrsig;
+ isc_result_t result;
+ unsigned int labels;
+ dns_section_t section;
+ dns_name_t *zonename;
+ dns_fixedname_t fzonename;
+ dns_name_t *closest;
+ dns_fixedname_t fclosest;
+ dns_name_t *nearest;
+ dns_fixedname_t fnearest;
+ dns_rdatatype_t found = dns_rdatatype_none;
+ dns_name_t *noqname = NULL;
+
+ FCTXTRACE("findnoqname");
+
+ REQUIRE(noqnamep != NULL && *noqnamep == NULL);
+
+ /*
+ * Find the SIG for this rdataset, if we have it.
+ */
+ for (sigrdataset = ISC_LIST_HEAD(name->list); sigrdataset != NULL;
+ sigrdataset = ISC_LIST_NEXT(sigrdataset, link))
+ {
+ if (sigrdataset->type == dns_rdatatype_rrsig &&
+ sigrdataset->covers == type)
+ {
+ break;
+ }
+ }
+
+ if (sigrdataset == NULL) {
+ return (ISC_R_NOTFOUND);
+ }
+
+ labels = dns_name_countlabels(name);
+
+ for (result = dns_rdataset_first(sigrdataset); result == ISC_R_SUCCESS;
+ result = dns_rdataset_next(sigrdataset))
+ {
+ dns_rdata_t rdata = DNS_RDATA_INIT;
+ dns_rdataset_current(sigrdataset, &rdata);
+ result = dns_rdata_tostruct(&rdata, &rrsig, NULL);
+ RUNTIME_CHECK(result == ISC_R_SUCCESS);
+ /* Wildcard has rrsig.labels < labels - 1. */
+ if (rrsig.labels + 1U >= labels) {
+ continue;
+ }
+ break;
+ }
+
+ if (result == ISC_R_NOMORE) {
+ return (ISC_R_NOTFOUND);
+ }
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+
+ zonename = dns_fixedname_initname(&fzonename);
+ closest = dns_fixedname_initname(&fclosest);
+ nearest = dns_fixedname_initname(&fnearest);
+
+#define NXND(x) ((x) == ISC_R_SUCCESS)
+
+ section = DNS_SECTION_AUTHORITY;
+ for (result = dns_message_firstname(message, section);
+ result == ISC_R_SUCCESS;
+ result = dns_message_nextname(message, section))
+ {
+ dns_name_t *nsec = NULL;
+ dns_message_currentname(message, section, &nsec);
+ for (nrdataset = ISC_LIST_HEAD(nsec->list); nrdataset != NULL;
+ nrdataset = next)
+ {
+ bool data = false, exists = false;
+ bool optout = false, unknown = false;
+ bool setclosest = false;
+ bool setnearest = false;
+
+ next = ISC_LIST_NEXT(nrdataset, link);
+ if (nrdataset->type != dns_rdatatype_nsec &&
+ nrdataset->type != dns_rdatatype_nsec3)
+ {
+ continue;
+ }
+
+ if (nrdataset->type == dns_rdatatype_nsec &&
+ NXND(dns_nsec_noexistnodata(
+ type, name, nsec, nrdataset, &exists, &data,
+ NULL, fctx_log, fctx)))
+ {
+ if (!exists) {
+ noqname = nsec;
+ found = dns_rdatatype_nsec;
+ }
+ }
+
+ if (nrdataset->type == dns_rdatatype_nsec3 &&
+ NXND(dns_nsec3_noexistnodata(
+ type, name, nsec, nrdataset, zonename,
+ &exists, &data, &optout, &unknown,
+ &setclosest, &setnearest, closest, nearest,
+ fctx_log, fctx)))
+ {
+ if (!exists && setnearest) {
+ noqname = nsec;
+ found = dns_rdatatype_nsec3;
+ }
+ }
+ }
+ }
+ if (result == ISC_R_NOMORE) {
+ result = ISC_R_SUCCESS;
+ }
+ if (noqname != NULL) {
+ for (sigrdataset = ISC_LIST_HEAD(noqname->list);
+ sigrdataset != NULL;
+ sigrdataset = ISC_LIST_NEXT(sigrdataset, link))
+ {
+ if (sigrdataset->type == dns_rdatatype_rrsig &&
+ sigrdataset->covers == found)
+ {
+ break;
+ }
+ }
+ if (sigrdataset != NULL) {
+ *noqnamep = noqname;
+ }
+ }
+ return (result);
+}
+
+static isc_result_t
+cache_name(fetchctx_t *fctx, dns_name_t *name, dns_message_t *message,
+ dns_adbaddrinfo_t *addrinfo, isc_stdtime_t now) {
+ dns_rdataset_t *rdataset = NULL, *sigrdataset = NULL;
+ dns_rdataset_t *addedrdataset = NULL;
+ dns_rdataset_t *ardataset = NULL, *asigrdataset = NULL;
+ dns_rdataset_t *valrdataset = NULL, *valsigrdataset = NULL;
+ dns_dbnode_t *node = NULL, **anodep = NULL;
+ dns_db_t **adbp = NULL;
+ dns_resolver_t *res = fctx->res;
+ bool need_validation = false;
+ bool secure_domain = false;
+ bool have_answer = false;
+ isc_result_t result, eresult = ISC_R_SUCCESS;
+ dns_fetchevent_t *event = NULL;
+ unsigned int options;
+ isc_task_t *task;
+ bool fail;
+ unsigned int valoptions = 0;
+ bool checknta = true;
+
+ FCTXTRACE("cache_name");
+
+ /*
+ * The appropriate bucket lock must be held.
+ */
+ task = res->buckets[fctx->bucketnum].task;
+
+ /*
+ * Is DNSSEC validation required for this name?
+ */
+ if ((fctx->options & DNS_FETCHOPT_NONTA) != 0) {
+ valoptions |= DNS_VALIDATOR_NONTA;
+ checknta = false;
+ }
+
+ if (res->view->enablevalidation) {
+ result = issecuredomain(res->view, name, fctx->type, now,
+ checknta, NULL, &secure_domain);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+ }
+
+ if ((fctx->options & DNS_FETCHOPT_NOCDFLAG) != 0) {
+ valoptions |= DNS_VALIDATOR_NOCDFLAG;
+ }
+
+ if ((fctx->options & DNS_FETCHOPT_NOVALIDATE) != 0) {
+ need_validation = false;
+ } else {
+ need_validation = secure_domain;
+ }
+
+ if (((name->attributes & DNS_NAMEATTR_ANSWER) != 0) &&
+ (!need_validation))
+ {
+ have_answer = true;
+ event = ISC_LIST_HEAD(fctx->events);
+
+ if (event != NULL) {
+ adbp = &event->db;
+ dns_name_copy(name, event->foundname);
+ anodep = &event->node;
+
+ /*
+ * If this is an ANY, SIG or RRSIG query, we're
+ * not going to return any rdatasets, unless we
+ * encountered a CNAME or DNAME as "the answer".
+ * In this case, we're going to return
+ * DNS_R_CNAME or DNS_R_DNAME and we must set up
+ * the rdatasets.
+ */
+ if ((fctx->type != dns_rdatatype_any &&
+ fctx->type != dns_rdatatype_rrsig &&
+ fctx->type != dns_rdatatype_sig) ||
+ (name->attributes & DNS_NAMEATTR_CHAINING) != 0)
+ {
+ ardataset = event->rdataset;
+ asigrdataset = event->sigrdataset;
+ }
+ }
+ }
+
+ /*
+ * Find or create the cache node.
+ */
+ node = NULL;
+ result = dns_db_findnode(fctx->cache, name, true, &node);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+
+ /*
+ * Cache or validate each cacheable rdataset.
+ */
+ fail = ((fctx->res->options & DNS_RESOLVER_CHECKNAMESFAIL) != 0);
+ for (rdataset = ISC_LIST_HEAD(name->list); rdataset != NULL;
+ rdataset = ISC_LIST_NEXT(rdataset, link))
+ {
+ if (!CACHE(rdataset)) {
+ continue;
+ }
+ if (CHECKNAMES(rdataset)) {
+ char namebuf[DNS_NAME_FORMATSIZE];
+ char typebuf[DNS_RDATATYPE_FORMATSIZE];
+ char classbuf[DNS_RDATATYPE_FORMATSIZE];
+
+ dns_name_format(name, namebuf, sizeof(namebuf));
+ dns_rdatatype_format(rdataset->type, typebuf,
+ sizeof(typebuf));
+ dns_rdataclass_format(rdataset->rdclass, classbuf,
+ sizeof(classbuf));
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_RESOLVER,
+ DNS_LOGMODULE_RESOLVER, ISC_LOG_NOTICE,
+ "check-names %s %s/%s/%s",
+ fail ? "failure" : "warning", namebuf,
+ typebuf, classbuf);
+ if (fail) {
+ if (ANSWER(rdataset)) {
+ dns_db_detachnode(fctx->cache, &node);
+ return (DNS_R_BADNAME);
+ }
+ continue;
+ }
+ }
+
+ /*
+ * Enforce the configure maximum cache TTL.
+ */
+ if (rdataset->ttl > res->view->maxcachettl) {
+ rdataset->ttl = res->view->maxcachettl;
+ }
+
+ /*
+ * Enforce configured minimum cache TTL.
+ */
+ if (rdataset->ttl < res->view->mincachettl) {
+ rdataset->ttl = res->view->mincachettl;
+ }
+
+ /*
+ * Mark the rdataset as being prefetch eligible.
+ */
+ if (rdataset->ttl >= fctx->res->view->prefetch_eligible) {
+ rdataset->attributes |= DNS_RDATASETATTR_PREFETCH;
+ }
+
+ /*
+ * Find the SIG for this rdataset, if we have it.
+ */
+ for (sigrdataset = ISC_LIST_HEAD(name->list);
+ sigrdataset != NULL;
+ sigrdataset = ISC_LIST_NEXT(sigrdataset, link))
+ {
+ if (sigrdataset->type == dns_rdatatype_rrsig &&
+ sigrdataset->covers == rdataset->type)
+ {
+ break;
+ }
+ }
+
+ /*
+ * If this RRset is in a secure domain, is in bailiwick,
+ * and is not glue, attempt DNSSEC validation. (We do
+ * not attempt to validate glue or out-of-bailiwick
+ * data--even though there might be some performance
+ * benefit to doing so--because it makes it simpler and
+ * safer to ensure that records from a secure domain are
+ * only cached if validated within the context of a
+ * query to the domain that owns them.)
+ */
+ if (secure_domain && rdataset->trust != dns_trust_glue &&
+ !EXTERNAL(rdataset))
+ {
+ dns_trust_t trust;
+
+ /*
+ * RRSIGs are validated as part of validating
+ * the type they cover.
+ */
+ if (rdataset->type == dns_rdatatype_rrsig) {
+ continue;
+ }
+
+ if (sigrdataset == NULL && need_validation &&
+ !ANSWER(rdataset))
+ {
+ /*
+ * Ignore unrelated non-answer
+ * rdatasets that are missing
+ * signatures.
+ */
+ continue;
+ }
+
+ /*
+ * Normalize the rdataset and sigrdataset TTLs.
+ */
+ if (sigrdataset != NULL) {
+ rdataset->ttl = ISC_MIN(rdataset->ttl,
+ sigrdataset->ttl);
+ sigrdataset->ttl = rdataset->ttl;
+ }
+
+ /*
+ * Mark the rdataset as being prefetch eligible.
+ */
+ if (rdataset->ttl >= fctx->res->view->prefetch_eligible)
+ {
+ rdataset->attributes |=
+ DNS_RDATASETATTR_PREFETCH;
+ }
+
+ /*
+ * Cache this rdataset/sigrdataset pair as
+ * pending data. Track whether it was
+ * additional or not. If this was a priming
+ * query, additional should be cached as glue.
+ */
+ if (rdataset->trust == dns_trust_additional) {
+ trust = dns_trust_pending_additional;
+ } else {
+ trust = dns_trust_pending_answer;
+ }
+
+ rdataset->trust = trust;
+ if (sigrdataset != NULL) {
+ sigrdataset->trust = trust;
+ }
+ if (!need_validation || !ANSWER(rdataset)) {
+ options = 0;
+ if (ANSWER(rdataset) &&
+ rdataset->type != dns_rdatatype_rrsig)
+ {
+ isc_result_t tresult;
+ dns_name_t *noqname = NULL;
+ tresult = findnoqname(
+ fctx, message, name,
+ rdataset->type, &noqname);
+ if (tresult == ISC_R_SUCCESS &&
+ noqname != NULL)
+ {
+ (void)dns_rdataset_addnoqname(
+ rdataset, noqname);
+ }
+ }
+ if ((fctx->options & DNS_FETCHOPT_PREFETCH) !=
+ 0)
+ {
+ options = DNS_DBADD_PREFETCH;
+ }
+ if ((fctx->options & DNS_FETCHOPT_NOCACHED) !=
+ 0)
+ {
+ options |= DNS_DBADD_FORCE;
+ }
+ addedrdataset = ardataset;
+ result = dns_db_addrdataset(
+ fctx->cache, node, NULL, now, rdataset,
+ options, addedrdataset);
+ if (result == DNS_R_UNCHANGED) {
+ result = ISC_R_SUCCESS;
+ if (!need_validation &&
+ ardataset != NULL &&
+ NEGATIVE(ardataset))
+ {
+ /*
+ * The answer in the
+ * cache is better than
+ * the answer we found,
+ * and is a negative
+ * cache entry, so we
+ * must set eresult
+ * appropriately.
+ */
+ if (NXDOMAIN(ardataset)) {
+ eresult =
+ DNS_R_NCACHENXDOMAIN;
+ } else {
+ eresult =
+ DNS_R_NCACHENXRRSET;
+ }
+ /*
+ * We have a negative
+ * response from the
+ * cache so don't
+ * attempt to add the
+ * RRSIG rrset.
+ */
+ continue;
+ }
+ }
+ if (result != ISC_R_SUCCESS) {
+ break;
+ }
+ if (sigrdataset != NULL) {
+ addedrdataset = asigrdataset;
+ result = dns_db_addrdataset(
+ fctx->cache, node, NULL, now,
+ sigrdataset, options,
+ addedrdataset);
+ if (result == DNS_R_UNCHANGED) {
+ result = ISC_R_SUCCESS;
+ }
+ if (result != ISC_R_SUCCESS) {
+ break;
+ }
+ } else if (!ANSWER(rdataset)) {
+ continue;
+ }
+ }
+
+ if (ANSWER(rdataset) && need_validation) {
+ if (fctx->type != dns_rdatatype_any &&
+ fctx->type != dns_rdatatype_rrsig &&
+ fctx->type != dns_rdatatype_sig)
+ {
+ /*
+ * This is The Answer. We will
+ * validate it, but first we
+ * cache the rest of the
+ * response - it may contain
+ * useful keys.
+ */
+ INSIST(valrdataset == NULL &&
+ valsigrdataset == NULL);
+ valrdataset = rdataset;
+ valsigrdataset = sigrdataset;
+ } else {
+ /*
+ * This is one of (potentially)
+ * multiple answers to an ANY
+ * or SIG query. To keep things
+ * simple, we just start the
+ * validator right away rather
+ * than caching first and
+ * having to remember which
+ * rdatasets needed validation.
+ */
+ result = valcreate(
+ fctx, message, addrinfo, name,
+ rdataset->type, rdataset,
+ sigrdataset, valoptions, task);
+ }
+ } else if (CHAINING(rdataset)) {
+ if (rdataset->type == dns_rdatatype_cname) {
+ eresult = DNS_R_CNAME;
+ } else {
+ INSIST(rdataset->type ==
+ dns_rdatatype_dname);
+ eresult = DNS_R_DNAME;
+ }
+ }
+ } else if (!EXTERNAL(rdataset)) {
+ /*
+ * It's OK to cache this rdataset now.
+ */
+ if (ANSWER(rdataset)) {
+ addedrdataset = ardataset;
+ } else if (ANSWERSIG(rdataset)) {
+ addedrdataset = asigrdataset;
+ } else {
+ addedrdataset = NULL;
+ }
+ if (CHAINING(rdataset)) {
+ if (rdataset->type == dns_rdatatype_cname) {
+ eresult = DNS_R_CNAME;
+ } else {
+ INSIST(rdataset->type ==
+ dns_rdatatype_dname);
+ eresult = DNS_R_DNAME;
+ }
+ }
+ if (rdataset->trust == dns_trust_glue &&
+ (rdataset->type == dns_rdatatype_ns ||
+ (rdataset->type == dns_rdatatype_rrsig &&
+ rdataset->covers == dns_rdatatype_ns)))
+ {
+ /*
+ * If the trust level is
+ * 'dns_trust_glue' then we are adding
+ * data from a referral we got while
+ * executing the search algorithm. New
+ * referral data always takes precedence
+ * over the existing cache contents.
+ */
+ options = DNS_DBADD_FORCE;
+ } else if ((fctx->options & DNS_FETCHOPT_PREFETCH) != 0)
+ {
+ options = DNS_DBADD_PREFETCH;
+ } else {
+ options = 0;
+ }
+
+ if (ANSWER(rdataset) &&
+ rdataset->type != dns_rdatatype_rrsig)
+ {
+ isc_result_t tresult;
+ dns_name_t *noqname = NULL;
+ tresult = findnoqname(fctx, message, name,
+ rdataset->type, &noqname);
+ if (tresult == ISC_R_SUCCESS && noqname != NULL)
+ {
+ (void)dns_rdataset_addnoqname(rdataset,
+ noqname);
+ }
+ }
+
+ /*
+ * Now we can add the rdataset.
+ */
+ result = dns_db_addrdataset(fctx->cache, node, NULL,
+ now, rdataset, options,
+ addedrdataset);
+
+ if (result == DNS_R_UNCHANGED) {
+ if (ANSWER(rdataset) && ardataset != NULL &&
+ NEGATIVE(ardataset))
+ {
+ /*
+ * The answer in the cache is
+ * better than the answer we
+ * found, and is a negative
+ * cache entry, so we must set
+ * eresult appropriately.
+ */
+ if (NXDOMAIN(ardataset)) {
+ eresult = DNS_R_NCACHENXDOMAIN;
+ } else {
+ eresult = DNS_R_NCACHENXRRSET;
+ }
+ }
+ result = ISC_R_SUCCESS;
+ } else if (result != ISC_R_SUCCESS) {
+ break;
+ }
+ }
+ }
+
+ if (valrdataset != NULL) {
+ dns_rdatatype_t vtype = fctx->type;
+ if (CHAINING(valrdataset)) {
+ if (valrdataset->type == dns_rdatatype_cname) {
+ vtype = dns_rdatatype_cname;
+ } else {
+ vtype = dns_rdatatype_dname;
+ }
+ }
+
+ result = valcreate(fctx, message, addrinfo, name, vtype,
+ valrdataset, valsigrdataset, valoptions,
+ task);
+ }
+
+ if (result == ISC_R_SUCCESS && have_answer) {
+ FCTX_ATTR_SET(fctx, FCTX_ATTR_HAVEANSWER);
+ if (event != NULL) {
+ /*
+ * Negative results must be indicated in
+ * event->result.
+ */
+ if (dns_rdataset_isassociated(event->rdataset) &&
+ NEGATIVE(event->rdataset))
+ {
+ INSIST(eresult == DNS_R_NCACHENXDOMAIN ||
+ eresult == DNS_R_NCACHENXRRSET);
+ }
+ event->result = eresult;
+ if (adbp != NULL && *adbp != NULL) {
+ if (anodep != NULL && *anodep != NULL) {
+ dns_db_detachnode(*adbp, anodep);
+ }
+ dns_db_detach(adbp);
+ }
+ dns_db_attach(fctx->cache, adbp);
+ dns_db_transfernode(fctx->cache, &node, anodep);
+ clone_results(fctx);
+ }
+ }
+
+ if (node != NULL) {
+ dns_db_detachnode(fctx->cache, &node);
+ }
+
+ return (result);
+}
+
+static isc_result_t
+cache_message(fetchctx_t *fctx, dns_message_t *message,
+ dns_adbaddrinfo_t *addrinfo, isc_stdtime_t now) {
+ isc_result_t result;
+ dns_section_t section;
+ dns_name_t *name;
+
+ FCTXTRACE("cache_message");
+
+ FCTX_ATTR_CLR(fctx, FCTX_ATTR_WANTCACHE);
+
+ LOCK(&fctx->res->buckets[fctx->bucketnum].lock);
+
+ for (section = DNS_SECTION_ANSWER; section <= DNS_SECTION_ADDITIONAL;
+ section++)
+ {
+ result = dns_message_firstname(message, section);
+ while (result == ISC_R_SUCCESS) {
+ name = NULL;
+ dns_message_currentname(message, section, &name);
+ if ((name->attributes & DNS_NAMEATTR_CACHE) != 0) {
+ result = cache_name(fctx, name, message,
+ addrinfo, now);
+ if (result != ISC_R_SUCCESS) {
+ break;
+ }
+ }
+ result = dns_message_nextname(message, section);
+ }
+ if (result != ISC_R_NOMORE) {
+ break;
+ }
+ }
+ if (result == ISC_R_NOMORE) {
+ result = ISC_R_SUCCESS;
+ }
+
+ UNLOCK(&fctx->res->buckets[fctx->bucketnum].lock);
+
+ return (result);
+}
+
+/*
+ * Do what dns_ncache_addoptout() does, and then compute an appropriate
+ * eresult.
+ */
+static isc_result_t
+ncache_adderesult(dns_message_t *message, dns_db_t *cache, dns_dbnode_t *node,
+ dns_rdatatype_t covers, isc_stdtime_t now, dns_ttl_t minttl,
+ dns_ttl_t maxttl, bool optout, bool secure,
+ dns_rdataset_t *ardataset, isc_result_t *eresultp) {
+ isc_result_t result;
+ dns_rdataset_t rdataset;
+
+ if (ardataset == NULL) {
+ dns_rdataset_init(&rdataset);
+ ardataset = &rdataset;
+ }
+ if (secure) {
+ result = dns_ncache_addoptout(message, cache, node, covers, now,
+ minttl, maxttl, optout,
+ ardataset);
+ } else {
+ result = dns_ncache_add(message, cache, node, covers, now,
+ minttl, maxttl, ardataset);
+ }
+ if (result == DNS_R_UNCHANGED || result == ISC_R_SUCCESS) {
+ /*
+ * If the cache now contains a negative entry and we
+ * care about whether it is DNS_R_NCACHENXDOMAIN or
+ * DNS_R_NCACHENXRRSET then extract it.
+ */
+ if (NEGATIVE(ardataset)) {
+ /*
+ * The cache data is a negative cache entry.
+ */
+ if (NXDOMAIN(ardataset)) {
+ *eresultp = DNS_R_NCACHENXDOMAIN;
+ } else {
+ *eresultp = DNS_R_NCACHENXRRSET;
+ }
+ } else {
+ /*
+ * Either we don't care about the nature of the
+ * cache rdataset (because no fetch is
+ * interested in the outcome), or the cache
+ * rdataset is not a negative cache entry.
+ * Whichever case it is, we can return success.
+ *
+ * XXXRTH There's a CNAME/DNAME problem here.
+ */
+ *eresultp = ISC_R_SUCCESS;
+ }
+ result = ISC_R_SUCCESS;
+ }
+ if (ardataset == &rdataset && dns_rdataset_isassociated(ardataset)) {
+ dns_rdataset_disassociate(ardataset);
+ }
+
+ return (result);
+}
+
+static isc_result_t
+ncache_message(fetchctx_t *fctx, dns_message_t *message,
+ dns_adbaddrinfo_t *addrinfo, dns_rdatatype_t covers,
+ isc_stdtime_t now) {
+ isc_result_t result, eresult = ISC_R_SUCCESS;
+ dns_name_t *name = fctx->name;
+ dns_resolver_t *res = fctx->res;
+ dns_db_t **adbp = NULL;
+ dns_dbnode_t *node = NULL, **anodep = NULL;
+ dns_rdataset_t *ardataset = NULL;
+ bool need_validation = false, secure_domain = false;
+ dns_fetchevent_t *event = NULL;
+ uint32_t ttl;
+ unsigned int valoptions = 0;
+ bool checknta = true;
+
+ FCTXTRACE("ncache_message");
+
+ FCTX_ATTR_CLR(fctx, FCTX_ATTR_WANTNCACHE);
+
+ POST(need_validation);
+
+ /*
+ * XXXMPA remove when we follow cnames and adjust the setting
+ * of FCTX_ATTR_WANTNCACHE in rctx_answer_none().
+ */
+ INSIST(message->counts[DNS_SECTION_ANSWER] == 0);
+
+ /*
+ * Is DNSSEC validation required for this name?
+ */
+ if ((fctx->options & DNS_FETCHOPT_NONTA) != 0) {
+ valoptions |= DNS_VALIDATOR_NONTA;
+ checknta = false;
+ }
+
+ if (fctx->res->view->enablevalidation) {
+ result = issecuredomain(res->view, name, fctx->type, now,
+ checknta, NULL, &secure_domain);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+ }
+
+ if ((fctx->options & DNS_FETCHOPT_NOCDFLAG) != 0) {
+ valoptions |= DNS_VALIDATOR_NOCDFLAG;
+ }
+
+ if ((fctx->options & DNS_FETCHOPT_NOVALIDATE) != 0) {
+ need_validation = false;
+ } else {
+ need_validation = secure_domain;
+ }
+
+ if (secure_domain) {
+ /*
+ * Mark all rdatasets as pending.
+ */
+ result = dns_message_firstname(message, DNS_SECTION_AUTHORITY);
+ while (result == ISC_R_SUCCESS) {
+ dns_rdataset_t *trdataset = NULL;
+ dns_name_t *tname = NULL;
+
+ dns_message_currentname(message, DNS_SECTION_AUTHORITY,
+ &tname);
+ for (trdataset = ISC_LIST_HEAD(tname->list);
+ trdataset != NULL;
+ trdataset = ISC_LIST_NEXT(trdataset, link))
+ {
+ trdataset->trust = dns_trust_pending_answer;
+ }
+ result = dns_message_nextname(message,
+ DNS_SECTION_AUTHORITY);
+ }
+ if (result != ISC_R_NOMORE) {
+ return (result);
+ }
+ }
+
+ if (need_validation) {
+ /*
+ * Do negative response validation.
+ */
+ result = valcreate(fctx, message, addrinfo, name, fctx->type,
+ NULL, NULL, valoptions,
+ res->buckets[fctx->bucketnum].task);
+ /*
+ * If validation is necessary, return now. Otherwise
+ * continue to process the message, letting the
+ * validation complete in its own good time.
+ */
+ return (result);
+ }
+
+ LOCK(&res->buckets[fctx->bucketnum].lock);
+
+ if (!HAVE_ANSWER(fctx)) {
+ event = ISC_LIST_HEAD(fctx->events);
+ if (event != NULL) {
+ adbp = &event->db;
+ dns_name_copy(name, event->foundname);
+ anodep = &event->node;
+ ardataset = event->rdataset;
+ }
+ }
+
+ result = dns_db_findnode(fctx->cache, name, true, &node);
+ if (result != ISC_R_SUCCESS) {
+ goto unlock;
+ }
+
+ /*
+ * If we are asking for a SOA record set the cache time
+ * to zero to facilitate locating the containing zone of
+ * a arbitrary zone.
+ */
+ ttl = fctx->res->view->maxncachettl;
+ if (fctx->type == dns_rdatatype_soa && covers == dns_rdatatype_any &&
+ fctx->res->zero_no_soa_ttl)
+ {
+ ttl = 0;
+ }
+
+ result = ncache_adderesult(message, fctx->cache, node, covers, now,
+ fctx->res->view->minncachettl, ttl, false,
+ false, ardataset, &eresult);
+ if (result != ISC_R_SUCCESS) {
+ goto unlock;
+ }
+
+ if (!HAVE_ANSWER(fctx)) {
+ FCTX_ATTR_SET(fctx, FCTX_ATTR_HAVEANSWER);
+ if (event != NULL) {
+ event->result = eresult;
+ if (adbp != NULL && *adbp != NULL) {
+ if (anodep != NULL && *anodep != NULL) {
+ dns_db_detachnode(*adbp, anodep);
+ }
+ dns_db_detach(adbp);
+ }
+ dns_db_attach(fctx->cache, adbp);
+ dns_db_transfernode(fctx->cache, &node, anodep);
+ clone_results(fctx);
+ }
+ }
+
+unlock:
+ UNLOCK(&res->buckets[fctx->bucketnum].lock);
+
+ if (node != NULL) {
+ dns_db_detachnode(fctx->cache, &node);
+ }
+
+ return (result);
+}
+
+static void
+mark_related(dns_name_t *name, dns_rdataset_t *rdataset, bool external,
+ bool gluing) {
+ name->attributes |= DNS_NAMEATTR_CACHE;
+ if (gluing) {
+ rdataset->trust = dns_trust_glue;
+ /*
+ * Glue with 0 TTL causes problems. We force the TTL to
+ * 1 second to prevent this.
+ */
+ if (rdataset->ttl == 0) {
+ rdataset->ttl = 1;
+ }
+ } else {
+ rdataset->trust = dns_trust_additional;
+ }
+ /*
+ * Avoid infinite loops by only marking new rdatasets.
+ */
+ if (!CACHE(rdataset)) {
+ name->attributes |= DNS_NAMEATTR_CHASE;
+ rdataset->attributes |= DNS_RDATASETATTR_CHASE;
+ }
+ rdataset->attributes |= DNS_RDATASETATTR_CACHE;
+ if (external) {
+ rdataset->attributes |= DNS_RDATASETATTR_EXTERNAL;
+ }
+}
+
+/*
+ * Returns true if 'name' is external to the namespace for which
+ * the server being queried can answer, either because it's not a
+ * subdomain or because it's below a forward declaration or a
+ * locally served zone.
+ */
+static inline bool
+name_external(const dns_name_t *name, dns_rdatatype_t type, fetchctx_t *fctx) {
+ isc_result_t result;
+ dns_forwarders_t *forwarders = NULL;
+ dns_fixedname_t fixed, zfixed;
+ dns_name_t *fname = dns_fixedname_initname(&fixed);
+ dns_name_t *zfname = dns_fixedname_initname(&zfixed);
+ dns_name_t *apex = NULL;
+ dns_name_t suffix;
+ dns_zone_t *zone = NULL;
+ unsigned int labels;
+ dns_namereln_t rel;
+
+ apex = (ISDUALSTACK(fctx->addrinfo) || !ISFORWARDER(fctx->addrinfo))
+ ? fctx->domain
+ : fctx->fwdname;
+
+ /*
+ * The name is outside the queried namespace.
+ */
+ rel = dns_name_fullcompare(name, apex, &(int){ 0 },
+ &(unsigned int){ 0U });
+ if (rel != dns_namereln_subdomain && rel != dns_namereln_equal) {
+ return (true);
+ }
+
+ /*
+ * If the record lives in the parent zone, adjust the name so we
+ * look for the correct zone or forward clause.
+ */
+ labels = dns_name_countlabels(name);
+ if (dns_rdatatype_atparent(type) && labels > 1U) {
+ dns_name_init(&suffix, NULL);
+ dns_name_getlabelsequence(name, 1, labels - 1, &suffix);
+ name = &suffix;
+ } else if (rel == dns_namereln_equal) {
+ /* If 'name' is 'apex', no further checking is needed. */
+ return (false);
+ }
+
+ /*
+ * If there is a locally served zone between 'apex' and 'name'
+ * then don't cache.
+ */
+ LOCK(&fctx->res->view->lock);
+ if (fctx->res->view->zonetable != NULL) {
+ unsigned int options = DNS_ZTFIND_NOEXACT | DNS_ZTFIND_MIRROR;
+ result = dns_zt_find(fctx->res->view->zonetable, name, options,
+ zfname, &zone);
+ if (zone != NULL) {
+ dns_zone_detach(&zone);
+ }
+ if (result == ISC_R_SUCCESS || result == DNS_R_PARTIALMATCH) {
+ if (dns_name_fullcompare(zfname, apex, &(int){ 0 },
+ &(unsigned int){ 0U }) ==
+ dns_namereln_subdomain)
+ {
+ UNLOCK(&fctx->res->view->lock);
+ return (true);
+ }
+ }
+ }
+ UNLOCK(&fctx->res->view->lock);
+
+ /*
+ * Look for a forward declaration below 'name'.
+ */
+ result = dns_fwdtable_find(fctx->res->view->fwdtable, name, fname,
+ &forwarders);
+
+ if (ISFORWARDER(fctx->addrinfo)) {
+ /*
+ * See if the forwarder declaration is better.
+ */
+ if (result == ISC_R_SUCCESS || result == DNS_R_PARTIALMATCH) {
+ return (!dns_name_equal(fname, fctx->fwdname));
+ }
+
+ /*
+ * If the lookup failed, the configuration must have
+ * changed: play it safe and don't cache.
+ */
+ return (true);
+ } else if ((result == ISC_R_SUCCESS || result == DNS_R_PARTIALMATCH) &&
+ forwarders->fwdpolicy == dns_fwdpolicy_only &&
+ !ISC_LIST_EMPTY(forwarders->fwdrs))
+ {
+ /*
+ * If 'name' is covered by a 'forward only' clause then we
+ * can't cache this repsonse.
+ */
+ return (true);
+ }
+
+ return (false);
+}
+
+static isc_result_t
+check_section(void *arg, const dns_name_t *addname, dns_rdatatype_t type,
+ dns_rdataset_t *found, dns_section_t section) {
+ respctx_t *rctx = arg;
+ fetchctx_t *fctx = rctx->fctx;
+ isc_result_t result;
+ dns_name_t *name = NULL;
+ dns_rdataset_t *rdataset = NULL;
+ bool external;
+ dns_rdatatype_t rtype;
+ bool gluing;
+
+ REQUIRE(VALID_FCTX(fctx));
+
+#if CHECK_FOR_GLUE_IN_ANSWER
+ if (section == DNS_SECTION_ANSWER && type != dns_rdatatype_a) {
+ return (ISC_R_SUCCESS);
+ }
+#endif /* if CHECK_FOR_GLUE_IN_ANSWER */
+
+ gluing = (GLUING(fctx) || (fctx->type == dns_rdatatype_ns &&
+ dns_name_equal(fctx->name, dns_rootname)));
+
+ result = dns_message_findname(rctx->query->rmessage, section, addname,
+ dns_rdatatype_any, 0, &name, NULL);
+ if (result == ISC_R_SUCCESS) {
+ external = name_external(name, type, fctx);
+ if (type == dns_rdatatype_a) {
+ for (rdataset = ISC_LIST_HEAD(name->list);
+ rdataset != NULL;
+ rdataset = ISC_LIST_NEXT(rdataset, link))
+ {
+ if (rdataset->type == dns_rdatatype_rrsig) {
+ rtype = rdataset->covers;
+ } else {
+ rtype = rdataset->type;
+ }
+ if (rtype == dns_rdatatype_a ||
+ rtype == dns_rdatatype_aaaa)
+ {
+ mark_related(name, rdataset, external,
+ gluing);
+ }
+ }
+ } else {
+ result = dns_message_findtype(name, type, 0, &rdataset);
+ if (result == ISC_R_SUCCESS) {
+ mark_related(name, rdataset, external, gluing);
+ if (found != NULL) {
+ dns_rdataset_clone(rdataset, found);
+ }
+ /*
+ * Do we have its SIG too?
+ */
+ rdataset = NULL;
+ result = dns_message_findtype(
+ name, dns_rdatatype_rrsig, type,
+ &rdataset);
+ if (result == ISC_R_SUCCESS) {
+ mark_related(name, rdataset, external,
+ gluing);
+ }
+ }
+ }
+ }
+
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+check_related(void *arg, const dns_name_t *addname, dns_rdatatype_t type,
+ dns_rdataset_t *found) {
+ return (check_section(arg, addname, type, found,
+ DNS_SECTION_ADDITIONAL));
+}
+
+#ifndef CHECK_FOR_GLUE_IN_ANSWER
+#define CHECK_FOR_GLUE_IN_ANSWER 0
+#endif /* ifndef CHECK_FOR_GLUE_IN_ANSWER */
+
+#if CHECK_FOR_GLUE_IN_ANSWER
+static isc_result_t
+check_answer(void *arg, const dns_name_t *addname, dns_rdatatype_t type,
+ dns_rdataset_t *found) {
+ return (check_section(arg, addname, type, found, DNS_SECTION_ANSWER));
+}
+#endif /* if CHECK_FOR_GLUE_IN_ANSWER */
+
+static bool
+is_answeraddress_allowed(dns_view_t *view, dns_name_t *name,
+ dns_rdataset_t *rdataset) {
+ isc_result_t result;
+ dns_rdata_t rdata = DNS_RDATA_INIT;
+ struct in_addr ina;
+ struct in6_addr in6a;
+ isc_netaddr_t netaddr;
+ char addrbuf[ISC_NETADDR_FORMATSIZE];
+ char namebuf[DNS_NAME_FORMATSIZE];
+ char classbuf[64];
+ char typebuf[64];
+ int match;
+
+ /* By default, we allow any addresses. */
+ if (view->denyansweracl == NULL) {
+ return (true);
+ }
+
+ /*
+ * If the owner name matches one in the exclusion list, either
+ * exactly or partially, allow it.
+ */
+ if (view->answeracl_exclude != NULL) {
+ dns_rbtnode_t *node = NULL;
+
+ result = dns_rbt_findnode(view->answeracl_exclude, name, NULL,
+ &node, NULL, 0, NULL, NULL);
+
+ if (result == ISC_R_SUCCESS || result == DNS_R_PARTIALMATCH) {
+ return (true);
+ }
+ }
+
+ /*
+ * Otherwise, search the filter list for a match for each
+ * address record. If a match is found, the address should be
+ * filtered, so should the entire answer.
+ */
+ for (result = dns_rdataset_first(rdataset); result == ISC_R_SUCCESS;
+ result = dns_rdataset_next(rdataset))
+ {
+ dns_rdata_reset(&rdata);
+ dns_rdataset_current(rdataset, &rdata);
+ if (rdataset->type == dns_rdatatype_a) {
+ INSIST(rdata.length == sizeof(ina.s_addr));
+ memmove(&ina.s_addr, rdata.data, sizeof(ina.s_addr));
+ isc_netaddr_fromin(&netaddr, &ina);
+ } else {
+ INSIST(rdata.length == sizeof(in6a.s6_addr));
+ memmove(in6a.s6_addr, rdata.data, sizeof(in6a.s6_addr));
+ isc_netaddr_fromin6(&netaddr, &in6a);
+ }
+
+ result = dns_acl_match(&netaddr, NULL, view->denyansweracl,
+ view->aclenv, &match, NULL);
+ if (result == ISC_R_SUCCESS && match > 0) {
+ isc_netaddr_format(&netaddr, addrbuf, sizeof(addrbuf));
+ dns_name_format(name, namebuf, sizeof(namebuf));
+ dns_rdatatype_format(rdataset->type, typebuf,
+ sizeof(typebuf));
+ dns_rdataclass_format(rdataset->rdclass, classbuf,
+ sizeof(classbuf));
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_RESOLVER,
+ DNS_LOGMODULE_RESOLVER, ISC_LOG_NOTICE,
+ "answer address %s denied for %s/%s/%s",
+ addrbuf, namebuf, typebuf, classbuf);
+ return (false);
+ }
+ }
+
+ return (true);
+}
+
+static bool
+is_answertarget_allowed(fetchctx_t *fctx, dns_name_t *qname, dns_name_t *rname,
+ dns_rdataset_t *rdataset, bool *chainingp) {
+ isc_result_t result;
+ dns_rbtnode_t *node = NULL;
+ char qnamebuf[DNS_NAME_FORMATSIZE];
+ char tnamebuf[DNS_NAME_FORMATSIZE];
+ char classbuf[64];
+ char typebuf[64];
+ dns_name_t *tname = NULL;
+ dns_rdata_cname_t cname;
+ dns_rdata_dname_t dname;
+ dns_view_t *view = fctx->res->view;
+ dns_rdata_t rdata = DNS_RDATA_INIT;
+ unsigned int nlabels;
+ dns_fixedname_t fixed;
+ dns_name_t prefix;
+ int order;
+
+ REQUIRE(rdataset != NULL);
+ REQUIRE(rdataset->type == dns_rdatatype_cname ||
+ rdataset->type == dns_rdatatype_dname);
+
+ /*
+ * By default, we allow any target name.
+ * If newqname != NULL we also need to extract the newqname.
+ */
+ if (chainingp == NULL && view->denyanswernames == NULL) {
+ return (true);
+ }
+
+ result = dns_rdataset_first(rdataset);
+ RUNTIME_CHECK(result == ISC_R_SUCCESS);
+ dns_rdataset_current(rdataset, &rdata);
+ switch (rdataset->type) {
+ case dns_rdatatype_cname:
+ result = dns_rdata_tostruct(&rdata, &cname, NULL);
+ RUNTIME_CHECK(result == ISC_R_SUCCESS);
+ tname = &cname.cname;
+ break;
+ case dns_rdatatype_dname:
+ if (dns_name_fullcompare(qname, rname, &order, &nlabels) !=
+ dns_namereln_subdomain)
+ {
+ return (true);
+ }
+ result = dns_rdata_tostruct(&rdata, &dname, NULL);
+ RUNTIME_CHECK(result == ISC_R_SUCCESS);
+ dns_name_init(&prefix, NULL);
+ tname = dns_fixedname_initname(&fixed);
+ nlabels = dns_name_countlabels(rname);
+ dns_name_split(qname, nlabels, &prefix, NULL);
+ result = dns_name_concatenate(&prefix, &dname.dname, tname,
+ NULL);
+ if (result == DNS_R_NAMETOOLONG) {
+ if (chainingp != NULL) {
+ *chainingp = true;
+ }
+ return (true);
+ }
+ RUNTIME_CHECK(result == ISC_R_SUCCESS);
+ break;
+ default:
+ UNREACHABLE();
+ }
+
+ if (chainingp != NULL) {
+ *chainingp = true;
+ }
+
+ if (view->denyanswernames == NULL) {
+ return (true);
+ }
+
+ /*
+ * If the owner name matches one in the exclusion list, either
+ * exactly or partially, allow it.
+ */
+ if (view->answernames_exclude != NULL) {
+ result = dns_rbt_findnode(view->answernames_exclude, qname,
+ NULL, &node, NULL, 0, NULL, NULL);
+ if (result == ISC_R_SUCCESS || result == DNS_R_PARTIALMATCH) {
+ return (true);
+ }
+ }
+
+ /*
+ * If the target name is a subdomain of the search domain, allow
+ * it.
+ *
+ * Note that if BIND is configured as a forwarding DNS server,
+ * the search domain will always match the root domain ("."), so
+ * we must also check whether forwarding is enabled so that
+ * filters can be applied; see GL #1574.
+ */
+ if (!fctx->forwarding && dns_name_issubdomain(tname, fctx->domain)) {
+ return (true);
+ }
+
+ /*
+ * Otherwise, apply filters.
+ */
+ result = dns_rbt_findnode(view->denyanswernames, tname, NULL, &node,
+ NULL, 0, NULL, NULL);
+ if (result == ISC_R_SUCCESS || result == DNS_R_PARTIALMATCH) {
+ dns_name_format(qname, qnamebuf, sizeof(qnamebuf));
+ dns_name_format(tname, tnamebuf, sizeof(tnamebuf));
+ dns_rdatatype_format(rdataset->type, typebuf, sizeof(typebuf));
+ dns_rdataclass_format(view->rdclass, classbuf,
+ sizeof(classbuf));
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_RESOLVER,
+ DNS_LOGMODULE_RESOLVER, ISC_LOG_NOTICE,
+ "%s target %s denied for %s/%s", typebuf,
+ tnamebuf, qnamebuf, classbuf);
+ return (false);
+ }
+
+ return (true);
+}
+
+static void
+trim_ns_ttl(fetchctx_t *fctx, dns_name_t *name, dns_rdataset_t *rdataset) {
+ char ns_namebuf[DNS_NAME_FORMATSIZE];
+ char namebuf[DNS_NAME_FORMATSIZE];
+ char tbuf[DNS_RDATATYPE_FORMATSIZE];
+
+ if (fctx->ns_ttl_ok && rdataset->ttl > fctx->ns_ttl) {
+ dns_name_format(name, ns_namebuf, sizeof(ns_namebuf));
+ dns_name_format(fctx->name, namebuf, sizeof(namebuf));
+ dns_rdatatype_format(fctx->type, tbuf, sizeof(tbuf));
+
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_RESOLVER,
+ DNS_LOGMODULE_RESOLVER, ISC_LOG_DEBUG(10),
+ "fctx %p: trimming ttl of %s/NS for %s/%s: "
+ "%u -> %u",
+ fctx, ns_namebuf, namebuf, tbuf, rdataset->ttl,
+ fctx->ns_ttl);
+ rdataset->ttl = fctx->ns_ttl;
+ }
+}
+
+static bool
+validinanswer(dns_rdataset_t *rdataset, fetchctx_t *fctx) {
+ if (rdataset->type == dns_rdatatype_nsec3) {
+ /*
+ * NSEC3 records are not allowed to
+ * appear in the answer section.
+ */
+ log_formerr(fctx, "NSEC3 in answer");
+ return (false);
+ }
+ if (rdataset->type == dns_rdatatype_tkey) {
+ /*
+ * TKEY is not a valid record in a
+ * response to any query we can make.
+ */
+ log_formerr(fctx, "TKEY in answer");
+ return (false);
+ }
+ if (rdataset->rdclass != fctx->res->rdclass) {
+ log_formerr(fctx, "Mismatched class in answer");
+ return (false);
+ }
+ return (true);
+}
+
+static void
+fctx__attach(fetchctx_t *fctx, fetchctx_t **fctxp, const char *file,
+ unsigned int line, const char *func) {
+ REQUIRE(VALID_FCTX(fctx));
+ REQUIRE(fctxp != NULL && *fctxp == NULL);
+ uint_fast32_t refs = isc_refcount_increment(&fctx->references);
+
+#ifdef FCTX_TRACE
+ fprintf(stderr, "%s:%s:%u:%s(%p, %p) -> %" PRIuFAST32 "\n", func, file,
+ line, __func__, fctx, fctxp, refs + 1);
+#else
+ UNUSED(refs);
+ UNUSED(file);
+ UNUSED(line);
+ UNUSED(func);
+#endif
+
+ *fctxp = fctx;
+}
+
+static void
+fctx__detach(fetchctx_t **fctxp, const char *file, unsigned int line,
+ const char *func) {
+ fetchctx_t *fctx = NULL;
+ uint_fast32_t refs;
+
+ REQUIRE(fctxp != NULL && VALID_FCTX(*fctxp));
+
+ fctx = *fctxp;
+ *fctxp = NULL;
+
+ refs = isc_refcount_decrement(&fctx->references);
+
+#ifdef FCTX_TRACE
+ fprintf(stderr, "%s:%s:%u:%s(%p, %p) -> %" PRIuFAST32 "\n", func, file,
+ line, __func__, fctx, fctxp, refs - 1);
+#else
+ UNUSED(refs);
+ UNUSED(file);
+ UNUSED(line);
+ UNUSED(func);
+#endif
+
+ if (refs == 1) {
+ fctx_destroy(fctx, true);
+ }
+}
+
+static void
+resume_dslookup(isc_task_t *task, isc_event_t *event) {
+ isc_result_t result;
+ dns_fetchevent_t *fevent = (dns_fetchevent_t *)event;
+ fetchctx_t *fctx = event->ev_arg;
+ dns_resolver_t *res = NULL;
+ dns_rdataset_t *frdataset = NULL, *nsrdataset = NULL;
+ dns_rdataset_t nameservers;
+ dns_fixedname_t fixed;
+ dns_name_t *domain = NULL;
+ unsigned int n;
+
+ REQUIRE(event->ev_type == DNS_EVENT_FETCHDONE);
+
+ REQUIRE(VALID_FCTX(fctx));
+ res = fctx->res;
+
+ UNUSED(task);
+ FCTXTRACE("resume_dslookup");
+
+ if (fevent->node != NULL) {
+ dns_db_detachnode(fevent->db, &fevent->node);
+ }
+ if (fevent->db != NULL) {
+ dns_db_detach(&fevent->db);
+ }
+
+ /* Preserve data from fevent before freeing it. */
+ frdataset = fevent->rdataset;
+ result = fevent->result;
+ isc_event_free(&event);
+
+ LOCK(&res->buckets[fctx->bucketnum].lock);
+ if (SHUTTINGDOWN(fctx)) {
+ maybe_cancel_validators(fctx, true);
+ UNLOCK(&res->buckets[fctx->bucketnum].lock);
+
+ if (dns_rdataset_isassociated(frdataset)) {
+ dns_rdataset_disassociate(frdataset);
+ }
+
+ dns_resolver_destroyfetch(&fctx->nsfetch);
+ fctx_detach(&fctx);
+ return;
+ }
+ UNLOCK(&res->buckets[fctx->bucketnum].lock);
+
+ /*
+ * Detach the extra reference that was set in rctx_chaseds()
+ * or a prior iteration of this function.
+ */
+ fctx_unref(fctx);
+
+ switch (result) {
+ case ISC_R_CANCELED:
+ dns_resolver_destroyfetch(&fctx->nsfetch);
+ if (dns_rdataset_isassociated(frdataset)) {
+ dns_rdataset_disassociate(frdataset);
+ }
+ fctx_done_detach(&fctx, ISC_R_CANCELED);
+ break;
+
+ case ISC_R_SUCCESS:
+ FCTXTRACE("resuming DS lookup");
+
+ dns_resolver_destroyfetch(&fctx->nsfetch);
+ if (dns_rdataset_isassociated(&fctx->nameservers)) {
+ dns_rdataset_disassociate(&fctx->nameservers);
+ }
+
+ dns_rdataset_clone(frdataset, &fctx->nameservers);
+ if (dns_rdataset_isassociated(frdataset)) {
+ dns_rdataset_disassociate(frdataset);
+ }
+ fctx->ns_ttl = fctx->nameservers.ttl;
+ fctx->ns_ttl_ok = true;
+ log_ns_ttl(fctx, "resume_dslookup");
+
+ fcount_decr(fctx);
+ dns_name_copy(fctx->nsname, fctx->domain);
+ result = fcount_incr(fctx, true);
+ if (result == ISC_R_SUCCESS) {
+ /*
+ * Try again.
+ */
+ fctx_try(fctx, true, false);
+ } else {
+ fctx_done_detach(&fctx, DNS_R_SERVFAIL);
+ }
+ break;
+
+ default:
+ if (dns_rdataset_isassociated(frdataset)) {
+ dns_rdataset_disassociate(frdataset);
+ }
+
+ /*
+ * Get domain from fctx->nsfetch before we destroy it.
+ */
+ domain = dns_fixedname_initname(&fixed);
+ dns_name_copy(fctx->nsfetch->private->domain, domain);
+
+ /*
+ * If the chain of resume_dslookup() invocations managed to
+ * chop off enough labels from the original DS owner name to
+ * reach the top of the namespace, no further progress can be
+ * made. Interrupt the DS chasing process, returning SERVFAIL.
+ */
+ if (dns_name_equal(fctx->nsname, domain)) {
+ dns_resolver_destroyfetch(&fctx->nsfetch);
+ fctx_done_detach(&fctx, DNS_R_SERVFAIL);
+ return;
+ }
+
+ /*
+ * Get nameservers from fctx->nsfetch before we destroy it.
+ */
+ dns_rdataset_init(&nameservers);
+ if (dns_rdataset_isassociated(
+ &fctx->nsfetch->private->nameservers))
+ {
+ dns_rdataset_clone(&fctx->nsfetch->private->nameservers,
+ &nameservers);
+ nsrdataset = &nameservers;
+ } else {
+ domain = NULL;
+ }
+
+ dns_resolver_destroyfetch(&fctx->nsfetch);
+
+ n = dns_name_countlabels(fctx->nsname);
+ dns_name_getlabelsequence(fctx->nsname, 1, n - 1, fctx->nsname);
+
+ FCTXTRACE("continuing to look for parent's NS records");
+
+ /* Starting a new fetch, so restore the extra reference */
+ fctx_addref(fctx);
+ result = dns_resolver_createfetch(
+ res, fctx->nsname, dns_rdatatype_ns, domain, nsrdataset,
+ NULL, NULL, 0, fctx->options, 0, NULL, task,
+ resume_dslookup, fctx, &fctx->nsrrset, NULL,
+ &fctx->nsfetch);
+ if (result != ISC_R_SUCCESS) {
+ if (result == DNS_R_DUPLICATE) {
+ result = DNS_R_SERVFAIL;
+ }
+ fctx_unref(fctx);
+ fctx_done_detach(&fctx, result);
+ }
+
+ if (dns_rdataset_isassociated(&nameservers)) {
+ dns_rdataset_disassociate(&nameservers);
+ }
+ }
+}
+
+static void
+checknamessection(dns_message_t *message, dns_section_t section) {
+ isc_result_t result;
+ dns_name_t *name;
+ dns_rdata_t rdata = DNS_RDATA_INIT;
+ dns_rdataset_t *rdataset;
+
+ for (result = dns_message_firstname(message, section);
+ result == ISC_R_SUCCESS;
+ result = dns_message_nextname(message, section))
+ {
+ name = NULL;
+ dns_message_currentname(message, section, &name);
+ for (rdataset = ISC_LIST_HEAD(name->list); rdataset != NULL;
+ rdataset = ISC_LIST_NEXT(rdataset, link))
+ {
+ for (result = dns_rdataset_first(rdataset);
+ result == ISC_R_SUCCESS;
+ result = dns_rdataset_next(rdataset))
+ {
+ dns_rdataset_current(rdataset, &rdata);
+ if (!dns_rdata_checkowner(name, rdata.rdclass,
+ rdata.type, false) ||
+ !dns_rdata_checknames(&rdata, name, NULL))
+ {
+ rdataset->attributes |=
+ DNS_RDATASETATTR_CHECKNAMES;
+ }
+ dns_rdata_reset(&rdata);
+ }
+ }
+ }
+}
+
+static void
+checknames(dns_message_t *message) {
+ checknamessection(message, DNS_SECTION_ANSWER);
+ checknamessection(message, DNS_SECTION_AUTHORITY);
+ checknamessection(message, DNS_SECTION_ADDITIONAL);
+}
+
+/*
+ * Log server NSID at log level 'level'
+ */
+static void
+log_nsid(isc_buffer_t *opt, size_t nsid_len, resquery_t *query, int level,
+ isc_mem_t *mctx) {
+ static const char hex[17] = "0123456789abcdef";
+ char addrbuf[ISC_SOCKADDR_FORMATSIZE];
+ size_t buflen;
+ unsigned char *p, *nsid;
+ unsigned char *buf = NULL, *pbuf = NULL;
+
+ REQUIRE(nsid_len <= UINT16_MAX);
+
+ /* Allocate buffer for storing hex version of the NSID */
+ buflen = nsid_len * 2 + 1;
+ buf = isc_mem_get(mctx, buflen);
+ pbuf = isc_mem_get(mctx, nsid_len + 1);
+
+ /* Convert to hex */
+ p = buf;
+ nsid = isc_buffer_current(opt);
+ for (size_t i = 0; i < nsid_len; i++) {
+ *p++ = hex[(nsid[i] >> 4) & 0xf];
+ *p++ = hex[nsid[i] & 0xf];
+ }
+ *p = '\0';
+
+ /* Make printable version */
+ p = pbuf;
+ for (size_t i = 0; i < nsid_len; i++) {
+ *p++ = isprint(nsid[i]) ? nsid[i] : '.';
+ }
+ *p = '\0';
+
+ isc_sockaddr_format(&query->addrinfo->sockaddr, addrbuf,
+ sizeof(addrbuf));
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_NSID, DNS_LOGMODULE_RESOLVER,
+ level, "received NSID %s (\"%s\") from %s", buf, pbuf,
+ addrbuf);
+
+ isc_mem_put(mctx, pbuf, nsid_len + 1);
+ isc_mem_put(mctx, buf, buflen);
+}
+
+static bool
+iscname(dns_message_t *message, dns_name_t *name) {
+ isc_result_t result;
+
+ result = dns_message_findname(message, DNS_SECTION_ANSWER, name,
+ dns_rdatatype_cname, 0, NULL, NULL);
+ return (result == ISC_R_SUCCESS ? true : false);
+}
+
+static bool
+betterreferral(respctx_t *rctx) {
+ isc_result_t result;
+ dns_name_t *name;
+ dns_rdataset_t *rdataset;
+
+ for (result = dns_message_firstname(rctx->query->rmessage,
+ DNS_SECTION_AUTHORITY);
+ result == ISC_R_SUCCESS;
+ result = dns_message_nextname(rctx->query->rmessage,
+ DNS_SECTION_AUTHORITY))
+ {
+ name = NULL;
+ dns_message_currentname(rctx->query->rmessage,
+ DNS_SECTION_AUTHORITY, &name);
+ if (!isstrictsubdomain(name, rctx->fctx->domain)) {
+ continue;
+ }
+ for (rdataset = ISC_LIST_HEAD(name->list); rdataset != NULL;
+ rdataset = ISC_LIST_NEXT(rdataset, link))
+ {
+ if (rdataset->type == dns_rdatatype_ns) {
+ return (true);
+ }
+ }
+ }
+ return (false);
+}
+
+/*
+ * resquery_response():
+ * Handles responses received in response to iterative queries sent by
+ * resquery_send(). Sets up a response context (respctx_t).
+ */
+static void
+resquery_response(isc_result_t eresult, isc_region_t *region, void *arg) {
+ isc_result_t result;
+ resquery_t *query = (resquery_t *)arg;
+ fetchctx_t *fctx = NULL;
+ respctx_t rctx;
+
+ if (eresult == ISC_R_CANCELED) {
+ return;
+ }
+
+ REQUIRE(VALID_QUERY(query));
+ fctx = query->fctx;
+ REQUIRE(VALID_FCTX(fctx));
+
+ QTRACE("response");
+
+ if (eresult == ISC_R_TIMEDOUT) {
+ result = resquery_timeout(query);
+ if (result == ISC_R_COMPLETE) {
+ return;
+ }
+ }
+
+ if (isc_sockaddr_pf(&query->addrinfo->sockaddr) == PF_INET) {
+ inc_stats(fctx->res, dns_resstatscounter_responsev4);
+ } else {
+ inc_stats(fctx->res, dns_resstatscounter_responsev6);
+ }
+
+ rctx_respinit(query, fctx, eresult, region, &rctx);
+
+ if (eresult == ISC_R_SHUTTINGDOWN ||
+ atomic_load_acquire(&fctx->res->exiting))
+ {
+ result = ISC_R_SHUTTINGDOWN;
+ FCTXTRACE("resolver shutting down");
+ rctx.finish = NULL;
+ rctx_done(&rctx, result);
+ return;
+ }
+
+ result = rctx_timedout(&rctx);
+ if (result == ISC_R_COMPLETE) {
+ FCTXTRACE("timed out");
+ return;
+ }
+
+ fctx->addrinfo = query->addrinfo;
+ fctx->timeout = false;
+ fctx->timeouts = 0;
+
+ /*
+ * Check whether the dispatcher has failed; if so we're done
+ */
+ result = rctx_dispfail(&rctx);
+ if (result == ISC_R_COMPLETE) {
+ return;
+ }
+
+ if (query->tsig != NULL) {
+ result = dns_message_setquerytsig(query->rmessage, query->tsig);
+ if (result != ISC_R_SUCCESS) {
+ FCTXTRACE3("unable to set query tsig", result);
+ rctx_done(&rctx, result);
+ return;
+ }
+ }
+
+ if (query->tsigkey != NULL) {
+ result = dns_message_settsigkey(query->rmessage,
+ query->tsigkey);
+ if (result != ISC_R_SUCCESS) {
+ FCTXTRACE3("unable to set tsig key", result);
+ rctx_done(&rctx, result);
+ return;
+ }
+ }
+
+ dns_message_setclass(query->rmessage, fctx->res->rdclass);
+
+ if ((rctx.retryopts & DNS_FETCHOPT_TCP) == 0) {
+ if ((rctx.retryopts & DNS_FETCHOPT_NOEDNS0) == 0) {
+ dns_adb_setudpsize(fctx->adb, query->addrinfo,
+ isc_buffer_usedlength(&rctx.buffer));
+ } else {
+ dns_adb_plainresponse(fctx->adb, query->addrinfo);
+ }
+ }
+
+ /*
+ * Parse response message.
+ */
+ result = rctx_parse(&rctx);
+ if (result == ISC_R_COMPLETE) {
+ return;
+ }
+
+ /*
+ * Log the incoming packet.
+ */
+ rctx_logpacket(&rctx);
+
+ if (query->rmessage->rdclass != fctx->res->rdclass) {
+ rctx.resend = true;
+ FCTXTRACE("bad class");
+ rctx_done(&rctx, result);
+ return;
+ }
+
+ /*
+ * Process receive opt record.
+ */
+ rctx.opt = dns_message_getopt(query->rmessage);
+ if (rctx.opt != NULL) {
+ rctx_opt(&rctx);
+ }
+
+ if (query->rmessage->cc_bad && (rctx.retryopts & DNS_FETCHOPT_TCP) == 0)
+ {
+ /*
+ * If the COOKIE is bad, assume it is an attack and
+ * keep listening for a good answer.
+ */
+ rctx.nextitem = true;
+ if (isc_log_wouldlog(dns_lctx, ISC_LOG_INFO)) {
+ char addrbuf[ISC_SOCKADDR_FORMATSIZE];
+ isc_sockaddr_format(&query->addrinfo->sockaddr, addrbuf,
+ sizeof(addrbuf));
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_RESOLVER,
+ DNS_LOGMODULE_RESOLVER, ISC_LOG_INFO,
+ "bad cookie from %s", addrbuf);
+ }
+ rctx_done(&rctx, result);
+ return;
+ }
+
+ /*
+ * Is the question the same as the one we asked?
+ * NOERROR/NXDOMAIN/YXDOMAIN/REFUSED/SERVFAIL/BADCOOKIE must
+ * have the same question. FORMERR/NOTIMP if they have a
+ * question section then it must match.
+ */
+ switch (query->rmessage->rcode) {
+ case dns_rcode_notimp:
+ case dns_rcode_formerr:
+ if (query->rmessage->counts[DNS_SECTION_QUESTION] == 0) {
+ break;
+ }
+ FALLTHROUGH;
+ case dns_rcode_nxrrset: /* Not expected. */
+ case dns_rcode_badcookie:
+ case dns_rcode_noerror:
+ case dns_rcode_nxdomain:
+ case dns_rcode_yxdomain:
+ case dns_rcode_refused:
+ case dns_rcode_servfail:
+ default:
+ result = same_question(fctx, query->rmessage);
+ if (result != ISC_R_SUCCESS) {
+ FCTXTRACE3("question section invalid", result);
+ rctx.nextitem = true;
+ rctx_done(&rctx, result);
+ return;
+ }
+ break;
+ }
+
+ /*
+ * If the message is signed, check the signature. If not, this
+ * returns success anyway.
+ */
+ result = dns_message_checksig(query->rmessage, fctx->res->view);
+ if (result != ISC_R_SUCCESS) {
+ FCTXTRACE3("signature check failed", result);
+ if (result == DNS_R_UNEXPECTEDTSIG ||
+ result == DNS_R_EXPECTEDTSIG)
+ {
+ rctx.nextitem = true;
+ }
+ rctx_done(&rctx, result);
+ return;
+ }
+
+ /*
+ * The dispatcher should ensure we only get responses with QR
+ * set.
+ */
+ INSIST((query->rmessage->flags & DNS_MESSAGEFLAG_QR) != 0);
+
+ /*
+ * If we have had a server cookie and don't get one retry over
+ * TCP. This may be a misconfigured anycast server or an attempt
+ * to send a spoofed response. Skip if we have a valid tsig.
+ */
+ if (dns_message_gettsig(query->rmessage, NULL) == NULL &&
+ !query->rmessage->cc_ok && !query->rmessage->cc_bad &&
+ (rctx.retryopts & DNS_FETCHOPT_TCP) == 0)
+ {
+ unsigned char cookie[COOKIE_BUFFER_SIZE];
+ if (dns_adb_getcookie(fctx->adb, query->addrinfo, cookie,
+ sizeof(cookie)) > CLIENT_COOKIE_SIZE)
+ {
+ if (isc_log_wouldlog(dns_lctx, ISC_LOG_INFO)) {
+ char addrbuf[ISC_SOCKADDR_FORMATSIZE];
+ isc_sockaddr_format(&query->addrinfo->sockaddr,
+ addrbuf, sizeof(addrbuf));
+ isc_log_write(
+ dns_lctx, DNS_LOGCATEGORY_RESOLVER,
+ DNS_LOGMODULE_RESOLVER, ISC_LOG_INFO,
+ "missing expected cookie "
+ "from %s",
+ addrbuf);
+ }
+ rctx.retryopts |= DNS_FETCHOPT_TCP;
+ rctx.resend = true;
+ rctx_done(&rctx, result);
+ return;
+ }
+ }
+
+ rctx_edns(&rctx);
+
+ /*
+ * Deal with truncated responses by retrying using TCP.
+ */
+ if ((query->rmessage->flags & DNS_MESSAGEFLAG_TC) != 0) {
+ rctx.truncated = true;
+ }
+
+ if (rctx.truncated) {
+ inc_stats(fctx->res, dns_resstatscounter_truncated);
+ if ((rctx.retryopts & DNS_FETCHOPT_TCP) != 0) {
+ rctx.broken_server = DNS_R_TRUNCATEDTCP;
+ rctx.next_server = true;
+ } else {
+ rctx.retryopts |= DNS_FETCHOPT_TCP;
+ rctx.resend = true;
+ }
+ FCTXTRACE3("message truncated", result);
+ rctx_done(&rctx, result);
+ return;
+ }
+
+ /*
+ * Is it a query response?
+ */
+ if (query->rmessage->opcode != dns_opcode_query) {
+ rctx.broken_server = DNS_R_UNEXPECTEDOPCODE;
+ rctx.next_server = true;
+ FCTXTRACE("invalid message opcode");
+ rctx_done(&rctx, result);
+ return;
+ }
+
+ /*
+ * Update statistics about erroneous responses.
+ */
+ switch (query->rmessage->rcode) {
+ case dns_rcode_noerror:
+ /* no error */
+ break;
+ case dns_rcode_nxdomain:
+ inc_stats(fctx->res, dns_resstatscounter_nxdomain);
+ break;
+ case dns_rcode_servfail:
+ inc_stats(fctx->res, dns_resstatscounter_servfail);
+ break;
+ case dns_rcode_formerr:
+ inc_stats(fctx->res, dns_resstatscounter_formerr);
+ break;
+ case dns_rcode_refused:
+ inc_stats(fctx->res, dns_resstatscounter_refused);
+ break;
+ case dns_rcode_badvers:
+ inc_stats(fctx->res, dns_resstatscounter_badvers);
+ break;
+ case dns_rcode_badcookie:
+ inc_stats(fctx->res, dns_resstatscounter_badcookie);
+ break;
+ default:
+ inc_stats(fctx->res, dns_resstatscounter_othererror);
+ break;
+ }
+
+ /*
+ * Bad server?
+ */
+ result = rctx_badserver(&rctx, result);
+ if (result == ISC_R_COMPLETE) {
+ return;
+ }
+
+ /*
+ * Lame server?
+ */
+ result = rctx_lameserver(&rctx);
+ if (result == ISC_R_COMPLETE) {
+ return;
+ }
+
+ /*
+ * Handle delegation-only zones like NET or COM.
+ */
+ rctx_delonly_zone(&rctx);
+
+ /*
+ * Optionally call dns_rdata_checkowner() and
+ * dns_rdata_checknames() to validate the names in the response
+ * message.
+ */
+ if ((fctx->res->options & DNS_RESOLVER_CHECKNAMES) != 0) {
+ checknames(query->rmessage);
+ }
+
+ /*
+ * Clear cache bits.
+ */
+ FCTX_ATTR_CLR(fctx, (FCTX_ATTR_WANTNCACHE | FCTX_ATTR_WANTCACHE));
+
+ /*
+ * Did we get any answers?
+ */
+ if (query->rmessage->counts[DNS_SECTION_ANSWER] > 0 &&
+ (query->rmessage->rcode == dns_rcode_noerror ||
+ query->rmessage->rcode == dns_rcode_yxdomain ||
+ query->rmessage->rcode == dns_rcode_nxdomain))
+ {
+ result = rctx_answer(&rctx);
+ if (result == ISC_R_COMPLETE) {
+ return;
+ }
+ } else if (query->rmessage->counts[DNS_SECTION_AUTHORITY] > 0 ||
+ query->rmessage->rcode == dns_rcode_noerror ||
+ query->rmessage->rcode == dns_rcode_nxdomain)
+ {
+ /*
+ * This might be an NXDOMAIN, NXRRSET, or referral.
+ * Call rctx_answer_none() to determine which it is.
+ */
+ result = rctx_answer_none(&rctx);
+ switch (result) {
+ case ISC_R_SUCCESS:
+ case DNS_R_CHASEDSSERVERS:
+ break;
+ case DNS_R_DELEGATION:
+ /*
+ * With NOFOLLOW we want to pass return
+ * DNS_R_DELEGATION to resume_qmin.
+ */
+ if ((fctx->options & DNS_FETCHOPT_NOFOLLOW) == 0) {
+ result = ISC_R_SUCCESS;
+ }
+ break;
+ default:
+ /*
+ * Something has gone wrong.
+ */
+ if (result == DNS_R_FORMERR) {
+ rctx.next_server = true;
+ }
+ FCTXTRACE3("rctx_answer_none", result);
+ rctx_done(&rctx, result);
+ return;
+ }
+ } else {
+ /*
+ * The server is insane.
+ */
+ /* XXXRTH Log */
+ rctx.broken_server = DNS_R_UNEXPECTEDRCODE;
+ rctx.next_server = true;
+ FCTXTRACE("broken server: unexpected rcode");
+ rctx_done(&rctx, result);
+ return;
+ }
+
+ /*
+ * Follow additional section data chains.
+ */
+ rctx_additional(&rctx);
+
+ /*
+ * Cache the cacheable parts of the message. This may also
+ * cause work to be queued to the DNSSEC validator.
+ */
+ if (WANTCACHE(fctx)) {
+ isc_result_t tresult;
+ tresult = cache_message(fctx, query->rmessage, query->addrinfo,
+ rctx.now);
+ if (tresult != ISC_R_SUCCESS) {
+ FCTXTRACE3("cache_message complete", tresult);
+ rctx_done(&rctx, tresult);
+ return;
+ }
+ }
+
+ /*
+ * Negative caching
+ */
+ rctx_ncache(&rctx);
+
+ FCTXTRACE("resquery_response done");
+ rctx_done(&rctx, result);
+}
+
+/*
+ * rctx_respinit():
+ * Initialize the response context structure 'rctx' to all zeroes, then
+ * set the task, event, query and fctx information from
+ * resquery_response().
+ */
+static void
+rctx_respinit(resquery_t *query, fetchctx_t *fctx, isc_result_t result,
+ isc_region_t *region, respctx_t *rctx) {
+ *rctx = (respctx_t){ .result = result,
+ .query = query,
+ .fctx = fctx,
+ .broken_type = badns_response,
+ .retryopts = query->options };
+ if (result == ISC_R_SUCCESS) {
+ REQUIRE(region != NULL);
+ isc_buffer_init(&rctx->buffer, region->base, region->length);
+ isc_buffer_add(&rctx->buffer, region->length);
+ } else {
+ isc_buffer_initnull(&rctx->buffer);
+ }
+ TIME_NOW(&rctx->tnow);
+ rctx->finish = &rctx->tnow;
+ rctx->now = (isc_stdtime_t)isc_time_seconds(&rctx->tnow);
+}
+
+/*
+ * rctx_answer_init():
+ * Clear and reinitialize those portions of 'rctx' that will be needed
+ * when scanning the answer section of the response message. This can be
+ * called more than once if scanning needs to be restarted (though
+ * currently there are no cases in which this occurs).
+ */
+static void
+rctx_answer_init(respctx_t *rctx) {
+ fetchctx_t *fctx = rctx->fctx;
+
+ rctx->aa = ((rctx->query->rmessage->flags & DNS_MESSAGEFLAG_AA) != 0);
+ if (rctx->aa) {
+ rctx->trust = dns_trust_authanswer;
+ } else {
+ rctx->trust = dns_trust_answer;
+ }
+
+ /*
+ * There can be multiple RRSIG and SIG records at a name so
+ * we treat these types as a subset of ANY.
+ */
+ rctx->type = fctx->type;
+ if (rctx->type == dns_rdatatype_rrsig ||
+ rctx->type == dns_rdatatype_sig)
+ {
+ rctx->type = dns_rdatatype_any;
+ }
+
+ /*
+ * Bigger than any valid DNAME label count.
+ */
+ rctx->dname_labels = dns_name_countlabels(fctx->name);
+ rctx->domain_labels = dns_name_countlabels(fctx->domain);
+
+ rctx->found_type = dns_rdatatype_none;
+
+ rctx->aname = NULL;
+ rctx->ardataset = NULL;
+
+ rctx->cname = NULL;
+ rctx->crdataset = NULL;
+
+ rctx->dname = NULL;
+ rctx->drdataset = NULL;
+
+ rctx->ns_name = NULL;
+ rctx->ns_rdataset = NULL;
+
+ rctx->soa_name = NULL;
+ rctx->ds_name = NULL;
+ rctx->found_name = NULL;
+}
+
+/*
+ * rctx_dispfail():
+ * Handle the case where the dispatcher failed
+ */
+static isc_result_t
+rctx_dispfail(respctx_t *rctx) {
+ fetchctx_t *fctx = rctx->fctx;
+
+ if (rctx->result == ISC_R_SUCCESS) {
+ return (ISC_R_SUCCESS);
+ }
+
+ /*
+ * There's no hope for this response.
+ */
+ rctx->next_server = true;
+
+ /*
+ * If this is a network failure, the operation is cancelled,
+ * or the network manager is being shut down, we mark the server
+ * as bad so that we won't try it for this fetch again. Also
+ * adjust finish and no_response so that we penalize this
+ * address in SRTT adjustments later.
+ */
+ switch (rctx->result) {
+ case ISC_R_EOF:
+ case ISC_R_HOSTUNREACH:
+ case ISC_R_NETUNREACH:
+ case ISC_R_CONNREFUSED:
+ case ISC_R_CONNECTIONRESET:
+ case ISC_R_INVALIDPROTO:
+ case ISC_R_CANCELED:
+ case ISC_R_SHUTTINGDOWN:
+ rctx->broken_server = rctx->result;
+ rctx->broken_type = badns_unreachable;
+ rctx->finish = NULL;
+ rctx->no_response = true;
+ break;
+ default:
+ break;
+ }
+
+ FCTXTRACE3("dispatcher failure", rctx->result);
+ rctx_done(rctx, ISC_R_SUCCESS);
+ return (ISC_R_COMPLETE);
+}
+
+/*
+ * rctx_timedout():
+ * Handle the case where a dispatch read timed out.
+ */
+static isc_result_t
+rctx_timedout(respctx_t *rctx) {
+ fetchctx_t *fctx = rctx->fctx;
+
+ if (rctx->result == ISC_R_TIMEDOUT) {
+ isc_time_t now;
+
+ inc_stats(fctx->res, dns_resstatscounter_querytimeout);
+ FCTX_ATTR_CLR(fctx, FCTX_ATTR_ADDRWAIT);
+ fctx->timeout = true;
+ fctx->timeouts++;
+
+ isc_time_now(&now);
+ /* netmgr timeouts are accurate to the millisecond */
+ if (isc_time_microdiff(&fctx->expires, &now) < US_PER_MS) {
+ FCTXTRACE("query timed out; stopped trying to make "
+ "fetch happen");
+ } else {
+ FCTXTRACE("query timed out; trying next server");
+ /* try next server */
+ rctx->no_response = true;
+ rctx->finish = NULL;
+ rctx->next_server = true;
+ }
+
+ rctx_done(rctx, rctx->result);
+ return (ISC_R_COMPLETE);
+ }
+
+ return (ISC_R_SUCCESS);
+}
+
+/*
+ * rctx_parse():
+ * Parse the response message.
+ */
+static isc_result_t
+rctx_parse(respctx_t *rctx) {
+ isc_result_t result;
+ fetchctx_t *fctx = rctx->fctx;
+ resquery_t *query = rctx->query;
+
+ result = dns_message_parse(query->rmessage, &rctx->buffer, 0);
+ if (result == ISC_R_SUCCESS) {
+ return (ISC_R_SUCCESS);
+ }
+
+ FCTXTRACE3("message failed to parse", result);
+
+ switch (result) {
+ case ISC_R_UNEXPECTEDEND:
+ if (query->rmessage->question_ok &&
+ (query->rmessage->flags & DNS_MESSAGEFLAG_TC) != 0 &&
+ (rctx->retryopts & DNS_FETCHOPT_TCP) == 0)
+ {
+ /*
+ * We defer retrying via TCP for a bit so we can
+ * check out this message further.
+ */
+ rctx->truncated = true;
+ return (ISC_R_SUCCESS);
+ }
+
+ /*
+ * Either the message ended prematurely,
+ * and/or wasn't marked as being truncated,
+ * and/or this is a response to a query we
+ * sent over TCP. In all of these cases,
+ * something is wrong with the remote
+ * server and we don't want to retry using
+ * TCP.
+ */
+ if ((rctx->retryopts & DNS_FETCHOPT_NOEDNS0) == 0) {
+ /*
+ * The problem might be that they
+ * don't understand EDNS0. Turn it
+ * off and try again.
+ */
+ rctx->retryopts |= DNS_FETCHOPT_NOEDNS0;
+ rctx->resend = true;
+ add_bad_edns(fctx, &query->addrinfo->sockaddr);
+ inc_stats(fctx->res, dns_resstatscounter_edns0fail);
+ } else {
+ rctx->broken_server = result;
+ rctx->next_server = true;
+ }
+
+ rctx_done(rctx, result);
+ break;
+ case DNS_R_FORMERR:
+ if ((rctx->retryopts & DNS_FETCHOPT_NOEDNS0) == 0) {
+ /*
+ * The problem might be that they
+ * don't understand EDNS0. Turn it
+ * off and try again.
+ */
+ rctx->retryopts |= DNS_FETCHOPT_NOEDNS0;
+ rctx->resend = true;
+ add_bad_edns(fctx, &query->addrinfo->sockaddr);
+ inc_stats(fctx->res, dns_resstatscounter_edns0fail);
+ } else {
+ rctx->broken_server = DNS_R_UNEXPECTEDRCODE;
+ rctx->next_server = true;
+ }
+
+ rctx_done(rctx, result);
+ break;
+ default:
+ /*
+ * Something bad has happened.
+ */
+ rctx_done(rctx, result);
+ break;
+ }
+
+ return (ISC_R_COMPLETE);
+}
+
+/*
+ * rctx_opt():
+ * Process the OPT record in the response.
+ */
+static void
+rctx_opt(respctx_t *rctx) {
+ resquery_t *query = rctx->query;
+ fetchctx_t *fctx = rctx->fctx;
+ dns_rdata_t rdata;
+ isc_buffer_t optbuf;
+ isc_result_t result;
+ uint16_t optcode;
+ uint16_t optlen;
+ unsigned char *optvalue;
+ dns_adbaddrinfo_t *addrinfo;
+ unsigned char cookie[CLIENT_COOKIE_SIZE];
+ bool seen_cookie = false;
+ bool seen_nsid = false;
+
+ result = dns_rdataset_first(rctx->opt);
+ if (result == ISC_R_SUCCESS) {
+ dns_rdata_init(&rdata);
+ dns_rdataset_current(rctx->opt, &rdata);
+ isc_buffer_init(&optbuf, rdata.data, rdata.length);
+ isc_buffer_add(&optbuf, rdata.length);
+ while (isc_buffer_remaininglength(&optbuf) >= 4) {
+ optcode = isc_buffer_getuint16(&optbuf);
+ optlen = isc_buffer_getuint16(&optbuf);
+ INSIST(optlen <= isc_buffer_remaininglength(&optbuf));
+ switch (optcode) {
+ case DNS_OPT_NSID:
+ if (!seen_nsid && (query->options &
+ DNS_FETCHOPT_WANTNSID) != 0)
+ {
+ log_nsid(&optbuf, optlen, query,
+ ISC_LOG_INFO, fctx->res->mctx);
+ }
+ isc_buffer_forward(&optbuf, optlen);
+ seen_nsid = true;
+ break;
+ case DNS_OPT_COOKIE:
+ /*
+ * Only process the first cookie option.
+ */
+ if (seen_cookie) {
+ isc_buffer_forward(&optbuf, optlen);
+ break;
+ }
+ optvalue = isc_buffer_current(&optbuf);
+ compute_cc(query, cookie, sizeof(cookie));
+ INSIST(query->rmessage->cc_bad == 0 &&
+ query->rmessage->cc_ok == 0);
+ if (optlen >= CLIENT_COOKIE_SIZE &&
+ memcmp(cookie, optvalue,
+ CLIENT_COOKIE_SIZE) == 0)
+ {
+ if (optlen == CLIENT_COOKIE_SIZE) {
+ query->rmessage->cc_echoed = 1;
+ } else {
+ query->rmessage->cc_ok = 1;
+ inc_stats(
+ fctx->res,
+ dns_resstatscounter_cookieok);
+ addrinfo = query->addrinfo;
+ dns_adb_setcookie(
+ fctx->adb, addrinfo,
+ optvalue, optlen);
+ }
+ } else {
+ query->rmessage->cc_bad = 1;
+ }
+ isc_buffer_forward(&optbuf, optlen);
+ inc_stats(fctx->res,
+ dns_resstatscounter_cookiein);
+ seen_cookie = true;
+ break;
+ default:
+ isc_buffer_forward(&optbuf, optlen);
+ break;
+ }
+ }
+ INSIST(isc_buffer_remaininglength(&optbuf) == 0U);
+ }
+}
+
+/*
+ * rctx_edns():
+ * Determine whether the remote server is using EDNS correctly or
+ * incorrectly and record that information if needed.
+ */
+static void
+rctx_edns(respctx_t *rctx) {
+ resquery_t *query = rctx->query;
+ fetchctx_t *fctx = rctx->fctx;
+
+ /*
+ * We have an affirmative response to the query and we have
+ * previously got a response from this server which indicated
+ * EDNS may not be supported so we can now cache the lack of
+ * EDNS support.
+ */
+ if (rctx->opt == NULL && !EDNSOK(query->addrinfo) &&
+ (query->rmessage->rcode == dns_rcode_noerror ||
+ query->rmessage->rcode == dns_rcode_nxdomain ||
+ query->rmessage->rcode == dns_rcode_refused ||
+ query->rmessage->rcode == dns_rcode_yxdomain) &&
+ bad_edns(fctx, &query->addrinfo->sockaddr))
+ {
+ dns_message_logpacket(
+ query->rmessage, "received packet (bad edns) from",
+ &query->addrinfo->sockaddr, DNS_LOGCATEGORY_RESOLVER,
+ DNS_LOGMODULE_RESOLVER, ISC_LOG_DEBUG(3),
+ fctx->res->mctx);
+ dns_adb_changeflags(fctx->adb, query->addrinfo,
+ FCTX_ADDRINFO_NOEDNS0,
+ FCTX_ADDRINFO_NOEDNS0);
+ } else if (rctx->opt == NULL &&
+ (query->rmessage->flags & DNS_MESSAGEFLAG_TC) == 0 &&
+ !EDNSOK(query->addrinfo) &&
+ (query->rmessage->rcode == dns_rcode_noerror ||
+ query->rmessage->rcode == dns_rcode_nxdomain) &&
+ (rctx->retryopts & DNS_FETCHOPT_NOEDNS0) == 0)
+ {
+ /*
+ * We didn't get a OPT record in response to a EDNS
+ * query.
+ *
+ * Old versions of named incorrectly drop the OPT record
+ * when there is a signed, truncated response so we
+ * check that TC is not set.
+ *
+ * Record that the server is not talking EDNS. While
+ * this should be safe to do for any rcode we limit it
+ * to NOERROR and NXDOMAIN.
+ */
+ dns_message_logpacket(
+ query->rmessage, "received packet (no opt) from",
+ &query->addrinfo->sockaddr, DNS_LOGCATEGORY_RESOLVER,
+ DNS_LOGMODULE_RESOLVER, ISC_LOG_DEBUG(3),
+ fctx->res->mctx);
+ dns_adb_changeflags(fctx->adb, query->addrinfo,
+ FCTX_ADDRINFO_NOEDNS0,
+ FCTX_ADDRINFO_NOEDNS0);
+ }
+
+ /*
+ * If we get a non error EDNS response record the fact so we
+ * won't fallback to plain DNS in the future for this server.
+ */
+ if (rctx->opt != NULL && !EDNSOK(query->addrinfo) &&
+ (rctx->retryopts & DNS_FETCHOPT_NOEDNS0) == 0 &&
+ (query->rmessage->rcode == dns_rcode_noerror ||
+ query->rmessage->rcode == dns_rcode_nxdomain ||
+ query->rmessage->rcode == dns_rcode_refused ||
+ query->rmessage->rcode == dns_rcode_yxdomain))
+ {
+ dns_adb_changeflags(fctx->adb, query->addrinfo,
+ FCTX_ADDRINFO_EDNSOK, FCTX_ADDRINFO_EDNSOK);
+ }
+}
+
+/*
+ * rctx_answer():
+ * We might have answers, or we might have a malformed delegation with
+ * records in the answer section. Call rctx_answer_positive() or
+ * rctx_answer_none() as appropriate.
+ */
+static isc_result_t
+rctx_answer(respctx_t *rctx) {
+ isc_result_t result;
+ fetchctx_t *fctx = rctx->fctx;
+ resquery_t *query = rctx->query;
+
+ if ((query->rmessage->flags & DNS_MESSAGEFLAG_AA) != 0 ||
+ ISFORWARDER(query->addrinfo))
+ {
+ result = rctx_answer_positive(rctx);
+ if (result != ISC_R_SUCCESS) {
+ FCTXTRACE3("rctx_answer_positive (AA/fwd)", result);
+ }
+ } else if (iscname(query->rmessage, fctx->name) &&
+ fctx->type != dns_rdatatype_any &&
+ fctx->type != dns_rdatatype_cname)
+ {
+ /*
+ * A BIND8 server could return a non-authoritative
+ * answer when a CNAME is followed. We should treat
+ * it as a valid answer.
+ */
+ result = rctx_answer_positive(rctx);
+ if (result != ISC_R_SUCCESS) {
+ FCTXTRACE3("rctx_answer_positive (!ANY/!CNAME)",
+ result);
+ }
+ } else if (fctx->type != dns_rdatatype_ns && !betterreferral(rctx)) {
+ result = rctx_answer_positive(rctx);
+ if (result != ISC_R_SUCCESS) {
+ FCTXTRACE3("rctx_answer_positive (!NS)", result);
+ }
+ } else {
+ /*
+ * This may be a delegation. First let's check for
+ */
+
+ if (fctx->type == dns_rdatatype_ns) {
+ /*
+ * A BIND 8 server could incorrectly return a
+ * non-authoritative answer to an NS query
+ * instead of a referral. Since this answer
+ * lacks the SIGs necessary to do DNSSEC
+ * validation, we must invoke the following
+ * special kludge to treat it as a referral.
+ */
+ rctx->ns_in_answer = true;
+ result = rctx_answer_none(rctx);
+ if (result != ISC_R_SUCCESS) {
+ FCTXTRACE3("rctx_answer_none (NS)", result);
+ }
+ } else {
+ /*
+ * Some other servers may still somehow include
+ * an answer when it should return a referral
+ * with an empty answer. Check to see if we can
+ * treat this as a referral by ignoring the
+ * answer. Further more, there may be an
+ * implementation that moves A/AAAA glue records
+ * to the answer section for that type of
+ * delegation when the query is for that glue
+ * record. glue_in_answer will handle
+ * such a corner case.
+ */
+ rctx->glue_in_answer = true;
+ result = rctx_answer_none(rctx);
+ if (result != ISC_R_SUCCESS) {
+ FCTXTRACE3("rctx_answer_none", result);
+ }
+ }
+
+ if (result == DNS_R_DELEGATION) {
+ /*
+ * With NOFOLLOW we want to return DNS_R_DELEGATION to
+ * resume_qmin.
+ */
+ if ((rctx->fctx->options & DNS_FETCHOPT_NOFOLLOW) != 0)
+ {
+ return (result);
+ }
+ result = ISC_R_SUCCESS;
+ } else {
+ /*
+ * At this point, AA is not set, the response
+ * is not a referral, and the server is not a
+ * forwarder. It is technically lame and it's
+ * easier to treat it as such than to figure out
+ * some more elaborate course of action.
+ */
+ rctx->broken_server = DNS_R_LAME;
+ rctx->next_server = true;
+ FCTXTRACE3("rctx_answer lame", result);
+ rctx_done(rctx, result);
+ return (ISC_R_COMPLETE);
+ }
+ }
+
+ if (result != ISC_R_SUCCESS) {
+ if (result == DNS_R_FORMERR) {
+ rctx->next_server = true;
+ }
+ FCTXTRACE3("rctx_answer failed", result);
+ rctx_done(rctx, result);
+ return (ISC_R_COMPLETE);
+ }
+
+ return (ISC_R_SUCCESS);
+}
+
+/*
+ * rctx_answer_positive():
+ * Handles positive responses. Depending which type of answer this is
+ * (matching QNAME/QTYPE, CNAME, DNAME, ANY) calls the proper routine
+ * to handle it (rctx_answer_match(), rctx_answer_cname(),
+ * rctx_answer_dname(), rctx_answer_any()).
+ */
+static isc_result_t
+rctx_answer_positive(respctx_t *rctx) {
+ isc_result_t result;
+ fetchctx_t *fctx = rctx->fctx;
+
+ FCTXTRACE("rctx_answer_positive");
+
+ rctx_answer_init(rctx);
+ rctx_answer_scan(rctx);
+
+ /*
+ * Determine which type of positive answer this is:
+ * type ANY, CNAME, DNAME, or an answer matching QNAME/QTYPE.
+ * Call the appropriate routine to handle the answer type.
+ */
+ if (rctx->aname != NULL && rctx->type == dns_rdatatype_any) {
+ result = rctx_answer_any(rctx);
+ if (result == ISC_R_COMPLETE) {
+ return (rctx->result);
+ }
+ } else if (rctx->aname != NULL) {
+ result = rctx_answer_match(rctx);
+ if (result == ISC_R_COMPLETE) {
+ return (rctx->result);
+ }
+ } else if (rctx->cname != NULL) {
+ result = rctx_answer_cname(rctx);
+ if (result == ISC_R_COMPLETE) {
+ return (rctx->result);
+ }
+ } else if (rctx->dname != NULL) {
+ result = rctx_answer_dname(rctx);
+ if (result == ISC_R_COMPLETE) {
+ return (rctx->result);
+ }
+ } else {
+ log_formerr(fctx, "reply has no answer");
+ return (DNS_R_FORMERR);
+ }
+
+ /*
+ * This response is now potentially cacheable.
+ */
+ FCTX_ATTR_SET(fctx, FCTX_ATTR_WANTCACHE);
+
+ /*
+ * Did chaining end before we got the final answer?
+ */
+ if (rctx->chaining) {
+ return (ISC_R_SUCCESS);
+ }
+
+ /*
+ * We didn't end with an incomplete chain, so the rcode should
+ * be "no error".
+ */
+ if (rctx->query->rmessage->rcode != dns_rcode_noerror) {
+ log_formerr(fctx, "CNAME/DNAME chain complete, but RCODE "
+ "indicates error");
+ return (DNS_R_FORMERR);
+ }
+
+ /*
+ * Cache records in the authority section, if
+ * there are any suitable for caching.
+ */
+ rctx_authority_positive(rctx);
+
+ log_ns_ttl(fctx, "rctx_answer");
+
+ if (rctx->ns_rdataset != NULL &&
+ dns_name_equal(fctx->domain, rctx->ns_name) &&
+ !dns_name_equal(rctx->ns_name, dns_rootname))
+ {
+ trim_ns_ttl(fctx, rctx->ns_name, rctx->ns_rdataset);
+ }
+
+ return (ISC_R_SUCCESS);
+}
+
+/*
+ * rctx_answer_scan():
+ * Perform a single pass over the answer section of a response, looking
+ * for an answer that matches QNAME/QTYPE, or a CNAME matching QNAME, or
+ * a covering DNAME. If more than one rdataset is found matching these
+ * criteria, then only one is kept. Order of preference is 1) the
+ * shortest DNAME, 2) the first matching answer, or 3) the first CNAME.
+ */
+static void
+rctx_answer_scan(respctx_t *rctx) {
+ isc_result_t result;
+ fetchctx_t *fctx = rctx->fctx;
+ dns_rdataset_t *rdataset = NULL;
+
+ for (result = dns_message_firstname(rctx->query->rmessage,
+ DNS_SECTION_ANSWER);
+ result == ISC_R_SUCCESS;
+ result = dns_message_nextname(rctx->query->rmessage,
+ DNS_SECTION_ANSWER))
+ {
+ int order;
+ unsigned int nlabels;
+ dns_namereln_t namereln;
+ dns_name_t *name = NULL;
+
+ dns_message_currentname(rctx->query->rmessage,
+ DNS_SECTION_ANSWER, &name);
+ namereln = dns_name_fullcompare(fctx->name, name, &order,
+ &nlabels);
+ switch (namereln) {
+ case dns_namereln_equal:
+ for (rdataset = ISC_LIST_HEAD(name->list);
+ rdataset != NULL;
+ rdataset = ISC_LIST_NEXT(rdataset, link))
+ {
+ if (rdataset->type == rctx->type ||
+ rctx->type == dns_rdatatype_any)
+ {
+ rctx->aname = name;
+ if (rctx->type != dns_rdatatype_any) {
+ rctx->ardataset = rdataset;
+ }
+ break;
+ }
+ if (rdataset->type == dns_rdatatype_cname) {
+ rctx->cname = name;
+ rctx->crdataset = rdataset;
+ break;
+ }
+ }
+ break;
+
+ case dns_namereln_subdomain:
+ /*
+ * Don't accept DNAME from parent namespace.
+ */
+ if (name_external(name, dns_rdatatype_dname, fctx)) {
+ continue;
+ }
+
+ /*
+ * In-scope DNAME records must have at least
+ * as many labels as the domain being queried.
+ * They also must be less that qname's labels
+ * and any previously found dname.
+ */
+ if (nlabels >= rctx->dname_labels ||
+ nlabels < rctx->domain_labels)
+ {
+ continue;
+ }
+
+ /*
+ * We are looking for the shortest DNAME if
+ * there are multiple ones (which there
+ * shouldn't be).
+ */
+ for (rdataset = ISC_LIST_HEAD(name->list);
+ rdataset != NULL;
+ rdataset = ISC_LIST_NEXT(rdataset, link))
+ {
+ if (rdataset->type != dns_rdatatype_dname) {
+ continue;
+ }
+ rctx->dname = name;
+ rctx->drdataset = rdataset;
+ rctx->dname_labels = nlabels;
+ break;
+ }
+ break;
+ default:
+ break;
+ }
+ }
+
+ /*
+ * If a DNAME was found, then any CNAME or other answer matching
+ * QNAME that may also have been found must be ignored.
+ * Similarly, if a matching answer was found along with a CNAME,
+ * the CNAME must be ignored.
+ */
+ if (rctx->dname != NULL) {
+ rctx->aname = NULL;
+ rctx->ardataset = NULL;
+ rctx->cname = NULL;
+ rctx->crdataset = NULL;
+ } else if (rctx->aname != NULL) {
+ rctx->cname = NULL;
+ rctx->crdataset = NULL;
+ }
+}
+
+/*
+ * rctx_answer_any():
+ * Handle responses to queries of type ANY. Scan the answer section,
+ * and as long as each RRset is of a type that is valid in the answer
+ * section, and the rdata isn't filtered, cache it.
+ */
+static isc_result_t
+rctx_answer_any(respctx_t *rctx) {
+ dns_rdataset_t *rdataset = NULL;
+ fetchctx_t *fctx = rctx->fctx;
+
+ for (rdataset = ISC_LIST_HEAD(rctx->aname->list); rdataset != NULL;
+ rdataset = ISC_LIST_NEXT(rdataset, link))
+ {
+ if (!validinanswer(rdataset, fctx)) {
+ rctx->result = DNS_R_FORMERR;
+ return (ISC_R_COMPLETE);
+ }
+
+ if ((fctx->type == dns_rdatatype_sig ||
+ fctx->type == dns_rdatatype_rrsig) &&
+ rdataset->type != fctx->type)
+ {
+ continue;
+ }
+
+ if ((rdataset->type == dns_rdatatype_a ||
+ rdataset->type == dns_rdatatype_aaaa) &&
+ !is_answeraddress_allowed(fctx->res->view, rctx->aname,
+ rdataset))
+ {
+ rctx->result = DNS_R_SERVFAIL;
+ return (ISC_R_COMPLETE);
+ }
+
+ if ((rdataset->type == dns_rdatatype_cname ||
+ rdataset->type == dns_rdatatype_dname) &&
+ !is_answertarget_allowed(fctx, fctx->name, rctx->aname,
+ rdataset, NULL))
+ {
+ rctx->result = DNS_R_SERVFAIL;
+ return (ISC_R_COMPLETE);
+ }
+
+ rctx->aname->attributes |= DNS_NAMEATTR_CACHE;
+ rctx->aname->attributes |= DNS_NAMEATTR_ANSWER;
+ rdataset->attributes |= DNS_RDATASETATTR_ANSWER;
+ rdataset->attributes |= DNS_RDATASETATTR_CACHE;
+ rdataset->trust = rctx->trust;
+
+ (void)dns_rdataset_additionaldata(rdataset, rctx->aname,
+ check_related, rctx);
+ }
+
+ return (ISC_R_SUCCESS);
+}
+
+/*
+ * rctx_answer_match():
+ * Handle responses that match the QNAME/QTYPE of the resolver query.
+ * If QTYPE is valid in the answer section and the rdata isn't filtered,
+ * the answer can be cached. If there is additional section data related
+ * to the answer, it can be cached as well.
+ */
+static isc_result_t
+rctx_answer_match(respctx_t *rctx) {
+ dns_rdataset_t *sigrdataset = NULL;
+ fetchctx_t *fctx = rctx->fctx;
+
+ if (!validinanswer(rctx->ardataset, fctx)) {
+ rctx->result = DNS_R_FORMERR;
+ return (ISC_R_COMPLETE);
+ }
+
+ if ((rctx->ardataset->type == dns_rdatatype_a ||
+ rctx->ardataset->type == dns_rdatatype_aaaa) &&
+ !is_answeraddress_allowed(fctx->res->view, rctx->aname,
+ rctx->ardataset))
+ {
+ rctx->result = DNS_R_SERVFAIL;
+ return (ISC_R_COMPLETE);
+ }
+ if ((rctx->ardataset->type == dns_rdatatype_cname ||
+ rctx->ardataset->type == dns_rdatatype_dname) &&
+ rctx->type != rctx->ardataset->type &&
+ rctx->type != dns_rdatatype_any &&
+ !is_answertarget_allowed(fctx, fctx->name, rctx->aname,
+ rctx->ardataset, NULL))
+ {
+ rctx->result = DNS_R_SERVFAIL;
+ return (ISC_R_COMPLETE);
+ }
+
+ rctx->aname->attributes |= DNS_NAMEATTR_CACHE;
+ rctx->aname->attributes |= DNS_NAMEATTR_ANSWER;
+ rctx->ardataset->attributes |= DNS_RDATASETATTR_ANSWER;
+ rctx->ardataset->attributes |= DNS_RDATASETATTR_CACHE;
+ rctx->ardataset->trust = rctx->trust;
+ (void)dns_rdataset_additionaldata(rctx->ardataset, rctx->aname,
+ check_related, rctx);
+
+ for (sigrdataset = ISC_LIST_HEAD(rctx->aname->list);
+ sigrdataset != NULL;
+ sigrdataset = ISC_LIST_NEXT(sigrdataset, link))
+ {
+ if (!validinanswer(sigrdataset, fctx)) {
+ rctx->result = DNS_R_FORMERR;
+ return (ISC_R_COMPLETE);
+ }
+
+ if (sigrdataset->type != dns_rdatatype_rrsig ||
+ sigrdataset->covers != rctx->type)
+ {
+ continue;
+ }
+
+ sigrdataset->attributes |= DNS_RDATASETATTR_ANSWERSIG;
+ sigrdataset->attributes |= DNS_RDATASETATTR_CACHE;
+ sigrdataset->trust = rctx->trust;
+ break;
+ }
+
+ return (ISC_R_SUCCESS);
+}
+
+/*
+ * rctx_answer_cname():
+ * Handle answers containing a CNAME. Cache the CNAME, and flag that
+ * there may be additional chain answers to find.
+ */
+static isc_result_t
+rctx_answer_cname(respctx_t *rctx) {
+ dns_rdataset_t *sigrdataset = NULL;
+ fetchctx_t *fctx = rctx->fctx;
+
+ if (!validinanswer(rctx->crdataset, fctx)) {
+ rctx->result = DNS_R_FORMERR;
+ return (ISC_R_COMPLETE);
+ }
+
+ if (rctx->type == dns_rdatatype_rrsig ||
+ rctx->type == dns_rdatatype_key || rctx->type == dns_rdatatype_nsec)
+ {
+ char buf[DNS_RDATATYPE_FORMATSIZE];
+ dns_rdatatype_format(rctx->type, buf, sizeof(buf));
+ log_formerr(fctx, "CNAME response for %s RR", buf);
+ rctx->result = DNS_R_FORMERR;
+ return (ISC_R_COMPLETE);
+ }
+
+ if (!is_answertarget_allowed(fctx, fctx->name, rctx->cname,
+ rctx->crdataset, NULL))
+ {
+ rctx->result = DNS_R_SERVFAIL;
+ return (ISC_R_COMPLETE);
+ }
+
+ rctx->cname->attributes |= DNS_NAMEATTR_CACHE;
+ rctx->cname->attributes |= DNS_NAMEATTR_ANSWER;
+ rctx->cname->attributes |= DNS_NAMEATTR_CHAINING;
+ rctx->crdataset->attributes |= DNS_RDATASETATTR_ANSWER;
+ rctx->crdataset->attributes |= DNS_RDATASETATTR_CACHE;
+ rctx->crdataset->attributes |= DNS_RDATASETATTR_CHAINING;
+ rctx->crdataset->trust = rctx->trust;
+
+ for (sigrdataset = ISC_LIST_HEAD(rctx->cname->list);
+ sigrdataset != NULL;
+ sigrdataset = ISC_LIST_NEXT(sigrdataset, link))
+ {
+ if (!validinanswer(sigrdataset, fctx)) {
+ rctx->result = DNS_R_FORMERR;
+ return (ISC_R_COMPLETE);
+ }
+
+ if (sigrdataset->type != dns_rdatatype_rrsig ||
+ sigrdataset->covers != dns_rdatatype_cname)
+ {
+ continue;
+ }
+
+ sigrdataset->attributes |= DNS_RDATASETATTR_ANSWERSIG;
+ sigrdataset->attributes |= DNS_RDATASETATTR_CACHE;
+ sigrdataset->trust = rctx->trust;
+ break;
+ }
+
+ rctx->chaining = true;
+ return (ISC_R_SUCCESS);
+}
+
+/*
+ * rctx_answer_dname():
+ * Handle responses with covering DNAME records.
+ */
+static isc_result_t
+rctx_answer_dname(respctx_t *rctx) {
+ dns_rdataset_t *sigrdataset = NULL;
+ fetchctx_t *fctx = rctx->fctx;
+
+ if (!validinanswer(rctx->drdataset, fctx)) {
+ rctx->result = DNS_R_FORMERR;
+ return (ISC_R_COMPLETE);
+ }
+
+ if (!is_answertarget_allowed(fctx, fctx->name, rctx->dname,
+ rctx->drdataset, &rctx->chaining))
+ {
+ rctx->result = DNS_R_SERVFAIL;
+ return (ISC_R_COMPLETE);
+ }
+
+ rctx->dname->attributes |= DNS_NAMEATTR_CACHE;
+ rctx->dname->attributes |= DNS_NAMEATTR_ANSWER;
+ rctx->dname->attributes |= DNS_NAMEATTR_CHAINING;
+ rctx->drdataset->attributes |= DNS_RDATASETATTR_ANSWER;
+ rctx->drdataset->attributes |= DNS_RDATASETATTR_CACHE;
+ rctx->drdataset->attributes |= DNS_RDATASETATTR_CHAINING;
+ rctx->drdataset->trust = rctx->trust;
+
+ for (sigrdataset = ISC_LIST_HEAD(rctx->dname->list);
+ sigrdataset != NULL;
+ sigrdataset = ISC_LIST_NEXT(sigrdataset, link))
+ {
+ if (!validinanswer(sigrdataset, fctx)) {
+ rctx->result = DNS_R_FORMERR;
+ return (ISC_R_COMPLETE);
+ }
+
+ if (sigrdataset->type != dns_rdatatype_rrsig ||
+ sigrdataset->covers != dns_rdatatype_dname)
+ {
+ continue;
+ }
+
+ sigrdataset->attributes |= DNS_RDATASETATTR_ANSWERSIG;
+ sigrdataset->attributes |= DNS_RDATASETATTR_CACHE;
+ sigrdataset->trust = rctx->trust;
+ break;
+ }
+
+ return (ISC_R_SUCCESS);
+}
+
+/*
+ * rctx_authority_positive():
+ * Examine the records in the authority section (if there are any) for a
+ * positive answer. We expect the names for all rdatasets in this
+ * section to be subdomains of the domain being queried; any that are
+ * not are skipped. We expect to find only *one* owner name; any names
+ * after the first one processed are ignored. We expect to find only
+ * rdatasets of type NS, RRSIG, or SIG; all others are ignored. Whatever
+ * remains can be cached at trust level authauthority or additional
+ * (depending on whether the AA bit was set on the answer).
+ */
+static void
+rctx_authority_positive(respctx_t *rctx) {
+ fetchctx_t *fctx = rctx->fctx;
+ bool done = false;
+ isc_result_t result;
+
+ result = dns_message_firstname(rctx->query->rmessage,
+ DNS_SECTION_AUTHORITY);
+ while (!done && result == ISC_R_SUCCESS) {
+ dns_name_t *name = NULL;
+
+ dns_message_currentname(rctx->query->rmessage,
+ DNS_SECTION_AUTHORITY, &name);
+
+ if (!name_external(name, dns_rdatatype_ns, fctx)) {
+ dns_rdataset_t *rdataset = NULL;
+
+ /*
+ * We expect to find NS or SIG NS rdatasets, and
+ * nothing else.
+ */
+ for (rdataset = ISC_LIST_HEAD(name->list);
+ rdataset != NULL;
+ rdataset = ISC_LIST_NEXT(rdataset, link))
+ {
+ if (rdataset->type == dns_rdatatype_ns ||
+ (rdataset->type == dns_rdatatype_rrsig &&
+ rdataset->covers == dns_rdatatype_ns))
+ {
+ name->attributes |= DNS_NAMEATTR_CACHE;
+ rdataset->attributes |=
+ DNS_RDATASETATTR_CACHE;
+
+ if (rctx->aa) {
+ rdataset->trust =
+ dns_trust_authauthority;
+ } else {
+ rdataset->trust =
+ dns_trust_additional;
+ }
+
+ if (rdataset->type == dns_rdatatype_ns)
+ {
+ rctx->ns_name = name;
+ rctx->ns_rdataset = rdataset;
+ }
+ /*
+ * Mark any additional data
+ * related to this rdataset.
+ */
+ (void)dns_rdataset_additionaldata(
+ rdataset, name, check_related,
+ rctx);
+ done = true;
+ }
+ }
+ }
+
+ result = dns_message_nextname(rctx->query->rmessage,
+ DNS_SECTION_AUTHORITY);
+ }
+}
+
+/*
+ * rctx_answer_none():
+ * Handles a response without an answer: this is either a negative
+ * response (NXDOMAIN or NXRRSET) or a referral. Determine which it is,
+ * then either scan the authority section for negative caching and
+ * DNSSEC proof of nonexistence, or else call rctx_referral().
+ */
+static isc_result_t
+rctx_answer_none(respctx_t *rctx) {
+ isc_result_t result;
+ fetchctx_t *fctx = rctx->fctx;
+
+ FCTXTRACE("rctx_answer_none");
+
+ rctx_answer_init(rctx);
+
+ /*
+ * Sometimes we can tell if its a negative response by looking
+ * at the message header.
+ */
+ if (rctx->query->rmessage->rcode == dns_rcode_nxdomain ||
+ (rctx->query->rmessage->counts[DNS_SECTION_ANSWER] == 0 &&
+ rctx->query->rmessage->counts[DNS_SECTION_AUTHORITY] == 0))
+ {
+ rctx->negative = true;
+ }
+
+ /*
+ * Process the authority section
+ */
+ result = rctx_authority_negative(rctx);
+ if (result == ISC_R_COMPLETE) {
+ return (rctx->result);
+ }
+
+ log_ns_ttl(fctx, "rctx_answer_none");
+
+ if (rctx->ns_rdataset != NULL &&
+ dns_name_equal(fctx->domain, rctx->ns_name) &&
+ !dns_name_equal(rctx->ns_name, dns_rootname))
+ {
+ trim_ns_ttl(fctx, rctx->ns_name, rctx->ns_rdataset);
+ }
+
+ /*
+ * A negative response has a SOA record (Type 2)
+ * and a optional NS RRset (Type 1) or it has neither
+ * a SOA or a NS RRset (Type 3, handled above) or
+ * rcode is NXDOMAIN (handled above) in which case
+ * the NS RRset is allowed (Type 4).
+ */
+ if (rctx->soa_name != NULL) {
+ rctx->negative = true;
+ }
+
+ if (!rctx->ns_in_answer && !rctx->glue_in_answer) {
+ /*
+ * Process DNSSEC records in the authority section.
+ */
+ result = rctx_authority_dnssec(rctx);
+ if (result == ISC_R_COMPLETE) {
+ return (rctx->result);
+ }
+ }
+
+ /*
+ * Trigger lookups for DNS nameservers.
+ */
+ if (rctx->negative &&
+ rctx->query->rmessage->rcode == dns_rcode_noerror &&
+ fctx->type == dns_rdatatype_ds && rctx->soa_name != NULL &&
+ dns_name_equal(rctx->soa_name, fctx->name) &&
+ !dns_name_equal(fctx->name, dns_rootname))
+ {
+ return (DNS_R_CHASEDSSERVERS);
+ }
+
+ /*
+ * Did we find anything?
+ */
+ if (!rctx->negative && rctx->ns_name == NULL) {
+ /*
+ * The responder is insane.
+ */
+ if (rctx->found_name == NULL) {
+ log_formerr(fctx, "invalid response");
+ return (DNS_R_FORMERR);
+ }
+ if (!dns_name_issubdomain(rctx->found_name, fctx->domain)) {
+ char nbuf[DNS_NAME_FORMATSIZE];
+ char dbuf[DNS_NAME_FORMATSIZE];
+ char tbuf[DNS_RDATATYPE_FORMATSIZE];
+
+ dns_rdatatype_format(rctx->found_type, tbuf,
+ sizeof(tbuf));
+ dns_name_format(rctx->found_name, nbuf, sizeof(nbuf));
+ dns_name_format(fctx->domain, dbuf, sizeof(dbuf));
+
+ log_formerr(fctx,
+ "Name %s (%s) not subdomain"
+ " of zone %s -- invalid response",
+ nbuf, tbuf, dbuf);
+ } else {
+ log_formerr(fctx, "invalid response");
+ }
+ return (DNS_R_FORMERR);
+ }
+
+ /*
+ * If we found both NS and SOA, they should be the same name.
+ */
+ if (rctx->ns_name != NULL && rctx->soa_name != NULL &&
+ rctx->ns_name != rctx->soa_name)
+ {
+ log_formerr(fctx, "NS/SOA mismatch");
+ return (DNS_R_FORMERR);
+ }
+
+ /*
+ * Handle a referral.
+ */
+ result = rctx_referral(rctx);
+ if (result == ISC_R_COMPLETE) {
+ return (rctx->result);
+ }
+
+ /*
+ * Since we're not doing a referral, we don't want to cache any
+ * NS RRs we may have found.
+ */
+ if (rctx->ns_name != NULL) {
+ rctx->ns_name->attributes &= ~DNS_NAMEATTR_CACHE;
+ }
+
+ if (rctx->negative) {
+ FCTX_ATTR_SET(fctx, FCTX_ATTR_WANTNCACHE);
+ }
+
+ return (ISC_R_SUCCESS);
+}
+
+/*
+ * rctx_authority_negative():
+ * Scan the authority section of a negative answer, handling
+ * NS and SOA records. (Note that this function does *not* handle
+ * DNSSEC records; those are addressed separately in
+ * rctx_authority_dnssec() below.)
+ */
+static isc_result_t
+rctx_authority_negative(respctx_t *rctx) {
+ isc_result_t result;
+ fetchctx_t *fctx = rctx->fctx;
+ dns_section_t section;
+ dns_rdataset_t *rdataset = NULL;
+ bool finished = false;
+
+ if (rctx->ns_in_answer) {
+ INSIST(fctx->type == dns_rdatatype_ns);
+ section = DNS_SECTION_ANSWER;
+ } else {
+ section = DNS_SECTION_AUTHORITY;
+ }
+
+ result = dns_message_firstname(rctx->query->rmessage, section);
+ if (result != ISC_R_SUCCESS) {
+ return (ISC_R_SUCCESS);
+ }
+
+ while (!finished) {
+ dns_name_t *name = NULL;
+
+ dns_message_currentname(rctx->query->rmessage, section, &name);
+ result = dns_message_nextname(rctx->query->rmessage, section);
+ if (result != ISC_R_SUCCESS) {
+ finished = true;
+ }
+
+ if (!dns_name_issubdomain(name, fctx->domain)) {
+ continue;
+ }
+
+ for (rdataset = ISC_LIST_HEAD(name->list); rdataset != NULL;
+ rdataset = ISC_LIST_NEXT(rdataset, link))
+ {
+ dns_rdatatype_t type = rdataset->type;
+ if (type == dns_rdatatype_rrsig) {
+ type = rdataset->covers;
+ }
+ if (((type == dns_rdatatype_ns ||
+ type == dns_rdatatype_soa) &&
+ !dns_name_issubdomain(fctx->name, name)))
+ {
+ char qbuf[DNS_NAME_FORMATSIZE];
+ char nbuf[DNS_NAME_FORMATSIZE];
+ char tbuf[DNS_RDATATYPE_FORMATSIZE];
+ dns_rdatatype_format(type, tbuf, sizeof(tbuf));
+ dns_name_format(name, nbuf, sizeof(nbuf));
+ dns_name_format(fctx->name, qbuf, sizeof(qbuf));
+ log_formerr(fctx,
+ "unrelated %s %s in "
+ "%s authority section",
+ tbuf, nbuf, qbuf);
+ break;
+ }
+
+ switch (type) {
+ case dns_rdatatype_ns:
+ /*
+ * NS or RRSIG NS.
+ *
+ * Only one set of NS RRs is allowed.
+ */
+ if (rdataset->type == dns_rdatatype_ns) {
+ if (rctx->ns_name != NULL &&
+ name != rctx->ns_name)
+ {
+ log_formerr(fctx, "multiple NS "
+ "RRsets "
+ "in "
+ "authority "
+ "section");
+ rctx->result = DNS_R_FORMERR;
+ return (ISC_R_COMPLETE);
+ }
+ rctx->ns_name = name;
+ rctx->ns_rdataset = rdataset;
+ }
+ name->attributes |= DNS_NAMEATTR_CACHE;
+ rdataset->attributes |= DNS_RDATASETATTR_CACHE;
+ rdataset->trust = dns_trust_glue;
+ break;
+ case dns_rdatatype_soa:
+ /*
+ * SOA, or RRSIG SOA.
+ *
+ * Only one SOA is allowed.
+ */
+ if (rdataset->type == dns_rdatatype_soa) {
+ if (rctx->soa_name != NULL &&
+ name != rctx->soa_name)
+ {
+ log_formerr(fctx, "multiple "
+ "SOA RRs "
+ "in "
+ "authority "
+ "section");
+ rctx->result = DNS_R_FORMERR;
+ return (ISC_R_COMPLETE);
+ }
+ rctx->soa_name = name;
+ }
+ name->attributes |= DNS_NAMEATTR_NCACHE;
+ rdataset->attributes |= DNS_RDATASETATTR_NCACHE;
+ if (rctx->aa) {
+ rdataset->trust =
+ dns_trust_authauthority;
+ } else if (ISFORWARDER(fctx->addrinfo)) {
+ rdataset->trust = dns_trust_answer;
+ } else {
+ rdataset->trust = dns_trust_additional;
+ }
+ break;
+ default:
+ continue;
+ }
+ }
+ }
+
+ return (ISC_R_SUCCESS);
+}
+
+/*
+ * rctx_ncache():
+ * Cache the negatively cacheable parts of the message. This may
+ * also cause work to be queued to the DNSSEC validator.
+ */
+static void
+rctx_ncache(respctx_t *rctx) {
+ isc_result_t result;
+ dns_rdatatype_t covers;
+ fetchctx_t *fctx = rctx->fctx;
+
+ if (!WANTNCACHE(fctx)) {
+ return;
+ }
+
+ /*
+ * Cache DS NXDOMAIN separately to other types.
+ */
+ if (rctx->query->rmessage->rcode == dns_rcode_nxdomain &&
+ fctx->type != dns_rdatatype_ds)
+ {
+ covers = dns_rdatatype_any;
+ } else {
+ covers = fctx->type;
+ }
+
+ /*
+ * Cache any negative cache entries in the message.
+ */
+ result = ncache_message(fctx, rctx->query->rmessage,
+ rctx->query->addrinfo, covers, rctx->now);
+ if (result != ISC_R_SUCCESS) {
+ FCTXTRACE3("ncache_message complete", result);
+ }
+}
+
+/*
+ * rctx_authority_dnssec():
+ *
+ * Scan the authority section of a negative answer or referral,
+ * handling DNSSEC records (i.e. NSEC, NSEC3, DS).
+ */
+static isc_result_t
+rctx_authority_dnssec(respctx_t *rctx) {
+ isc_result_t result;
+ fetchctx_t *fctx = rctx->fctx;
+ dns_rdataset_t *rdataset = NULL;
+ bool finished = false;
+
+ REQUIRE(!rctx->ns_in_answer && !rctx->glue_in_answer);
+
+ result = dns_message_firstname(rctx->query->rmessage,
+ DNS_SECTION_AUTHORITY);
+ if (result != ISC_R_SUCCESS) {
+ return (ISC_R_SUCCESS);
+ }
+
+ while (!finished) {
+ dns_name_t *name = NULL;
+
+ dns_message_currentname(rctx->query->rmessage,
+ DNS_SECTION_AUTHORITY, &name);
+ result = dns_message_nextname(rctx->query->rmessage,
+ DNS_SECTION_AUTHORITY);
+ if (result != ISC_R_SUCCESS) {
+ finished = true;
+ }
+
+ if (!dns_name_issubdomain(name, fctx->domain)) {
+ /*
+ * Invalid name found; preserve it for logging
+ * later.
+ */
+ rctx->found_name = name;
+ rctx->found_type = ISC_LIST_HEAD(name->list)->type;
+ continue;
+ }
+
+ for (rdataset = ISC_LIST_HEAD(name->list); rdataset != NULL;
+ rdataset = ISC_LIST_NEXT(rdataset, link))
+ {
+ bool checknta = true;
+ bool secure_domain = false;
+ dns_rdatatype_t type = rdataset->type;
+
+ if (type == dns_rdatatype_rrsig) {
+ type = rdataset->covers;
+ }
+
+ switch (type) {
+ case dns_rdatatype_nsec:
+ case dns_rdatatype_nsec3:
+ if (rctx->negative) {
+ name->attributes |= DNS_NAMEATTR_NCACHE;
+ rdataset->attributes |=
+ DNS_RDATASETATTR_NCACHE;
+ } else if (type == dns_rdatatype_nsec) {
+ name->attributes |= DNS_NAMEATTR_CACHE;
+ rdataset->attributes |=
+ DNS_RDATASETATTR_CACHE;
+ }
+
+ if (rctx->aa) {
+ rdataset->trust =
+ dns_trust_authauthority;
+ } else if (ISFORWARDER(fctx->addrinfo)) {
+ rdataset->trust = dns_trust_answer;
+ } else {
+ rdataset->trust = dns_trust_additional;
+ }
+ /*
+ * No additional data needs to be
+ * marked.
+ */
+ break;
+ case dns_rdatatype_ds:
+ /*
+ * DS or SIG DS.
+ *
+ * These should only be here if this is
+ * a referral, and there should only be
+ * one DS RRset.
+ */
+ if (rctx->ns_name == NULL) {
+ log_formerr(fctx, "DS with no "
+ "referral");
+ rctx->result = DNS_R_FORMERR;
+ return (ISC_R_COMPLETE);
+ }
+
+ if (rdataset->type == dns_rdatatype_ds) {
+ if (rctx->ds_name != NULL &&
+ name != rctx->ds_name)
+ {
+ log_formerr(fctx, "DS doesn't "
+ "match "
+ "referral "
+ "(NS)");
+ rctx->result = DNS_R_FORMERR;
+ return (ISC_R_COMPLETE);
+ }
+ rctx->ds_name = name;
+ }
+
+ name->attributes |= DNS_NAMEATTR_CACHE;
+ rdataset->attributes |= DNS_RDATASETATTR_CACHE;
+
+ if ((fctx->options & DNS_FETCHOPT_NONTA) != 0) {
+ checknta = false;
+ }
+ if (fctx->res->view->enablevalidation) {
+ result = issecuredomain(
+ fctx->res->view, name,
+ dns_rdatatype_ds, fctx->now,
+ checknta, NULL, &secure_domain);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+ }
+ if (secure_domain) {
+ rdataset->trust =
+ dns_trust_pending_answer;
+ } else if (rctx->aa) {
+ rdataset->trust =
+ dns_trust_authauthority;
+ } else if (ISFORWARDER(fctx->addrinfo)) {
+ rdataset->trust = dns_trust_answer;
+ } else {
+ rdataset->trust = dns_trust_additional;
+ }
+ break;
+ default:
+ continue;
+ }
+ }
+ }
+
+ return (ISC_R_SUCCESS);
+}
+
+/*
+ * rctx_referral():
+ * Handles referral responses. Check for sanity, find glue as needed,
+ * and update the fetch context to follow the delegation.
+ */
+static isc_result_t
+rctx_referral(respctx_t *rctx) {
+ isc_result_t result;
+ fetchctx_t *fctx = rctx->fctx;
+
+ if (rctx->negative || rctx->ns_name == NULL) {
+ return (ISC_R_SUCCESS);
+ }
+
+ /*
+ * We already know ns_name is a subdomain of fctx->domain.
+ * If ns_name is equal to fctx->domain, we're not making
+ * progress. We return DNS_R_FORMERR so that we'll keep
+ * trying other servers.
+ */
+ if (dns_name_equal(rctx->ns_name, fctx->domain)) {
+ log_formerr(fctx, "non-improving referral");
+ rctx->result = DNS_R_FORMERR;
+ return (ISC_R_COMPLETE);
+ }
+
+ /*
+ * If the referral name is not a parent of the query
+ * name, consider the responder insane.
+ */
+ if (!dns_name_issubdomain(fctx->name, rctx->ns_name)) {
+ /* Logged twice */
+ log_formerr(fctx, "referral to non-parent");
+ FCTXTRACE("referral to non-parent");
+ rctx->result = DNS_R_FORMERR;
+ return (ISC_R_COMPLETE);
+ }
+
+ /*
+ * Mark any additional data related to this rdataset.
+ * It's important that we do this before we change the
+ * query domain.
+ */
+ INSIST(rctx->ns_rdataset != NULL);
+ FCTX_ATTR_SET(fctx, FCTX_ATTR_GLUING);
+ (void)dns_rdataset_additionaldata(rctx->ns_rdataset, rctx->ns_name,
+ check_related, rctx);
+#if CHECK_FOR_GLUE_IN_ANSWER
+ /*
+ * Look in the answer section for "glue" that is incorrectly
+ * returned as a answer. This is needed if the server also
+ * minimizes the response size by not adding records to the
+ * additional section that are in the answer section or if
+ * the record gets dropped due to message size constraints.
+ */
+ if (rctx->glue_in_answer &&
+ (fctx->type == dns_rdatatype_aaaa || fctx->type == dns_rdatatype_a))
+ {
+ (void)dns_rdataset_additionaldata(
+ rctx->ns_rdataset, rctx->ns_name, check_answer, fctx);
+ }
+#endif /* if CHECK_FOR_GLUE_IN_ANSWER */
+ FCTX_ATTR_CLR(fctx, FCTX_ATTR_GLUING);
+
+ /*
+ * NS rdatasets with 0 TTL cause problems.
+ * dns_view_findzonecut() will not find them when we
+ * try to follow the referral, and we'll SERVFAIL
+ * because the best nameservers are now above QDOMAIN.
+ * We force the TTL to 1 second to prevent this.
+ */
+ if (rctx->ns_rdataset->ttl == 0) {
+ rctx->ns_rdataset->ttl = 1;
+ }
+
+ /*
+ * Set the current query domain to the referral name.
+ *
+ * XXXRTH We should check if we're in forward-only mode, and
+ * if so we should bail out.
+ */
+ INSIST(dns_name_countlabels(fctx->domain) > 0);
+ fcount_decr(fctx);
+
+ if (dns_rdataset_isassociated(&fctx->nameservers)) {
+ dns_rdataset_disassociate(&fctx->nameservers);
+ }
+
+ dns_name_copy(rctx->ns_name, fctx->domain);
+
+ if ((fctx->options & DNS_FETCHOPT_QMINIMIZE) != 0) {
+ dns_name_copy(rctx->ns_name, fctx->qmindcname);
+
+ fctx_minimize_qname(fctx);
+ }
+
+ result = fcount_incr(fctx, true);
+ if (result != ISC_R_SUCCESS) {
+ rctx->result = result;
+ return (ISC_R_COMPLETE);
+ }
+
+ FCTX_ATTR_SET(fctx, FCTX_ATTR_WANTCACHE);
+ fctx->ns_ttl_ok = false;
+ log_ns_ttl(fctx, "DELEGATION");
+ rctx->result = DNS_R_DELEGATION;
+
+ /*
+ * Reinitialize 'rctx' to prepare for following the delegation:
+ * set the get_nameservers and next_server flags appropriately
+ * and reset the fetch context counters.
+ *
+ */
+ if ((rctx->fctx->options & DNS_FETCHOPT_NOFOLLOW) == 0) {
+ rctx->get_nameservers = true;
+ rctx->next_server = true;
+ rctx->fctx->restarts = 0;
+ rctx->fctx->referrals++;
+ rctx->fctx->querysent = 0;
+ rctx->fctx->lamecount = 0;
+ rctx->fctx->quotacount = 0;
+ rctx->fctx->neterr = 0;
+ rctx->fctx->badresp = 0;
+ rctx->fctx->adberr = 0;
+ }
+
+ return (ISC_R_COMPLETE);
+}
+
+/*
+ * rctx_additional():
+ * Scan the additional section of a response to find records related
+ * to answers we were interested in.
+ */
+static void
+rctx_additional(respctx_t *rctx) {
+ bool rescan;
+ dns_section_t section = DNS_SECTION_ADDITIONAL;
+ isc_result_t result;
+
+again:
+ rescan = false;
+
+ for (result = dns_message_firstname(rctx->query->rmessage, section);
+ result == ISC_R_SUCCESS;
+ result = dns_message_nextname(rctx->query->rmessage, section))
+ {
+ dns_name_t *name = NULL;
+ dns_rdataset_t *rdataset;
+ dns_message_currentname(rctx->query->rmessage,
+ DNS_SECTION_ADDITIONAL, &name);
+ if ((name->attributes & DNS_NAMEATTR_CHASE) == 0) {
+ continue;
+ }
+ name->attributes &= ~DNS_NAMEATTR_CHASE;
+ for (rdataset = ISC_LIST_HEAD(name->list); rdataset != NULL;
+ rdataset = ISC_LIST_NEXT(rdataset, link))
+ {
+ if (CHASE(rdataset)) {
+ rdataset->attributes &= ~DNS_RDATASETATTR_CHASE;
+ (void)dns_rdataset_additionaldata(
+ rdataset, name, check_related, rctx);
+ rescan = true;
+ }
+ }
+ }
+ if (rescan) {
+ goto again;
+ }
+}
+
+/*
+ * rctx_nextserver():
+ * We found something wrong with the remote server, but it may be
+ * useful to try another one.
+ */
+static void
+rctx_nextserver(respctx_t *rctx, dns_message_t *message,
+ dns_adbaddrinfo_t *addrinfo, isc_result_t result) {
+ fetchctx_t *fctx = rctx->fctx;
+ bool retrying = true;
+
+ if (result == DNS_R_FORMERR) {
+ rctx->broken_server = DNS_R_FORMERR;
+ }
+ if (rctx->broken_server != ISC_R_SUCCESS) {
+ /*
+ * Add this server to the list of bad servers for
+ * this fctx.
+ */
+ add_bad(fctx, message, addrinfo, rctx->broken_server,
+ rctx->broken_type);
+ }
+
+ if (rctx->get_nameservers) {
+ dns_fixedname_t foundname, founddc;
+ dns_name_t *name, *fname, *dcname;
+ unsigned int findoptions = 0;
+
+ fname = dns_fixedname_initname(&foundname);
+ dcname = dns_fixedname_initname(&founddc);
+
+ if (result != ISC_R_SUCCESS) {
+ fctx_done_detach(&rctx->fctx, DNS_R_SERVFAIL);
+ return;
+ }
+ if (dns_rdatatype_atparent(fctx->type)) {
+ findoptions |= DNS_DBFIND_NOEXACT;
+ }
+ if ((rctx->retryopts & DNS_FETCHOPT_UNSHARED) == 0) {
+ name = fctx->name;
+ } else {
+ name = fctx->domain;
+ }
+ result = dns_view_findzonecut(
+ fctx->res->view, name, fname, dcname, fctx->now,
+ findoptions, true, true, &fctx->nameservers, NULL);
+ if (result != ISC_R_SUCCESS) {
+ FCTXTRACE("couldn't find a zonecut");
+ fctx_done_detach(&rctx->fctx, DNS_R_SERVFAIL);
+ return;
+ }
+ if (!dns_name_issubdomain(fname, fctx->domain)) {
+ /*
+ * The best nameservers are now above our
+ * QDOMAIN.
+ */
+ FCTXTRACE("nameservers now above QDOMAIN");
+ fctx_done_detach(&rctx->fctx, DNS_R_SERVFAIL);
+ return;
+ }
+
+ fcount_decr(fctx);
+
+ dns_name_copy(fname, fctx->domain);
+ dns_name_copy(dcname, fctx->qmindcname);
+
+ result = fcount_incr(fctx, true);
+ if (result != ISC_R_SUCCESS) {
+ fctx_done_detach(&rctx->fctx, DNS_R_SERVFAIL);
+ return;
+ }
+ fctx->ns_ttl = fctx->nameservers.ttl;
+ fctx->ns_ttl_ok = true;
+ fctx_cancelqueries(fctx, true, false);
+ fctx_cleanup(fctx);
+ retrying = false;
+ }
+
+ /*
+ * Try again.
+ */
+ fctx_try(fctx, retrying, false);
+}
+
+/*
+ * rctx_resend():
+ *
+ * Resend the query, probably with the options changed. Calls
+ * fctx_query(), passing rctx->retryopts (which is based on
+ * query->options, but may have been updated since the last time
+ * fctx_query() was called).
+ */
+static void
+rctx_resend(respctx_t *rctx, dns_adbaddrinfo_t *addrinfo) {
+ fetchctx_t *fctx = rctx->fctx;
+ isc_result_t result;
+
+ FCTXTRACE("resend");
+ inc_stats(fctx->res, dns_resstatscounter_retry);
+ result = fctx_query(fctx, addrinfo, rctx->retryopts);
+ if (result != ISC_R_SUCCESS) {
+ fctx_done_detach(&rctx->fctx, result);
+ }
+}
+
+/*
+ * rctx_next():
+ * We got what appeared to be a response but it didn't match the
+ * question or the cookie; it may have been meant for someone else, or
+ * it may be a spoofing attack. Drop it and continue listening for the
+ * response we wanted.
+ */
+static isc_result_t
+rctx_next(respctx_t *rctx) {
+ fetchctx_t *fctx = rctx->fctx;
+ isc_result_t result;
+
+ FCTXTRACE("nextitem");
+ inc_stats(rctx->fctx->res, dns_resstatscounter_nextitem);
+ INSIST(rctx->query->dispentry != NULL);
+ dns_message_reset(rctx->query->rmessage, DNS_MESSAGE_INTENTPARSE);
+ result = dns_dispatch_getnext(rctx->query->dispentry);
+ return (result);
+}
+
+/*
+ * rctx_chaseds():
+ * Look up the parent zone's NS records so that DS records can be
+ * fetched.
+ */
+static void
+rctx_chaseds(respctx_t *rctx, dns_message_t *message,
+ dns_adbaddrinfo_t *addrinfo, isc_result_t result) {
+ fetchctx_t *fctx = rctx->fctx;
+ isc_task_t *task = NULL;
+ unsigned int n;
+
+ add_bad(fctx, message, addrinfo, result, rctx->broken_type);
+ fctx_cancelqueries(fctx, true, false);
+ fctx_cleanup(fctx);
+
+ n = dns_name_countlabels(fctx->name);
+ dns_name_getlabelsequence(fctx->name, 1, n - 1, fctx->nsname);
+
+ FCTXTRACE("suspending DS lookup to find parent's NS records");
+
+ fctx_addref(fctx);
+ task = fctx->res->buckets[fctx->bucketnum].task;
+ result = dns_resolver_createfetch(
+ fctx->res, fctx->nsname, dns_rdatatype_ns, NULL, NULL, NULL,
+ NULL, 0, fctx->options, 0, NULL, task, resume_dslookup, fctx,
+ &fctx->nsrrset, NULL, &fctx->nsfetch);
+ if (result != ISC_R_SUCCESS) {
+ if (result == DNS_R_DUPLICATE) {
+ result = DNS_R_SERVFAIL;
+ }
+ fctx_detach(&fctx);
+ fctx_done_detach(&rctx->fctx, result);
+ }
+}
+
+/*
+ * rctx_done():
+ * This resolver query response is finished, either because we
+ * encountered a problem or because we've gotten all the information
+ * from it that we can. We either wait for another response, resend the
+ * query to the same server, resend to a new server, or clean up and
+ * shut down the fetch.
+ */
+static void
+rctx_done(respctx_t *rctx, isc_result_t result) {
+ resquery_t *query = rctx->query;
+ fetchctx_t *fctx = rctx->fctx;
+ dns_adbaddrinfo_t *addrinfo = query->addrinfo;
+ dns_message_t *message = NULL;
+
+ /*
+ * Need to attach to the message until the scope
+ * of this function ends, since there are many places
+ * where the message is used and/or may be destroyed
+ * before this function ends.
+ */
+ dns_message_attach(query->rmessage, &message);
+
+ FCTXTRACE4("query canceled in rctx_done();",
+ rctx->no_response ? "no response" : "responding", result);
+
+#ifdef ENABLE_AFL
+ if (dns_fuzzing_resolver &&
+ (rctx->next_server || rctx->resend || rctx->nextitem))
+ {
+ fctx_cancelquery(&query, rctx->finish, rctx->no_response,
+ false);
+ fctx_done_detach(&rctx->fctx, DNS_R_SERVFAIL);
+ goto detach;
+ }
+#endif /* ifdef ENABLE_AFL */
+
+ if (rctx->nextitem) {
+ REQUIRE(!rctx->next_server);
+ REQUIRE(!rctx->resend);
+
+ result = rctx_next(rctx);
+ if (result == ISC_R_SUCCESS) {
+ goto detach;
+ }
+ }
+
+ /* Cancel the query */
+ fctx_cancelquery(&query, rctx->finish, rctx->no_response, false);
+
+ /*
+ * If nobody's waiting for results, don't resend or try next server.
+ */
+ LOCK(&fctx->res->buckets[fctx->bucketnum].lock);
+ if (ISC_LIST_EMPTY(fctx->events)) {
+ rctx->next_server = false;
+ rctx->resend = false;
+ }
+ UNLOCK(&fctx->res->buckets[fctx->bucketnum].lock);
+
+ if (rctx->next_server) {
+ rctx_nextserver(rctx, message, addrinfo, result);
+ } else if (rctx->resend) {
+ rctx_resend(rctx, addrinfo);
+ } else if (result == DNS_R_CHASEDSSERVERS) {
+ rctx_chaseds(rctx, message, addrinfo, result);
+ } else if (result == ISC_R_SUCCESS && !HAVE_ANSWER(fctx)) {
+ /*
+ * All has gone well so far, but we are waiting for the DNSSEC
+ * validator to validate the answer.
+ */
+ FCTXTRACE("wait for validator");
+ fctx_cancelqueries(fctx, true, false);
+ } else {
+ /*
+ * We're done.
+ */
+ fctx_done_detach(&rctx->fctx, result);
+ }
+
+detach:
+ dns_message_detach(&message);
+}
+
+/*
+ * rctx_logpacket():
+ * Log the incoming packet; also log to DNSTAP if configured.
+ */
+static void
+rctx_logpacket(respctx_t *rctx) {
+#ifdef HAVE_DNSTAP
+ isc_result_t result;
+ fetchctx_t *fctx = rctx->fctx;
+ isc_sockaddr_t localaddr, *la = NULL;
+ unsigned char zone[DNS_NAME_MAXWIRE];
+ dns_dtmsgtype_t dtmsgtype;
+ dns_compress_t cctx;
+ isc_region_t zr;
+ isc_buffer_t zb;
+#endif /* HAVE_DNSTAP */
+
+ dns_message_logfmtpacket(
+ rctx->query->rmessage, "received packet from",
+ &rctx->query->addrinfo->sockaddr, DNS_LOGCATEGORY_RESOLVER,
+ DNS_LOGMODULE_PACKETS, &dns_master_style_comment,
+ ISC_LOG_DEBUG(10), rctx->fctx->res->mctx);
+
+#ifdef HAVE_DNSTAP
+ /*
+ * Log the response via dnstap.
+ */
+ memset(&zr, 0, sizeof(zr));
+ result = dns_compress_init(&cctx, -1, fctx->res->mctx);
+ if (result == ISC_R_SUCCESS) {
+ isc_buffer_init(&zb, zone, sizeof(zone));
+ dns_compress_setmethods(&cctx, DNS_COMPRESS_NONE);
+ result = dns_name_towire(fctx->domain, &cctx, &zb);
+ if (result == ISC_R_SUCCESS) {
+ isc_buffer_usedregion(&zb, &zr);
+ }
+ dns_compress_invalidate(&cctx);
+ }
+
+ if ((fctx->qmessage->flags & DNS_MESSAGEFLAG_RD) != 0) {
+ dtmsgtype = DNS_DTTYPE_FR;
+ } else {
+ dtmsgtype = DNS_DTTYPE_RR;
+ }
+
+ result = dns_dispentry_getlocaladdress(rctx->query->dispentry,
+ &localaddr);
+ if (result == ISC_R_SUCCESS) {
+ la = &localaddr;
+ }
+
+ dns_dt_send(fctx->res->view, dtmsgtype, la,
+ &rctx->query->addrinfo->sockaddr,
+ ((rctx->query->options & DNS_FETCHOPT_TCP) != 0), &zr,
+ &rctx->query->start, NULL, &rctx->buffer);
+#endif /* HAVE_DNSTAP */
+}
+
+/*
+ * rctx_badserver():
+ * Is the remote server broken, or does it dislike us?
+ */
+static isc_result_t
+rctx_badserver(respctx_t *rctx, isc_result_t result) {
+ fetchctx_t *fctx = rctx->fctx;
+ resquery_t *query = rctx->query;
+ isc_buffer_t b;
+ char code[64];
+ dns_rcode_t rcode = rctx->query->rmessage->rcode;
+
+ if (rcode == dns_rcode_noerror || rcode == dns_rcode_yxdomain ||
+ rcode == dns_rcode_nxdomain)
+ {
+ return (ISC_R_SUCCESS);
+ }
+
+ if ((rcode == dns_rcode_formerr) && rctx->opt == NULL &&
+ (rctx->retryopts & DNS_FETCHOPT_NOEDNS0) == 0)
+ {
+ /*
+ * It's very likely they don't like EDNS0.
+ */
+ rctx->retryopts |= DNS_FETCHOPT_NOEDNS0;
+ rctx->resend = true;
+ /*
+ * Remember that they may not like EDNS0.
+ */
+ add_bad_edns(fctx, &query->addrinfo->sockaddr);
+ inc_stats(fctx->res, dns_resstatscounter_edns0fail);
+ } else if (rcode == dns_rcode_formerr) {
+ if (query->rmessage->cc_echoed) {
+ /*
+ * Retry without DNS COOKIE.
+ */
+ query->addrinfo->flags |= FCTX_ADDRINFO_NOCOOKIE;
+ rctx->resend = true;
+ log_formerr(fctx, "server sent FORMERR with echoed DNS "
+ "COOKIE");
+ } else {
+ /*
+ * The server (or forwarder) doesn't understand us,
+ * but others might.
+ */
+ rctx->next_server = true;
+ rctx->broken_server = DNS_R_REMOTEFORMERR;
+ log_formerr(fctx, "server sent FORMERR");
+ }
+ } else if (rcode == dns_rcode_badvers) {
+ unsigned int version;
+#if DNS_EDNS_VERSION > 0
+ unsigned int flags, mask;
+#endif /* if DNS_EDNS_VERSION > 0 */
+
+ INSIST(rctx->opt != NULL);
+ version = (rctx->opt->ttl >> 16) & 0xff;
+#if DNS_EDNS_VERSION > 0
+ flags = (version << DNS_FETCHOPT_EDNSVERSIONSHIFT) |
+ DNS_FETCHOPT_EDNSVERSIONSET;
+ mask = DNS_FETCHOPT_EDNSVERSIONMASK |
+ DNS_FETCHOPT_EDNSVERSIONSET;
+#endif /* if DNS_EDNS_VERSION > 0 */
+
+ /*
+ * Record that we got a good EDNS response.
+ */
+ if (query->ednsversion > (int)version &&
+ !EDNSOK(query->addrinfo))
+ {
+ dns_adb_changeflags(fctx->adb, query->addrinfo,
+ FCTX_ADDRINFO_EDNSOK,
+ FCTX_ADDRINFO_EDNSOK);
+ }
+
+ /*
+ * RFC 2671 was not clear that unknown options should
+ * be ignored. RFC 6891 is clear that that they
+ * should be ignored. If we are supporting the
+ * experimental EDNS > 0 then perform strict
+ * version checking of badvers responses. We won't
+ * be sending COOKIE etc. in that case.
+ */
+#if DNS_EDNS_VERSION > 0
+ if ((int)version < query->ednsversion) {
+ dns_adb_changeflags(fctx->adb, query->addrinfo, flags,
+ mask);
+ rctx->resend = true;
+ } else {
+ rctx->broken_server = DNS_R_BADVERS;
+ rctx->next_server = true;
+ }
+#else /* if DNS_EDNS_VERSION > 0 */
+ rctx->broken_server = DNS_R_BADVERS;
+ rctx->next_server = true;
+#endif /* if DNS_EDNS_VERSION > 0 */
+ } else if (rcode == dns_rcode_badcookie && rctx->query->rmessage->cc_ok)
+ {
+ /*
+ * We have recorded the new cookie.
+ */
+ if (BADCOOKIE(query->addrinfo)) {
+ rctx->retryopts |= DNS_FETCHOPT_TCP;
+ }
+ query->addrinfo->flags |= FCTX_ADDRINFO_BADCOOKIE;
+ rctx->resend = true;
+ } else {
+ rctx->broken_server = DNS_R_UNEXPECTEDRCODE;
+ rctx->next_server = true;
+ }
+
+ isc_buffer_init(&b, code, sizeof(code) - 1);
+ dns_rcode_totext(rcode, &b);
+ code[isc_buffer_usedlength(&b)] = '\0';
+ FCTXTRACE2("remote server broken: returned ", code);
+ rctx_done(rctx, result);
+
+ return (ISC_R_COMPLETE);
+}
+
+/*
+ * rctx_lameserver():
+ * Is the server lame?
+ */
+static isc_result_t
+rctx_lameserver(respctx_t *rctx) {
+ isc_result_t result = ISC_R_SUCCESS;
+ fetchctx_t *fctx = rctx->fctx;
+ resquery_t *query = rctx->query;
+
+ if (ISFORWARDER(query->addrinfo) || !is_lame(fctx, query->rmessage)) {
+ return (ISC_R_SUCCESS);
+ }
+
+ inc_stats(fctx->res, dns_resstatscounter_lame);
+ log_lame(fctx, query->addrinfo);
+ if (fctx->res->lame_ttl != 0) {
+ result = dns_adb_marklame(fctx->adb, query->addrinfo,
+ fctx->name, fctx->type,
+ rctx->now + fctx->res->lame_ttl);
+ if (result != ISC_R_SUCCESS) {
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_RESOLVER,
+ DNS_LOGMODULE_RESOLVER, ISC_LOG_ERROR,
+ "could not mark server as lame: %s",
+ isc_result_totext(result));
+ }
+ }
+ rctx->broken_server = DNS_R_LAME;
+ rctx->next_server = true;
+ FCTXTRACE("lame server");
+ rctx_done(rctx, result);
+
+ return (ISC_R_COMPLETE);
+}
+
+/*
+ * rctx_delonly_zone():
+ * Handle delegation-only zones like NET and COM.
+ */
+static void
+rctx_delonly_zone(respctx_t *rctx) {
+ fetchctx_t *fctx = rctx->fctx;
+ char namebuf[DNS_NAME_FORMATSIZE];
+ char domainbuf[DNS_NAME_FORMATSIZE];
+ char addrbuf[ISC_SOCKADDR_FORMATSIZE];
+ char classbuf[64];
+ char typebuf[64];
+
+ if (ISFORWARDER(rctx->query->addrinfo) ||
+ !dns_view_isdelegationonly(fctx->res->view, fctx->domain) ||
+ dns_name_equal(fctx->domain, fctx->name) ||
+ !fix_mustbedelegationornxdomain(rctx->query->rmessage, fctx))
+ {
+ return;
+ }
+
+ dns_name_format(fctx->name, namebuf, sizeof(namebuf));
+ dns_name_format(fctx->domain, domainbuf, sizeof(domainbuf));
+ dns_rdatatype_format(fctx->type, typebuf, sizeof(typebuf));
+ dns_rdataclass_format(fctx->res->rdclass, classbuf, sizeof(classbuf));
+ isc_sockaddr_format(&rctx->query->addrinfo->sockaddr, addrbuf,
+ sizeof(addrbuf));
+
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_DELEGATION_ONLY,
+ DNS_LOGMODULE_RESOLVER, ISC_LOG_NOTICE,
+ "enforced delegation-only for '%s' (%s/%s/%s) from %s",
+ domainbuf, namebuf, typebuf, classbuf, addrbuf);
+}
+
+/***
+ *** Resolver Methods
+ ***/
+static void
+destroy(dns_resolver_t *res) {
+ unsigned int i;
+ alternate_t *a;
+
+ isc_refcount_destroy(&res->references);
+ REQUIRE(!atomic_load_acquire(&res->priming));
+ REQUIRE(res->primefetch == NULL);
+
+ RTRACE("destroy");
+
+ REQUIRE(atomic_load_acquire(&res->nfctx) == 0);
+
+ isc_mutex_destroy(&res->primelock);
+ isc_mutex_destroy(&res->lock);
+ for (i = 0; i < res->nbuckets; i++) {
+ INSIST(ISC_LIST_EMPTY(res->buckets[i].fctxs));
+ isc_task_shutdown(res->buckets[i].task);
+ isc_task_detach(&res->buckets[i].task);
+ isc_mutex_destroy(&res->buckets[i].lock);
+ }
+ isc_mem_put(res->mctx, res->buckets,
+ res->nbuckets * sizeof(fctxbucket_t));
+ for (i = 0; i < HASHSIZE(res->dhashbits); i++) {
+ INSIST(ISC_LIST_EMPTY(res->dbuckets[i].list));
+ isc_mutex_destroy(&res->dbuckets[i].lock);
+ }
+ isc_mem_put(res->mctx, res->dbuckets,
+ HASHSIZE(res->dhashbits) * sizeof(zonebucket_t));
+ if (res->dispatches4 != NULL) {
+ dns_dispatchset_destroy(&res->dispatches4);
+ }
+ if (res->dispatches6 != NULL) {
+ dns_dispatchset_destroy(&res->dispatches6);
+ }
+ while ((a = ISC_LIST_HEAD(res->alternates)) != NULL) {
+ ISC_LIST_UNLINK(res->alternates, a, link);
+ if (!a->isaddress) {
+ dns_name_free(&a->_u._n.name, res->mctx);
+ }
+ isc_mem_put(res->mctx, a, sizeof(*a));
+ }
+ dns_resolver_reset_algorithms(res);
+ dns_resolver_reset_ds_digests(res);
+ dns_badcache_destroy(&res->badcache);
+ dns_resolver_resetmustbesecure(res);
+ isc_timer_destroy(&res->spillattimer);
+ res->magic = 0;
+ isc_mem_putanddetach(&res->mctx, res, sizeof(*res));
+}
+
+static void
+send_shutdown_events(dns_resolver_t *res) {
+ isc_event_t *event, *next_event;
+ isc_task_t *etask;
+
+ LOCK(&res->lock);
+ for (event = ISC_LIST_HEAD(res->whenshutdown); event != NULL;
+ event = next_event)
+ {
+ next_event = ISC_LIST_NEXT(event, ev_link);
+ ISC_LIST_UNLINK(res->whenshutdown, event, ev_link);
+ etask = event->ev_sender;
+ event->ev_sender = res;
+ isc_task_sendanddetach(&etask, &event);
+ }
+ UNLOCK(&res->lock);
+}
+
+static void
+spillattimer_countdown(isc_task_t *task, isc_event_t *event) {
+ dns_resolver_t *res = event->ev_arg;
+ isc_result_t result;
+ unsigned int count;
+ bool logit = false;
+
+ REQUIRE(VALID_RESOLVER(res));
+
+ UNUSED(task);
+
+ LOCK(&res->lock);
+ if (res->spillat > res->spillatmin) {
+ res->spillat--;
+ logit = true;
+ }
+ if (res->spillat <= res->spillatmin) {
+ result = isc_timer_reset(res->spillattimer,
+ isc_timertype_inactive, NULL, NULL,
+ true);
+ RUNTIME_CHECK(result == ISC_R_SUCCESS);
+ }
+ count = res->spillat;
+ UNLOCK(&res->lock);
+ if (logit) {
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_RESOLVER,
+ DNS_LOGMODULE_RESOLVER, ISC_LOG_NOTICE,
+ "clients-per-query decreased to %u", count);
+ }
+
+ isc_event_free(&event);
+}
+
+isc_result_t
+dns_resolver_create(dns_view_t *view, isc_taskmgr_t *taskmgr,
+ unsigned int ntasks, unsigned int ndisp, isc_nm_t *nm,
+ isc_timermgr_t *timermgr, unsigned int options,
+ dns_dispatchmgr_t *dispatchmgr, dns_dispatch_t *dispatchv4,
+ dns_dispatch_t *dispatchv6, dns_resolver_t **resp) {
+ isc_result_t result = ISC_R_SUCCESS;
+ char name[sizeof("res4294967295")];
+ dns_resolver_t *res = NULL;
+ isc_task_t *task = NULL;
+
+ /*
+ * Create a resolver.
+ */
+
+ REQUIRE(DNS_VIEW_VALID(view));
+ REQUIRE(ntasks > 0);
+ REQUIRE(ndisp > 0);
+ REQUIRE(resp != NULL && *resp == NULL);
+ REQUIRE(dispatchmgr != NULL);
+ REQUIRE(dispatchv4 != NULL || dispatchv6 != NULL);
+
+ RTRACE("create");
+ res = isc_mem_get(view->mctx, sizeof(*res));
+ *res = (dns_resolver_t){ .rdclass = view->rdclass,
+ .nm = nm,
+ .timermgr = timermgr,
+ .taskmgr = taskmgr,
+ .dispatchmgr = dispatchmgr,
+ .view = view,
+ .options = options,
+ .udpsize = DEFAULT_EDNS_BUFSIZE,
+ .spillatmin = 10,
+ .spillat = 10,
+ .spillatmax = 100,
+ .retryinterval = 10000,
+ .nonbackofftries = 3,
+ .query_timeout = DEFAULT_QUERY_TIMEOUT,
+ .maxdepth = DEFAULT_RECURSION_DEPTH,
+ .maxqueries = DEFAULT_MAX_QUERIES,
+ .nbuckets = ntasks,
+ .dhashbits = RES_DOMAIN_HASH_BITS };
+
+ atomic_init(&res->activebuckets, res->nbuckets);
+
+ isc_mem_attach(view->mctx, &res->mctx);
+
+ res->quotaresp[dns_quotatype_zone] = DNS_R_DROP;
+ res->quotaresp[dns_quotatype_server] = DNS_R_SERVFAIL;
+ isc_refcount_init(&res->references, 1);
+ atomic_init(&res->exiting, false);
+ atomic_init(&res->priming, false);
+ atomic_init(&res->zspill, 0);
+ atomic_init(&res->nfctx, 0);
+ ISC_LIST_INIT(res->whenshutdown);
+ ISC_LIST_INIT(res->alternates);
+
+ result = dns_badcache_init(res->mctx, DNS_RESOLVER_BADCACHESIZE,
+ &res->badcache);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup_res;
+ }
+
+ if (view->resstats != NULL) {
+ isc_stats_set(view->resstats, res->nbuckets,
+ dns_resstatscounter_buckets);
+ }
+
+ res->buckets = isc_mem_get(view->mctx,
+ res->nbuckets * sizeof(res->buckets[0]));
+ for (uint32_t i = 0; i < ntasks; i++) {
+ res->buckets[i] = (fctxbucket_t){ 0 };
+
+ isc_mutex_init(&res->buckets[i].lock);
+
+ /*
+ * Since we have a pool of tasks we bind them to task
+ * queues to spread the load evenly
+ */
+ result = isc_task_create_bound(taskmgr, 0,
+ &res->buckets[i].task, i);
+ if (result != ISC_R_SUCCESS) {
+ ntasks = i;
+ isc_mutex_destroy(&res->buckets[i].lock);
+ goto cleanup_buckets;
+ }
+
+ snprintf(name, sizeof(name), "res%" PRIu32, i);
+ isc_task_setname(res->buckets[i].task, name, res);
+
+ ISC_LIST_INIT(res->buckets[i].fctxs);
+ atomic_init(&res->buckets[i].exiting, false);
+ }
+
+ res->dbuckets = isc_mem_get(view->mctx,
+ HASHSIZE(res->dhashbits) *
+ sizeof(res->dbuckets[0]));
+ for (size_t i = 0; i < HASHSIZE(res->dhashbits); i++) {
+ res->dbuckets[i] = (zonebucket_t){ .list = { 0 } };
+ ISC_LIST_INIT(res->dbuckets[i].list);
+ isc_mutex_init(&res->dbuckets[i].lock);
+ }
+
+ if (dispatchv4 != NULL) {
+ dns_dispatchset_create(view->mctx, dispatchv4,
+ &res->dispatches4, ndisp);
+ }
+
+ if (dispatchv6 != NULL) {
+ dns_dispatchset_create(view->mctx, dispatchv6,
+ &res->dispatches6, ndisp);
+ }
+
+ isc_mutex_init(&res->lock);
+ isc_mutex_init(&res->primelock);
+
+ result = isc_task_create(taskmgr, 0, &task);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup_primelock;
+ }
+ isc_task_setname(task, "resolver_task", NULL);
+
+ result = isc_timer_create(timermgr, isc_timertype_inactive, NULL, NULL,
+ task, spillattimer_countdown, res,
+ &res->spillattimer);
+ isc_task_detach(&task);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup_primelock;
+ }
+
+ res->magic = RES_MAGIC;
+
+ *resp = res;
+
+ return (ISC_R_SUCCESS);
+
+cleanup_primelock:
+ isc_mutex_destroy(&res->primelock);
+ isc_mutex_destroy(&res->lock);
+
+ if (res->dispatches6 != NULL) {
+ dns_dispatchset_destroy(&res->dispatches6);
+ }
+ if (res->dispatches4 != NULL) {
+ dns_dispatchset_destroy(&res->dispatches4);
+ }
+
+ for (size_t i = 0; i < HASHSIZE(res->dhashbits); i++) {
+ isc_mutex_destroy(&res->dbuckets[i].lock);
+ }
+ isc_mem_put(view->mctx, res->dbuckets,
+ HASHSIZE(res->dhashbits) * sizeof(zonebucket_t));
+
+cleanup_buckets:
+ for (size_t i = 0; i < ntasks; i++) {
+ isc_mutex_destroy(&res->buckets[i].lock);
+ isc_task_shutdown(res->buckets[i].task);
+ isc_task_detach(&res->buckets[i].task);
+ }
+ isc_mem_put(view->mctx, res->buckets,
+ res->nbuckets * sizeof(fctxbucket_t));
+
+ dns_badcache_destroy(&res->badcache);
+
+cleanup_res:
+ isc_mem_put(view->mctx, res, sizeof(*res));
+
+ return (result);
+}
+
+static void
+prime_done(isc_task_t *task, isc_event_t *event) {
+ dns_resolver_t *res;
+ dns_fetchevent_t *fevent;
+ dns_fetch_t *fetch;
+ dns_db_t *db = NULL;
+
+ REQUIRE(event->ev_type == DNS_EVENT_FETCHDONE);
+ fevent = (dns_fetchevent_t *)event;
+ res = event->ev_arg;
+ REQUIRE(VALID_RESOLVER(res));
+
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_RESOLVER,
+ DNS_LOGMODULE_RESOLVER, ISC_LOG_INFO,
+ "resolver priming query complete: %s",
+ isc_result_totext(fevent->result));
+
+ UNUSED(task);
+
+ LOCK(&res->primelock);
+ fetch = res->primefetch;
+ res->primefetch = NULL;
+ UNLOCK(&res->primelock);
+
+ atomic_compare_exchange_enforced(&res->priming, &(bool){ true }, false);
+
+ if (fevent->result == ISC_R_SUCCESS && res->view->cache != NULL &&
+ res->view->hints != NULL)
+ {
+ dns_cache_attachdb(res->view->cache, &db);
+ dns_root_checkhints(res->view, res->view->hints, db);
+ dns_db_detach(&db);
+ }
+
+ if (fevent->node != NULL) {
+ dns_db_detachnode(fevent->db, &fevent->node);
+ }
+ if (fevent->db != NULL) {
+ dns_db_detach(&fevent->db);
+ }
+ if (dns_rdataset_isassociated(fevent->rdataset)) {
+ dns_rdataset_disassociate(fevent->rdataset);
+ }
+ INSIST(fevent->sigrdataset == NULL);
+
+ isc_mem_put(res->mctx, fevent->rdataset, sizeof(*fevent->rdataset));
+
+ isc_event_free(&event);
+ dns_resolver_destroyfetch(&fetch);
+}
+
+void
+dns_resolver_prime(dns_resolver_t *res) {
+ bool want_priming = false;
+ dns_rdataset_t *rdataset;
+ isc_result_t result;
+
+ REQUIRE(VALID_RESOLVER(res));
+ REQUIRE(res->frozen);
+
+ RTRACE("dns_resolver_prime");
+
+ if (!atomic_load_acquire(&res->exiting)) {
+ want_priming = atomic_compare_exchange_strong_acq_rel(
+ &res->priming, &(bool){ false }, true);
+ }
+
+ if (want_priming) {
+ /*
+ * To avoid any possible recursive locking problems, we
+ * start the priming fetch like any other fetch, and
+ * holding no resolver locks. No one else will try to
+ * start it because we're the ones who set res->priming
+ * to true. Any other callers of dns_resolver_prime()
+ * while we're running will see that res->priming is
+ * already true and do nothing.
+ */
+ RTRACE("priming");
+ rdataset = isc_mem_get(res->mctx, sizeof(*rdataset));
+ dns_rdataset_init(rdataset);
+
+ LOCK(&res->primelock);
+ INSIST(res->primefetch == NULL);
+ result = dns_resolver_createfetch(
+ res, dns_rootname, dns_rdatatype_ns, NULL, NULL, NULL,
+ NULL, 0, DNS_FETCHOPT_NOFORWARD, 0, NULL,
+ res->buckets[0].task, prime_done, res, rdataset, NULL,
+ &res->primefetch);
+ UNLOCK(&res->primelock);
+
+ if (result != ISC_R_SUCCESS) {
+ isc_mem_put(res->mctx, rdataset, sizeof(*rdataset));
+ atomic_compare_exchange_enforced(
+ &res->priming, &(bool){ true }, false);
+ }
+ inc_stats(res, dns_resstatscounter_priming);
+ }
+}
+
+void
+dns_resolver_freeze(dns_resolver_t *res) {
+ /*
+ * Freeze resolver.
+ */
+
+ REQUIRE(VALID_RESOLVER(res));
+
+ res->frozen = true;
+}
+
+void
+dns_resolver_attach(dns_resolver_t *source, dns_resolver_t **targetp) {
+ REQUIRE(VALID_RESOLVER(source));
+ REQUIRE(targetp != NULL && *targetp == NULL);
+
+ RRTRACE(source, "attach");
+
+ isc_refcount_increment(&source->references);
+
+ *targetp = source;
+}
+
+void
+dns_resolver_whenshutdown(dns_resolver_t *res, isc_task_t *task,
+ isc_event_t **eventp) {
+ isc_event_t *event = NULL;
+
+ REQUIRE(VALID_RESOLVER(res));
+ REQUIRE(eventp != NULL);
+
+ event = *eventp;
+ *eventp = NULL;
+
+ LOCK(&res->lock);
+
+ if (atomic_load_acquire(&res->exiting) &&
+ atomic_load_acquire(&res->activebuckets) == 0)
+ {
+ /*
+ * We're already shutdown. Send the event.
+ */
+ event->ev_sender = res;
+ isc_task_send(task, &event);
+ } else {
+ isc_task_attach(task, &(isc_task_t *){ NULL });
+ event->ev_sender = task;
+ ISC_LIST_APPEND(res->whenshutdown, event, ev_link);
+ }
+
+ UNLOCK(&res->lock);
+}
+
+void
+dns_resolver_shutdown(dns_resolver_t *res) {
+ unsigned int i;
+ fetchctx_t *fctx;
+ isc_result_t result;
+ bool is_false = false;
+ bool is_done = false;
+
+ REQUIRE(VALID_RESOLVER(res));
+
+ RTRACE("shutdown");
+
+ if (atomic_compare_exchange_strong(&res->exiting, &is_false, true)) {
+ RTRACE("exiting");
+
+ for (i = 0; i < res->nbuckets; i++) {
+ LOCK(&res->buckets[i].lock);
+ for (fctx = ISC_LIST_HEAD(res->buckets[i].fctxs);
+ fctx != NULL; fctx = ISC_LIST_NEXT(fctx, link))
+ {
+ fctx_shutdown(fctx);
+ }
+ atomic_store(&res->buckets[i].exiting, true);
+ if (ISC_LIST_EMPTY(res->buckets[i].fctxs)) {
+ if (isc_refcount_decrement(
+ &res->activebuckets) == 1)
+ {
+ is_done = true;
+ }
+ }
+ UNLOCK(&res->buckets[i].lock);
+ }
+ if (is_done) {
+ send_shutdown_events(res);
+ }
+ result = isc_timer_reset(res->spillattimer,
+ isc_timertype_inactive, NULL, NULL,
+ true);
+ RUNTIME_CHECK(result == ISC_R_SUCCESS);
+ }
+}
+
+void
+dns_resolver_detach(dns_resolver_t **resp) {
+ dns_resolver_t *res;
+
+ REQUIRE(resp != NULL);
+ res = *resp;
+ *resp = NULL;
+ REQUIRE(VALID_RESOLVER(res));
+
+ RTRACE("detach");
+
+ if (isc_refcount_decrement(&res->references) == 1) {
+ isc_refcount_destroy(&res->activebuckets);
+ INSIST(atomic_load_acquire(&res->exiting));
+ destroy(res);
+ }
+}
+
+static bool
+fctx_match(fetchctx_t *fctx, const dns_name_t *name, dns_rdatatype_t type,
+ unsigned int options) {
+ /*
+ * Don't match fetch contexts that are shutting down.
+ */
+ if (fctx->cloned || fctx->state == fetchstate_done ||
+ ISC_LIST_EMPTY(fctx->events))
+ {
+ return (false);
+ }
+
+ if (fctx->type != type || fctx->options != options) {
+ return (false);
+ }
+ return (dns_name_equal(fctx->name, name));
+}
+
+static void
+log_fetch(const dns_name_t *name, dns_rdatatype_t type) {
+ char namebuf[DNS_NAME_FORMATSIZE];
+ char typebuf[DNS_RDATATYPE_FORMATSIZE];
+ int level = ISC_LOG_DEBUG(1);
+
+ /*
+ * If there's no chance of logging it, don't render (format) the
+ * name and RDATA type (further below), and return early.
+ */
+ if (!isc_log_wouldlog(dns_lctx, level)) {
+ return;
+ }
+
+ dns_name_format(name, namebuf, sizeof(namebuf));
+ dns_rdatatype_format(type, typebuf, sizeof(typebuf));
+
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_RESOLVER,
+ DNS_LOGMODULE_RESOLVER, level, "fetch: %s/%s", namebuf,
+ typebuf);
+}
+
+static void
+fctx_minimize_qname(fetchctx_t *fctx) {
+ isc_result_t result;
+ unsigned int dlabels, nlabels;
+ dns_name_t name;
+
+ REQUIRE(VALID_FCTX(fctx));
+
+ dns_name_init(&name, NULL);
+
+ dlabels = dns_name_countlabels(fctx->qmindcname);
+ nlabels = dns_name_countlabels(fctx->name);
+
+ if (dlabels > fctx->qmin_labels) {
+ fctx->qmin_labels = dlabels + 1;
+ } else {
+ fctx->qmin_labels++;
+ }
+
+ if (fctx->ip6arpaskip) {
+ /*
+ * For ip6.arpa we want to skip some of the labels, with
+ * boundaries at /16, /32, /48, /56, /64 and /128
+ * In 'label count' terms that's equal to
+ * 7 11 15 17 19 35
+ * We fix fctx->qmin_labels to point to the nearest
+ * boundary
+ */
+ if (fctx->qmin_labels < 7) {
+ fctx->qmin_labels = 7;
+ } else if (fctx->qmin_labels < 11) {
+ fctx->qmin_labels = 11;
+ } else if (fctx->qmin_labels < 15) {
+ fctx->qmin_labels = 15;
+ } else if (fctx->qmin_labels < 17) {
+ fctx->qmin_labels = 17;
+ } else if (fctx->qmin_labels < 19) {
+ fctx->qmin_labels = 19;
+ } else if (fctx->qmin_labels < 35) {
+ fctx->qmin_labels = 35;
+ } else {
+ fctx->qmin_labels = nlabels;
+ }
+ } else if (fctx->qmin_labels > DNS_QMIN_MAXLABELS) {
+ fctx->qmin_labels = DNS_MAX_LABELS + 1;
+ }
+
+ if (fctx->qmin_labels < nlabels) {
+ dns_rdataset_t rdataset;
+ dns_fixedname_t fixed;
+ dns_name_t *fname = dns_fixedname_initname(&fixed);
+ dns_rdataset_init(&rdataset);
+ do {
+ /*
+ * We want to query for qmin_labels from fctx->name.
+ */
+ dns_name_split(fctx->name, fctx->qmin_labels, NULL,
+ &name);
+ /*
+ * Look to see if we have anything cached about NS
+ * RRsets at this name and if so skip this name and
+ * try with an additional label prepended.
+ */
+ result = dns_db_find(fctx->cache, &name, NULL,
+ dns_rdatatype_ns, 0, 0, NULL,
+ fname, &rdataset, NULL);
+ if (dns_rdataset_isassociated(&rdataset)) {
+ dns_rdataset_disassociate(&rdataset);
+ }
+ switch (result) {
+ case ISC_R_SUCCESS:
+ case DNS_R_CNAME:
+ case DNS_R_DNAME:
+ case DNS_R_NCACHENXDOMAIN:
+ case DNS_R_NCACHENXRRSET:
+ fctx->qmin_labels++;
+ continue;
+ default:
+ break;
+ }
+ break;
+ } while (fctx->qmin_labels < nlabels);
+ }
+
+ if (fctx->qmin_labels < nlabels) {
+ dns_name_copy(&name, fctx->qminname);
+ fctx->qmintype = dns_rdatatype_ns;
+ fctx->minimized = true;
+ } else {
+ /* Minimization is done, we'll ask for whole qname */
+ dns_name_copy(fctx->name, fctx->qminname);
+ fctx->qmintype = fctx->type;
+ fctx->minimized = false;
+ }
+
+ char domainbuf[DNS_NAME_FORMATSIZE];
+ dns_name_format(fctx->qminname, domainbuf, sizeof(domainbuf));
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_RESOLVER,
+ DNS_LOGMODULE_RESOLVER, ISC_LOG_DEBUG(5),
+ "QNAME minimization - %s minimized, qmintype %d "
+ "qminname %s",
+ fctx->minimized ? "" : "not", fctx->qmintype, domainbuf);
+}
+
+isc_result_t
+dns_resolver_createfetch(dns_resolver_t *res, const dns_name_t *name,
+ dns_rdatatype_t type, const dns_name_t *domain,
+ dns_rdataset_t *nameservers,
+ dns_forwarders_t *forwarders,
+ const isc_sockaddr_t *client, dns_messageid_t id,
+ unsigned int options, unsigned int depth,
+ isc_counter_t *qc, isc_task_t *task,
+ isc_taskaction_t action, void *arg,
+ dns_rdataset_t *rdataset, dns_rdataset_t *sigrdataset,
+ dns_fetch_t **fetchp) {
+ dns_fetch_t *fetch;
+ fetchctx_t *fctx = NULL;
+ isc_result_t result = ISC_R_SUCCESS;
+ unsigned int bucketnum;
+ bool new_fctx = false;
+ isc_event_t *event;
+ unsigned int count = 0;
+ unsigned int spillat;
+ unsigned int spillatmin;
+ bool dodestroy = false;
+
+ UNUSED(forwarders);
+
+ REQUIRE(VALID_RESOLVER(res));
+ REQUIRE(res->frozen);
+ /* XXXRTH Check for meta type */
+ if (domain != NULL) {
+ REQUIRE(DNS_RDATASET_VALID(nameservers));
+ REQUIRE(nameservers->type == dns_rdatatype_ns);
+ } else {
+ REQUIRE(nameservers == NULL);
+ }
+ REQUIRE(forwarders == NULL);
+ REQUIRE(!dns_rdataset_isassociated(rdataset));
+ REQUIRE(sigrdataset == NULL || !dns_rdataset_isassociated(sigrdataset));
+ REQUIRE(fetchp != NULL && *fetchp == NULL);
+
+ if (atomic_load_acquire(&res->exiting)) {
+ return (ISC_R_SHUTTINGDOWN);
+ }
+
+ log_fetch(name, type);
+
+ fetch = isc_mem_get(res->mctx, sizeof(*fetch));
+ *fetch = (dns_fetch_t){ 0 };
+
+ dns_resolver_attach(res, &fetch->res);
+ isc_mem_attach(res->mctx, &fetch->mctx);
+
+ bucketnum = dns_name_fullhash(name, false) % res->nbuckets;
+
+ LOCK(&res->lock);
+ spillat = res->spillat;
+ spillatmin = res->spillatmin;
+ UNLOCK(&res->lock);
+ LOCK(&res->buckets[bucketnum].lock);
+
+ if (atomic_load(&res->buckets[bucketnum].exiting)) {
+ result = ISC_R_SHUTTINGDOWN;
+ goto unlock;
+ }
+
+ if ((options & DNS_FETCHOPT_UNSHARED) == 0) {
+ for (fctx = ISC_LIST_HEAD(res->buckets[bucketnum].fctxs);
+ fctx != NULL; fctx = ISC_LIST_NEXT(fctx, link))
+ {
+ if (fctx_match(fctx, name, type, options)) {
+ break;
+ }
+ }
+ }
+
+ /*
+ * Is this a duplicate?
+ */
+ if (fctx != NULL && client != NULL) {
+ dns_fetchevent_t *fevent;
+ for (fevent = ISC_LIST_HEAD(fctx->events); fevent != NULL;
+ fevent = ISC_LIST_NEXT(fevent, ev_link))
+ {
+ if (fevent->client != NULL && fevent->id == id &&
+ isc_sockaddr_equal(fevent->client, client))
+ {
+ result = DNS_R_DUPLICATE;
+ goto unlock;
+ }
+
+ /*
+ * Only the regular fetch events should be
+ * counted for the clients-per-query limit, in
+ * case if there are multiple events registered
+ * for a single client.
+ */
+ if (fevent->ev_type == DNS_EVENT_FETCHDONE) {
+ count++;
+ }
+ }
+ }
+ if (count >= spillatmin && spillatmin != 0) {
+ INSIST(fctx != NULL);
+ if (count >= spillat) {
+ fctx->spilled = true;
+ }
+ if (fctx->spilled) {
+ inc_stats(res, dns_resstatscounter_clientquota);
+ result = DNS_R_DROP;
+ goto unlock;
+ }
+ }
+
+ if (fctx == NULL) {
+ result = fctx_create(res, task, name, type, domain, nameservers,
+ client, options, bucketnum, depth, qc,
+ &fctx);
+ if (result != ISC_R_SUCCESS) {
+ goto unlock;
+ }
+ new_fctx = true;
+ } else if (fctx->depth > depth) {
+ fctx->depth = depth;
+ }
+
+ result = fctx_join(fctx, task, client, id, action, arg, rdataset,
+ sigrdataset, fetch);
+
+ if (result == ISC_R_SUCCESS &&
+ ((options & DNS_FETCHOPT_TRYSTALE_ONTIMEOUT) != 0))
+ {
+ fctx_add_event(fctx, task, client, id, action, arg, NULL, NULL,
+ fetch, DNS_EVENT_TRYSTALE);
+ }
+
+ if (new_fctx) {
+ if (result == ISC_R_SUCCESS) {
+ /*
+ * Launch this fctx.
+ */
+ event = &fctx->control_event;
+ fctx_addref(fctx);
+ ISC_EVENT_INIT(event, sizeof(*event), 0, NULL,
+ DNS_EVENT_FETCHCONTROL, fctx_start, fctx,
+ NULL, NULL, NULL);
+ isc_task_send(res->buckets[bucketnum].task, &event);
+ } else {
+ dodestroy = true;
+ }
+ }
+
+unlock:
+ UNLOCK(&res->buckets[bucketnum].lock);
+
+ if (dodestroy) {
+ fctx_destroy(fctx, false);
+ }
+
+ if (result == ISC_R_SUCCESS) {
+ FTRACE("created");
+ *fetchp = fetch;
+ } else {
+ dns_resolver_detach(&fetch->res);
+ isc_mem_putanddetach(&fetch->mctx, fetch, sizeof(*fetch));
+ }
+
+ return (result);
+}
+
+void
+dns_resolver_cancelfetch(dns_fetch_t *fetch) {
+ fetchctx_t *fctx = NULL;
+ dns_resolver_t *res = NULL;
+ dns_fetchevent_t *event = NULL;
+ dns_fetchevent_t *event_trystale = NULL;
+ dns_fetchevent_t *event_fetchdone = NULL;
+
+ REQUIRE(DNS_FETCH_VALID(fetch));
+ fctx = fetch->private;
+ REQUIRE(VALID_FCTX(fctx));
+ res = fctx->res;
+
+ FTRACE("cancelfetch");
+
+ LOCK(&res->buckets[fctx->bucketnum].lock);
+
+ /*
+ * Find the events for this fetch (as opposed
+ * to those for other fetches that have joined the same
+ * fctx) and send them with result = ISC_R_CANCELED.
+ */
+ if (fctx->state != fetchstate_done) {
+ dns_fetchevent_t *next_event = NULL;
+ for (event = ISC_LIST_HEAD(fctx->events); event != NULL;
+ event = next_event)
+ {
+ next_event = ISC_LIST_NEXT(event, ev_link);
+ if (event->fetch == fetch) {
+ ISC_LIST_UNLINK(fctx->events, event, ev_link);
+ switch (event->ev_type) {
+ case DNS_EVENT_TRYSTALE:
+ INSIST(event_trystale == NULL);
+ event_trystale = event;
+ break;
+ case DNS_EVENT_FETCHDONE:
+ INSIST(event_fetchdone == NULL);
+ event_fetchdone = event;
+ break;
+ default:
+ UNREACHABLE();
+ }
+ if (event_trystale != NULL &&
+ event_fetchdone != NULL)
+ {
+ break;
+ }
+ }
+ }
+ }
+ /*
+ * The "trystale" event must be sent before the "fetchdone" event,
+ * because the latter clears the "recursing" query attribute, which is
+ * required by both events (handled by the same callback function).
+ */
+ if (event_trystale != NULL) {
+ isc_task_t *etask = event_trystale->ev_sender;
+ event_trystale->ev_sender = fctx;
+ event_trystale->result = ISC_R_CANCELED;
+ isc_task_sendanddetach(&etask, ISC_EVENT_PTR(&event_trystale));
+ }
+ if (event_fetchdone != NULL) {
+ isc_task_t *etask = event_fetchdone->ev_sender;
+ event_fetchdone->ev_sender = fctx;
+ event_fetchdone->result = ISC_R_CANCELED;
+ isc_task_sendanddetach(&etask, ISC_EVENT_PTR(&event_fetchdone));
+ }
+
+ /*
+ * The fctx continues running even if no fetches remain;
+ * the answer is still cached.
+ */
+ UNLOCK(&res->buckets[fctx->bucketnum].lock);
+}
+
+void
+dns_resolver_destroyfetch(dns_fetch_t **fetchp) {
+ dns_fetch_t *fetch = NULL;
+ dns_resolver_t *res = NULL;
+ fetchctx_t *fctx = NULL;
+ unsigned int bucketnum;
+
+ REQUIRE(fetchp != NULL);
+ fetch = *fetchp;
+ *fetchp = NULL;
+ REQUIRE(DNS_FETCH_VALID(fetch));
+ fctx = fetch->private;
+ REQUIRE(VALID_FCTX(fctx));
+ res = fetch->res;
+
+ FTRACE("destroyfetch");
+
+ fetch->magic = 0;
+
+ bucketnum = fctx->bucketnum;
+ LOCK(&res->buckets[bucketnum].lock);
+
+ /*
+ * Sanity check: the caller should have gotten its event before
+ * trying to destroy the fetch.
+ */
+ if (fctx->state != fetchstate_done) {
+ dns_fetchevent_t *event = NULL, *next_event = NULL;
+ for (event = ISC_LIST_HEAD(fctx->events); event != NULL;
+ event = next_event)
+ {
+ next_event = ISC_LIST_NEXT(event, ev_link);
+ RUNTIME_CHECK(event->fetch != fetch);
+ }
+ }
+ UNLOCK(&res->buckets[bucketnum].lock);
+
+ isc_mem_putanddetach(&fetch->mctx, fetch, sizeof(*fetch));
+
+ fctx_detach(&fctx);
+ dns_resolver_detach(&res);
+}
+
+void
+dns_resolver_logfetch(dns_fetch_t *fetch, isc_log_t *lctx,
+ isc_logcategory_t *category, isc_logmodule_t *module,
+ int level, bool duplicateok) {
+ fetchctx_t *fctx;
+ dns_resolver_t *res;
+ char domainbuf[DNS_NAME_FORMATSIZE];
+
+ REQUIRE(DNS_FETCH_VALID(fetch));
+ fctx = fetch->private;
+ REQUIRE(VALID_FCTX(fctx));
+ res = fctx->res;
+
+ LOCK(&res->buckets[fctx->bucketnum].lock);
+
+ INSIST(fctx->exitline >= 0);
+ if (!fctx->logged || duplicateok) {
+ dns_name_format(fctx->domain, domainbuf, sizeof(domainbuf));
+ isc_log_write(lctx, category, module, level,
+ "fetch completed at %s:%d for %s in "
+ "%" PRIu64 "."
+ "%06" PRIu64 ": %s/%s "
+ "[domain:%s,referral:%u,restart:%u,qrysent:%u,"
+ "timeout:%u,lame:%u,quota:%u,neterr:%u,"
+ "badresp:%u,adberr:%u,findfail:%u,valfail:%u]",
+ __FILE__, fctx->exitline, fctx->info,
+ fctx->duration / US_PER_SEC,
+ fctx->duration % US_PER_SEC,
+ isc_result_totext(fctx->result),
+ isc_result_totext(fctx->vresult), domainbuf,
+ fctx->referrals, fctx->restarts, fctx->querysent,
+ fctx->timeouts, fctx->lamecount, fctx->quotacount,
+ fctx->neterr, fctx->badresp, fctx->adberr,
+ fctx->findfail, fctx->valfail);
+ fctx->logged = true;
+ }
+
+ UNLOCK(&res->buckets[fctx->bucketnum].lock);
+}
+
+dns_dispatchmgr_t *
+dns_resolver_dispatchmgr(dns_resolver_t *resolver) {
+ REQUIRE(VALID_RESOLVER(resolver));
+ return (resolver->dispatchmgr);
+}
+
+dns_dispatch_t *
+dns_resolver_dispatchv4(dns_resolver_t *resolver) {
+ REQUIRE(VALID_RESOLVER(resolver));
+ return (dns_dispatchset_get(resolver->dispatches4));
+}
+
+dns_dispatch_t *
+dns_resolver_dispatchv6(dns_resolver_t *resolver) {
+ REQUIRE(VALID_RESOLVER(resolver));
+ return (dns_dispatchset_get(resolver->dispatches6));
+}
+
+isc_taskmgr_t *
+dns_resolver_taskmgr(dns_resolver_t *resolver) {
+ REQUIRE(VALID_RESOLVER(resolver));
+ return (resolver->taskmgr);
+}
+
+uint32_t
+dns_resolver_getlamettl(dns_resolver_t *resolver) {
+ REQUIRE(VALID_RESOLVER(resolver));
+ return (resolver->lame_ttl);
+}
+
+void
+dns_resolver_setlamettl(dns_resolver_t *resolver, uint32_t lame_ttl) {
+ REQUIRE(VALID_RESOLVER(resolver));
+ resolver->lame_ttl = lame_ttl;
+}
+
+void
+dns_resolver_addalternate(dns_resolver_t *resolver, const isc_sockaddr_t *alt,
+ const dns_name_t *name, in_port_t port) {
+ alternate_t *a;
+
+ REQUIRE(VALID_RESOLVER(resolver));
+ REQUIRE(!resolver->frozen);
+ REQUIRE((alt == NULL) ^ (name == NULL));
+
+ a = isc_mem_get(resolver->mctx, sizeof(*a));
+ if (alt != NULL) {
+ a->isaddress = true;
+ a->_u.addr = *alt;
+ } else {
+ a->isaddress = false;
+ a->_u._n.port = port;
+ dns_name_init(&a->_u._n.name, NULL);
+ dns_name_dup(name, resolver->mctx, &a->_u._n.name);
+ }
+ ISC_LINK_INIT(a, link);
+ ISC_LIST_APPEND(resolver->alternates, a, link);
+}
+
+void
+dns_resolver_setudpsize(dns_resolver_t *resolver, uint16_t udpsize) {
+ REQUIRE(VALID_RESOLVER(resolver));
+ resolver->udpsize = udpsize;
+}
+
+uint16_t
+dns_resolver_getudpsize(dns_resolver_t *resolver) {
+ REQUIRE(VALID_RESOLVER(resolver));
+ return (resolver->udpsize);
+}
+
+void
+dns_resolver_flushbadcache(dns_resolver_t *resolver, const dns_name_t *name) {
+ if (name != NULL) {
+ dns_badcache_flushname(resolver->badcache, name);
+ } else {
+ dns_badcache_flush(resolver->badcache);
+ }
+}
+
+void
+dns_resolver_flushbadnames(dns_resolver_t *resolver, const dns_name_t *name) {
+ dns_badcache_flushtree(resolver->badcache, name);
+}
+
+void
+dns_resolver_addbadcache(dns_resolver_t *resolver, const dns_name_t *name,
+ dns_rdatatype_t type, isc_time_t *expire) {
+#ifdef ENABLE_AFL
+ if (!dns_fuzzing_resolver)
+#endif /* ifdef ENABLE_AFL */
+ {
+ dns_badcache_add(resolver->badcache, name, type, false, 0,
+ expire);
+ }
+}
+
+bool
+dns_resolver_getbadcache(dns_resolver_t *resolver, const dns_name_t *name,
+ dns_rdatatype_t type, isc_time_t *now) {
+ return (dns_badcache_find(resolver->badcache, name, type, NULL, now));
+}
+
+void
+dns_resolver_printbadcache(dns_resolver_t *resolver, FILE *fp) {
+ (void)dns_badcache_print(resolver->badcache, "Bad cache", fp);
+}
+
+static void
+free_algorithm(void *node, void *arg) {
+ unsigned char *algorithms = node;
+ isc_mem_t *mctx = arg;
+
+ isc_mem_put(mctx, algorithms, *algorithms);
+}
+
+void
+dns_resolver_reset_algorithms(dns_resolver_t *resolver) {
+ REQUIRE(VALID_RESOLVER(resolver));
+
+ if (resolver->algorithms != NULL) {
+ dns_rbt_destroy(&resolver->algorithms);
+ }
+}
+
+isc_result_t
+dns_resolver_disable_algorithm(dns_resolver_t *resolver, const dns_name_t *name,
+ unsigned int alg) {
+ unsigned int len, mask;
+ unsigned char *tmp;
+ unsigned char *algorithms;
+ isc_result_t result;
+ dns_rbtnode_t *node = NULL;
+
+ /*
+ * Whether an algorithm is disabled (or not) is stored in a
+ * per-name bitfield that is stored as the node data of an
+ * RBT.
+ */
+
+ REQUIRE(VALID_RESOLVER(resolver));
+ if (alg > 255) {
+ return (ISC_R_RANGE);
+ }
+
+ if (resolver->algorithms == NULL) {
+ result = dns_rbt_create(resolver->mctx, free_algorithm,
+ resolver->mctx, &resolver->algorithms);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+ }
+
+ len = alg / 8 + 2;
+ mask = 1 << (alg % 8);
+
+ result = dns_rbt_addnode(resolver->algorithms, name, &node);
+
+ if (result == ISC_R_SUCCESS || result == ISC_R_EXISTS) {
+ algorithms = node->data;
+ /*
+ * If algorithms is set, algorithms[0] contains its
+ * length.
+ */
+ if (algorithms == NULL || len > *algorithms) {
+ /*
+ * If no bitfield exists in the node data, or if
+ * it is not long enough, allocate a new
+ * bitfield and copy the old (smaller) bitfield
+ * into it if one exists.
+ */
+ tmp = isc_mem_get(resolver->mctx, len);
+ memset(tmp, 0, len);
+ if (algorithms != NULL) {
+ memmove(tmp, algorithms, *algorithms);
+ }
+ tmp[len - 1] |= mask;
+ /* tmp[0] should contain the length of 'tmp'. */
+ *tmp = len;
+ node->data = tmp;
+ /* Free the older bitfield. */
+ if (algorithms != NULL) {
+ isc_mem_put(resolver->mctx, algorithms,
+ *algorithms);
+ }
+ } else {
+ algorithms[len - 1] |= mask;
+ }
+ }
+ result = ISC_R_SUCCESS;
+cleanup:
+ return (result);
+}
+
+bool
+dns_resolver_algorithm_supported(dns_resolver_t *resolver,
+ const dns_name_t *name, unsigned int alg) {
+ unsigned int len, mask;
+ unsigned char *algorithms;
+ void *data = NULL;
+ isc_result_t result;
+ bool found = false;
+
+ REQUIRE(VALID_RESOLVER(resolver));
+
+ /*
+ * DH is unsupported for DNSKEYs, see RFC 4034 sec. A.1.
+ */
+ if ((alg == DST_ALG_DH) || (alg == DST_ALG_INDIRECT)) {
+ return (false);
+ }
+
+ if (resolver->algorithms == NULL) {
+ goto unlock;
+ }
+ result = dns_rbt_findname(resolver->algorithms, name, 0, NULL, &data);
+ if (result == ISC_R_SUCCESS || result == DNS_R_PARTIALMATCH) {
+ len = alg / 8 + 2;
+ mask = 1 << (alg % 8);
+ algorithms = data;
+ if (len <= *algorithms && (algorithms[len - 1] & mask) != 0) {
+ found = true;
+ }
+ }
+unlock:
+ if (found) {
+ return (false);
+ }
+
+ return (dst_algorithm_supported(alg));
+}
+
+static void
+free_digest(void *node, void *arg) {
+ unsigned char *digests = node;
+ isc_mem_t *mctx = arg;
+
+ isc_mem_put(mctx, digests, *digests);
+}
+
+void
+dns_resolver_reset_ds_digests(dns_resolver_t *resolver) {
+ REQUIRE(VALID_RESOLVER(resolver));
+
+ if (resolver->digests != NULL) {
+ dns_rbt_destroy(&resolver->digests);
+ }
+}
+
+isc_result_t
+dns_resolver_disable_ds_digest(dns_resolver_t *resolver, const dns_name_t *name,
+ unsigned int digest_type) {
+ unsigned int len, mask;
+ unsigned char *tmp;
+ unsigned char *digests;
+ isc_result_t result;
+ dns_rbtnode_t *node = NULL;
+
+ /*
+ * Whether a digest is disabled (or not) is stored in a per-name
+ * bitfield that is stored as the node data of an RBT.
+ */
+
+ REQUIRE(VALID_RESOLVER(resolver));
+ if (digest_type > 255) {
+ return (ISC_R_RANGE);
+ }
+
+ if (resolver->digests == NULL) {
+ result = dns_rbt_create(resolver->mctx, free_digest,
+ resolver->mctx, &resolver->digests);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+ }
+
+ len = digest_type / 8 + 2;
+ mask = 1 << (digest_type % 8);
+
+ result = dns_rbt_addnode(resolver->digests, name, &node);
+
+ if (result == ISC_R_SUCCESS || result == ISC_R_EXISTS) {
+ digests = node->data;
+ /* If digests is set, digests[0] contains its length. */
+ if (digests == NULL || len > *digests) {
+ /*
+ * If no bitfield exists in the node data, or if
+ * it is not long enough, allocate a new
+ * bitfield and copy the old (smaller) bitfield
+ * into it if one exists.
+ */
+ tmp = isc_mem_get(resolver->mctx, len);
+ memset(tmp, 0, len);
+ if (digests != NULL) {
+ memmove(tmp, digests, *digests);
+ }
+ tmp[len - 1] |= mask;
+ /* tmp[0] should contain the length of 'tmp'. */
+ *tmp = len;
+ node->data = tmp;
+ /* Free the older bitfield. */
+ if (digests != NULL) {
+ isc_mem_put(resolver->mctx, digests, *digests);
+ }
+ } else {
+ digests[len - 1] |= mask;
+ }
+ }
+ result = ISC_R_SUCCESS;
+cleanup:
+ return (result);
+}
+
+bool
+dns_resolver_ds_digest_supported(dns_resolver_t *resolver,
+ const dns_name_t *name,
+ unsigned int digest_type) {
+ unsigned int len, mask;
+ unsigned char *digests;
+ void *data = NULL;
+ isc_result_t result;
+ bool found = false;
+
+ REQUIRE(VALID_RESOLVER(resolver));
+
+ if (resolver->digests == NULL) {
+ goto unlock;
+ }
+ result = dns_rbt_findname(resolver->digests, name, 0, NULL, &data);
+ if (result == ISC_R_SUCCESS || result == DNS_R_PARTIALMATCH) {
+ len = digest_type / 8 + 2;
+ mask = 1 << (digest_type % 8);
+ digests = data;
+ if (len <= *digests && (digests[len - 1] & mask) != 0) {
+ found = true;
+ }
+ }
+unlock:
+ if (found) {
+ return (false);
+ }
+ return (dst_ds_digest_supported(digest_type));
+}
+
+void
+dns_resolver_resetmustbesecure(dns_resolver_t *resolver) {
+ REQUIRE(VALID_RESOLVER(resolver));
+
+ if (resolver->mustbesecure != NULL) {
+ dns_rbt_destroy(&resolver->mustbesecure);
+ }
+}
+
+static bool yes = true, no = false;
+
+isc_result_t
+dns_resolver_setmustbesecure(dns_resolver_t *resolver, const dns_name_t *name,
+ bool value) {
+ isc_result_t result;
+
+ REQUIRE(VALID_RESOLVER(resolver));
+
+ if (resolver->mustbesecure == NULL) {
+ result = dns_rbt_create(resolver->mctx, NULL, NULL,
+ &resolver->mustbesecure);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+ }
+ result = dns_rbt_addname(resolver->mustbesecure, name,
+ value ? &yes : &no);
+cleanup:
+ return (result);
+}
+
+bool
+dns_resolver_getmustbesecure(dns_resolver_t *resolver, const dns_name_t *name) {
+ void *data = NULL;
+ bool value = false;
+ isc_result_t result;
+
+ REQUIRE(VALID_RESOLVER(resolver));
+
+ if (resolver->mustbesecure == NULL) {
+ goto unlock;
+ }
+ result = dns_rbt_findname(resolver->mustbesecure, name, 0, NULL, &data);
+ if (result == ISC_R_SUCCESS || result == DNS_R_PARTIALMATCH) {
+ value = *(bool *)data;
+ }
+unlock:
+ return (value);
+}
+
+void
+dns_resolver_getclientsperquery(dns_resolver_t *resolver, uint32_t *cur,
+ uint32_t *min, uint32_t *max) {
+ REQUIRE(VALID_RESOLVER(resolver));
+
+ LOCK(&resolver->lock);
+ if (cur != NULL) {
+ *cur = resolver->spillat;
+ }
+ if (min != NULL) {
+ *min = resolver->spillatmin;
+ }
+ if (max != NULL) {
+ *max = resolver->spillatmax;
+ }
+ UNLOCK(&resolver->lock);
+}
+
+void
+dns_resolver_setclientsperquery(dns_resolver_t *resolver, uint32_t min,
+ uint32_t max) {
+ REQUIRE(VALID_RESOLVER(resolver));
+
+ LOCK(&resolver->lock);
+ resolver->spillatmin = resolver->spillat = min;
+ resolver->spillatmax = max;
+ UNLOCK(&resolver->lock);
+}
+
+void
+dns_resolver_setfetchesperzone(dns_resolver_t *resolver, uint32_t clients) {
+ REQUIRE(VALID_RESOLVER(resolver));
+
+ atomic_store_release(&resolver->zspill, clients);
+}
+
+bool
+dns_resolver_getzeronosoattl(dns_resolver_t *resolver) {
+ REQUIRE(VALID_RESOLVER(resolver));
+
+ return (resolver->zero_no_soa_ttl);
+}
+
+void
+dns_resolver_setzeronosoattl(dns_resolver_t *resolver, bool state) {
+ REQUIRE(VALID_RESOLVER(resolver));
+
+ resolver->zero_no_soa_ttl = state;
+}
+
+unsigned int
+dns_resolver_getoptions(dns_resolver_t *resolver) {
+ REQUIRE(VALID_RESOLVER(resolver));
+
+ return (resolver->options);
+}
+
+unsigned int
+dns_resolver_gettimeout(dns_resolver_t *resolver) {
+ REQUIRE(VALID_RESOLVER(resolver));
+
+ return (resolver->query_timeout);
+}
+
+void
+dns_resolver_settimeout(dns_resolver_t *resolver, unsigned int timeout) {
+ REQUIRE(VALID_RESOLVER(resolver));
+
+ if (timeout <= 300) {
+ timeout *= 1000;
+ }
+
+ if (timeout == 0) {
+ timeout = DEFAULT_QUERY_TIMEOUT;
+ }
+ if (timeout > MAXIMUM_QUERY_TIMEOUT) {
+ timeout = MAXIMUM_QUERY_TIMEOUT;
+ }
+ if (timeout < MINIMUM_QUERY_TIMEOUT) {
+ timeout = MINIMUM_QUERY_TIMEOUT;
+ }
+
+ resolver->query_timeout = timeout;
+}
+
+void
+dns_resolver_setmaxdepth(dns_resolver_t *resolver, unsigned int maxdepth) {
+ REQUIRE(VALID_RESOLVER(resolver));
+ resolver->maxdepth = maxdepth;
+}
+
+unsigned int
+dns_resolver_getmaxdepth(dns_resolver_t *resolver) {
+ REQUIRE(VALID_RESOLVER(resolver));
+ return (resolver->maxdepth);
+}
+
+void
+dns_resolver_setmaxqueries(dns_resolver_t *resolver, unsigned int queries) {
+ REQUIRE(VALID_RESOLVER(resolver));
+ resolver->maxqueries = queries;
+}
+
+unsigned int
+dns_resolver_getmaxqueries(dns_resolver_t *resolver) {
+ REQUIRE(VALID_RESOLVER(resolver));
+ return (resolver->maxqueries);
+}
+
+void
+dns_resolver_dumpfetches(dns_resolver_t *resolver, isc_statsformat_t format,
+ FILE *fp) {
+ REQUIRE(VALID_RESOLVER(resolver));
+ REQUIRE(fp != NULL);
+ REQUIRE(format == isc_statsformat_file);
+
+ for (size_t i = 0; i < HASHSIZE(resolver->dhashbits); i++) {
+ fctxcount_t *fc;
+ LOCK(&resolver->dbuckets[i].lock);
+ for (fc = ISC_LIST_HEAD(resolver->dbuckets[i].list); fc != NULL;
+ fc = ISC_LIST_NEXT(fc, link))
+ {
+ dns_name_print(fc->domain, fp);
+ fprintf(fp,
+ ": %u active (%u spilled, %u "
+ "allowed)\n",
+ fc->count, fc->dropped, fc->allowed);
+ }
+ UNLOCK(&resolver->dbuckets[i].lock);
+ }
+}
+
+void
+dns_resolver_setquotaresponse(dns_resolver_t *resolver, dns_quotatype_t which,
+ isc_result_t resp) {
+ REQUIRE(VALID_RESOLVER(resolver));
+ REQUIRE(which == dns_quotatype_zone || which == dns_quotatype_server);
+ REQUIRE(resp == DNS_R_DROP || resp == DNS_R_SERVFAIL);
+
+ resolver->quotaresp[which] = resp;
+}
+
+isc_result_t
+dns_resolver_getquotaresponse(dns_resolver_t *resolver, dns_quotatype_t which) {
+ REQUIRE(VALID_RESOLVER(resolver));
+ REQUIRE(which == dns_quotatype_zone || which == dns_quotatype_server);
+
+ return (resolver->quotaresp[which]);
+}
+
+unsigned int
+dns_resolver_getretryinterval(dns_resolver_t *resolver) {
+ REQUIRE(VALID_RESOLVER(resolver));
+
+ return (resolver->retryinterval);
+}
+
+void
+dns_resolver_setretryinterval(dns_resolver_t *resolver, unsigned int interval) {
+ REQUIRE(VALID_RESOLVER(resolver));
+ REQUIRE(interval > 0);
+
+ resolver->retryinterval = ISC_MIN(interval, 2000);
+}
+
+unsigned int
+dns_resolver_getnonbackofftries(dns_resolver_t *resolver) {
+ REQUIRE(VALID_RESOLVER(resolver));
+
+ return (resolver->nonbackofftries);
+}
+
+void
+dns_resolver_setnonbackofftries(dns_resolver_t *resolver, unsigned int tries) {
+ REQUIRE(VALID_RESOLVER(resolver));
+ REQUIRE(tries > 0);
+
+ resolver->nonbackofftries = tries;
+}
diff --git a/lib/dns/result.c b/lib/dns/result.c
new file mode 100644
index 0000000..bb9afa9
--- /dev/null
+++ b/lib/dns/result.c
@@ -0,0 +1,121 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/*! \file */
+
+#include <isc/once.h>
+#include <isc/result.h>
+#include <isc/util.h>
+
+#include <dns/result.h>
+
+dns_rcode_t
+dns_result_torcode(isc_result_t result) {
+ /* Try to supply an appropriate rcode. */
+ switch (result) {
+ case DNS_R_NOERROR:
+ case ISC_R_SUCCESS:
+ return (dns_rcode_noerror);
+ case DNS_R_FORMERR:
+ case ISC_R_BADBASE64:
+ case ISC_R_RANGE:
+ case ISC_R_UNEXPECTEDEND:
+ case DNS_R_BADAAAA:
+ /* case DNS_R_BADBITSTRING: deprecated */
+ case DNS_R_BADCKSUM:
+ case DNS_R_BADCLASS:
+ case DNS_R_BADLABELTYPE:
+ case DNS_R_BADPOINTER:
+ case DNS_R_BADTTL:
+ case DNS_R_BADZONE:
+ /* case DNS_R_BITSTRINGTOOLONG: deprecated */
+ case DNS_R_EXTRADATA:
+ case DNS_R_LABELTOOLONG:
+ case DNS_R_NOREDATA:
+ case DNS_R_SYNTAX:
+ case DNS_R_TEXTTOOLONG:
+ case DNS_R_TOOMANYHOPS:
+ case DNS_R_TSIGERRORSET:
+ case DNS_R_UNKNOWN:
+ case DNS_R_NAMETOOLONG:
+ case DNS_R_OPTERR:
+ return (dns_rcode_formerr);
+ case DNS_R_SERVFAIL:
+ return (dns_rcode_servfail);
+ case DNS_R_NXDOMAIN:
+ return (dns_rcode_nxdomain);
+ case DNS_R_NOTIMP:
+ return (dns_rcode_notimp);
+ case DNS_R_REFUSED:
+ case DNS_R_DISALLOWED:
+ return (dns_rcode_refused);
+ case DNS_R_YXDOMAIN:
+ return (dns_rcode_yxdomain);
+ case DNS_R_YXRRSET:
+ return (dns_rcode_yxrrset);
+ case DNS_R_NXRRSET:
+ return (dns_rcode_nxrrset);
+ case DNS_R_NOTAUTH:
+ case DNS_R_TSIGVERIFYFAILURE:
+ case DNS_R_CLOCKSKEW:
+ return (dns_rcode_notauth);
+ case DNS_R_NOTZONE:
+ return (dns_rcode_notzone);
+ case DNS_R_RCODE11:
+ case DNS_R_RCODE12:
+ case DNS_R_RCODE13:
+ case DNS_R_RCODE14:
+ case DNS_R_RCODE15:
+ return (result - DNS_R_NOERROR);
+ case DNS_R_BADVERS:
+ return (dns_rcode_badvers);
+ case DNS_R_BADCOOKIE:
+ return (dns_rcode_badcookie);
+ default:
+ return (dns_rcode_servfail);
+ }
+}
+
+isc_result_t
+dns_result_fromrcode(dns_rcode_t rcode) {
+ switch (rcode) {
+ case dns_rcode_noerror:
+ return (DNS_R_NOERROR);
+ case dns_rcode_formerr:
+ return (DNS_R_FORMERR);
+ case dns_rcode_servfail:
+ return (DNS_R_SERVFAIL);
+ case dns_rcode_nxdomain:
+ return (DNS_R_NXDOMAIN);
+ case dns_rcode_notimp:
+ return (DNS_R_NOTIMP);
+ case dns_rcode_refused:
+ return (DNS_R_REFUSED);
+ case dns_rcode_yxdomain:
+ return (DNS_R_YXDOMAIN);
+ case dns_rcode_yxrrset:
+ return (DNS_R_YXRRSET);
+ case dns_rcode_nxrrset:
+ return (DNS_R_NXRRSET);
+ case dns_rcode_notauth:
+ return (DNS_R_NOTAUTH);
+ case dns_rcode_notzone:
+ return (DNS_R_NOTZONE);
+ case dns_rcode_badvers:
+ return (DNS_R_BADVERS);
+ case dns_rcode_badcookie:
+ return (DNS_R_BADCOOKIE);
+ default:
+ return (DNS_R_SERVFAIL);
+ }
+}
diff --git a/lib/dns/rootns.c b/lib/dns/rootns.c
new file mode 100644
index 0000000..9a61587
--- /dev/null
+++ b/lib/dns/rootns.c
@@ -0,0 +1,566 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/*! \file */
+
+#include <stdbool.h>
+
+#include <isc/buffer.h>
+#include <isc/result.h>
+#include <isc/string.h> /* Required for HP/UX (and others?) */
+#include <isc/util.h>
+
+#include <dns/callbacks.h>
+#include <dns/db.h>
+#include <dns/dbiterator.h>
+#include <dns/fixedname.h>
+#include <dns/log.h>
+#include <dns/master.h>
+#include <dns/rdata.h>
+#include <dns/rdataset.h>
+#include <dns/rdatasetiter.h>
+#include <dns/rdatastruct.h>
+#include <dns/rdatatype.h>
+#include <dns/rootns.h>
+#include <dns/view.h>
+
+static char root_ns[] =
+ ";\n"
+ "; Internet Root Nameservers\n"
+ ";\n"
+ "$TTL 518400\n"
+ ". 518400 IN NS A.ROOT-SERVERS.NET.\n"
+ ". 518400 IN NS B.ROOT-SERVERS.NET.\n"
+ ". 518400 IN NS C.ROOT-SERVERS.NET.\n"
+ ". 518400 IN NS D.ROOT-SERVERS.NET.\n"
+ ". 518400 IN NS E.ROOT-SERVERS.NET.\n"
+ ". 518400 IN NS F.ROOT-SERVERS.NET.\n"
+ ". 518400 IN NS G.ROOT-SERVERS.NET.\n"
+ ". 518400 IN NS H.ROOT-SERVERS.NET.\n"
+ ". 518400 IN NS I.ROOT-SERVERS.NET.\n"
+ ". 518400 IN NS J.ROOT-SERVERS.NET.\n"
+ ". 518400 IN NS K.ROOT-SERVERS.NET.\n"
+ ". 518400 IN NS L.ROOT-SERVERS.NET.\n"
+ ". 518400 IN NS M.ROOT-SERVERS.NET.\n"
+ "A.ROOT-SERVERS.NET. 3600000 IN A 198.41.0.4\n"
+ "A.ROOT-SERVERS.NET. 3600000 IN AAAA 2001:503:BA3E::2:30\n"
+ "B.ROOT-SERVERS.NET. 3600000 IN A 199.9.14.201\n"
+ "B.ROOT-SERVERS.NET. 3600000 IN AAAA 2001:500:200::b\n"
+ "C.ROOT-SERVERS.NET. 3600000 IN A 192.33.4.12\n"
+ "C.ROOT-SERVERS.NET. 3600000 IN AAAA 2001:500:2::c\n"
+ "D.ROOT-SERVERS.NET. 3600000 IN A 199.7.91.13\n"
+ "D.ROOT-SERVERS.NET. 3600000 IN AAAA 2001:500:2d::d\n"
+ "E.ROOT-SERVERS.NET. 3600000 IN A 192.203.230.10\n"
+ "E.ROOT-SERVERS.NET. 3600000 IN AAAA 2001:500:a8::e\n"
+ "F.ROOT-SERVERS.NET. 3600000 IN A 192.5.5.241\n"
+ "F.ROOT-SERVERS.NET. 3600000 IN AAAA 2001:500:2F::F\n"
+ "G.ROOT-SERVERS.NET. 3600000 IN A 192.112.36.4\n"
+ "G.ROOT-SERVERS.NET. 3600000 IN AAAA 2001:500:12::d0d\n"
+ "H.ROOT-SERVERS.NET. 3600000 IN A 198.97.190.53\n"
+ "H.ROOT-SERVERS.NET. 3600000 IN AAAA 2001:500:1::53\n"
+ "I.ROOT-SERVERS.NET. 3600000 IN A 192.36.148.17\n"
+ "I.ROOT-SERVERS.NET. 3600000 IN AAAA 2001:7fe::53\n"
+ "J.ROOT-SERVERS.NET. 3600000 IN A 192.58.128.30\n"
+ "J.ROOT-SERVERS.NET. 3600000 IN AAAA 2001:503:C27::2:30\n"
+ "K.ROOT-SERVERS.NET. 3600000 IN A 193.0.14.129\n"
+ "K.ROOT-SERVERS.NET. 3600000 IN AAAA 2001:7FD::1\n"
+ "L.ROOT-SERVERS.NET. 3600000 IN A 199.7.83.42\n"
+ "L.ROOT-SERVERS.NET. 3600000 IN AAAA 2001:500:9f::42\n"
+ "M.ROOT-SERVERS.NET. 3600000 IN A 202.12.27.33\n"
+ "M.ROOT-SERVERS.NET. 3600000 IN AAAA 2001:DC3::35\n";
+
+static isc_result_t
+in_rootns(dns_rdataset_t *rootns, dns_name_t *name) {
+ isc_result_t result;
+ dns_rdata_t rdata = DNS_RDATA_INIT;
+ dns_rdata_ns_t ns;
+
+ if (!dns_rdataset_isassociated(rootns)) {
+ return (ISC_R_NOTFOUND);
+ }
+
+ result = dns_rdataset_first(rootns);
+ while (result == ISC_R_SUCCESS) {
+ dns_rdataset_current(rootns, &rdata);
+ result = dns_rdata_tostruct(&rdata, &ns, NULL);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+ if (dns_name_compare(name, &ns.name) == 0) {
+ return (ISC_R_SUCCESS);
+ }
+ result = dns_rdataset_next(rootns);
+ dns_rdata_reset(&rdata);
+ }
+ if (result == ISC_R_NOMORE) {
+ result = ISC_R_NOTFOUND;
+ }
+ return (result);
+}
+
+static isc_result_t
+check_node(dns_rdataset_t *rootns, dns_name_t *name,
+ dns_rdatasetiter_t *rdsiter) {
+ isc_result_t result;
+ dns_rdataset_t rdataset;
+
+ dns_rdataset_init(&rdataset);
+ result = dns_rdatasetiter_first(rdsiter);
+ while (result == ISC_R_SUCCESS) {
+ dns_rdatasetiter_current(rdsiter, &rdataset);
+ switch (rdataset.type) {
+ case dns_rdatatype_a:
+ case dns_rdatatype_aaaa:
+ result = in_rootns(rootns, name);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+ break;
+ case dns_rdatatype_ns:
+ if (dns_name_compare(name, dns_rootname) == 0) {
+ break;
+ }
+ FALLTHROUGH;
+ default:
+ result = ISC_R_FAILURE;
+ goto cleanup;
+ }
+ dns_rdataset_disassociate(&rdataset);
+ result = dns_rdatasetiter_next(rdsiter);
+ }
+ if (result == ISC_R_NOMORE) {
+ result = ISC_R_SUCCESS;
+ }
+cleanup:
+ if (dns_rdataset_isassociated(&rdataset)) {
+ dns_rdataset_disassociate(&rdataset);
+ }
+ return (result);
+}
+
+static isc_result_t
+check_hints(dns_db_t *db) {
+ isc_result_t result;
+ dns_rdataset_t rootns;
+ dns_dbiterator_t *dbiter = NULL;
+ dns_dbnode_t *node = NULL;
+ isc_stdtime_t now;
+ dns_fixedname_t fixname;
+ dns_name_t *name;
+ dns_rdatasetiter_t *rdsiter = NULL;
+
+ isc_stdtime_get(&now);
+
+ name = dns_fixedname_initname(&fixname);
+
+ dns_rdataset_init(&rootns);
+ (void)dns_db_find(db, dns_rootname, NULL, dns_rdatatype_ns, 0, now,
+ NULL, name, &rootns, NULL);
+ result = dns_db_createiterator(db, 0, &dbiter);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+ result = dns_dbiterator_first(dbiter);
+ while (result == ISC_R_SUCCESS) {
+ result = dns_dbiterator_current(dbiter, &node, name);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+ result = dns_db_allrdatasets(db, node, NULL, 0, now, &rdsiter);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+ result = check_node(&rootns, name, rdsiter);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+ dns_rdatasetiter_destroy(&rdsiter);
+ dns_db_detachnode(db, &node);
+ result = dns_dbiterator_next(dbiter);
+ }
+ if (result == ISC_R_NOMORE) {
+ result = ISC_R_SUCCESS;
+ }
+
+cleanup:
+ if (dns_rdataset_isassociated(&rootns)) {
+ dns_rdataset_disassociate(&rootns);
+ }
+ if (rdsiter != NULL) {
+ dns_rdatasetiter_destroy(&rdsiter);
+ }
+ if (node != NULL) {
+ dns_db_detachnode(db, &node);
+ }
+ if (dbiter != NULL) {
+ dns_dbiterator_destroy(&dbiter);
+ }
+ return (result);
+}
+
+isc_result_t
+dns_rootns_create(isc_mem_t *mctx, dns_rdataclass_t rdclass,
+ const char *filename, dns_db_t **target) {
+ isc_result_t result, eresult;
+ isc_buffer_t source;
+ unsigned int len;
+ dns_rdatacallbacks_t callbacks;
+ dns_db_t *db = NULL;
+
+ REQUIRE(target != NULL && *target == NULL);
+
+ result = dns_db_create(mctx, "rbt", dns_rootname, dns_dbtype_zone,
+ rdclass, 0, NULL, &db);
+ if (result != ISC_R_SUCCESS) {
+ goto failure;
+ }
+
+ len = strlen(root_ns);
+ isc_buffer_init(&source, root_ns, len);
+ isc_buffer_add(&source, len);
+
+ dns_rdatacallbacks_init(&callbacks);
+ result = dns_db_beginload(db, &callbacks);
+ if (result != ISC_R_SUCCESS) {
+ goto failure;
+ }
+ if (filename != NULL) {
+ /*
+ * Load the hints from the specified filename.
+ */
+ result = dns_master_loadfile(filename, &db->origin, &db->origin,
+ db->rdclass, DNS_MASTER_HINT, 0,
+ &callbacks, NULL, NULL, db->mctx,
+ dns_masterformat_text, 0);
+ } else if (rdclass == dns_rdataclass_in) {
+ /*
+ * Default to using the Internet root servers.
+ */
+ result = dns_master_loadbuffer(
+ &source, &db->origin, &db->origin, db->rdclass,
+ DNS_MASTER_HINT, &callbacks, db->mctx);
+ } else {
+ result = ISC_R_NOTFOUND;
+ }
+ eresult = dns_db_endload(db, &callbacks);
+ if (result == ISC_R_SUCCESS || result == DNS_R_SEENINCLUDE) {
+ result = eresult;
+ }
+ if (result != ISC_R_SUCCESS && result != DNS_R_SEENINCLUDE) {
+ goto failure;
+ }
+ if (check_hints(db) != ISC_R_SUCCESS) {
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL,
+ DNS_LOGMODULE_HINTS, ISC_LOG_WARNING,
+ "extra data in root hints '%s'",
+ (filename != NULL) ? filename : "<BUILT-IN>");
+ }
+ *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 : "<BUILT-IN>",
+ 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, &current);
+ if (dns_rdata_compare(rdata, &current) == 0) {
+ return (true);
+ }
+ dns_rdata_reset(&current);
+ result = dns_rdataset_next(rrset);
+ }
+ return (false);
+}
+
+/*
+ * Check that the address RRsets match.
+ *
+ * Note we don't complain about missing glue records.
+ */
+
+static void
+check_address_records(dns_view_t *view, dns_db_t *hints, dns_db_t *db,
+ dns_name_t *name, isc_stdtime_t now) {
+ isc_result_t hresult, rresult, result;
+ dns_rdataset_t hintrrset, rootrrset;
+ dns_rdata_t rdata = DNS_RDATA_INIT;
+ dns_name_t *foundname;
+ dns_fixedname_t fixed;
+
+ dns_rdataset_init(&hintrrset);
+ dns_rdataset_init(&rootrrset);
+ foundname = dns_fixedname_initname(&fixed);
+
+ hresult = dns_db_find(hints, name, NULL, dns_rdatatype_a, 0, now, NULL,
+ foundname, &hintrrset, NULL);
+ rresult = dns_db_find(db, name, NULL, dns_rdatatype_a,
+ DNS_DBFIND_GLUEOK, now, NULL, foundname,
+ &rootrrset, NULL);
+ if (hresult == ISC_R_SUCCESS &&
+ (rresult == ISC_R_SUCCESS || rresult == DNS_R_GLUE))
+ {
+ result = dns_rdataset_first(&rootrrset);
+ while (result == ISC_R_SUCCESS) {
+ dns_rdata_reset(&rdata);
+ dns_rdataset_current(&rootrrset, &rdata);
+ if (!inrrset(&hintrrset, &rdata)) {
+ report(view, name, true, &rdata);
+ }
+ result = dns_rdataset_next(&rootrrset);
+ }
+ result = dns_rdataset_first(&hintrrset);
+ while (result == ISC_R_SUCCESS) {
+ dns_rdata_reset(&rdata);
+ dns_rdataset_current(&hintrrset, &rdata);
+ if (!inrrset(&rootrrset, &rdata)) {
+ report(view, name, false, &rdata);
+ }
+ result = dns_rdataset_next(&hintrrset);
+ }
+ }
+ if (hresult == ISC_R_NOTFOUND &&
+ (rresult == ISC_R_SUCCESS || rresult == DNS_R_GLUE))
+ {
+ result = dns_rdataset_first(&rootrrset);
+ while (result == ISC_R_SUCCESS) {
+ dns_rdata_reset(&rdata);
+ dns_rdataset_current(&rootrrset, &rdata);
+ report(view, name, true, &rdata);
+ result = dns_rdataset_next(&rootrrset);
+ }
+ }
+ if (dns_rdataset_isassociated(&rootrrset)) {
+ dns_rdataset_disassociate(&rootrrset);
+ }
+ if (dns_rdataset_isassociated(&hintrrset)) {
+ dns_rdataset_disassociate(&hintrrset);
+ }
+
+ /*
+ * Check AAAA records.
+ */
+ hresult = dns_db_find(hints, name, NULL, dns_rdatatype_aaaa, 0, now,
+ NULL, foundname, &hintrrset, NULL);
+ rresult = dns_db_find(db, name, NULL, dns_rdatatype_aaaa,
+ DNS_DBFIND_GLUEOK, now, NULL, foundname,
+ &rootrrset, NULL);
+ if (hresult == ISC_R_SUCCESS &&
+ (rresult == ISC_R_SUCCESS || rresult == DNS_R_GLUE))
+ {
+ result = dns_rdataset_first(&rootrrset);
+ while (result == ISC_R_SUCCESS) {
+ dns_rdata_reset(&rdata);
+ dns_rdataset_current(&rootrrset, &rdata);
+ if (!inrrset(&hintrrset, &rdata)) {
+ report(view, name, true, &rdata);
+ }
+ dns_rdata_reset(&rdata);
+ result = dns_rdataset_next(&rootrrset);
+ }
+ result = dns_rdataset_first(&hintrrset);
+ while (result == ISC_R_SUCCESS) {
+ dns_rdata_reset(&rdata);
+ dns_rdataset_current(&hintrrset, &rdata);
+ if (!inrrset(&rootrrset, &rdata)) {
+ report(view, name, false, &rdata);
+ }
+ dns_rdata_reset(&rdata);
+ result = dns_rdataset_next(&hintrrset);
+ }
+ }
+ if (hresult == ISC_R_NOTFOUND &&
+ (rresult == ISC_R_SUCCESS || rresult == DNS_R_GLUE))
+ {
+ result = dns_rdataset_first(&rootrrset);
+ while (result == ISC_R_SUCCESS) {
+ dns_rdata_reset(&rdata);
+ dns_rdataset_current(&rootrrset, &rdata);
+ report(view, name, true, &rdata);
+ dns_rdata_reset(&rdata);
+ result = dns_rdataset_next(&rootrrset);
+ }
+ }
+ if (dns_rdataset_isassociated(&rootrrset)) {
+ dns_rdataset_disassociate(&rootrrset);
+ }
+ if (dns_rdataset_isassociated(&hintrrset)) {
+ dns_rdataset_disassociate(&hintrrset);
+ }
+}
+
+void
+dns_root_checkhints(dns_view_t *view, dns_db_t *hints, dns_db_t *db) {
+ isc_result_t result;
+ dns_rdata_t rdata = DNS_RDATA_INIT;
+ dns_rdata_ns_t ns;
+ dns_rdataset_t hintns, rootns;
+ const char *viewname = "", *sep = "";
+ isc_stdtime_t now;
+ dns_name_t *name;
+ dns_fixedname_t fixed;
+
+ REQUIRE(hints != NULL);
+ REQUIRE(db != NULL);
+ REQUIRE(view != NULL);
+
+ isc_stdtime_get(&now);
+
+ if (strcmp(view->name, "_bind") != 0 &&
+ strcmp(view->name, "_default") != 0)
+ {
+ viewname = view->name;
+ sep = ": view ";
+ }
+
+ dns_rdataset_init(&hintns);
+ dns_rdataset_init(&rootns);
+ name = dns_fixedname_initname(&fixed);
+
+ result = dns_db_find(hints, dns_rootname, NULL, dns_rdatatype_ns, 0,
+ now, NULL, name, &hintns, NULL);
+ if (result != ISC_R_SUCCESS) {
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL,
+ DNS_LOGMODULE_HINTS, ISC_LOG_WARNING,
+ "checkhints%s%s: unable to get root NS rrset "
+ "from hints: %s",
+ sep, viewname, isc_result_totext(result));
+ goto cleanup;
+ }
+
+ result = dns_db_find(db, dns_rootname, NULL, dns_rdatatype_ns, 0, now,
+ NULL, name, &rootns, NULL);
+ if (result != ISC_R_SUCCESS) {
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL,
+ DNS_LOGMODULE_HINTS, ISC_LOG_WARNING,
+ "checkhints%s%s: unable to get root NS rrset "
+ "from cache: %s",
+ sep, viewname, isc_result_totext(result));
+ goto cleanup;
+ }
+
+ /*
+ * Look for missing root NS names.
+ */
+ result = dns_rdataset_first(&rootns);
+ while (result == ISC_R_SUCCESS) {
+ dns_rdataset_current(&rootns, &rdata);
+ result = dns_rdata_tostruct(&rdata, &ns, NULL);
+ RUNTIME_CHECK(result == ISC_R_SUCCESS);
+ result = in_rootns(&hintns, &ns.name);
+ if (result != ISC_R_SUCCESS) {
+ char namebuf[DNS_NAME_FORMATSIZE];
+ /* missing from hints */
+ dns_name_format(&ns.name, namebuf, sizeof(namebuf));
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL,
+ DNS_LOGMODULE_HINTS, ISC_LOG_WARNING,
+ "checkhints%s%s: unable to find root "
+ "NS '%s' in hints",
+ sep, viewname, namebuf);
+ } else {
+ check_address_records(view, hints, db, &ns.name, now);
+ }
+ dns_rdata_reset(&rdata);
+ result = dns_rdataset_next(&rootns);
+ }
+ if (result != ISC_R_NOMORE) {
+ goto cleanup;
+ }
+
+ /*
+ * Look for extra root NS names.
+ */
+ result = dns_rdataset_first(&hintns);
+ while (result == ISC_R_SUCCESS) {
+ dns_rdataset_current(&hintns, &rdata);
+ result = dns_rdata_tostruct(&rdata, &ns, NULL);
+ RUNTIME_CHECK(result == ISC_R_SUCCESS);
+ result = in_rootns(&rootns, &ns.name);
+ if (result != ISC_R_SUCCESS) {
+ char namebuf[DNS_NAME_FORMATSIZE];
+ /* extra entry in hints */
+ dns_name_format(&ns.name, namebuf, sizeof(namebuf));
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL,
+ DNS_LOGMODULE_HINTS, ISC_LOG_WARNING,
+ "checkhints%s%s: extra NS '%s' in hints",
+ sep, viewname, namebuf);
+ }
+ dns_rdata_reset(&rdata);
+ result = dns_rdataset_next(&hintns);
+ }
+ if (result != ISC_R_NOMORE) {
+ goto cleanup;
+ }
+
+cleanup:
+ if (dns_rdataset_isassociated(&rootns)) {
+ dns_rdataset_disassociate(&rootns);
+ }
+ if (dns_rdataset_isassociated(&hintns)) {
+ dns_rdataset_disassociate(&hintns);
+ }
+}
diff --git a/lib/dns/rpz.c b/lib/dns/rpz.c
new file mode 100644
index 0000000..62d13d3
--- /dev/null
+++ b/lib/dns/rpz.c
@@ -0,0 +1,2734 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/*! \file */
+
+#include <inttypes.h>
+#include <stdbool.h>
+#include <stdlib.h>
+
+#include <isc/buffer.h>
+#include <isc/magic.h>
+#include <isc/mem.h>
+#include <isc/net.h>
+#include <isc/netaddr.h>
+#include <isc/print.h>
+#include <isc/result.h>
+#include <isc/rwlock.h>
+#include <isc/string.h>
+#include <isc/task.h>
+#include <isc/util.h>
+
+#include <dns/db.h>
+#include <dns/dbiterator.h>
+#include <dns/dnsrps.h>
+#include <dns/events.h>
+#include <dns/fixedname.h>
+#include <dns/log.h>
+#include <dns/rbt.h>
+#include <dns/rdata.h>
+#include <dns/rdataset.h>
+#include <dns/rdatasetiter.h>
+#include <dns/rdatastruct.h>
+#include <dns/rpz.h>
+#include <dns/view.h>
+
+#define DNS_RPZ_ZONE_MAGIC ISC_MAGIC('r', 'p', 'z', ' ')
+#define DNS_RPZ_ZONES_MAGIC ISC_MAGIC('r', 'p', 'z', 's')
+
+#define DNS_RPZ_ZONE_VALID(rpz) ISC_MAGIC_VALID(rpz, DNS_RPZ_ZONE_MAGIC)
+#define DNS_RPZ_ZONES_VALID(rpzs) ISC_MAGIC_VALID(rpzs, DNS_RPZ_ZONES_MAGIC)
+
+/*
+ * Parallel radix trees for databases of response policy IP addresses
+ *
+ * The radix or patricia trees are somewhat specialized to handle response
+ * policy addresses by representing the two sets of IP addresses and name
+ * server IP addresses in a single tree. One set of IP addresses is
+ * for rpz-ip policies or policies triggered by addresses in A or
+ * AAAA records in responses.
+ * The second set is for rpz-nsip policies or policies triggered by addresses
+ * in A or AAAA records for NS records that are authorities for responses.
+ *
+ * Each leaf indicates that an IP address is listed in the IP address or the
+ * name server IP address policy sub-zone (or both) of the corresponding
+ * response policy zone. The policy data such as a CNAME or an A record
+ * is kept in the policy zone. After an IP address has been found in a radix
+ * tree, the node in the policy zone's database is found by converting
+ * the IP address to a domain name in a canonical form.
+ *
+ *
+ * The response policy zone canonical form of an IPv6 address is one of:
+ * prefix.W.W.W.W.W.W.W.W
+ * prefix.WORDS.zz
+ * prefix.WORDS.zz.WORDS
+ * prefix.zz.WORDS
+ * where
+ * prefix is the prefix length of the IPv6 address between 1 and 128
+ * W is a number between 0 and 65535
+ * WORDS is one or more numbers W separated with "."
+ * zz corresponds to :: in the standard IPv6 text representation
+ *
+ * The canonical form of IPv4 addresses is:
+ * prefix.B.B.B.B
+ * where
+ * prefix is the prefix length of the address between 1 and 32
+ * B is a number between 0 and 255
+ *
+ * Names for IPv4 addresses are distinguished from IPv6 addresses by having
+ * 5 labels all of which are numbers, and a prefix between 1 and 32.
+ */
+
+/*
+ * Nodes hashtable calculation parameters
+ */
+#define DNS_RPZ_HTSIZE_MAX 24
+#define DNS_RPZ_HTSIZE_DIV 3
+
+static isc_result_t
+dns__rpz_shuttingdown(dns_rpz_zones_t *rpzs);
+static void
+dns__rpz_timer_cb(isc_task_t *task, isc_event_t *event);
+
+/*
+ * Use a private definition of IPv6 addresses because s6_addr32 is not
+ * always defined and our IPv6 addresses are in non-standard byte order
+ */
+typedef uint32_t dns_rpz_cidr_word_t;
+#define DNS_RPZ_CIDR_WORD_BITS ((int)sizeof(dns_rpz_cidr_word_t) * 8)
+#define DNS_RPZ_CIDR_KEY_BITS ((int)sizeof(dns_rpz_cidr_key_t) * 8)
+#define DNS_RPZ_CIDR_WORDS (128 / DNS_RPZ_CIDR_WORD_BITS)
+typedef struct {
+ dns_rpz_cidr_word_t w[DNS_RPZ_CIDR_WORDS];
+} dns_rpz_cidr_key_t;
+
+#define ADDR_V4MAPPED 0xffff
+#define KEY_IS_IPV4(prefix, ip) \
+ ((prefix) >= 96 && (ip)->w[0] == 0 && (ip)->w[1] == 0 && \
+ (ip)->w[2] == ADDR_V4MAPPED)
+
+#define DNS_RPZ_WORD_MASK(b) \
+ ((b) == 0 ? (dns_rpz_cidr_word_t)(-1) \
+ : ((dns_rpz_cidr_word_t)(-1) \
+ << (DNS_RPZ_CIDR_WORD_BITS - (b))))
+
+/*
+ * Get bit #n from the array of words of an IP address.
+ */
+#define DNS_RPZ_IP_BIT(ip, n) \
+ (1 & ((ip)->w[(n) / DNS_RPZ_CIDR_WORD_BITS] >> \
+ (DNS_RPZ_CIDR_WORD_BITS - 1 - ((n) % DNS_RPZ_CIDR_WORD_BITS))))
+
+/*
+ * A triplet of arrays of bits flagging the existence of
+ * client-IP, IP, and NSIP policy triggers.
+ */
+typedef struct dns_rpz_addr_zbits dns_rpz_addr_zbits_t;
+struct dns_rpz_addr_zbits {
+ dns_rpz_zbits_t client_ip;
+ dns_rpz_zbits_t ip;
+ dns_rpz_zbits_t nsip;
+};
+
+/*
+ * A CIDR or radix tree node.
+ */
+struct dns_rpz_cidr_node {
+ dns_rpz_cidr_node_t *parent;
+ dns_rpz_cidr_node_t *child[2];
+ dns_rpz_cidr_key_t ip;
+ dns_rpz_prefix_t prefix;
+ dns_rpz_addr_zbits_t set;
+ dns_rpz_addr_zbits_t sum;
+};
+
+/*
+ * A pair of arrays of bits flagging the existence of
+ * QNAME and NSDNAME policy triggers.
+ */
+typedef struct dns_rpz_nm_zbits dns_rpz_nm_zbits_t;
+struct dns_rpz_nm_zbits {
+ dns_rpz_zbits_t qname;
+ dns_rpz_zbits_t ns;
+};
+
+/*
+ * The data in a RBT node has two pairs of bits for policy zones.
+ * One pair is for the corresponding name of the node such as example.com
+ * and the other pair is for a wildcard child such as *.example.com.
+ */
+typedef struct dns_rpz_nm_data dns_rpz_nm_data_t;
+struct dns_rpz_nm_data {
+ dns_rpz_nm_zbits_t set;
+ dns_rpz_nm_zbits_t wild;
+};
+
+static isc_result_t
+rpz_add(dns_rpz_zone_t *rpz, const dns_name_t *src_name);
+static void
+rpz_del(dns_rpz_zone_t *rpz, const dns_name_t *src_name);
+
+const char *
+dns_rpz_type2str(dns_rpz_type_t type) {
+ switch (type) {
+ case DNS_RPZ_TYPE_CLIENT_IP:
+ return ("CLIENT-IP");
+ case DNS_RPZ_TYPE_QNAME:
+ return ("QNAME");
+ case DNS_RPZ_TYPE_IP:
+ return ("IP");
+ case DNS_RPZ_TYPE_NSIP:
+ return ("NSIP");
+ case DNS_RPZ_TYPE_NSDNAME:
+ return ("NSDNAME");
+ case DNS_RPZ_TYPE_BAD:
+ break;
+ }
+ FATAL_ERROR("impossible rpz type %d", type);
+ return ("impossible");
+}
+
+dns_rpz_policy_t
+dns_rpz_str2policy(const char *str) {
+ static struct {
+ const char *str;
+ dns_rpz_policy_t policy;
+ } tbl[] = {
+ { "given", DNS_RPZ_POLICY_GIVEN },
+ { "disabled", DNS_RPZ_POLICY_DISABLED },
+ { "passthru", DNS_RPZ_POLICY_PASSTHRU },
+ { "drop", DNS_RPZ_POLICY_DROP },
+ { "tcp-only", DNS_RPZ_POLICY_TCP_ONLY },
+ { "nxdomain", DNS_RPZ_POLICY_NXDOMAIN },
+ { "nodata", DNS_RPZ_POLICY_NODATA },
+ { "cname", DNS_RPZ_POLICY_CNAME },
+ { "no-op", DNS_RPZ_POLICY_PASSTHRU }, /* old passthru */
+ };
+ unsigned int n;
+
+ if (str == NULL) {
+ return (DNS_RPZ_POLICY_ERROR);
+ }
+ for (n = 0; n < sizeof(tbl) / sizeof(tbl[0]); ++n) {
+ if (!strcasecmp(tbl[n].str, str)) {
+ return (tbl[n].policy);
+ }
+ }
+ return (DNS_RPZ_POLICY_ERROR);
+}
+
+const char *
+dns_rpz_policy2str(dns_rpz_policy_t policy) {
+ const char *str = NULL;
+
+ switch (policy) {
+ case DNS_RPZ_POLICY_PASSTHRU:
+ str = "PASSTHRU";
+ break;
+ case DNS_RPZ_POLICY_DROP:
+ str = "DROP";
+ break;
+ case DNS_RPZ_POLICY_TCP_ONLY:
+ str = "TCP-ONLY";
+ break;
+ case DNS_RPZ_POLICY_NXDOMAIN:
+ str = "NXDOMAIN";
+ break;
+ case DNS_RPZ_POLICY_NODATA:
+ str = "NODATA";
+ break;
+ case DNS_RPZ_POLICY_RECORD:
+ str = "Local-Data";
+ break;
+ case DNS_RPZ_POLICY_CNAME:
+ case DNS_RPZ_POLICY_WILDCNAME:
+ str = "CNAME";
+ break;
+ case DNS_RPZ_POLICY_MISS:
+ str = "MISS";
+ break;
+ case DNS_RPZ_POLICY_DNS64:
+ str = "DNS64";
+ break;
+ case DNS_RPZ_POLICY_ERROR:
+ str = "ERROR";
+ break;
+ default:
+ UNREACHABLE();
+ }
+ return (str);
+}
+
+/*
+ * Return the bit number of the highest set bit in 'zbit'.
+ * (for example, 0x01 returns 0, 0xFF returns 7, etc.)
+ */
+static int
+zbit_to_num(dns_rpz_zbits_t zbit) {
+ dns_rpz_num_t rpz_num;
+
+ REQUIRE(zbit != 0);
+ rpz_num = 0;
+ if ((zbit & 0xffffffff00000000ULL) != 0) {
+ zbit >>= 32;
+ rpz_num += 32;
+ }
+ if ((zbit & 0xffff0000) != 0) {
+ zbit >>= 16;
+ rpz_num += 16;
+ }
+ if ((zbit & 0xff00) != 0) {
+ zbit >>= 8;
+ rpz_num += 8;
+ }
+ if ((zbit & 0xf0) != 0) {
+ zbit >>= 4;
+ rpz_num += 4;
+ }
+ if ((zbit & 0xc) != 0) {
+ zbit >>= 2;
+ rpz_num += 2;
+ }
+ if ((zbit & 2) != 0) {
+ ++rpz_num;
+ }
+ return (rpz_num);
+}
+
+/*
+ * Make a set of bit masks given one or more bits and their type.
+ */
+static void
+make_addr_set(dns_rpz_addr_zbits_t *tgt_set, dns_rpz_zbits_t zbits,
+ dns_rpz_type_t type) {
+ switch (type) {
+ case DNS_RPZ_TYPE_CLIENT_IP:
+ tgt_set->client_ip = zbits;
+ tgt_set->ip = 0;
+ tgt_set->nsip = 0;
+ break;
+ case DNS_RPZ_TYPE_IP:
+ tgt_set->client_ip = 0;
+ tgt_set->ip = zbits;
+ tgt_set->nsip = 0;
+ break;
+ case DNS_RPZ_TYPE_NSIP:
+ tgt_set->client_ip = 0;
+ tgt_set->ip = 0;
+ tgt_set->nsip = zbits;
+ break;
+ default:
+ UNREACHABLE();
+ }
+}
+
+static void
+make_nm_set(dns_rpz_nm_zbits_t *tgt_set, dns_rpz_num_t rpz_num,
+ dns_rpz_type_t type) {
+ switch (type) {
+ case DNS_RPZ_TYPE_QNAME:
+ tgt_set->qname = DNS_RPZ_ZBIT(rpz_num);
+ tgt_set->ns = 0;
+ break;
+ case DNS_RPZ_TYPE_NSDNAME:
+ tgt_set->qname = 0;
+ tgt_set->ns = DNS_RPZ_ZBIT(rpz_num);
+ break;
+ default:
+ UNREACHABLE();
+ }
+}
+
+/*
+ * Mark a node and all of its parents as having client-IP, IP, or NSIP data
+ */
+static void
+set_sum_pair(dns_rpz_cidr_node_t *cnode) {
+ dns_rpz_addr_zbits_t sum;
+
+ do {
+ dns_rpz_cidr_node_t *child = cnode->child[0];
+ sum = cnode->set;
+
+ if (child != NULL) {
+ sum.client_ip |= child->sum.client_ip;
+ sum.ip |= child->sum.ip;
+ sum.nsip |= child->sum.nsip;
+ }
+
+ child = cnode->child[1];
+ if (child != NULL) {
+ sum.client_ip |= child->sum.client_ip;
+ sum.ip |= child->sum.ip;
+ sum.nsip |= child->sum.nsip;
+ }
+
+ if (cnode->sum.client_ip == sum.client_ip &&
+ cnode->sum.ip == sum.ip && cnode->sum.nsip == sum.nsip)
+ {
+ break;
+ }
+ cnode->sum = sum;
+ cnode = cnode->parent;
+ } while (cnode != NULL);
+}
+
+/* Caller must hold rpzs->maint_lock */
+static void
+fix_qname_skip_recurse(dns_rpz_zones_t *rpzs) {
+ dns_rpz_zbits_t mask;
+
+ /*
+ * qname_wait_recurse and qname_skip_recurse are used to
+ * implement the "qname-wait-recurse" config option.
+ *
+ * When "qname-wait-recurse" is yes, no processing happens without
+ * recursion. In this case, qname_wait_recurse is true, and
+ * qname_skip_recurse (a bit field indicating which policy zones
+ * can be processed without recursion) is set to all 0's by
+ * fix_qname_skip_recurse().
+ *
+ * When "qname-wait-recurse" is no, qname_skip_recurse may be
+ * set to a non-zero value by fix_qname_skip_recurse(). The mask
+ * has to have bits set for the policy zones for which
+ * processing may continue without recursion, and bits cleared
+ * for the rest.
+ *
+ * (1) The ARM says:
+ *
+ * The "qname-wait-recurse no" option overrides that default
+ * behavior when recursion cannot change a non-error
+ * response. The option does not affect QNAME or client-IP
+ * triggers in policy zones listed after other zones
+ * containing IP, NSIP and NSDNAME triggers, because those may
+ * depend on the A, AAAA, and NS records that would be found
+ * during recursive resolution.
+ *
+ * Let's consider the following:
+ *
+ * zbits_req = (rpzs->have.ipv4 | rpzs->have.ipv6 |
+ * rpzs->have.nsdname |
+ * rpzs->have.nsipv4 | rpzs->have.nsipv6);
+ *
+ * zbits_req now contains bits set for zones which require
+ * recursion.
+ *
+ * But going by the description in the ARM, if the first policy
+ * zone requires recursion, then all zones after that (higher
+ * order bits) have to wait as well. If the Nth zone requires
+ * recursion, then (N+1)th zone onwards all need to wait.
+ *
+ * So mapping this, examples:
+ *
+ * zbits_req = 0b000 mask = 0xffffffff (no zones have to wait for
+ * recursion)
+ * zbits_req = 0b001 mask = 0x00000000 (all zones have to wait)
+ * zbits_req = 0b010 mask = 0x00000001 (the first zone doesn't have to
+ * wait, second zone onwards need
+ * to wait)
+ * zbits_req = 0b011 mask = 0x00000000 (all zones have to wait)
+ * zbits_req = 0b100 mask = 0x00000011 (the 1st and 2nd zones don't
+ * have to wait, third zone
+ * onwards need to wait)
+ *
+ * More generally, we have to count the number of trailing 0
+ * bits in zbits_req and only these can be processed without
+ * recursion. All the rest need to wait.
+ *
+ * (2) The ARM says that "qname-wait-recurse no" option
+ * overrides the default behavior when recursion cannot change a
+ * non-error response. So, in the order of listing of policy
+ * zones, within the first policy zone where recursion may be
+ * required, we should first allow CLIENT-IP and QNAME policy
+ * records to be attempted without recursion.
+ */
+
+ /*
+ * Get a mask covering all policy zones that are not subordinate to
+ * other policy zones containing triggers that require that the
+ * qname be resolved before they can be checked.
+ */
+ rpzs->have.client_ip = rpzs->have.client_ipv4 | rpzs->have.client_ipv6;
+ rpzs->have.ip = rpzs->have.ipv4 | rpzs->have.ipv6;
+ rpzs->have.nsip = rpzs->have.nsipv4 | rpzs->have.nsipv6;
+
+ if (rpzs->p.qname_wait_recurse) {
+ mask = 0;
+ } else {
+ dns_rpz_zbits_t zbits_req;
+ dns_rpz_zbits_t zbits_notreq;
+ dns_rpz_zbits_t mask2;
+ dns_rpz_zbits_t req_mask;
+
+ /*
+ * Get the masks of zones with policies that
+ * do/don't require recursion
+ */
+
+ zbits_req = (rpzs->have.ipv4 | rpzs->have.ipv6 |
+ rpzs->have.nsdname | rpzs->have.nsipv4 |
+ rpzs->have.nsipv6);
+ zbits_notreq = (rpzs->have.client_ip | rpzs->have.qname);
+
+ if (zbits_req == 0) {
+ mask = DNS_RPZ_ALL_ZBITS;
+ goto set;
+ }
+
+ /*
+ * req_mask is a mask covering used bits in
+ * zbits_req. (For instance, 0b1 => 0b1, 0b101 => 0b111,
+ * 0b11010101 => 0b11111111).
+ */
+ req_mask = zbits_req;
+ req_mask |= req_mask >> 1;
+ req_mask |= req_mask >> 2;
+ req_mask |= req_mask >> 4;
+ req_mask |= req_mask >> 8;
+ req_mask |= req_mask >> 16;
+ req_mask |= req_mask >> 32;
+
+ /*
+ * There's no point in skipping recursion for a later
+ * zone if it is required in a previous zone.
+ */
+ if ((zbits_notreq & req_mask) == 0) {
+ mask = 0;
+ goto set;
+ }
+
+ /*
+ * This bit arithmetic creates a mask of zones in which
+ * it is okay to skip recursion. After the first zone
+ * that has to wait for recursion, all the others have
+ * to wait as well, so we want to create a mask in which
+ * all the trailing zeroes in zbits_req are are 1, and
+ * more significant bits are 0. (For instance,
+ * 0x0700 => 0x00ff, 0x0007 => 0x0000)
+ */
+ mask = ~(zbits_req | ((~zbits_req) + 1));
+
+ /*
+ * As mentioned in (2) above, the zone corresponding to
+ * the least significant zero could have its CLIENT-IP
+ * and QNAME policies checked before recursion, if it
+ * has any of those policies. So if it does, we
+ * can set its 0 to 1.
+ *
+ * Locate the least significant 0 bit in the mask (for
+ * instance, 0xff => 0x100)...
+ */
+ mask2 = (mask << 1) & ~mask;
+
+ /*
+ * Also set the bit for zone 0, because if it's in
+ * zbits_notreq then it's definitely okay to attempt to
+ * skip recursion for zone 0...
+ */
+ mask2 |= 1;
+
+ /* Clear any bits *not* in zbits_notreq... */
+ mask2 &= zbits_notreq;
+
+ /* And merge the result into the skip-recursion mask */
+ mask |= mask2;
+ }
+
+set:
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_RPZ, DNS_LOGMODULE_RBTDB,
+ DNS_RPZ_DEBUG_QUIET,
+ "computed RPZ qname_skip_recurse mask=0x%" PRIx64,
+ (uint64_t)mask);
+ rpzs->have.qname_skip_recurse = mask;
+}
+
+static void
+adj_trigger_cnt(dns_rpz_zone_t *rpz, dns_rpz_type_t rpz_type,
+ const dns_rpz_cidr_key_t *tgt_ip, dns_rpz_prefix_t tgt_prefix,
+ bool inc) {
+ dns_rpz_trigger_counter_t *cnt = NULL;
+ dns_rpz_zbits_t *have = NULL;
+
+ switch (rpz_type) {
+ case DNS_RPZ_TYPE_CLIENT_IP:
+ REQUIRE(tgt_ip != NULL);
+ if (KEY_IS_IPV4(tgt_prefix, tgt_ip)) {
+ cnt = &rpz->rpzs->triggers[rpz->num].client_ipv4;
+ have = &rpz->rpzs->have.client_ipv4;
+ } else {
+ cnt = &rpz->rpzs->triggers[rpz->num].client_ipv6;
+ have = &rpz->rpzs->have.client_ipv6;
+ }
+ break;
+ case DNS_RPZ_TYPE_QNAME:
+ cnt = &rpz->rpzs->triggers[rpz->num].qname;
+ have = &rpz->rpzs->have.qname;
+ break;
+ case DNS_RPZ_TYPE_IP:
+ REQUIRE(tgt_ip != NULL);
+ if (KEY_IS_IPV4(tgt_prefix, tgt_ip)) {
+ cnt = &rpz->rpzs->triggers[rpz->num].ipv4;
+ have = &rpz->rpzs->have.ipv4;
+ } else {
+ cnt = &rpz->rpzs->triggers[rpz->num].ipv6;
+ have = &rpz->rpzs->have.ipv6;
+ }
+ break;
+ case DNS_RPZ_TYPE_NSDNAME:
+ cnt = &rpz->rpzs->triggers[rpz->num].nsdname;
+ have = &rpz->rpzs->have.nsdname;
+ break;
+ case DNS_RPZ_TYPE_NSIP:
+ REQUIRE(tgt_ip != NULL);
+ if (KEY_IS_IPV4(tgt_prefix, tgt_ip)) {
+ cnt = &rpz->rpzs->triggers[rpz->num].nsipv4;
+ have = &rpz->rpzs->have.nsipv4;
+ } else {
+ cnt = &rpz->rpzs->triggers[rpz->num].nsipv6;
+ have = &rpz->rpzs->have.nsipv6;
+ }
+ break;
+ default:
+ UNREACHABLE();
+ }
+
+ if (inc) {
+ if (++*cnt == 1U) {
+ *have |= DNS_RPZ_ZBIT(rpz->num);
+ fix_qname_skip_recurse(rpz->rpzs);
+ }
+ } else {
+ REQUIRE(*cnt != 0U);
+ if (--*cnt == 0U) {
+ *have &= ~DNS_RPZ_ZBIT(rpz->num);
+ fix_qname_skip_recurse(rpz->rpzs);
+ }
+ }
+}
+
+static dns_rpz_cidr_node_t *
+new_node(dns_rpz_zones_t *rpzs, const dns_rpz_cidr_key_t *ip,
+ dns_rpz_prefix_t prefix, const dns_rpz_cidr_node_t *child) {
+ dns_rpz_cidr_node_t *node = NULL;
+ int i, words, wlen;
+
+ node = isc_mem_get(rpzs->mctx, sizeof(*node));
+ *node = (dns_rpz_cidr_node_t){
+ .prefix = prefix,
+ };
+
+ if (child != NULL) {
+ node->sum = child->sum;
+ }
+
+ words = prefix / DNS_RPZ_CIDR_WORD_BITS;
+ wlen = prefix % DNS_RPZ_CIDR_WORD_BITS;
+ i = 0;
+ while (i < words) {
+ node->ip.w[i] = ip->w[i];
+ ++i;
+ }
+ if (wlen != 0) {
+ node->ip.w[i] = ip->w[i] & DNS_RPZ_WORD_MASK(wlen);
+ ++i;
+ }
+ while (i < DNS_RPZ_CIDR_WORDS) {
+ node->ip.w[i++] = 0;
+ }
+
+ return (node);
+}
+
+static void
+badname(int level, const dns_name_t *name, const char *str1, const char *str2) {
+ /*
+ * bin/tests/system/rpz/tests.sh looks for "invalid rpz".
+ */
+ if (level < DNS_RPZ_DEBUG_QUIET && isc_log_wouldlog(dns_lctx, level)) {
+ char namebuf[DNS_NAME_FORMATSIZE];
+ dns_name_format(name, namebuf, sizeof(namebuf));
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_RPZ,
+ DNS_LOGMODULE_RBTDB, level,
+ "invalid rpz IP address \"%s\"%s%s", namebuf,
+ str1, str2);
+ }
+}
+
+/*
+ * Convert an IP address from radix tree binary (host byte order) to
+ * to its canonical response policy domain name without the origin of the
+ * policy zone.
+ *
+ * Generate a name for an IPv6 address that fits RFC 5952, except that our
+ * reversed format requires that when the length of the consecutive 16-bit
+ * 0 fields are equal (e.g., 1.0.0.1.0.0.db8.2001 corresponding to
+ * 2001:db8:0:0:1:0:0:1), we shorted the last instead of the first
+ * (e.g., 1.0.0.1.zz.db8.2001 corresponding to 2001:db8::1:0:0:1).
+ */
+static isc_result_t
+ip2name(const dns_rpz_cidr_key_t *tgt_ip, dns_rpz_prefix_t tgt_prefix,
+ const dns_name_t *base_name, dns_name_t *ip_name) {
+#ifndef INET6_ADDRSTRLEN
+#define INET6_ADDRSTRLEN 46
+#endif /* ifndef INET6_ADDRSTRLEN */
+ char str[1 + 8 + 1 + INET6_ADDRSTRLEN + 1];
+ isc_buffer_t buffer;
+ isc_result_t result;
+ int len;
+
+ if (KEY_IS_IPV4(tgt_prefix, tgt_ip)) {
+ len = snprintf(str, sizeof(str), "%u.%u.%u.%u.%u",
+ tgt_prefix - 96U, tgt_ip->w[3] & 0xffU,
+ (tgt_ip->w[3] >> 8) & 0xffU,
+ (tgt_ip->w[3] >> 16) & 0xffU,
+ (tgt_ip->w[3] >> 24) & 0xffU);
+ if (len < 0 || (size_t)len >= sizeof(str)) {
+ return (ISC_R_FAILURE);
+ }
+ } else {
+ int w[DNS_RPZ_CIDR_WORDS * 2];
+ int best_first, best_len, cur_first, cur_len;
+
+ len = snprintf(str, sizeof(str), "%d", tgt_prefix);
+ if (len < 0 || (size_t)len >= sizeof(str)) {
+ return (ISC_R_FAILURE);
+ }
+
+ for (int n = 0; n < DNS_RPZ_CIDR_WORDS; n++) {
+ w[n * 2 + 1] =
+ ((tgt_ip->w[DNS_RPZ_CIDR_WORDS - 1 - n] >> 16) &
+ 0xffff);
+ w[n * 2] = tgt_ip->w[DNS_RPZ_CIDR_WORDS - 1 - n] &
+ 0xffff;
+ }
+ /*
+ * Find the start and length of the first longest sequence
+ * of zeros in the address.
+ */
+ best_first = -1;
+ best_len = 0;
+ cur_first = -1;
+ cur_len = 0;
+ for (int n = 0; n <= 7; ++n) {
+ if (w[n] != 0) {
+ cur_len = 0;
+ cur_first = -1;
+ } else {
+ ++cur_len;
+ if (cur_first < 0) {
+ cur_first = n;
+ } else if (cur_len >= best_len) {
+ best_first = cur_first;
+ best_len = cur_len;
+ }
+ }
+ }
+
+ for (int n = 0; n <= 7; ++n) {
+ int i;
+
+ INSIST(len > 0 && (size_t)len < sizeof(str));
+ if (n == best_first) {
+ i = snprintf(str + len, sizeof(str) - len,
+ ".zz");
+ n += best_len - 1;
+ } else {
+ i = snprintf(str + len, sizeof(str) - len,
+ ".%x", w[n]);
+ }
+ if (i < 0 || (size_t)i >= (size_t)(sizeof(str) - len)) {
+ return (ISC_R_FAILURE);
+ }
+ len += i;
+ }
+ }
+
+ isc_buffer_init(&buffer, str, sizeof(str));
+ isc_buffer_add(&buffer, len);
+ result = dns_name_fromtext(ip_name, &buffer, base_name, 0, NULL);
+ return (result);
+}
+
+/*
+ * Determine the type of a name in a response policy zone.
+ */
+static dns_rpz_type_t
+type_from_name(const dns_rpz_zones_t *rpzs, dns_rpz_zone_t *rpz,
+ const dns_name_t *name) {
+ if (dns_name_issubdomain(name, &rpz->ip)) {
+ return (DNS_RPZ_TYPE_IP);
+ }
+
+ if (dns_name_issubdomain(name, &rpz->client_ip)) {
+ return (DNS_RPZ_TYPE_CLIENT_IP);
+ }
+
+ if ((rpzs->p.nsip_on & DNS_RPZ_ZBIT(rpz->num)) != 0 &&
+ dns_name_issubdomain(name, &rpz->nsip))
+ {
+ return (DNS_RPZ_TYPE_NSIP);
+ }
+
+ if ((rpzs->p.nsdname_on & DNS_RPZ_ZBIT(rpz->num)) != 0 &&
+ dns_name_issubdomain(name, &rpz->nsdname))
+ {
+ return (DNS_RPZ_TYPE_NSDNAME);
+ }
+
+ return (DNS_RPZ_TYPE_QNAME);
+}
+
+/*
+ * Convert an IP address from canonical response policy domain name form
+ * to radix tree binary (host byte order) for adding or deleting IP or NSIP
+ * data.
+ */
+static isc_result_t
+name2ipkey(int log_level, dns_rpz_zone_t *rpz, dns_rpz_type_t rpz_type,
+ const dns_name_t *src_name, dns_rpz_cidr_key_t *tgt_ip,
+ dns_rpz_prefix_t *tgt_prefix, dns_rpz_addr_zbits_t *new_set) {
+ char ip_str[DNS_NAME_FORMATSIZE];
+ dns_offsets_t ip_name_offsets;
+ dns_fixedname_t ip_name2f;
+ dns_name_t ip_name;
+ const char *prefix_str = NULL, *cp = NULL, *end = NULL;
+ char *cp2;
+ int ip_labels;
+ dns_rpz_prefix_t prefix;
+ unsigned long prefix_num, l;
+ isc_result_t result;
+ int i;
+
+ REQUIRE(rpz != NULL);
+ REQUIRE(rpz->rpzs != NULL && rpz->num < rpz->rpzs->p.num_zones);
+
+ make_addr_set(new_set, DNS_RPZ_ZBIT(rpz->num), rpz_type);
+
+ ip_labels = dns_name_countlabels(src_name);
+ if (rpz_type == DNS_RPZ_TYPE_QNAME) {
+ ip_labels -= dns_name_countlabels(&rpz->origin);
+ } else {
+ ip_labels -= dns_name_countlabels(&rpz->nsdname);
+ }
+ if (ip_labels < 2) {
+ badname(log_level, src_name, "; too short", "");
+ return (ISC_R_FAILURE);
+ }
+ dns_name_init(&ip_name, ip_name_offsets);
+ dns_name_getlabelsequence(src_name, 0, ip_labels, &ip_name);
+
+ /*
+ * Get text for the IP address
+ */
+ dns_name_format(&ip_name, ip_str, sizeof(ip_str));
+ end = &ip_str[strlen(ip_str) + 1];
+ prefix_str = ip_str;
+
+ prefix_num = strtoul(prefix_str, &cp2, 10);
+ if (*cp2 != '.') {
+ badname(log_level, src_name, "; invalid leading prefix length",
+ "");
+ return (ISC_R_FAILURE);
+ }
+ /*
+ * Patch in trailing nul character to print just the length
+ * label (for various cases below).
+ */
+ *cp2 = '\0';
+ if (prefix_num < 1U || prefix_num > 128U) {
+ badname(log_level, src_name, "; invalid prefix length of ",
+ prefix_str);
+ return (ISC_R_FAILURE);
+ }
+ cp = cp2 + 1;
+
+ if (--ip_labels == 4 && !strchr(cp, 'z')) {
+ /*
+ * Convert an IPv4 address
+ * from the form "prefix.z.y.x.w"
+ */
+ if (prefix_num > 32U) {
+ badname(log_level, src_name,
+ "; invalid IPv4 prefix length of ", prefix_str);
+ return (ISC_R_FAILURE);
+ }
+ prefix_num += 96;
+ *tgt_prefix = (dns_rpz_prefix_t)prefix_num;
+ tgt_ip->w[0] = 0;
+ tgt_ip->w[1] = 0;
+ tgt_ip->w[2] = ADDR_V4MAPPED;
+ tgt_ip->w[3] = 0;
+ for (i = 0; i < 32; i += 8) {
+ l = strtoul(cp, &cp2, 10);
+ if (l > 255U || (*cp2 != '.' && *cp2 != '\0')) {
+ if (*cp2 == '.') {
+ *cp2 = '\0';
+ }
+ badname(log_level, src_name,
+ "; invalid IPv4 octet ", cp);
+ return (ISC_R_FAILURE);
+ }
+ tgt_ip->w[3] |= l << i;
+ cp = cp2 + 1;
+ }
+ } else {
+ /*
+ * Convert a text IPv6 address.
+ */
+ *tgt_prefix = (dns_rpz_prefix_t)prefix_num;
+ for (i = 0; ip_labels > 0 && i < DNS_RPZ_CIDR_WORDS * 2;
+ ip_labels--)
+ {
+ if (cp[0] == 'z' && cp[1] == 'z' &&
+ (cp[2] == '.' || cp[2] == '\0') && i <= 6)
+ {
+ do {
+ if ((i & 1) == 0) {
+ tgt_ip->w[3 - i / 2] = 0;
+ }
+ ++i;
+ } while (ip_labels + i <= 8);
+ cp += 3;
+ } else {
+ l = strtoul(cp, &cp2, 16);
+ if (l > 0xffffu ||
+ (*cp2 != '.' && *cp2 != '\0'))
+ {
+ if (*cp2 == '.') {
+ *cp2 = '\0';
+ }
+ badname(log_level, src_name,
+ "; invalid IPv6 word ", cp);
+ return (ISC_R_FAILURE);
+ }
+ if ((i & 1) == 0) {
+ tgt_ip->w[3 - i / 2] = l;
+ } else {
+ tgt_ip->w[3 - i / 2] |= l << 16;
+ }
+ i++;
+ cp = cp2 + 1;
+ }
+ }
+ }
+ if (cp != end) {
+ badname(log_level, src_name, "", "");
+ return (ISC_R_FAILURE);
+ }
+
+ /*
+ * Check for 1s after the prefix length.
+ */
+ prefix = (dns_rpz_prefix_t)prefix_num;
+ while (prefix < DNS_RPZ_CIDR_KEY_BITS) {
+ dns_rpz_cidr_word_t aword;
+
+ i = prefix % DNS_RPZ_CIDR_WORD_BITS;
+ aword = tgt_ip->w[prefix / DNS_RPZ_CIDR_WORD_BITS];
+ if ((aword & ~DNS_RPZ_WORD_MASK(i)) != 0) {
+ badname(log_level, src_name,
+ "; too small prefix length of ", prefix_str);
+ return (ISC_R_FAILURE);
+ }
+ prefix -= i;
+ prefix += DNS_RPZ_CIDR_WORD_BITS;
+ }
+
+ /*
+ * Complain about bad names but be generous and accept them.
+ */
+ if (log_level < DNS_RPZ_DEBUG_QUIET &&
+ isc_log_wouldlog(dns_lctx, log_level))
+ {
+ /*
+ * Convert the address back to a canonical domain name
+ * to ensure that the original name is in canonical form.
+ */
+ dns_name_t *ip_name2 = dns_fixedname_initname(&ip_name2f);
+ result = ip2name(tgt_ip, (dns_rpz_prefix_t)prefix_num, NULL,
+ ip_name2);
+ if (result != ISC_R_SUCCESS ||
+ !dns_name_equal(&ip_name, ip_name2))
+ {
+ char ip2_str[DNS_NAME_FORMATSIZE];
+ dns_name_format(ip_name2, ip2_str, sizeof(ip2_str));
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_RPZ,
+ DNS_LOGMODULE_RBTDB, log_level,
+ "rpz IP address \"%s\""
+ " is not the canonical \"%s\"",
+ ip_str, ip2_str);
+ }
+ }
+
+ return (ISC_R_SUCCESS);
+}
+
+/*
+ * Get trigger name and data bits for adding or deleting summary NSDNAME
+ * or QNAME data.
+ */
+static void
+name2data(dns_rpz_zone_t *rpz, dns_rpz_type_t rpz_type,
+ const dns_name_t *src_name, dns_name_t *trig_name,
+ dns_rpz_nm_data_t *new_data) {
+ dns_offsets_t tmp_name_offsets;
+ dns_name_t tmp_name;
+ unsigned int prefix_len, n;
+
+ REQUIRE(rpz != NULL);
+ REQUIRE(rpz->rpzs != NULL && rpz->num < rpz->rpzs->p.num_zones);
+
+ /*
+ * Handle wildcards by putting only the parent into the
+ * summary RBT. The summary database only causes a check of the
+ * real policy zone where wildcards will be handled.
+ */
+ if (dns_name_iswildcard(src_name)) {
+ prefix_len = 1;
+ memset(&new_data->set, 0, sizeof(new_data->set));
+ make_nm_set(&new_data->wild, rpz->num, rpz_type);
+ } else {
+ prefix_len = 0;
+ make_nm_set(&new_data->set, rpz->num, rpz_type);
+ memset(&new_data->wild, 0, sizeof(new_data->wild));
+ }
+
+ dns_name_init(&tmp_name, tmp_name_offsets);
+ n = dns_name_countlabels(src_name);
+ n -= prefix_len;
+ if (rpz_type == DNS_RPZ_TYPE_QNAME) {
+ n -= dns_name_countlabels(&rpz->origin);
+ } else {
+ n -= dns_name_countlabels(&rpz->nsdname);
+ }
+ dns_name_getlabelsequence(src_name, prefix_len, n, &tmp_name);
+ (void)dns_name_concatenate(&tmp_name, dns_rootname, trig_name, NULL);
+}
+
+#ifndef HAVE_BUILTIN_CLZ
+/**
+ * \brief Count Leading Zeros: Find the location of the left-most set
+ * bit.
+ */
+static unsigned int
+clz(dns_rpz_cidr_word_t w) {
+ unsigned int bit;
+
+ bit = DNS_RPZ_CIDR_WORD_BITS - 1;
+
+ if ((w & 0xffff0000) != 0) {
+ w >>= 16;
+ bit -= 16;
+ }
+
+ if ((w & 0xff00) != 0) {
+ w >>= 8;
+ bit -= 8;
+ }
+
+ if ((w & 0xf0) != 0) {
+ w >>= 4;
+ bit -= 4;
+ }
+
+ if ((w & 0xc) != 0) {
+ w >>= 2;
+ bit -= 2;
+ }
+
+ if ((w & 2) != 0) {
+ --bit;
+ }
+
+ return (bit);
+}
+#endif /* ifndef HAVE_BUILTIN_CLZ */
+
+/*
+ * Find the first differing bit in two keys (IP addresses).
+ */
+static int
+diff_keys(const dns_rpz_cidr_key_t *key1, dns_rpz_prefix_t prefix1,
+ const dns_rpz_cidr_key_t *key2, dns_rpz_prefix_t prefix2) {
+ dns_rpz_cidr_word_t delta;
+ dns_rpz_prefix_t maxbit, bit;
+ int i;
+
+ bit = 0;
+ maxbit = ISC_MIN(prefix1, prefix2);
+
+ /*
+ * find the first differing words
+ */
+ for (i = 0; bit < maxbit; i++, bit += DNS_RPZ_CIDR_WORD_BITS) {
+ delta = key1->w[i] ^ key2->w[i];
+ if (delta != 0) {
+#ifdef HAVE_BUILTIN_CLZ
+ bit += __builtin_clz(delta);
+#else /* ifdef HAVE_BUILTIN_CLZ */
+ bit += clz(delta);
+#endif /* ifdef HAVE_BUILTIN_CLZ */
+ break;
+ }
+ }
+ return (ISC_MIN(bit, maxbit));
+}
+
+/*
+ * Given a hit while searching the radix trees,
+ * clear all bits for higher numbered zones.
+ */
+static dns_rpz_zbits_t
+trim_zbits(dns_rpz_zbits_t zbits, dns_rpz_zbits_t found) {
+ dns_rpz_zbits_t x;
+
+ /*
+ * Isolate the first or smallest numbered hit bit.
+ * Make a mask of that bit and all smaller numbered bits.
+ */
+ x = zbits & found;
+ x &= (~x + 1);
+ x = (x << 1) - 1;
+ zbits &= x;
+ return (zbits);
+}
+
+/*
+ * Search a radix tree for an IP address for ordinary lookup
+ * or for a CIDR block adding or deleting an entry
+ *
+ * Return ISC_R_SUCCESS, DNS_R_PARTIALMATCH, ISC_R_NOTFOUND,
+ * and *found=longest match node
+ * or with create==true, ISC_R_EXISTS or ISC_R_NOMEMORY
+ */
+static isc_result_t
+search(dns_rpz_zones_t *rpzs, const dns_rpz_cidr_key_t *tgt_ip,
+ dns_rpz_prefix_t tgt_prefix, const dns_rpz_addr_zbits_t *tgt_set,
+ bool create, dns_rpz_cidr_node_t **found) {
+ dns_rpz_cidr_node_t *cur = NULL, *parent = NULL, *child = NULL;
+ dns_rpz_cidr_node_t *new_parent = NULL, *sibling = NULL;
+ dns_rpz_addr_zbits_t set;
+ int cur_num, child_num;
+ isc_result_t find_result;
+
+ set = *tgt_set;
+ find_result = ISC_R_NOTFOUND;
+ *found = NULL;
+ cur = rpzs->cidr;
+ parent = NULL;
+ cur_num = 0;
+ for (;;) {
+ dns_rpz_prefix_t dbit;
+ if (cur == NULL) {
+ /*
+ * No child so we cannot go down.
+ * Quit with whatever we already found
+ * or add the target as a child of the current parent.
+ */
+ if (!create) {
+ return (find_result);
+ }
+ child = new_node(rpzs, tgt_ip, tgt_prefix, NULL);
+ if (child == NULL) {
+ return (ISC_R_NOMEMORY);
+ }
+ if (parent == NULL) {
+ rpzs->cidr = child;
+ } else {
+ parent->child[cur_num] = child;
+ }
+ child->parent = parent;
+ child->set.client_ip |= tgt_set->client_ip;
+ child->set.ip |= tgt_set->ip;
+ child->set.nsip |= tgt_set->nsip;
+ set_sum_pair(child);
+ *found = child;
+ return (ISC_R_SUCCESS);
+ }
+
+ if ((cur->sum.client_ip & set.client_ip) == 0 &&
+ (cur->sum.ip & set.ip) == 0 &&
+ (cur->sum.nsip & set.nsip) == 0)
+ {
+ /*
+ * This node has no relevant data
+ * and is in none of the target trees.
+ * Pretend it does not exist if we are not adding.
+ *
+ * If we are adding, continue down to eventually add
+ * a node and mark/put this node in the correct tree.
+ */
+ if (!create) {
+ return (find_result);
+ }
+ }
+
+ dbit = diff_keys(tgt_ip, tgt_prefix, &cur->ip, cur->prefix);
+ /*
+ * dbit <= tgt_prefix and dbit <= cur->prefix always.
+ * We are finished searching if we matched all of the target.
+ */
+ if (dbit == tgt_prefix) {
+ if (tgt_prefix == cur->prefix) {
+ /*
+ * The node's key matches the target exactly.
+ */
+ if ((cur->set.client_ip & set.client_ip) != 0 ||
+ (cur->set.ip & set.ip) != 0 ||
+ (cur->set.nsip & set.nsip) != 0)
+ {
+ /*
+ * It is the answer if it has data.
+ */
+ *found = cur;
+ if (create) {
+ find_result = ISC_R_EXISTS;
+ } else {
+ find_result = ISC_R_SUCCESS;
+ }
+ } else if (create) {
+ /*
+ * The node lacked relevant data,
+ * but will have it now.
+ */
+ cur->set.client_ip |=
+ tgt_set->client_ip;
+ cur->set.ip |= tgt_set->ip;
+ cur->set.nsip |= tgt_set->nsip;
+ set_sum_pair(cur);
+ *found = cur;
+ find_result = ISC_R_SUCCESS;
+ }
+ return (find_result);
+ }
+
+ /*
+ * We know tgt_prefix < cur->prefix which means that
+ * the target is shorter than the current node.
+ * Add the target as the current node's parent.
+ */
+ if (!create) {
+ return (find_result);
+ }
+
+ new_parent = new_node(rpzs, tgt_ip, tgt_prefix, cur);
+ if (new_parent == NULL) {
+ return (ISC_R_NOMEMORY);
+ }
+ new_parent->parent = parent;
+ if (parent == NULL) {
+ rpzs->cidr = new_parent;
+ } else {
+ parent->child[cur_num] = new_parent;
+ }
+ child_num = DNS_RPZ_IP_BIT(&cur->ip, tgt_prefix);
+ new_parent->child[child_num] = cur;
+ cur->parent = new_parent;
+ new_parent->set = *tgt_set;
+ set_sum_pair(new_parent);
+ *found = new_parent;
+ return (ISC_R_SUCCESS);
+ }
+
+ if (dbit == cur->prefix) {
+ if ((cur->set.client_ip & set.client_ip) != 0 ||
+ (cur->set.ip & set.ip) != 0 ||
+ (cur->set.nsip & set.nsip) != 0)
+ {
+ /*
+ * We have a partial match between of all of the
+ * current node but only part of the target.
+ * Continue searching for other hits in the
+ * same or lower numbered trees.
+ */
+ find_result = DNS_R_PARTIALMATCH;
+ *found = cur;
+ set.client_ip = trim_zbits(set.client_ip,
+ cur->set.client_ip);
+ set.ip = trim_zbits(set.ip, cur->set.ip);
+ set.nsip = trim_zbits(set.nsip, cur->set.nsip);
+ }
+ parent = cur;
+ cur_num = DNS_RPZ_IP_BIT(tgt_ip, dbit);
+ cur = cur->child[cur_num];
+ continue;
+ }
+
+ /*
+ * dbit < tgt_prefix and dbit < cur->prefix,
+ * so we failed to match both the target and the current node.
+ * Insert a fork of a parent above the current node and
+ * add the target as a sibling of the current node
+ */
+ if (!create) {
+ return (find_result);
+ }
+
+ sibling = new_node(rpzs, tgt_ip, tgt_prefix, NULL);
+ if (sibling == NULL) {
+ return (ISC_R_NOMEMORY);
+ }
+ new_parent = new_node(rpzs, tgt_ip, dbit, cur);
+ if (new_parent == NULL) {
+ isc_mem_put(rpzs->mctx, sibling, sizeof(*sibling));
+ return (ISC_R_NOMEMORY);
+ }
+ new_parent->parent = parent;
+ if (parent == NULL) {
+ rpzs->cidr = new_parent;
+ } else {
+ parent->child[cur_num] = new_parent;
+ }
+ child_num = DNS_RPZ_IP_BIT(tgt_ip, dbit);
+ new_parent->child[child_num] = sibling;
+ new_parent->child[1 - child_num] = cur;
+ cur->parent = new_parent;
+ sibling->parent = new_parent;
+ sibling->set = *tgt_set;
+ set_sum_pair(sibling);
+ *found = sibling;
+ return (ISC_R_SUCCESS);
+ }
+}
+
+/*
+ * Add an IP address to the radix tree.
+ */
+static isc_result_t
+add_cidr(dns_rpz_zone_t *rpz, dns_rpz_type_t rpz_type,
+ const dns_name_t *src_name) {
+ dns_rpz_cidr_key_t tgt_ip;
+ dns_rpz_prefix_t tgt_prefix;
+ dns_rpz_addr_zbits_t set;
+ dns_rpz_cidr_node_t *found = NULL;
+ isc_result_t result;
+
+ result = name2ipkey(DNS_RPZ_ERROR_LEVEL, rpz, rpz_type, src_name,
+ &tgt_ip, &tgt_prefix, &set);
+ /*
+ * Log complaints about bad owner names but let the zone load.
+ */
+ if (result != ISC_R_SUCCESS) {
+ return (ISC_R_SUCCESS);
+ }
+
+ result = search(rpz->rpzs, &tgt_ip, tgt_prefix, &set, true, &found);
+ if (result != ISC_R_SUCCESS) {
+ char namebuf[DNS_NAME_FORMATSIZE];
+
+ /*
+ * Do not worry if the radix tree already exists,
+ * because diff_apply() likes to add nodes before deleting.
+ */
+ if (result == ISC_R_EXISTS) {
+ return (ISC_R_SUCCESS);
+ }
+
+ /*
+ * bin/tests/system/rpz/tests.sh looks for "rpz.*failed".
+ */
+ dns_name_format(src_name, namebuf, sizeof(namebuf));
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_RPZ,
+ DNS_LOGMODULE_RBTDB, DNS_RPZ_ERROR_LEVEL,
+ "rpz add_cidr(%s) failed: %s", namebuf,
+ isc_result_totext(result));
+ return (result);
+ }
+
+ adj_trigger_cnt(rpz, rpz_type, &tgt_ip, tgt_prefix, true);
+ return (result);
+}
+
+static isc_result_t
+add_nm(dns_rpz_zones_t *rpzs, dns_name_t *trig_name,
+ const dns_rpz_nm_data_t *new_data) {
+ dns_rbtnode_t *nmnode = NULL;
+ dns_rpz_nm_data_t *nm_data = NULL;
+ isc_result_t result;
+
+ nmnode = NULL;
+ result = dns_rbt_addnode(rpzs->rbt, trig_name, &nmnode);
+ switch (result) {
+ case ISC_R_SUCCESS:
+ case ISC_R_EXISTS:
+ nm_data = nmnode->data;
+ if (nm_data == NULL) {
+ nm_data = isc_mem_get(rpzs->mctx, sizeof(*nm_data));
+ *nm_data = *new_data;
+ nmnode->data = nm_data;
+ return (ISC_R_SUCCESS);
+ }
+ break;
+ default:
+ return (result);
+ }
+
+ /*
+ * Do not count bits that are already present
+ */
+ if ((nm_data->set.qname & new_data->set.qname) != 0 ||
+ (nm_data->set.ns & new_data->set.ns) != 0 ||
+ (nm_data->wild.qname & new_data->wild.qname) != 0 ||
+ (nm_data->wild.ns & new_data->wild.ns) != 0)
+ {
+ return (ISC_R_EXISTS);
+ }
+
+ nm_data->set.qname |= new_data->set.qname;
+ nm_data->set.ns |= new_data->set.ns;
+ nm_data->wild.qname |= new_data->wild.qname;
+ nm_data->wild.ns |= new_data->wild.ns;
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+add_name(dns_rpz_zone_t *rpz, dns_rpz_type_t rpz_type,
+ const dns_name_t *src_name) {
+ dns_rpz_nm_data_t new_data;
+ dns_fixedname_t trig_namef;
+ dns_name_t *trig_name = NULL;
+ isc_result_t result;
+
+ /*
+ * We need a summary database of names even with 1 policy zone,
+ * because wildcard triggers are handled differently.
+ */
+
+ trig_name = dns_fixedname_initname(&trig_namef);
+ name2data(rpz, rpz_type, src_name, trig_name, &new_data);
+
+ result = add_nm(rpz->rpzs, trig_name, &new_data);
+
+ /*
+ * Do not worry if the node already exists,
+ * because diff_apply() likes to add nodes before deleting.
+ */
+ if (result == ISC_R_EXISTS) {
+ return (ISC_R_SUCCESS);
+ }
+ if (result == ISC_R_SUCCESS) {
+ adj_trigger_cnt(rpz, rpz_type, NULL, 0, true);
+ }
+ return (result);
+}
+
+/*
+ * Callback to free the data for a node in the summary RBT database.
+ */
+static void
+rpz_node_deleter(void *nm_data, void *mctx) {
+ isc_mem_put(mctx, nm_data, sizeof(dns_rpz_nm_data_t));
+}
+
+/*
+ * Get ready for a new set of policy zones for a view.
+ */
+isc_result_t
+dns_rpz_new_zones(isc_mem_t *mctx, isc_taskmgr_t *taskmgr,
+ isc_timermgr_t *timermgr, char *rps_cstr,
+ size_t rps_cstr_size, dns_rpz_zones_t **rpzsp) {
+ dns_rpz_zones_t *rpzs = NULL;
+ isc_result_t result = ISC_R_SUCCESS;
+
+ REQUIRE(rpzsp != NULL && *rpzsp == NULL);
+
+ rpzs = isc_mem_get(mctx, sizeof(*rpzs));
+ *rpzs = (dns_rpz_zones_t){
+ .rps_cstr = rps_cstr,
+ .rps_cstr_size = rps_cstr_size,
+ .taskmgr = taskmgr,
+ .timermgr = timermgr,
+ .magic = DNS_RPZ_ZONES_MAGIC,
+ };
+
+ isc_rwlock_init(&rpzs->search_lock, 0, 0);
+ isc_mutex_init(&rpzs->maint_lock);
+ isc_refcount_init(&rpzs->references, 1);
+
+#ifdef USE_DNSRPS
+ if (rps_cstr != NULL) {
+ result = dns_dnsrps_view_init(rpzs, rps_cstr);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup_rbt;
+ }
+ }
+#else /* ifdef USE_DNSRPS */
+ INSIST(!rpzs->p.dnsrps_enabled);
+#endif /* ifdef USE_DNSRPS */
+ if (!rpzs->p.dnsrps_enabled) {
+ result = dns_rbt_create(mctx, rpz_node_deleter, mctx,
+ &rpzs->rbt);
+ }
+
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup_rbt;
+ }
+
+ result = isc_taskmgr_excltask(taskmgr, &rpzs->updater);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup_task;
+ }
+
+ isc_mem_attach(mctx, &rpzs->mctx);
+
+ *rpzsp = rpzs;
+ return (ISC_R_SUCCESS);
+
+cleanup_task:
+ dns_rbt_destroy(&rpzs->rbt);
+
+cleanup_rbt:
+ isc_refcount_decrementz(&rpzs->references);
+ isc_refcount_destroy(&rpzs->references);
+ isc_mutex_destroy(&rpzs->maint_lock);
+ isc_rwlock_destroy(&rpzs->search_lock);
+ isc_mem_put(mctx, rpzs, sizeof(*rpzs));
+
+ return (result);
+}
+
+isc_result_t
+dns_rpz_new_zone(dns_rpz_zones_t *rpzs, dns_rpz_zone_t **rpzp) {
+ isc_result_t result;
+ dns_rpz_zone_t *rpz = NULL;
+
+ REQUIRE(DNS_RPZ_ZONES_VALID(rpzs));
+ REQUIRE(rpzp != NULL && *rpzp == NULL);
+
+ if (rpzs->p.num_zones >= DNS_RPZ_MAX_ZONES) {
+ return (ISC_R_NOSPACE);
+ }
+
+ result = dns__rpz_shuttingdown(rpzs);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+
+ rpz = isc_mem_get(rpzs->mctx, sizeof(*rpz));
+ *rpz = (dns_rpz_zone_t){
+ .addsoa = true,
+ .magic = DNS_RPZ_ZONE_MAGIC,
+ .rpzs = rpzs,
+ };
+
+ result = isc_timer_create(rpzs->timermgr, isc_timertype_inactive, NULL,
+ NULL, rpzs->updater, dns__rpz_timer_cb, rpz,
+ &rpz->updatetimer);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup_timer;
+ }
+
+ /*
+ * This will never be used, but costs us nothing and
+ * simplifies dns__rpz_timer_cb().
+ */
+
+ isc_ht_init(&rpz->nodes, rpzs->mctx, 1, ISC_HT_CASE_SENSITIVE);
+
+ dns_name_init(&rpz->origin, NULL);
+ dns_name_init(&rpz->client_ip, NULL);
+ dns_name_init(&rpz->ip, NULL);
+ dns_name_init(&rpz->nsdname, NULL);
+ dns_name_init(&rpz->nsip, NULL);
+ dns_name_init(&rpz->passthru, NULL);
+ dns_name_init(&rpz->drop, NULL);
+ dns_name_init(&rpz->tcp_only, NULL);
+ dns_name_init(&rpz->cname, NULL);
+
+ isc_time_settoepoch(&rpz->lastupdated);
+
+ ISC_EVENT_INIT(&rpz->updateevent, sizeof(rpz->updateevent), 0, NULL, 0,
+ NULL, NULL, NULL, NULL, NULL);
+
+ rpz->num = rpzs->p.num_zones++;
+ rpzs->zones[rpz->num] = rpz;
+
+ *rpzp = rpz;
+
+ return (ISC_R_SUCCESS);
+
+cleanup_timer:
+ isc_mem_put(rpzs->mctx, rpz, sizeof(*rpz));
+
+ return (result);
+}
+
+isc_result_t
+dns_rpz_dbupdate_callback(dns_db_t *db, void *fn_arg) {
+ dns_rpz_zone_t *rpz = (dns_rpz_zone_t *)fn_arg;
+ isc_time_t now;
+ isc_result_t result = ISC_R_SUCCESS;
+ char dname[DNS_NAME_FORMATSIZE];
+
+ REQUIRE(DNS_DB_VALID(db));
+ REQUIRE(DNS_RPZ_ZONE_VALID(rpz));
+
+ LOCK(&rpz->rpzs->maint_lock);
+
+ if (rpz->rpzs->shuttingdown) {
+ result = ISC_R_SHUTTINGDOWN;
+ goto unlock;
+ }
+
+ /* New zone came as AXFR */
+ if (rpz->db != NULL && rpz->db != db) {
+ /* We need to clean up the old DB */
+ if (rpz->dbversion != NULL) {
+ dns_db_closeversion(rpz->db, &rpz->dbversion, false);
+ }
+ dns_db_updatenotify_unregister(rpz->db,
+ dns_rpz_dbupdate_callback, rpz);
+ dns_db_detach(&rpz->db);
+ }
+
+ if (rpz->db == NULL) {
+ RUNTIME_CHECK(rpz->dbversion == NULL);
+ dns_db_attach(db, &rpz->db);
+ }
+
+ dns_name_format(&rpz->origin, dname, DNS_NAME_FORMATSIZE);
+
+ if (!rpz->updatepending && !rpz->updaterunning) {
+ uint64_t tdiff;
+
+ rpz->updatepending = true;
+
+ isc_time_now(&now);
+ tdiff = isc_time_microdiff(&now, &rpz->lastupdated) / 1000000;
+ if (tdiff < rpz->min_update_interval) {
+ uint64_t defer = rpz->min_update_interval - tdiff;
+ isc_interval_t interval;
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL,
+ DNS_LOGMODULE_MASTER, ISC_LOG_INFO,
+ "rpz: %s: new zone version came "
+ "too soon, deferring update for "
+ "%" PRIu64 " seconds",
+ dname, defer);
+ isc_interval_set(&interval, (unsigned int)defer, 0);
+ dns_db_currentversion(rpz->db, &rpz->dbversion);
+ (void)isc_timer_reset(rpz->updatetimer,
+ isc_timertype_once, NULL,
+ &interval, true);
+ } else {
+ isc_event_t *event = NULL;
+
+ dns_db_currentversion(rpz->db, &rpz->dbversion);
+ INSIST(!ISC_LINK_LINKED(&rpz->updateevent, ev_link));
+ ISC_EVENT_INIT(&rpz->updateevent,
+ sizeof(rpz->updateevent), 0, NULL,
+ DNS_EVENT_RPZUPDATED, dns__rpz_timer_cb,
+ rpz, rpz, NULL, NULL);
+ event = &rpz->updateevent;
+ isc_task_send(rpz->rpzs->updater, &event);
+ }
+ } else {
+ rpz->updatepending = true;
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL,
+ DNS_LOGMODULE_MASTER, ISC_LOG_DEBUG(3),
+ "rpz: %s: update already queued or running",
+ dname);
+ if (rpz->dbversion != NULL) {
+ dns_db_closeversion(rpz->db, &rpz->dbversion, false);
+ }
+ dns_db_currentversion(rpz->db, &rpz->dbversion);
+ }
+
+unlock:
+ UNLOCK(&rpz->rpzs->maint_lock);
+
+ return (result);
+}
+
+static void
+update_rpz_done_cb(void *data, isc_result_t result) {
+ dns_rpz_zone_t *rpz = (dns_rpz_zone_t *)data;
+ char dname[DNS_NAME_FORMATSIZE];
+
+ REQUIRE(DNS_RPZ_ZONE_VALID(rpz));
+
+ if (result == ISC_R_SUCCESS && rpz->updateresult != ISC_R_SUCCESS) {
+ result = rpz->updateresult;
+ }
+
+ LOCK(&rpz->rpzs->maint_lock);
+ rpz->updaterunning = false;
+
+ dns_name_format(&rpz->origin, dname, DNS_NAME_FORMATSIZE);
+
+ /* If there's no update pending, or if shutting down, finish. */
+ if (!rpz->updatepending || rpz->rpzs->shuttingdown) {
+ goto done;
+ }
+
+ /* If there's an update pending, schedule it */
+ if (rpz->min_update_interval > 0) {
+ uint64_t defer = rpz->min_update_interval;
+ isc_interval_t interval;
+
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL,
+ DNS_LOGMODULE_MASTER, ISC_LOG_INFO,
+ "rpz: %s: new zone version came "
+ "too soon, deferring update for "
+ "%" PRIu64 " seconds",
+ dname, defer);
+ isc_interval_set(&interval, (unsigned int)defer, 0);
+ (void)isc_timer_reset(rpz->updatetimer, isc_timertype_once,
+ NULL, &interval, true);
+ } else {
+ isc_event_t *event = NULL;
+ INSIST(!ISC_LINK_LINKED(&rpz->updateevent, ev_link));
+ ISC_EVENT_INIT(&rpz->updateevent, sizeof(rpz->updateevent), 0,
+ NULL, DNS_EVENT_RPZUPDATED, dns__rpz_timer_cb,
+ rpz, rpz, NULL, NULL);
+ event = &rpz->updateevent;
+ isc_task_send(rpz->rpzs->updater, &event);
+ }
+
+done:
+ dns_db_closeversion(rpz->updb, &rpz->updbversion, false);
+ dns_db_detach(&rpz->updb);
+
+ UNLOCK(&rpz->rpzs->maint_lock);
+
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL, DNS_LOGMODULE_MASTER,
+ ISC_LOG_INFO, "rpz: %s: reload done: %s", dname,
+ isc_result_totext(result));
+
+ dns_rpz_unref_rpzs(rpz->rpzs);
+}
+
+static isc_result_t
+update_nodes(dns_rpz_zone_t *rpz, isc_ht_t *newnodes) {
+ isc_result_t result;
+ dns_dbiterator_t *updbit = NULL;
+ dns_name_t *name = NULL;
+ dns_fixedname_t fixname;
+ char domain[DNS_NAME_FORMATSIZE];
+
+ dns_name_format(&rpz->origin, domain, DNS_NAME_FORMATSIZE);
+
+ name = dns_fixedname_initname(&fixname);
+
+ result = dns_db_createiterator(rpz->updb, DNS_DB_NONSEC3, &updbit);
+ if (result != ISC_R_SUCCESS) {
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL,
+ DNS_LOGMODULE_MASTER, ISC_LOG_ERROR,
+ "rpz: %s: failed to create DB iterator - %s",
+ domain, isc_result_totext(result));
+ return (result);
+ }
+
+ result = dns_dbiterator_first(updbit);
+ if (result != ISC_R_SUCCESS && result != ISC_R_NOMORE) {
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL,
+ DNS_LOGMODULE_MASTER, ISC_LOG_ERROR,
+ "rpz: %s: failed to get db iterator - %s", domain,
+ isc_result_totext(result));
+ goto cleanup;
+ }
+
+ while (result == ISC_R_SUCCESS) {
+ char namebuf[DNS_NAME_FORMATSIZE];
+ dns_rdatasetiter_t *rdsiter = NULL;
+ dns_dbnode_t *node = NULL;
+
+ result = dns__rpz_shuttingdown(rpz->rpzs);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+
+ result = dns_dbiterator_current(updbit, &node, name);
+ if (result != ISC_R_SUCCESS) {
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL,
+ DNS_LOGMODULE_MASTER, ISC_LOG_ERROR,
+ "rpz: %s: failed to get dbiterator - %s",
+ domain, isc_result_totext(result));
+ goto cleanup;
+ }
+
+ result = dns_dbiterator_pause(updbit);
+ RUNTIME_CHECK(result == ISC_R_SUCCESS);
+
+ result = dns_db_allrdatasets(rpz->updb, node, rpz->updbversion,
+ 0, 0, &rdsiter);
+ if (result != ISC_R_SUCCESS) {
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL,
+ DNS_LOGMODULE_MASTER, ISC_LOG_ERROR,
+ "rpz: %s: failed to fetch "
+ "rrdatasets - %s",
+ domain, isc_result_totext(result));
+ dns_db_detachnode(rpz->updb, &node);
+ goto cleanup;
+ }
+
+ result = dns_rdatasetiter_first(rdsiter);
+
+ dns_rdatasetiter_destroy(&rdsiter);
+ dns_db_detachnode(rpz->updb, &node);
+
+ if (result != ISC_R_SUCCESS) { /* skip empty non-terminal */
+ if (result != ISC_R_NOMORE) {
+ isc_log_write(
+ dns_lctx, DNS_LOGCATEGORY_GENERAL,
+ DNS_LOGMODULE_MASTER, ISC_LOG_ERROR,
+ "rpz: %s: error %s while creating "
+ "rdatasetiter",
+ domain, isc_result_totext(result));
+ }
+ goto next;
+ }
+
+ dns_name_downcase(name, name, NULL);
+
+ /* Add entry to the new nodes table */
+ result = isc_ht_add(newnodes, name->ndata, name->length, rpz);
+ if (result != ISC_R_SUCCESS) {
+ dns_name_format(name, namebuf, sizeof(namebuf));
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL,
+ DNS_LOGMODULE_MASTER, ISC_LOG_ERROR,
+ "rpz: %s, adding node %s to HT error %s",
+ domain, namebuf,
+ isc_result_totext(result));
+ goto next;
+ }
+
+ /* Does the entry exist in the old nodes table? */
+ result = isc_ht_find(rpz->nodes, name->ndata, name->length,
+ NULL);
+ if (result == ISC_R_SUCCESS) { /* found */
+ isc_ht_delete(rpz->nodes, name->ndata, name->length);
+ goto next;
+ }
+
+ /*
+ * Only the single rpz updates are serialized, so we need to
+ * lock here because we can be processing more updates to
+ * different rpz zones at the same time
+ */
+ LOCK(&rpz->rpzs->maint_lock);
+ result = rpz_add(rpz, name);
+ UNLOCK(&rpz->rpzs->maint_lock);
+
+ if (result != ISC_R_SUCCESS) {
+ dns_name_format(name, namebuf, sizeof(namebuf));
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL,
+ DNS_LOGMODULE_MASTER, ISC_LOG_ERROR,
+ "rpz: %s: adding node %s "
+ "to RPZ error %s",
+ domain, namebuf,
+ isc_result_totext(result));
+ } else if (isc_log_wouldlog(dns_lctx, ISC_LOG_DEBUG(3))) {
+ dns_name_format(name, namebuf, sizeof(namebuf));
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL,
+ DNS_LOGMODULE_MASTER, ISC_LOG_DEBUG(3),
+ "rpz: %s: adding node %s", domain,
+ namebuf);
+ }
+
+ next:
+ result = dns_dbiterator_next(updbit);
+ }
+ INSIST(result != ISC_R_SUCCESS);
+ if (result == ISC_R_NOMORE) {
+ result = ISC_R_SUCCESS;
+ }
+
+cleanup:
+ dns_dbiterator_destroy(&updbit);
+
+ return (result);
+}
+
+static isc_result_t
+cleanup_nodes(dns_rpz_zone_t *rpz) {
+ isc_result_t result;
+ isc_ht_iter_t *iter = NULL;
+ dns_name_t *name = NULL;
+ dns_fixedname_t fixname;
+
+ name = dns_fixedname_initname(&fixname);
+
+ isc_ht_iter_create(rpz->nodes, &iter);
+
+ for (result = isc_ht_iter_first(iter); result == ISC_R_SUCCESS;
+ result = isc_ht_iter_delcurrent_next(iter))
+ {
+ isc_region_t region;
+ unsigned char *key = NULL;
+ size_t keysize;
+
+ result = dns__rpz_shuttingdown(rpz->rpzs);
+ if (result != ISC_R_SUCCESS) {
+ break;
+ }
+
+ isc_ht_iter_currentkey(iter, &key, &keysize);
+ region.base = key;
+ region.length = (unsigned int)keysize;
+ dns_name_fromregion(name, &region);
+
+ LOCK(&rpz->rpzs->maint_lock);
+ rpz_del(rpz, name);
+ UNLOCK(&rpz->rpzs->maint_lock);
+ }
+ INSIST(result != ISC_R_SUCCESS);
+ if (result == ISC_R_NOMORE) {
+ result = ISC_R_SUCCESS;
+ }
+
+ isc_ht_iter_destroy(&iter);
+
+ return (result);
+}
+
+static isc_result_t
+dns__rpz_shuttingdown(dns_rpz_zones_t *rpzs) {
+ bool shuttingdown = false;
+
+ LOCK(&rpzs->maint_lock);
+ shuttingdown = rpzs->shuttingdown;
+ UNLOCK(&rpzs->maint_lock);
+
+ if (shuttingdown) {
+ return (ISC_R_SHUTTINGDOWN);
+ }
+
+ return (ISC_R_SUCCESS);
+}
+
+static void
+update_rpz_cb(void *data) {
+ dns_rpz_zone_t *rpz = (dns_rpz_zone_t *)data;
+ isc_result_t result = ISC_R_SUCCESS;
+ isc_ht_t *newnodes = NULL;
+
+ REQUIRE(rpz->nodes != NULL);
+
+ result = dns__rpz_shuttingdown(rpz->rpzs);
+ if (result != ISC_R_SUCCESS) {
+ goto shuttingdown;
+ }
+
+ isc_ht_init(&newnodes, rpz->rpzs->mctx, 1, ISC_HT_CASE_SENSITIVE);
+
+ result = update_nodes(rpz, newnodes);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+
+ result = cleanup_nodes(rpz);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+
+ /* Finalize the update */
+ ISC_SWAP(rpz->nodes, newnodes);
+
+cleanup:
+ isc_ht_destroy(&newnodes);
+
+shuttingdown:
+ rpz->updateresult = result;
+}
+
+static void
+dns__rpz_timer_cb(isc_task_t *task, isc_event_t *event) {
+ char domain[DNS_NAME_FORMATSIZE];
+ isc_result_t result;
+ dns_rpz_zone_t *rpz = NULL;
+
+ UNUSED(task);
+ REQUIRE(event != NULL);
+ REQUIRE(event->ev_arg != NULL);
+
+ rpz = (dns_rpz_zone_t *)event->ev_arg;
+ isc_event_free(&event);
+
+ REQUIRE(isc_nm_tid() >= 0);
+ REQUIRE(DNS_RPZ_ZONE_VALID(rpz));
+
+ LOCK(&rpz->rpzs->maint_lock);
+
+ if (rpz->rpzs->shuttingdown) {
+ goto unlock;
+ }
+
+ rpz->updatepending = false;
+ rpz->updaterunning = true;
+ rpz->updateresult = ISC_R_UNSET;
+
+ INSIST(rpz->updb == NULL);
+ INSIST(rpz->updbversion == NULL);
+ INSIST(rpz->dbversion != NULL);
+ INSIST(DNS_DB_VALID(rpz->db));
+ dns_db_attach(rpz->db, &rpz->updb);
+ rpz->updbversion = rpz->dbversion;
+ rpz->dbversion = NULL;
+
+ dns_name_format(&rpz->origin, domain, DNS_NAME_FORMATSIZE);
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL, DNS_LOGMODULE_MASTER,
+ ISC_LOG_INFO, "rpz: %s: reload start", domain);
+
+ dns_rpz_ref_rpzs(rpz->rpzs);
+ isc_nm_work_offload(isc_task_getnetmgr(rpz->rpzs->updater),
+ update_rpz_cb, update_rpz_done_cb, rpz);
+
+ result = isc_time_now(&rpz->lastupdated);
+ RUNTIME_CHECK(result == ISC_R_SUCCESS);
+
+unlock:
+ UNLOCK(&rpz->rpzs->maint_lock);
+}
+
+/*
+ * Free the radix tree of a response policy database.
+ */
+static void
+cidr_free(dns_rpz_zones_t *rpzs) {
+ dns_rpz_cidr_node_t *cur = NULL, *child = NULL, *parent = NULL;
+
+ cur = rpzs->cidr;
+ while (cur != NULL) {
+ /* Depth first. */
+ child = cur->child[0];
+ if (child != NULL) {
+ cur = child;
+ continue;
+ }
+ child = cur->child[1];
+ if (child != NULL) {
+ cur = child;
+ continue;
+ }
+
+ /* Delete this leaf and go up. */
+ parent = cur->parent;
+ if (parent == NULL) {
+ rpzs->cidr = NULL;
+ } else {
+ parent->child[parent->child[1] == cur] = NULL;
+ }
+ isc_mem_put(rpzs->mctx, cur, sizeof(*cur));
+ cur = parent;
+ }
+}
+
+static void
+dns__rpz_shutdown(dns_rpz_zone_t *rpz) {
+ /* maint_lock must be locked */
+ if (rpz->updatetimer != NULL) {
+ isc_result_t result;
+
+ /* Don't wait for timer to trigger for shutdown */
+ result = isc_timer_reset(rpz->updatetimer,
+ isc_timertype_inactive, NULL, NULL,
+ true);
+ RUNTIME_CHECK(result == ISC_R_SUCCESS);
+ }
+}
+
+static void
+dns_rpz_zone_destroy(dns_rpz_zone_t **rpzp) {
+ dns_rpz_zone_t *rpz = NULL;
+ dns_rpz_zones_t *rpzs;
+
+ rpz = *rpzp;
+ *rpzp = NULL;
+
+ rpzs = rpz->rpzs;
+ rpz->rpzs = NULL;
+
+ if (dns_name_dynamic(&rpz->origin)) {
+ dns_name_free(&rpz->origin, rpzs->mctx);
+ }
+ if (dns_name_dynamic(&rpz->client_ip)) {
+ dns_name_free(&rpz->client_ip, rpzs->mctx);
+ }
+ if (dns_name_dynamic(&rpz->ip)) {
+ dns_name_free(&rpz->ip, rpzs->mctx);
+ }
+ if (dns_name_dynamic(&rpz->nsdname)) {
+ dns_name_free(&rpz->nsdname, rpzs->mctx);
+ }
+ if (dns_name_dynamic(&rpz->nsip)) {
+ dns_name_free(&rpz->nsip, rpzs->mctx);
+ }
+ if (dns_name_dynamic(&rpz->passthru)) {
+ dns_name_free(&rpz->passthru, rpzs->mctx);
+ }
+ if (dns_name_dynamic(&rpz->drop)) {
+ dns_name_free(&rpz->drop, rpzs->mctx);
+ }
+ if (dns_name_dynamic(&rpz->tcp_only)) {
+ dns_name_free(&rpz->tcp_only, rpzs->mctx);
+ }
+ if (dns_name_dynamic(&rpz->cname)) {
+ dns_name_free(&rpz->cname, rpzs->mctx);
+ }
+ if (rpz->db != NULL) {
+ if (rpz->dbversion != NULL) {
+ dns_db_closeversion(rpz->db, &rpz->dbversion, false);
+ }
+ dns_db_updatenotify_unregister(rpz->db,
+ dns_rpz_dbupdate_callback, rpz);
+ dns_db_detach(&rpz->db);
+ }
+ INSIST(!rpz->updaterunning);
+
+ isc_timer_reset(rpz->updatetimer, isc_timertype_inactive, NULL, NULL,
+ true);
+ isc_timer_destroy(&rpz->updatetimer);
+
+ isc_ht_destroy(&rpz->nodes);
+
+ isc_mem_put(rpzs->mctx, rpz, sizeof(*rpz));
+}
+
+static void
+dns__rpz_zones_destroy(dns_rpz_zones_t *rpzs) {
+ REQUIRE(rpzs->shuttingdown);
+
+ isc_refcount_destroy(&rpzs->references);
+
+ for (dns_rpz_num_t rpz_num = 0; rpz_num < DNS_RPZ_MAX_ZONES; ++rpz_num)
+ {
+ if (rpzs->zones[rpz_num] == NULL) {
+ continue;
+ }
+
+ dns_rpz_zone_destroy(&rpzs->zones[rpz_num]);
+ }
+
+ if (rpzs->rps_cstr_size != 0) {
+#ifdef USE_DNSRPS
+ librpz->client_detach(&rpzs->rps_client);
+#endif /* ifdef USE_DNSRPS */
+ isc_mem_put(rpzs->mctx, rpzs->rps_cstr, rpzs->rps_cstr_size);
+ }
+
+ cidr_free(rpzs);
+ if (rpzs->rbt != NULL) {
+ dns_rbt_destroy(&rpzs->rbt);
+ }
+ isc_task_detach(&rpzs->updater);
+ isc_mutex_destroy(&rpzs->maint_lock);
+ isc_rwlock_destroy(&rpzs->search_lock);
+ isc_mem_putanddetach(&rpzs->mctx, rpzs, sizeof(*rpzs));
+}
+
+void
+dns_rpz_zones_shutdown(dns_rpz_zones_t *rpzs) {
+ REQUIRE(DNS_RPZ_ZONES_VALID(rpzs));
+ /*
+ * Forget the last of the view's rpz machinery when shutting down.
+ */
+
+ LOCK(&rpzs->maint_lock);
+ if (rpzs->shuttingdown) {
+ UNLOCK(&rpzs->maint_lock);
+ return;
+ }
+
+ rpzs->shuttingdown = true;
+
+ for (dns_rpz_num_t rpz_num = 0; rpz_num < DNS_RPZ_MAX_ZONES; ++rpz_num)
+ {
+ if (rpzs->zones[rpz_num] == NULL) {
+ continue;
+ }
+
+ dns__rpz_shutdown(rpzs->zones[rpz_num]);
+ }
+ UNLOCK(&rpzs->maint_lock);
+}
+
+#ifdef DNS_RPZ_TRACE
+ISC_REFCOUNT_TRACE_IMPL(dns_rpz_zones, dns__rpz_zones_destroy);
+#else
+ISC_REFCOUNT_IMPL(dns_rpz_zones, dns__rpz_zones_destroy);
+#endif
+
+/*
+ * Add an IP address to the radix tree or a name to the summary database.
+ */
+static isc_result_t
+rpz_add(dns_rpz_zone_t *rpz, const dns_name_t *src_name) {
+ dns_rpz_type_t rpz_type;
+ isc_result_t result = ISC_R_FAILURE;
+ dns_rpz_zones_t *rpzs = NULL;
+ dns_rpz_num_t rpz_num;
+
+ REQUIRE(rpz != NULL);
+
+ rpzs = rpz->rpzs;
+ rpz_num = rpz->num;
+
+ REQUIRE(rpzs != NULL && rpz_num < rpzs->p.num_zones);
+
+ RWLOCK(&rpzs->search_lock, isc_rwlocktype_write);
+
+ rpz_type = type_from_name(rpzs, rpz, src_name);
+
+ switch (rpz_type) {
+ case DNS_RPZ_TYPE_QNAME:
+ case DNS_RPZ_TYPE_NSDNAME:
+ result = add_name(rpz, rpz_type, src_name);
+ break;
+ case DNS_RPZ_TYPE_CLIENT_IP:
+ case DNS_RPZ_TYPE_IP:
+ case DNS_RPZ_TYPE_NSIP:
+ result = add_cidr(rpz, rpz_type, src_name);
+ break;
+ case DNS_RPZ_TYPE_BAD:
+ break;
+ }
+ RWUNLOCK(&rpzs->search_lock, isc_rwlocktype_write);
+
+ return (result);
+}
+
+/*
+ * Remove an IP address from the radix tree.
+ */
+static void
+del_cidr(dns_rpz_zone_t *rpz, dns_rpz_type_t rpz_type,
+ const dns_name_t *src_name) {
+ isc_result_t result;
+ dns_rpz_cidr_key_t tgt_ip;
+ dns_rpz_prefix_t tgt_prefix;
+ dns_rpz_addr_zbits_t tgt_set;
+ dns_rpz_cidr_node_t *tgt = NULL, *parent = NULL, *child = NULL;
+
+ /*
+ * Do not worry about invalid rpz IP address names. If we
+ * are here, then something relevant was added and so was
+ * valid. Invalid names here are usually internal RBTDB nodes.
+ */
+ result = name2ipkey(DNS_RPZ_DEBUG_QUIET, rpz, rpz_type, src_name,
+ &tgt_ip, &tgt_prefix, &tgt_set);
+ if (result != ISC_R_SUCCESS) {
+ return;
+ }
+
+ result = search(rpz->rpzs, &tgt_ip, tgt_prefix, &tgt_set, false, &tgt);
+ if (result != ISC_R_SUCCESS) {
+ INSIST(result == ISC_R_NOTFOUND ||
+ result == DNS_R_PARTIALMATCH);
+ /*
+ * Do not worry about missing summary RBT nodes that probably
+ * correspond to RBTDB nodes that were implicit RBT nodes
+ * that were later added for (often empty) wildcards
+ * and then to the RBTDB deferred cleanup list.
+ */
+ return;
+ }
+
+ /*
+ * Mark the node and its parents to reflect the deleted IP address.
+ * Do not count bits that are already clear for internal RBTDB nodes.
+ */
+ tgt_set.client_ip &= tgt->set.client_ip;
+ tgt_set.ip &= tgt->set.ip;
+ tgt_set.nsip &= tgt->set.nsip;
+ tgt->set.client_ip &= ~tgt_set.client_ip;
+ tgt->set.ip &= ~tgt_set.ip;
+ tgt->set.nsip &= ~tgt_set.nsip;
+ set_sum_pair(tgt);
+
+ adj_trigger_cnt(rpz, rpz_type, &tgt_ip, tgt_prefix, false);
+
+ /*
+ * We might need to delete 2 nodes.
+ */
+ do {
+ /*
+ * The node is now useless if it has no data of its own
+ * and 0 or 1 children. We are finished if it is not
+ * useless.
+ */
+ if ((child = tgt->child[0]) != NULL) {
+ if (tgt->child[1] != NULL) {
+ break;
+ }
+ } else {
+ child = tgt->child[1];
+ }
+ if (tgt->set.client_ip != 0 || tgt->set.ip != 0 ||
+ tgt->set.nsip != 0)
+ {
+ break;
+ }
+
+ /*
+ * Replace the pointer to this node in the parent with
+ * the remaining child or NULL.
+ */
+ parent = tgt->parent;
+ if (parent == NULL) {
+ rpz->rpzs->cidr = child;
+ } else {
+ parent->child[parent->child[1] == tgt] = child;
+ }
+
+ /*
+ * If the child exists fix up its parent pointer.
+ */
+ if (child != NULL) {
+ child->parent = parent;
+ }
+ isc_mem_put(rpz->rpzs->mctx, tgt, sizeof(*tgt));
+
+ tgt = parent;
+ } while (tgt != NULL);
+}
+
+static void
+del_name(dns_rpz_zone_t *rpz, dns_rpz_type_t rpz_type,
+ const dns_name_t *src_name) {
+ char namebuf[DNS_NAME_FORMATSIZE];
+ dns_fixedname_t trig_namef;
+ dns_name_t *trig_name = NULL;
+ dns_rbtnode_t *nmnode = NULL;
+ dns_rpz_nm_data_t *nm_data = NULL;
+ dns_rpz_nm_data_t del_data;
+ isc_result_t result;
+ bool exists;
+
+ /*
+ * We need a summary database of names even with 1 policy zone,
+ * because wildcard triggers are handled differently.
+ */
+
+ trig_name = dns_fixedname_initname(&trig_namef);
+ name2data(rpz, rpz_type, src_name, trig_name, &del_data);
+
+ nmnode = NULL;
+ result = dns_rbt_findnode(rpz->rpzs->rbt, trig_name, NULL, &nmnode,
+ NULL, 0, NULL, NULL);
+ if (result != ISC_R_SUCCESS) {
+ /*
+ * Do not worry about missing summary RBT nodes that probably
+ * correspond to RBTDB nodes that were implicit RBT nodes
+ * that were later added for (often empty) wildcards
+ * and then to the RBTDB deferred cleanup list.
+ */
+ if (result == ISC_R_NOTFOUND || result == DNS_R_PARTIALMATCH) {
+ return;
+ }
+ dns_name_format(src_name, namebuf, sizeof(namebuf));
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_RPZ,
+ DNS_LOGMODULE_RBTDB, DNS_RPZ_ERROR_LEVEL,
+ "rpz del_name(%s) node search failed: %s",
+ namebuf, isc_result_totext(result));
+ return;
+ }
+
+ nm_data = nmnode->data;
+ INSIST(nm_data != NULL);
+
+ /*
+ * Do not count bits that next existed for RBT nodes that would we
+ * would not have found in a summary for a single RBTDB tree.
+ */
+ del_data.set.qname &= nm_data->set.qname;
+ del_data.set.ns &= nm_data->set.ns;
+ del_data.wild.qname &= nm_data->wild.qname;
+ del_data.wild.ns &= nm_data->wild.ns;
+
+ exists = (del_data.set.qname != 0 || del_data.set.ns != 0 ||
+ del_data.wild.qname != 0 || del_data.wild.ns != 0);
+
+ nm_data->set.qname &= ~del_data.set.qname;
+ nm_data->set.ns &= ~del_data.set.ns;
+ nm_data->wild.qname &= ~del_data.wild.qname;
+ nm_data->wild.ns &= ~del_data.wild.ns;
+
+ if (nm_data->set.qname == 0 && nm_data->set.ns == 0 &&
+ nm_data->wild.qname == 0 && nm_data->wild.ns == 0)
+ {
+ result = dns_rbt_deletenode(rpz->rpzs->rbt, nmnode, false);
+ if (result != ISC_R_SUCCESS) {
+ /*
+ * bin/tests/system/rpz/tests.sh looks for
+ * "rpz.*failed".
+ */
+ dns_name_format(src_name, namebuf, sizeof(namebuf));
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_RPZ,
+ DNS_LOGMODULE_RBTDB, DNS_RPZ_ERROR_LEVEL,
+ "rpz del_name(%s) node delete "
+ "failed: %s",
+ namebuf, isc_result_totext(result));
+ }
+ }
+
+ if (exists) {
+ adj_trigger_cnt(rpz, rpz_type, NULL, 0, false);
+ }
+}
+
+/*
+ * Remove an IP address from the radix tree or a name from the summary database.
+ */
+static void
+rpz_del(dns_rpz_zone_t *rpz, const dns_name_t *src_name) {
+ dns_rpz_type_t rpz_type;
+ dns_rpz_zones_t *rpzs = NULL;
+ dns_rpz_num_t rpz_num;
+
+ REQUIRE(rpz != NULL);
+
+ rpzs = rpz->rpzs;
+ rpz_num = rpz->num;
+
+ REQUIRE(rpzs != NULL && rpz_num < rpzs->p.num_zones);
+
+ RWLOCK(&rpzs->search_lock, isc_rwlocktype_write);
+
+ rpz_type = type_from_name(rpzs, rpz, src_name);
+
+ switch (rpz_type) {
+ case DNS_RPZ_TYPE_QNAME:
+ case DNS_RPZ_TYPE_NSDNAME:
+ del_name(rpz, rpz_type, src_name);
+ break;
+ case DNS_RPZ_TYPE_CLIENT_IP:
+ case DNS_RPZ_TYPE_IP:
+ case DNS_RPZ_TYPE_NSIP:
+ del_cidr(rpz, rpz_type, src_name);
+ break;
+ case DNS_RPZ_TYPE_BAD:
+ break;
+ }
+
+ RWUNLOCK(&rpzs->search_lock, isc_rwlocktype_write);
+}
+
+/*
+ * Search the summary radix tree to get a relative owner name in a
+ * policy zone relevant to a triggering IP address.
+ * rpz_type and zbits limit the search for IP address netaddr
+ * return the policy zone's number or DNS_RPZ_INVALID_NUM
+ * ip_name is the relative owner name found and
+ * *prefixp is its prefix length.
+ */
+dns_rpz_num_t
+dns_rpz_find_ip(dns_rpz_zones_t *rpzs, dns_rpz_type_t rpz_type,
+ dns_rpz_zbits_t zbits, const isc_netaddr_t *netaddr,
+ dns_name_t *ip_name, dns_rpz_prefix_t *prefixp) {
+ dns_rpz_cidr_key_t tgt_ip;
+ dns_rpz_addr_zbits_t tgt_set;
+ dns_rpz_cidr_node_t *found = NULL;
+ isc_result_t result;
+ dns_rpz_num_t rpz_num = 0;
+ dns_rpz_have_t have;
+ int i;
+
+ RWLOCK(&rpzs->search_lock, isc_rwlocktype_read);
+ have = rpzs->have;
+ RWUNLOCK(&rpzs->search_lock, isc_rwlocktype_read);
+
+ /*
+ * Convert IP address to CIDR tree key.
+ */
+ if (netaddr->family == AF_INET) {
+ tgt_ip.w[0] = 0;
+ tgt_ip.w[1] = 0;
+ tgt_ip.w[2] = ADDR_V4MAPPED;
+ tgt_ip.w[3] = ntohl(netaddr->type.in.s_addr);
+ switch (rpz_type) {
+ case DNS_RPZ_TYPE_CLIENT_IP:
+ zbits &= have.client_ipv4;
+ break;
+ case DNS_RPZ_TYPE_IP:
+ zbits &= have.ipv4;
+ break;
+ case DNS_RPZ_TYPE_NSIP:
+ zbits &= have.nsipv4;
+ break;
+ default:
+ UNREACHABLE();
+ break;
+ }
+ } else if (netaddr->family == AF_INET6) {
+ dns_rpz_cidr_key_t src_ip6;
+
+ /*
+ * Given the int aligned struct in_addr member of netaddr->type
+ * one could cast netaddr->type.in6 to dns_rpz_cidr_key_t *,
+ * but some people object.
+ */
+ memmove(src_ip6.w, &netaddr->type.in6, sizeof(src_ip6.w));
+ for (i = 0; i < 4; i++) {
+ tgt_ip.w[i] = ntohl(src_ip6.w[i]);
+ }
+ switch (rpz_type) {
+ case DNS_RPZ_TYPE_CLIENT_IP:
+ zbits &= have.client_ipv6;
+ break;
+ case DNS_RPZ_TYPE_IP:
+ zbits &= have.ipv6;
+ break;
+ case DNS_RPZ_TYPE_NSIP:
+ zbits &= have.nsipv6;
+ break;
+ default:
+ UNREACHABLE();
+ break;
+ }
+ } else {
+ return (DNS_RPZ_INVALID_NUM);
+ }
+
+ if (zbits == 0) {
+ return (DNS_RPZ_INVALID_NUM);
+ }
+ make_addr_set(&tgt_set, zbits, rpz_type);
+
+ RWLOCK(&rpzs->search_lock, isc_rwlocktype_read);
+ result = search(rpzs, &tgt_ip, 128, &tgt_set, false, &found);
+ if (result == ISC_R_NOTFOUND) {
+ /*
+ * There are no eligible zones for this IP address.
+ */
+ RWUNLOCK(&rpzs->search_lock, isc_rwlocktype_read);
+ return (DNS_RPZ_INVALID_NUM);
+ }
+
+ /*
+ * Construct the trigger name for the longest matching trigger
+ * in the first eligible zone with a match.
+ */
+ *prefixp = found->prefix;
+ switch (rpz_type) {
+ case DNS_RPZ_TYPE_CLIENT_IP:
+ rpz_num = zbit_to_num(found->set.client_ip & tgt_set.client_ip);
+ break;
+ case DNS_RPZ_TYPE_IP:
+ rpz_num = zbit_to_num(found->set.ip & tgt_set.ip);
+ break;
+ case DNS_RPZ_TYPE_NSIP:
+ rpz_num = zbit_to_num(found->set.nsip & tgt_set.nsip);
+ break;
+ default:
+ UNREACHABLE();
+ }
+ result = ip2name(&found->ip, found->prefix, dns_rootname, ip_name);
+ RWUNLOCK(&rpzs->search_lock, isc_rwlocktype_read);
+ if (result != ISC_R_SUCCESS) {
+ /*
+ * bin/tests/system/rpz/tests.sh looks for "rpz.*failed".
+ */
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_RPZ,
+ DNS_LOGMODULE_RBTDB, DNS_RPZ_ERROR_LEVEL,
+ "rpz ip2name() failed: %s",
+ isc_result_totext(result));
+ return (DNS_RPZ_INVALID_NUM);
+ }
+ return (rpz_num);
+}
+
+/*
+ * Search the summary radix tree for policy zones with triggers matching
+ * a name.
+ */
+dns_rpz_zbits_t
+dns_rpz_find_name(dns_rpz_zones_t *rpzs, dns_rpz_type_t rpz_type,
+ dns_rpz_zbits_t zbits, dns_name_t *trig_name) {
+ char namebuf[DNS_NAME_FORMATSIZE];
+ dns_rbtnode_t *nmnode = NULL;
+ const dns_rpz_nm_data_t *nm_data = NULL;
+ dns_rpz_zbits_t found_zbits;
+ dns_rbtnodechain_t chain;
+ isc_result_t result;
+ int i;
+
+ if (zbits == 0) {
+ return (0);
+ }
+
+ found_zbits = 0;
+
+ dns_rbtnodechain_init(&chain);
+
+ RWLOCK(&rpzs->search_lock, isc_rwlocktype_read);
+
+ nmnode = NULL;
+ result = dns_rbt_findnode(rpzs->rbt, trig_name, NULL, &nmnode, &chain,
+ DNS_RBTFIND_EMPTYDATA, NULL, NULL);
+
+ switch (result) {
+ case ISC_R_SUCCESS:
+ nm_data = nmnode->data;
+ if (nm_data != NULL) {
+ if (rpz_type == DNS_RPZ_TYPE_QNAME) {
+ found_zbits = nm_data->set.qname;
+ } else {
+ found_zbits = nm_data->set.ns;
+ }
+ }
+ FALLTHROUGH;
+
+ case DNS_R_PARTIALMATCH:
+ i = chain.level_matches;
+ nmnode = chain.levels[chain.level_matches];
+
+ /*
+ * Whenever an exact match is found by dns_rbt_findnode(),
+ * the highest level node in the chain will not be put into
+ * chain->levels[] array, but instead the chain->end
+ * pointer will be adjusted to point to that node.
+ *
+ * Suppose we have the following entries in a rpz zone:
+ * example.com CNAME rpz-passthru.
+ * *.example.com CNAME rpz-passthru.
+ *
+ * A query for www.example.com would result in the
+ * following chain object returned by dns_rbt_findnode():
+ * chain->level_count = 2
+ * chain->level_matches = 2
+ * chain->levels[0] = .
+ * chain->levels[1] = example.com
+ * chain->levels[2] = NULL
+ * chain->end = www
+ *
+ * Since exact matches only care for testing rpz set bits,
+ * we need to test for rpz wild bits through iterating the
+ * nodechain, and that includes testing the rpz wild bits
+ * in the highest level node found. In the case of an exact
+ * match, chain->levels[chain->level_matches] will be NULL,
+ * to address that we must use chain->end as the start
+ * point, then iterate over the remaining levels in the
+ * chain.
+ */
+ if (nmnode == NULL) {
+ --i;
+ nmnode = chain.end;
+ }
+
+ while (nmnode != NULL) {
+ nm_data = nmnode->data;
+ if (nm_data != NULL) {
+ if (rpz_type == DNS_RPZ_TYPE_QNAME) {
+ found_zbits |= nm_data->wild.qname;
+ } else {
+ found_zbits |= nm_data->wild.ns;
+ }
+ }
+
+ if (i >= 0) {
+ nmnode = chain.levels[i];
+ --i;
+ } else {
+ break;
+ }
+ }
+ break;
+
+ case ISC_R_NOTFOUND:
+ break;
+
+ default:
+ /*
+ * bin/tests/system/rpz/tests.sh looks for "rpz.*failed".
+ */
+ dns_name_format(trig_name, namebuf, sizeof(namebuf));
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_RPZ,
+ DNS_LOGMODULE_RBTDB, DNS_RPZ_ERROR_LEVEL,
+ "dns_rpz_find_name(%s) failed: %s", namebuf,
+ isc_result_totext(result));
+ break;
+ }
+
+ RWUNLOCK(&rpzs->search_lock, isc_rwlocktype_read);
+
+ dns_rbtnodechain_invalidate(&chain);
+
+ return (zbits & found_zbits);
+}
+
+/*
+ * Translate CNAME rdata to a QNAME response policy action.
+ */
+dns_rpz_policy_t
+dns_rpz_decode_cname(dns_rpz_zone_t *rpz, dns_rdataset_t *rdataset,
+ dns_name_t *selfname) {
+ dns_rdata_t rdata = DNS_RDATA_INIT;
+ dns_rdata_cname_t cname;
+ isc_result_t result;
+
+ result = dns_rdataset_first(rdataset);
+ INSIST(result == ISC_R_SUCCESS);
+ dns_rdataset_current(rdataset, &rdata);
+ result = dns_rdata_tostruct(&rdata, &cname, NULL);
+ INSIST(result == ISC_R_SUCCESS);
+ dns_rdata_reset(&rdata);
+
+ /*
+ * CNAME . means NXDOMAIN
+ */
+ if (dns_name_equal(&cname.cname, dns_rootname)) {
+ return (DNS_RPZ_POLICY_NXDOMAIN);
+ }
+
+ if (dns_name_iswildcard(&cname.cname)) {
+ /*
+ * CNAME *. means NODATA
+ */
+ if (dns_name_countlabels(&cname.cname) == 2) {
+ return (DNS_RPZ_POLICY_NODATA);
+ }
+
+ /*
+ * A qname of www.evil.com and a policy of
+ * *.evil.com CNAME *.garden.net
+ * gives a result of
+ * evil.com CNAME evil.com.garden.net
+ */
+ if (dns_name_countlabels(&cname.cname) > 2) {
+ return (DNS_RPZ_POLICY_WILDCNAME);
+ }
+ }
+
+ /*
+ * CNAME rpz-tcp-only. means "send truncated UDP responses."
+ */
+ if (dns_name_equal(&cname.cname, &rpz->tcp_only)) {
+ return (DNS_RPZ_POLICY_TCP_ONLY);
+ }
+
+ /*
+ * CNAME rpz-drop. means "do not respond."
+ */
+ if (dns_name_equal(&cname.cname, &rpz->drop)) {
+ return (DNS_RPZ_POLICY_DROP);
+ }
+
+ /*
+ * CNAME rpz-passthru. means "do not rewrite."
+ */
+ if (dns_name_equal(&cname.cname, &rpz->passthru)) {
+ return (DNS_RPZ_POLICY_PASSTHRU);
+ }
+
+ /*
+ * 128.1.0.127.rpz-ip CNAME 128.1.0.0.127. is obsolete PASSTHRU
+ */
+ if (selfname != NULL && dns_name_equal(&cname.cname, selfname)) {
+ return (DNS_RPZ_POLICY_PASSTHRU);
+ }
+
+ /*
+ * Any other rdata gives a response consisting of the rdata.
+ */
+ return (DNS_RPZ_POLICY_RECORD);
+}
diff --git a/lib/dns/rriterator.c b/lib/dns/rriterator.c
new file mode 100644
index 0000000..93548bc
--- /dev/null
+++ b/lib/dns/rriterator.c
@@ -0,0 +1,220 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/*! \file */
+
+/***
+ *** Imports
+ ***/
+
+#include <inttypes.h>
+
+#include <isc/result.h>
+#include <isc/string.h>
+#include <isc/util.h>
+
+#include <dns/db.h>
+#include <dns/dbiterator.h>
+#include <dns/rdata.h>
+#include <dns/rdataset.h>
+#include <dns/rdatasetiter.h>
+#include <dns/rriterator.h>
+
+/***
+ *** RRiterator methods
+ ***/
+
+isc_result_t
+dns_rriterator_init(dns_rriterator_t *it, dns_db_t *db, dns_dbversion_t *ver,
+ isc_stdtime_t now) {
+ isc_result_t result;
+ it->magic = RRITERATOR_MAGIC;
+ it->db = db;
+ it->dbit = NULL;
+ it->ver = ver;
+ it->now = now;
+ it->node = NULL;
+ result = dns_db_createiterator(it->db, 0, &it->dbit);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+ it->rdatasetit = NULL;
+ dns_rdata_init(&it->rdata);
+ dns_rdataset_init(&it->rdataset);
+ dns_fixedname_init(&it->fixedname);
+ INSIST(!dns_rdataset_isassociated(&it->rdataset));
+ it->result = ISC_R_SUCCESS;
+ return (it->result);
+}
+
+isc_result_t
+dns_rriterator_first(dns_rriterator_t *it) {
+ REQUIRE(VALID_RRITERATOR(it));
+ /* Reset state */
+ if (dns_rdataset_isassociated(&it->rdataset)) {
+ dns_rdataset_disassociate(&it->rdataset);
+ }
+ if (it->rdatasetit != NULL) {
+ dns_rdatasetiter_destroy(&it->rdatasetit);
+ }
+ if (it->node != NULL) {
+ dns_db_detachnode(it->db, &it->node);
+ }
+ it->result = dns_dbiterator_first(it->dbit);
+
+ /*
+ * The top node may be empty when out of zone glue exists.
+ * Walk the tree to find the first node with data.
+ */
+ while (it->result == ISC_R_SUCCESS) {
+ it->result = dns_dbiterator_current(
+ it->dbit, &it->node,
+ dns_fixedname_name(&it->fixedname));
+ if (it->result != ISC_R_SUCCESS) {
+ return (it->result);
+ }
+
+ it->result = dns_db_allrdatasets(it->db, it->node, it->ver, 0,
+ it->now, &it->rdatasetit);
+ if (it->result != ISC_R_SUCCESS) {
+ return (it->result);
+ }
+
+ it->result = dns_rdatasetiter_first(it->rdatasetit);
+ if (it->result != ISC_R_SUCCESS) {
+ /*
+ * This node is empty. Try next node.
+ */
+ dns_rdatasetiter_destroy(&it->rdatasetit);
+ dns_db_detachnode(it->db, &it->node);
+ it->result = dns_dbiterator_next(it->dbit);
+ continue;
+ }
+ dns_rdatasetiter_current(it->rdatasetit, &it->rdataset);
+ dns_rdataset_getownercase(&it->rdataset,
+ dns_fixedname_name(&it->fixedname));
+ it->rdataset.attributes |= DNS_RDATASETATTR_LOADORDER;
+ it->result = dns_rdataset_first(&it->rdataset);
+ return (it->result);
+ }
+ return (it->result);
+}
+
+isc_result_t
+dns_rriterator_nextrrset(dns_rriterator_t *it) {
+ REQUIRE(VALID_RRITERATOR(it));
+ if (dns_rdataset_isassociated(&it->rdataset)) {
+ dns_rdataset_disassociate(&it->rdataset);
+ }
+ it->result = dns_rdatasetiter_next(it->rdatasetit);
+ /*
+ * The while loop body is executed more than once
+ * only when an empty dbnode needs to be skipped.
+ */
+ while (it->result == ISC_R_NOMORE) {
+ dns_rdatasetiter_destroy(&it->rdatasetit);
+ dns_db_detachnode(it->db, &it->node);
+ it->result = dns_dbiterator_next(it->dbit);
+ if (it->result == ISC_R_NOMORE) {
+ /* We are at the end of the entire database. */
+ return (it->result);
+ }
+ if (it->result != ISC_R_SUCCESS) {
+ return (it->result);
+ }
+ it->result = dns_dbiterator_current(
+ it->dbit, &it->node,
+ dns_fixedname_name(&it->fixedname));
+ if (it->result != ISC_R_SUCCESS) {
+ return (it->result);
+ }
+ it->result = dns_db_allrdatasets(it->db, it->node, it->ver, 0,
+ it->now, &it->rdatasetit);
+ if (it->result != ISC_R_SUCCESS) {
+ return (it->result);
+ }
+ it->result = dns_rdatasetiter_first(it->rdatasetit);
+ }
+ if (it->result != ISC_R_SUCCESS) {
+ return (it->result);
+ }
+ dns_rdatasetiter_current(it->rdatasetit, &it->rdataset);
+ dns_rdataset_getownercase(&it->rdataset,
+ dns_fixedname_name(&it->fixedname));
+ it->rdataset.attributes |= DNS_RDATASETATTR_LOADORDER;
+ it->result = dns_rdataset_first(&it->rdataset);
+ return (it->result);
+}
+
+isc_result_t
+dns_rriterator_next(dns_rriterator_t *it) {
+ REQUIRE(VALID_RRITERATOR(it));
+ if (it->result != ISC_R_SUCCESS) {
+ return (it->result);
+ }
+
+ INSIST(it->dbit != NULL);
+ INSIST(it->node != NULL);
+ INSIST(it->rdatasetit != NULL);
+
+ it->result = dns_rdataset_next(&it->rdataset);
+ if (it->result == ISC_R_NOMORE) {
+ return (dns_rriterator_nextrrset(it));
+ }
+ return (it->result);
+}
+
+void
+dns_rriterator_pause(dns_rriterator_t *it) {
+ REQUIRE(VALID_RRITERATOR(it));
+ RUNTIME_CHECK(dns_dbiterator_pause(it->dbit) == ISC_R_SUCCESS);
+}
+
+void
+dns_rriterator_destroy(dns_rriterator_t *it) {
+ REQUIRE(VALID_RRITERATOR(it));
+ if (dns_rdataset_isassociated(&it->rdataset)) {
+ dns_rdataset_disassociate(&it->rdataset);
+ }
+ if (it->rdatasetit != NULL) {
+ dns_rdatasetiter_destroy(&it->rdatasetit);
+ }
+ if (it->node != NULL) {
+ dns_db_detachnode(it->db, &it->node);
+ }
+ dns_dbiterator_destroy(&it->dbit);
+}
+
+void
+dns_rriterator_current(dns_rriterator_t *it, dns_name_t **name, uint32_t *ttl,
+ dns_rdataset_t **rdataset, dns_rdata_t **rdata) {
+ REQUIRE(name != NULL && *name == NULL);
+ REQUIRE(VALID_RRITERATOR(it));
+ REQUIRE(it->result == ISC_R_SUCCESS);
+ REQUIRE(rdataset == NULL || *rdataset == NULL);
+ REQUIRE(rdata == NULL || *rdata == NULL);
+
+ *name = dns_fixedname_name(&it->fixedname);
+ *ttl = it->rdataset.ttl;
+
+ dns_rdata_reset(&it->rdata);
+ dns_rdataset_current(&it->rdataset, &it->rdata);
+
+ if (rdataset != NULL) {
+ *rdataset = &it->rdataset;
+ }
+
+ if (rdata != NULL) {
+ *rdata = &it->rdata;
+ }
+}
diff --git a/lib/dns/rrl.c b/lib/dns/rrl.c
new file mode 100644
index 0000000..3be91b6
--- /dev/null
+++ b/lib/dns/rrl.c
@@ -0,0 +1,1367 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/*! \file */
+
+/*
+ * Rate limit DNS responses.
+ */
+
+/* #define ISC_LIST_CHECKINIT */
+
+#include <inttypes.h>
+#include <stdbool.h>
+
+#include <isc/mem.h>
+#include <isc/net.h>
+#include <isc/netaddr.h>
+#include <isc/print.h>
+#include <isc/result.h>
+#include <isc/util.h>
+
+#include <dns/log.h>
+#include <dns/name.h>
+#include <dns/rcode.h>
+#include <dns/rdataclass.h>
+#include <dns/rdatatype.h>
+#include <dns/rrl.h>
+#include <dns/view.h>
+#include <dns/zone.h>
+
+static void
+log_end(dns_rrl_t *rrl, dns_rrl_entry_t *e, bool early, char *log_buf,
+ unsigned int log_buf_len);
+
+/*
+ * Get a modulus for a hash function that is tolerably likely to be
+ * relatively prime to most inputs. Of course, we get a prime for for initial
+ * values not larger than the square of the last prime. We often get a prime
+ * after that.
+ * This works well in practice for hash tables up to at least 100
+ * times the square of the last prime and better than a multiplicative hash.
+ */
+static int
+hash_divisor(unsigned int initial) {
+ static uint16_t primes[] = {
+ 3,
+ 5,
+ 7,
+ 11,
+ 13,
+ 17,
+ 19,
+ 23,
+ 29,
+ 31,
+ 37,
+ 41,
+ 43,
+ 47,
+ 53,
+ 59,
+ 61,
+ 67,
+ 71,
+ 73,
+ 79,
+ 83,
+ 89,
+ 97,
+#if 0
+ 101, 103, 107, 109, 113, 127, 131, 137, 139, 149, 151, 157,
+ 163, 167, 173, 179, 181, 191, 193, 197, 199, 211, 223, 227,
+ 229, 233, 239, 241, 251, 257, 263, 269, 271, 277, 281, 283,
+ 293, 307, 311, 313, 317, 331, 337, 347, 349, 353, 359, 367,
+ 373, 379, 383, 389, 397, 401, 409, 419, 421, 431, 433, 439,
+ 443, 449, 457, 461, 463, 467, 479, 487, 491, 499, 503, 509,
+ 521, 523, 541, 547, 557, 563, 569, 571, 577, 587, 593, 599,
+ 601, 607, 613, 617, 619, 631, 641, 643, 647, 653, 659, 661,
+ 673, 677, 683, 691, 701, 709, 719, 727, 733, 739, 743, 751,
+ 757, 761, 769, 773, 787, 797, 809, 811, 821, 823, 827, 829,
+ 839, 853, 857, 859, 863, 877, 881, 883, 887, 907, 911, 919,
+ 929, 937, 941, 947, 953, 967, 971, 977, 983, 991, 997, 1009,
+#endif /* if 0 */
+ };
+ int divisions, tries;
+ unsigned int result;
+ uint16_t *pp, p;
+
+ result = initial;
+
+ if (primes[sizeof(primes) / sizeof(primes[0]) - 1] >= result) {
+ pp = primes;
+ while (*pp < result) {
+ ++pp;
+ }
+ return (*pp);
+ }
+
+ if ((result & 1) == 0) {
+ ++result;
+ }
+
+ divisions = 0;
+ tries = 1;
+ pp = primes;
+ do {
+ p = *pp++;
+ ++divisions;
+ if ((result % p) == 0) {
+ ++tries;
+ result += 2;
+ pp = primes;
+ }
+ } while (pp < &primes[sizeof(primes) / sizeof(primes[0])]);
+
+ if (isc_log_wouldlog(dns_lctx, DNS_RRL_LOG_DEBUG3)) {
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_RRL,
+ DNS_LOGMODULE_REQUEST, DNS_RRL_LOG_DEBUG3,
+ "%d hash_divisor() divisions in %d tries"
+ " to get %d from %d",
+ divisions, tries, result, initial);
+ }
+
+ return (result);
+}
+
+/*
+ * Convert a timestamp to a number of seconds in the past.
+ */
+static int
+delta_rrl_time(isc_stdtime_t ts, isc_stdtime_t now) {
+ int delta;
+
+ delta = now - ts;
+ if (delta >= 0) {
+ return (delta);
+ }
+
+ /*
+ * The timestamp is in the future. That future might result from
+ * re-ordered requests, because we use timestamps on requests
+ * instead of consulting a clock. Timestamps in the distant future are
+ * assumed to result from clock changes. When the clock changes to
+ * the past, make existing timestamps appear to be in the past.
+ */
+ if (delta < -DNS_RRL_MAX_TIME_TRAVEL) {
+ return (DNS_RRL_FOREVER);
+ }
+ return (0);
+}
+
+static int
+get_age(const dns_rrl_t *rrl, const dns_rrl_entry_t *e, isc_stdtime_t now) {
+ if (!e->ts_valid) {
+ return (DNS_RRL_FOREVER);
+ }
+ return (delta_rrl_time(e->ts + rrl->ts_bases[e->ts_gen], now));
+}
+
+static void
+set_age(dns_rrl_t *rrl, dns_rrl_entry_t *e, isc_stdtime_t now) {
+ dns_rrl_entry_t *e_old;
+ unsigned int ts_gen;
+ int i, ts;
+
+ ts_gen = rrl->ts_gen;
+ ts = now - rrl->ts_bases[ts_gen];
+ if (ts < 0) {
+ if (ts < -DNS_RRL_MAX_TIME_TRAVEL) {
+ ts = DNS_RRL_FOREVER;
+ } else {
+ ts = 0;
+ }
+ }
+
+ /*
+ * Make a new timestamp base if the current base is too old.
+ * All entries older than DNS_RRL_MAX_WINDOW seconds are ancient,
+ * useless history. Their timestamps can be treated as if they are
+ * all the same.
+ * We only do arithmetic on more recent timestamps, so bases for
+ * older timestamps can be recycled provided the old timestamps are
+ * marked as ancient history.
+ * This loop is almost always very short because most entries are
+ * recycled after one second and any entries that need to be marked
+ * are older than (DNS_RRL_TS_BASES)*DNS_RRL_MAX_TS seconds.
+ */
+ if (ts >= DNS_RRL_MAX_TS) {
+ ts_gen = (ts_gen + 1) % DNS_RRL_TS_BASES;
+ for (e_old = ISC_LIST_TAIL(rrl->lru), i = 0;
+ e_old != NULL && (e_old->ts_gen == ts_gen ||
+ !ISC_LINK_LINKED(e_old, hlink));
+ e_old = ISC_LIST_PREV(e_old, lru), ++i)
+ {
+ e_old->ts_valid = false;
+ }
+ if (i != 0) {
+ isc_log_write(
+ dns_lctx, DNS_LOGCATEGORY_RRL,
+ DNS_LOGMODULE_REQUEST, DNS_RRL_LOG_DEBUG1,
+ "rrl new time base scanned %d entries"
+ " at %d for %d %d %d %d",
+ i, now, rrl->ts_bases[ts_gen],
+ rrl->ts_bases[(ts_gen + 1) % DNS_RRL_TS_BASES],
+ rrl->ts_bases[(ts_gen + 2) % DNS_RRL_TS_BASES],
+ rrl->ts_bases[(ts_gen + 3) % DNS_RRL_TS_BASES]);
+ }
+ rrl->ts_gen = ts_gen;
+ rrl->ts_bases[ts_gen] = now;
+ ts = 0;
+ }
+
+ e->ts_gen = ts_gen;
+ e->ts = ts;
+ e->ts_valid = true;
+}
+
+static isc_result_t
+expand_entries(dns_rrl_t *rrl, int newsize) {
+ unsigned int bsize;
+ dns_rrl_block_t *b;
+ dns_rrl_entry_t *e;
+ double rate;
+ int i;
+
+ if (rrl->num_entries + newsize >= rrl->max_entries &&
+ rrl->max_entries != 0)
+ {
+ newsize = rrl->max_entries - rrl->num_entries;
+ if (newsize <= 0) {
+ return (ISC_R_SUCCESS);
+ }
+ }
+
+ /*
+ * Log expansions so that the user can tune max-table-size
+ * and min-table-size.
+ */
+ if (isc_log_wouldlog(dns_lctx, DNS_RRL_LOG_DROP) && rrl->hash != NULL) {
+ rate = rrl->probes;
+ if (rrl->searches != 0) {
+ rate /= rrl->searches;
+ }
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_RRL,
+ DNS_LOGMODULE_REQUEST, DNS_RRL_LOG_DROP,
+ "increase from %d to %d RRL entries with"
+ " %d bins; average search length %.1f",
+ rrl->num_entries, rrl->num_entries + newsize,
+ rrl->hash->length, rate);
+ }
+
+ bsize = sizeof(dns_rrl_block_t) +
+ (newsize - 1) * sizeof(dns_rrl_entry_t);
+ b = isc_mem_get(rrl->mctx, bsize);
+ memset(b, 0, bsize);
+ b->size = bsize;
+
+ e = b->entries;
+ for (i = 0; i < newsize; ++i, ++e) {
+ ISC_LINK_INIT(e, hlink);
+ ISC_LIST_INITANDAPPEND(rrl->lru, e, lru);
+ }
+ rrl->num_entries += newsize;
+ ISC_LIST_INITANDAPPEND(rrl->blocks, b, link);
+
+ return (ISC_R_SUCCESS);
+}
+
+static dns_rrl_bin_t *
+get_bin(dns_rrl_hash_t *hash, unsigned int hval) {
+ INSIST(hash != NULL);
+ return (&hash->bins[hval % hash->length]);
+}
+
+static void
+free_old_hash(dns_rrl_t *rrl) {
+ dns_rrl_hash_t *old_hash;
+ dns_rrl_bin_t *old_bin;
+ dns_rrl_entry_t *e, *e_next;
+
+ old_hash = rrl->old_hash;
+ for (old_bin = &old_hash->bins[0];
+ old_bin < &old_hash->bins[old_hash->length]; ++old_bin)
+ {
+ for (e = ISC_LIST_HEAD(*old_bin); e != NULL; e = e_next) {
+ e_next = ISC_LIST_NEXT(e, hlink);
+ ISC_LINK_INIT(e, hlink);
+ }
+ }
+
+ isc_mem_put(rrl->mctx, old_hash,
+ sizeof(*old_hash) +
+ (old_hash->length - 1) * sizeof(old_hash->bins[0]));
+ rrl->old_hash = NULL;
+}
+
+static isc_result_t
+expand_rrl_hash(dns_rrl_t *rrl, isc_stdtime_t now) {
+ dns_rrl_hash_t *hash;
+ int old_bins, new_bins, hsize;
+ double rate;
+
+ if (rrl->old_hash != NULL) {
+ free_old_hash(rrl);
+ }
+
+ /*
+ * Most searches fail and so go to the end of the chain.
+ * Use a small hash table load factor.
+ */
+ old_bins = (rrl->hash == NULL) ? 0 : rrl->hash->length;
+ new_bins = old_bins / 8 + old_bins;
+ if (new_bins < rrl->num_entries) {
+ new_bins = rrl->num_entries;
+ }
+ new_bins = hash_divisor(new_bins);
+
+ hsize = sizeof(dns_rrl_hash_t) + (new_bins - 1) * sizeof(hash->bins[0]);
+ hash = isc_mem_get(rrl->mctx, hsize);
+ memset(hash, 0, hsize);
+ hash->length = new_bins;
+ rrl->hash_gen ^= 1;
+ hash->gen = rrl->hash_gen;
+
+ if (isc_log_wouldlog(dns_lctx, DNS_RRL_LOG_DROP) && old_bins != 0) {
+ rate = rrl->probes;
+ if (rrl->searches != 0) {
+ rate /= rrl->searches;
+ }
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_RRL,
+ DNS_LOGMODULE_REQUEST, DNS_RRL_LOG_DROP,
+ "increase from %d to %d RRL bins for"
+ " %d entries; average search length %.1f",
+ old_bins, new_bins, rrl->num_entries, rate);
+ }
+
+ rrl->old_hash = rrl->hash;
+ if (rrl->old_hash != NULL) {
+ rrl->old_hash->check_time = now;
+ }
+ rrl->hash = hash;
+
+ return (ISC_R_SUCCESS);
+}
+
+static void
+ref_entry(dns_rrl_t *rrl, dns_rrl_entry_t *e, int probes, isc_stdtime_t now) {
+ /*
+ * Make the entry most recently used.
+ */
+ if (ISC_LIST_HEAD(rrl->lru) != e) {
+ if (e == rrl->last_logged) {
+ rrl->last_logged = ISC_LIST_PREV(e, lru);
+ }
+ ISC_LIST_UNLINK(rrl->lru, e, lru);
+ ISC_LIST_PREPEND(rrl->lru, e, lru);
+ }
+
+ /*
+ * Expand the hash table if it is time and necessary.
+ * This will leave the newly referenced entry in a chain in the
+ * old hash table. It will migrate to the new hash table the next
+ * time it is used or be cut loose when the old hash table is destroyed.
+ */
+ rrl->probes += probes;
+ ++rrl->searches;
+ if (rrl->searches > 100 &&
+ delta_rrl_time(rrl->hash->check_time, now) > 1)
+ {
+ if (rrl->probes / rrl->searches > 2) {
+ expand_rrl_hash(rrl, now);
+ }
+ rrl->hash->check_time = now;
+ rrl->probes = 0;
+ rrl->searches = 0;
+ }
+}
+
+static bool
+key_cmp(const dns_rrl_key_t *a, const dns_rrl_key_t *b) {
+ if (memcmp(a, b, sizeof(dns_rrl_key_t)) == 0) {
+ return (true);
+ }
+ return (false);
+}
+
+static uint32_t
+hash_key(const dns_rrl_key_t *key) {
+ uint32_t hval;
+ int i;
+
+ hval = key->w[0];
+ for (i = sizeof(key->w) / sizeof(key->w[0]) - 1; i >= 0; --i) {
+ hval = key->w[i] + (hval << 1);
+ }
+ return (hval);
+}
+
+/*
+ * Construct the hash table key.
+ * Use a hash of the DNS query name to save space in the database.
+ * Collisions result in legitimate rate limiting responses for one
+ * query name also limiting responses for other names to the
+ * same client. This is rare and benign enough given the large
+ * space costs compared to keeping the entire name in the database
+ * entry or the time costs of dynamic allocation.
+ */
+static void
+make_key(const dns_rrl_t *rrl, dns_rrl_key_t *key,
+ const isc_sockaddr_t *client_addr, dns_zone_t *zone,
+ dns_rdatatype_t qtype, const dns_name_t *qname,
+ dns_rdataclass_t qclass, dns_rrl_rtype_t rtype) {
+ int i;
+
+ memset(key, 0, sizeof(*key));
+
+ key->s.rtype = rtype;
+ if (rtype == DNS_RRL_RTYPE_QUERY) {
+ key->s.qtype = qtype;
+ key->s.qclass = qclass & 0xff;
+ } else if (rtype == DNS_RRL_RTYPE_REFERRAL ||
+ rtype == DNS_RRL_RTYPE_NODATA)
+ {
+ /*
+ * Because there is no qtype in the empty answer sections of
+ * referral and NODATA responses, count them as the same.
+ */
+ key->s.qclass = qclass & 0xff;
+ }
+
+ if (qname != NULL && qname->labels != 0) {
+ dns_name_t *origin = NULL;
+
+ if ((qname->attributes & DNS_NAMEATTR_WILDCARD) != 0 &&
+ zone != NULL && (origin = dns_zone_getorigin(zone)) != NULL)
+ {
+ dns_fixedname_t fixed;
+ dns_name_t *wild;
+ isc_result_t result;
+
+ /*
+ * Put all wildcard names in one bucket using the zone's
+ * origin name concatenated to the "*" name.
+ */
+ wild = dns_fixedname_initname(&fixed);
+ result = dns_name_concatenate(dns_wildcardname, origin,
+ wild, NULL);
+ if (result != ISC_R_SUCCESS) {
+ /*
+ * Fallback to use the zone's origin name
+ * instead of the concatenated name.
+ */
+ wild = origin;
+ }
+ key->s.qname_hash = dns_name_fullhash(wild, false);
+ } else {
+ key->s.qname_hash = dns_name_fullhash(qname, false);
+ }
+ }
+
+ switch (client_addr->type.sa.sa_family) {
+ case AF_INET:
+ key->s.ip[0] = (client_addr->type.sin.sin_addr.s_addr &
+ rrl->ipv4_mask);
+ break;
+ case AF_INET6:
+ key->s.ipv6 = true;
+ memmove(key->s.ip, &client_addr->type.sin6.sin6_addr,
+ sizeof(key->s.ip));
+ for (i = 0; i < DNS_RRL_MAX_PREFIX / 32; ++i) {
+ key->s.ip[i] &= rrl->ipv6_mask[i];
+ }
+ break;
+ }
+}
+
+static dns_rrl_rate_t *
+get_rate(dns_rrl_t *rrl, dns_rrl_rtype_t rtype) {
+ switch (rtype) {
+ case DNS_RRL_RTYPE_QUERY:
+ return (&rrl->responses_per_second);
+ case DNS_RRL_RTYPE_REFERRAL:
+ return (&rrl->referrals_per_second);
+ case DNS_RRL_RTYPE_NODATA:
+ return (&rrl->nodata_per_second);
+ case DNS_RRL_RTYPE_NXDOMAIN:
+ return (&rrl->nxdomains_per_second);
+ case DNS_RRL_RTYPE_ERROR:
+ return (&rrl->errors_per_second);
+ case DNS_RRL_RTYPE_ALL:
+ return (&rrl->all_per_second);
+ default:
+ UNREACHABLE();
+ }
+}
+
+static int
+response_balance(dns_rrl_t *rrl, const dns_rrl_entry_t *e, int age) {
+ dns_rrl_rate_t *ratep;
+ int balance, rate;
+
+ if (e->key.s.rtype == DNS_RRL_RTYPE_TCP) {
+ rate = 1;
+ } else {
+ ratep = get_rate(rrl, e->key.s.rtype);
+ rate = ratep->scaled;
+ }
+
+ balance = e->responses + age * rate;
+ if (balance > rate) {
+ balance = rate;
+ }
+ return (balance);
+}
+
+/*
+ * Search for an entry for a response and optionally create it.
+ */
+static dns_rrl_entry_t *
+get_entry(dns_rrl_t *rrl, const isc_sockaddr_t *client_addr, dns_zone_t *zone,
+ dns_rdataclass_t qclass, dns_rdatatype_t qtype,
+ const dns_name_t *qname, dns_rrl_rtype_t rtype, isc_stdtime_t now,
+ bool create, char *log_buf, unsigned int log_buf_len) {
+ dns_rrl_key_t key;
+ uint32_t hval;
+ dns_rrl_entry_t *e;
+ dns_rrl_hash_t *hash;
+ dns_rrl_bin_t *new_bin, *old_bin;
+ int probes, age;
+
+ make_key(rrl, &key, client_addr, zone, qtype, qname, qclass, rtype);
+ hval = hash_key(&key);
+
+ /*
+ * Look for the entry in the current hash table.
+ */
+ new_bin = get_bin(rrl->hash, hval);
+ probes = 1;
+ e = ISC_LIST_HEAD(*new_bin);
+ while (e != NULL) {
+ if (key_cmp(&e->key, &key)) {
+ ref_entry(rrl, e, probes, now);
+ return (e);
+ }
+ ++probes;
+ e = ISC_LIST_NEXT(e, hlink);
+ }
+
+ /*
+ * Look in the old hash table.
+ */
+ if (rrl->old_hash != NULL) {
+ old_bin = get_bin(rrl->old_hash, hval);
+ e = ISC_LIST_HEAD(*old_bin);
+ while (e != NULL) {
+ if (key_cmp(&e->key, &key)) {
+ ISC_LIST_UNLINK(*old_bin, e, hlink);
+ ISC_LIST_PREPEND(*new_bin, e, hlink);
+ e->hash_gen = rrl->hash_gen;
+ ref_entry(rrl, e, probes, now);
+ return (e);
+ }
+ e = ISC_LIST_NEXT(e, hlink);
+ }
+
+ /*
+ * Discard previous hash table when all of its entries are old.
+ */
+ age = delta_rrl_time(rrl->old_hash->check_time, now);
+ if (age > rrl->window) {
+ free_old_hash(rrl);
+ }
+ }
+
+ if (!create) {
+ return (NULL);
+ }
+
+ /*
+ * The entry does not exist, so create it by finding a free entry.
+ * Keep currently penalized and logged entries.
+ * Try to make more entries if none are idle.
+ * Steal the oldest entry if we cannot create more.
+ */
+ for (e = ISC_LIST_TAIL(rrl->lru); e != NULL; e = ISC_LIST_PREV(e, lru))
+ {
+ if (!ISC_LINK_LINKED(e, hlink)) {
+ break;
+ }
+ age = get_age(rrl, e, now);
+ if (age <= 1) {
+ e = NULL;
+ break;
+ }
+ if (!e->logged && response_balance(rrl, e, age) > 0) {
+ break;
+ }
+ }
+ if (e == NULL) {
+ expand_entries(rrl, ISC_MIN((rrl->num_entries + 1) / 2, 1000));
+ e = ISC_LIST_TAIL(rrl->lru);
+ }
+ if (e->logged) {
+ log_end(rrl, e, true, log_buf, log_buf_len);
+ }
+ if (ISC_LINK_LINKED(e, hlink)) {
+ if (e->hash_gen == rrl->hash_gen) {
+ hash = rrl->hash;
+ } else {
+ hash = rrl->old_hash;
+ }
+ old_bin = get_bin(hash, hash_key(&e->key));
+ ISC_LIST_UNLINK(*old_bin, e, hlink);
+ }
+ ISC_LIST_PREPEND(*new_bin, e, hlink);
+ e->hash_gen = rrl->hash_gen;
+ e->key = key;
+ e->ts_valid = false;
+ ref_entry(rrl, e, probes, now);
+ return (e);
+}
+
+static void
+debit_log(const dns_rrl_entry_t *e, int age, const char *action) {
+ char buf[sizeof("age=2147483647")];
+ const char *age_str;
+
+ if (age == DNS_RRL_FOREVER) {
+ age_str = "";
+ } else {
+ snprintf(buf, sizeof(buf), "age=%d", age);
+ age_str = buf;
+ }
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_RRL, DNS_LOGMODULE_REQUEST,
+ DNS_RRL_LOG_DEBUG3, "rrl %08x %6s responses=%-3d %s",
+ hash_key(&e->key), age_str, e->responses, action);
+}
+
+static dns_rrl_result_t
+debit_rrl_entry(dns_rrl_t *rrl, dns_rrl_entry_t *e, double qps, double scale,
+ const isc_sockaddr_t *client_addr, isc_stdtime_t now,
+ char *log_buf, unsigned int log_buf_len) {
+ int rate, new_rate, slip, new_slip, age, log_secs, min;
+ dns_rrl_rate_t *ratep;
+ dns_rrl_entry_t const *credit_e;
+
+ /*
+ * Pick the rate counter.
+ * Optionally adjust the rate by the estimated query/second rate.
+ */
+ ratep = get_rate(rrl, e->key.s.rtype);
+ rate = ratep->r;
+ if (rate == 0) {
+ return (DNS_RRL_RESULT_OK);
+ }
+
+ if (scale < 1.0) {
+ /*
+ * The limit for clients that have used TCP is not scaled.
+ */
+ credit_e = get_entry(
+ rrl, client_addr, NULL, 0, dns_rdatatype_none, NULL,
+ DNS_RRL_RTYPE_TCP, now, false, log_buf, log_buf_len);
+ if (credit_e != NULL) {
+ age = get_age(rrl, e, now);
+ if (age < rrl->window) {
+ scale = 1.0;
+ }
+ }
+ }
+ if (scale < 1.0) {
+ new_rate = (int)(rate * scale);
+ if (new_rate < 1) {
+ new_rate = 1;
+ }
+ if (ratep->scaled != new_rate) {
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_RRL,
+ DNS_LOGMODULE_REQUEST, DNS_RRL_LOG_DEBUG1,
+ "%d qps scaled %s by %.2f"
+ " from %d to %d",
+ (int)qps, ratep->str, scale, rate,
+ new_rate);
+ rate = new_rate;
+ ratep->scaled = rate;
+ }
+ }
+
+ min = -rrl->window * rate;
+
+ /*
+ * Treat time jumps into the recent past as no time.
+ * Treat entries older than the window as if they were just created
+ * Credit other entries.
+ */
+ age = get_age(rrl, e, now);
+ if (age > 0) {
+ /*
+ * Credit tokens earned during elapsed time.
+ */
+ if (age > rrl->window) {
+ e->responses = rate;
+ e->slip_cnt = 0;
+ } else {
+ e->responses += rate * age;
+ if (e->responses > rate) {
+ e->responses = rate;
+ e->slip_cnt = 0;
+ }
+ }
+ /*
+ * Find the seconds since last log message without overflowing
+ * small counter. This counter is reset when an entry is
+ * created. It is not necessarily reset when some requests
+ * are answered provided other requests continue to be dropped
+ * or slipped. This can happen when the request rate is just
+ * at the limit.
+ */
+ if (e->logged) {
+ log_secs = e->log_secs;
+ log_secs += age;
+ if (log_secs > DNS_RRL_MAX_LOG_SECS || log_secs < 0) {
+ log_secs = DNS_RRL_MAX_LOG_SECS;
+ }
+ e->log_secs = log_secs;
+ }
+ }
+ set_age(rrl, e, now);
+
+ /*
+ * Debit the entry for this response.
+ */
+ if (--e->responses >= 0) {
+ if (isc_log_wouldlog(dns_lctx, DNS_RRL_LOG_DEBUG3)) {
+ debit_log(e, age, "");
+ }
+ return (DNS_RRL_RESULT_OK);
+ }
+
+ if (e->responses < min) {
+ e->responses = min;
+ }
+
+ /*
+ * Drop this response unless it should slip or leak.
+ */
+ slip = rrl->slip.r;
+ if (slip > 2 && scale < 1.0) {
+ new_slip = (int)(slip * scale);
+ if (new_slip < 2) {
+ new_slip = 2;
+ }
+ if (rrl->slip.scaled != new_slip) {
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_RRL,
+ DNS_LOGMODULE_REQUEST, DNS_RRL_LOG_DEBUG1,
+ "%d qps scaled slip"
+ " by %.2f from %d to %d",
+ (int)qps, scale, slip, new_slip);
+ slip = new_slip;
+ rrl->slip.scaled = slip;
+ }
+ }
+ if (slip != 0 && e->key.s.rtype != DNS_RRL_RTYPE_ALL) {
+ if (e->slip_cnt++ == 0) {
+ if ((int)e->slip_cnt >= slip) {
+ e->slip_cnt = 0;
+ }
+ if (isc_log_wouldlog(dns_lctx, DNS_RRL_LOG_DEBUG3)) {
+ debit_log(e, age, "slip");
+ }
+ return (DNS_RRL_RESULT_SLIP);
+ } else if ((int)e->slip_cnt >= slip) {
+ e->slip_cnt = 0;
+ }
+ }
+
+ if (isc_log_wouldlog(dns_lctx, DNS_RRL_LOG_DEBUG3)) {
+ debit_log(e, age, "drop");
+ }
+ return (DNS_RRL_RESULT_DROP);
+}
+
+static dns_rrl_qname_buf_t *
+get_qname(dns_rrl_t *rrl, const dns_rrl_entry_t *e) {
+ dns_rrl_qname_buf_t *qbuf;
+
+ qbuf = rrl->qnames[e->log_qname];
+ if (qbuf == NULL || qbuf->e != e) {
+ return (NULL);
+ }
+ return (qbuf);
+}
+
+static void
+free_qname(dns_rrl_t *rrl, dns_rrl_entry_t *e) {
+ dns_rrl_qname_buf_t *qbuf;
+
+ qbuf = get_qname(rrl, e);
+ if (qbuf != NULL) {
+ qbuf->e = NULL;
+ ISC_LIST_APPEND(rrl->qname_free, qbuf, link);
+ }
+}
+
+static void
+add_log_str(isc_buffer_t *lb, const char *str, unsigned int str_len) {
+ isc_region_t region;
+
+ isc_buffer_availableregion(lb, &region);
+ if (str_len >= region.length) {
+ if (region.length == 0U) {
+ return;
+ }
+ str_len = region.length;
+ }
+ memmove(region.base, str, str_len);
+ isc_buffer_add(lb, str_len);
+}
+
+#define ADD_LOG_CSTR(eb, s) add_log_str(eb, s, sizeof(s) - 1)
+
+/*
+ * Build strings for the logs
+ */
+static void
+make_log_buf(dns_rrl_t *rrl, dns_rrl_entry_t *e, const char *str1,
+ const char *str2, bool plural, const dns_name_t *qname,
+ bool save_qname, dns_rrl_result_t rrl_result,
+ isc_result_t resp_result, char *log_buf,
+ unsigned int log_buf_len) {
+ isc_buffer_t lb;
+ dns_rrl_qname_buf_t *qbuf;
+ isc_netaddr_t cidr;
+ char strbuf[ISC_MAX(sizeof("/123"), sizeof(" (12345678)"))];
+ const char *rstr;
+ isc_result_t msg_result;
+
+ if (log_buf_len <= 1) {
+ if (log_buf_len == 1) {
+ log_buf[0] = '\0';
+ }
+ return;
+ }
+ isc_buffer_init(&lb, log_buf, log_buf_len - 1);
+
+ if (str1 != NULL) {
+ add_log_str(&lb, str1, strlen(str1));
+ }
+ if (str2 != NULL) {
+ add_log_str(&lb, str2, strlen(str2));
+ }
+
+ switch (rrl_result) {
+ case DNS_RRL_RESULT_OK:
+ break;
+ case DNS_RRL_RESULT_DROP:
+ ADD_LOG_CSTR(&lb, "drop ");
+ break;
+ case DNS_RRL_RESULT_SLIP:
+ ADD_LOG_CSTR(&lb, "slip ");
+ break;
+ default:
+ UNREACHABLE();
+ }
+
+ switch (e->key.s.rtype) {
+ case DNS_RRL_RTYPE_QUERY:
+ break;
+ case DNS_RRL_RTYPE_REFERRAL:
+ ADD_LOG_CSTR(&lb, "referral ");
+ break;
+ case DNS_RRL_RTYPE_NODATA:
+ ADD_LOG_CSTR(&lb, "NODATA ");
+ break;
+ case DNS_RRL_RTYPE_NXDOMAIN:
+ ADD_LOG_CSTR(&lb, "NXDOMAIN ");
+ break;
+ case DNS_RRL_RTYPE_ERROR:
+ if (resp_result == ISC_R_SUCCESS) {
+ ADD_LOG_CSTR(&lb, "error ");
+ } else {
+ rstr = isc_result_totext(resp_result);
+ add_log_str(&lb, rstr, strlen(rstr));
+ ADD_LOG_CSTR(&lb, " error ");
+ }
+ break;
+ case DNS_RRL_RTYPE_ALL:
+ ADD_LOG_CSTR(&lb, "all ");
+ break;
+ default:
+ UNREACHABLE();
+ }
+
+ if (plural) {
+ ADD_LOG_CSTR(&lb, "responses to ");
+ } else {
+ ADD_LOG_CSTR(&lb, "response to ");
+ }
+
+ memset(&cidr, 0, sizeof(cidr));
+ if (e->key.s.ipv6) {
+ snprintf(strbuf, sizeof(strbuf), "/%d", rrl->ipv6_prefixlen);
+ cidr.family = AF_INET6;
+ memset(&cidr.type.in6, 0, sizeof(cidr.type.in6));
+ memmove(&cidr.type.in6, e->key.s.ip, sizeof(e->key.s.ip));
+ } else {
+ snprintf(strbuf, sizeof(strbuf), "/%d", rrl->ipv4_prefixlen);
+ cidr.family = AF_INET;
+ cidr.type.in.s_addr = e->key.s.ip[0];
+ }
+ msg_result = isc_netaddr_totext(&cidr, &lb);
+ if (msg_result != ISC_R_SUCCESS) {
+ ADD_LOG_CSTR(&lb, "?");
+ }
+ add_log_str(&lb, strbuf, strlen(strbuf));
+
+ if (e->key.s.rtype == DNS_RRL_RTYPE_QUERY ||
+ e->key.s.rtype == DNS_RRL_RTYPE_REFERRAL ||
+ e->key.s.rtype == DNS_RRL_RTYPE_NODATA ||
+ e->key.s.rtype == DNS_RRL_RTYPE_NXDOMAIN)
+ {
+ qbuf = get_qname(rrl, e);
+ if (save_qname && qbuf == NULL && qname != NULL &&
+ dns_name_isabsolute(qname))
+ {
+ /*
+ * Capture the qname for the "stop limiting" message.
+ */
+ qbuf = ISC_LIST_TAIL(rrl->qname_free);
+ if (qbuf != NULL) {
+ ISC_LIST_UNLINK(rrl->qname_free, qbuf, link);
+ } else if (rrl->num_qnames < DNS_RRL_QNAMES) {
+ qbuf = isc_mem_get(rrl->mctx, sizeof(*qbuf));
+ {
+ memset(qbuf, 0, sizeof(*qbuf));
+ ISC_LINK_INIT(qbuf, link);
+ qbuf->index = rrl->num_qnames;
+ rrl->qnames[rrl->num_qnames++] = qbuf;
+ }
+ }
+ if (qbuf != NULL) {
+ e->log_qname = qbuf->index;
+ qbuf->e = e;
+ dns_fixedname_init(&qbuf->qname);
+ dns_name_copy(qname,
+ dns_fixedname_name(&qbuf->qname));
+ }
+ }
+ if (qbuf != NULL) {
+ qname = dns_fixedname_name(&qbuf->qname);
+ }
+ if (qname != NULL) {
+ ADD_LOG_CSTR(&lb, " for ");
+ (void)dns_name_totext(qname, true, &lb);
+ } else {
+ ADD_LOG_CSTR(&lb, " for (?)");
+ }
+ if (e->key.s.rtype != DNS_RRL_RTYPE_NXDOMAIN) {
+ ADD_LOG_CSTR(&lb, " ");
+ (void)dns_rdataclass_totext(e->key.s.qclass, &lb);
+ if (e->key.s.rtype == DNS_RRL_RTYPE_QUERY) {
+ ADD_LOG_CSTR(&lb, " ");
+ (void)dns_rdatatype_totext(e->key.s.qtype, &lb);
+ }
+ }
+ snprintf(strbuf, sizeof(strbuf), " (%08" PRIx32 ")",
+ e->key.s.qname_hash);
+ add_log_str(&lb, strbuf, strlen(strbuf));
+ }
+
+ /*
+ * We saved room for '\0'.
+ */
+ log_buf[isc_buffer_usedlength(&lb)] = '\0';
+}
+
+static void
+log_end(dns_rrl_t *rrl, dns_rrl_entry_t *e, bool early, char *log_buf,
+ unsigned int log_buf_len) {
+ if (e->logged) {
+ make_log_buf(rrl, e, early ? "*" : NULL,
+ rrl->log_only ? "would stop limiting "
+ : "stop limiting ",
+ true, NULL, false, DNS_RRL_RESULT_OK,
+ ISC_R_SUCCESS, log_buf, log_buf_len);
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_RRL,
+ DNS_LOGMODULE_REQUEST, DNS_RRL_LOG_DROP, "%s",
+ log_buf);
+ free_qname(rrl, e);
+ e->logged = false;
+ --rrl->num_logged;
+ }
+}
+
+/*
+ * Log messages for streams that have stopped being rate limited.
+ */
+static void
+log_stops(dns_rrl_t *rrl, isc_stdtime_t now, int limit, char *log_buf,
+ unsigned int log_buf_len) {
+ dns_rrl_entry_t *e;
+ int age;
+
+ for (e = rrl->last_logged; e != NULL; e = ISC_LIST_PREV(e, lru)) {
+ if (!e->logged) {
+ continue;
+ }
+ if (now != 0) {
+ age = get_age(rrl, e, now);
+ if (age < DNS_RRL_STOP_LOG_SECS ||
+ response_balance(rrl, e, age) < 0)
+ {
+ break;
+ }
+ }
+
+ log_end(rrl, e, now == 0, log_buf, log_buf_len);
+ if (rrl->num_logged <= 0) {
+ break;
+ }
+
+ /*
+ * Too many messages could stall real work.
+ */
+ if (--limit < 0) {
+ rrl->last_logged = ISC_LIST_PREV(e, lru);
+ return;
+ }
+ }
+ if (e == NULL) {
+ INSIST(rrl->num_logged == 0);
+ rrl->log_stops_time = now;
+ }
+ rrl->last_logged = e;
+}
+
+/*
+ * Main rate limit interface.
+ */
+dns_rrl_result_t
+dns_rrl(dns_view_t *view, dns_zone_t *zone, const isc_sockaddr_t *client_addr,
+ bool is_tcp, dns_rdataclass_t qclass, dns_rdatatype_t qtype,
+ const dns_name_t *qname, isc_result_t resp_result, isc_stdtime_t now,
+ bool wouldlog, char *log_buf, unsigned int log_buf_len) {
+ dns_rrl_t *rrl;
+ dns_rrl_rtype_t rtype;
+ dns_rrl_entry_t *e;
+ isc_netaddr_t netclient;
+ int secs;
+ double qps, scale;
+ int exempt_match;
+ isc_result_t result;
+ dns_rrl_result_t rrl_result;
+
+ INSIST(log_buf != NULL && log_buf_len > 0);
+
+ rrl = view->rrl;
+ if (rrl->exempt != NULL) {
+ isc_netaddr_fromsockaddr(&netclient, client_addr);
+ result = dns_acl_match(&netclient, NULL, rrl->exempt,
+ view->aclenv, &exempt_match, NULL);
+ if (result == ISC_R_SUCCESS && exempt_match > 0) {
+ return (DNS_RRL_RESULT_OK);
+ }
+ }
+
+ LOCK(&rrl->lock);
+
+ /*
+ * Estimate total query per second rate when scaling by qps.
+ */
+ if (rrl->qps_scale == 0) {
+ qps = 0.0;
+ scale = 1.0;
+ } else {
+ ++rrl->qps_responses;
+ secs = delta_rrl_time(rrl->qps_time, now);
+ if (secs <= 0) {
+ qps = rrl->qps;
+ } else {
+ qps = (1.0 * rrl->qps_responses) / secs;
+ if (secs >= rrl->window) {
+ if (isc_log_wouldlog(dns_lctx,
+ DNS_RRL_LOG_DEBUG3))
+ {
+ isc_log_write(dns_lctx,
+ DNS_LOGCATEGORY_RRL,
+ DNS_LOGMODULE_REQUEST,
+ DNS_RRL_LOG_DEBUG3,
+ "%d responses/%d seconds"
+ " = %d qps",
+ rrl->qps_responses, secs,
+ (int)qps);
+ }
+ rrl->qps = qps;
+ rrl->qps_responses = 0;
+ rrl->qps_time = now;
+ } else if (qps < rrl->qps) {
+ qps = rrl->qps;
+ }
+ }
+ scale = rrl->qps_scale / qps;
+ }
+
+ /*
+ * Do maintenance once per second.
+ */
+ if (rrl->num_logged > 0 && rrl->log_stops_time != now) {
+ log_stops(rrl, now, 8, log_buf, log_buf_len);
+ }
+
+ /*
+ * Notice TCP responses when scaling limits by qps.
+ * Do not try to rate limit TCP responses.
+ */
+ if (is_tcp) {
+ if (scale < 1.0) {
+ e = get_entry(rrl, client_addr, NULL, 0,
+ dns_rdatatype_none, NULL,
+ DNS_RRL_RTYPE_TCP, now, true, log_buf,
+ log_buf_len);
+ if (e != NULL) {
+ e->responses = -(rrl->window + 1);
+ set_age(rrl, e, now);
+ }
+ }
+ UNLOCK(&rrl->lock);
+ return (DNS_RRL_RESULT_OK);
+ }
+
+ /*
+ * Find the right kind of entry, creating it if necessary.
+ * If that is impossible, then nothing more can be done
+ */
+ switch (resp_result) {
+ case ISC_R_SUCCESS:
+ rtype = DNS_RRL_RTYPE_QUERY;
+ break;
+ case DNS_R_DELEGATION:
+ rtype = DNS_RRL_RTYPE_REFERRAL;
+ break;
+ case DNS_R_NXRRSET:
+ rtype = DNS_RRL_RTYPE_NODATA;
+ break;
+ case DNS_R_NXDOMAIN:
+ rtype = DNS_RRL_RTYPE_NXDOMAIN;
+ break;
+ default:
+ rtype = DNS_RRL_RTYPE_ERROR;
+ break;
+ }
+ e = get_entry(rrl, client_addr, zone, qclass, qtype, qname, rtype, now,
+ true, log_buf, log_buf_len);
+ if (e == NULL) {
+ UNLOCK(&rrl->lock);
+ return (DNS_RRL_RESULT_OK);
+ }
+
+ if (isc_log_wouldlog(dns_lctx, DNS_RRL_LOG_DEBUG1)) {
+ /*
+ * Do not worry about speed or releasing the lock.
+ * This message appears before messages from debit_rrl_entry().
+ */
+ make_log_buf(rrl, e, "consider limiting ", NULL, false, qname,
+ false, DNS_RRL_RESULT_OK, resp_result, log_buf,
+ log_buf_len);
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_RRL,
+ DNS_LOGMODULE_REQUEST, DNS_RRL_LOG_DEBUG1, "%s",
+ log_buf);
+ }
+
+ rrl_result = debit_rrl_entry(rrl, e, qps, scale, client_addr, now,
+ log_buf, log_buf_len);
+
+ if (rrl->all_per_second.r != 0) {
+ /*
+ * We must debit the all-per-second token bucket if we have
+ * an all-per-second limit for the IP address.
+ * The all-per-second limit determines the log message
+ * when both limits are hit.
+ * The response limiting must continue if the
+ * all-per-second limiting lapses.
+ */
+ dns_rrl_entry_t *e_all;
+ dns_rrl_result_t rrl_all_result;
+
+ e_all = get_entry(rrl, client_addr, zone, 0, dns_rdatatype_none,
+ NULL, DNS_RRL_RTYPE_ALL, now, true, log_buf,
+ log_buf_len);
+ if (e_all == NULL) {
+ UNLOCK(&rrl->lock);
+ return (DNS_RRL_RESULT_OK);
+ }
+ rrl_all_result = debit_rrl_entry(rrl, e_all, qps, scale,
+ client_addr, now, log_buf,
+ log_buf_len);
+ if (rrl_all_result != DNS_RRL_RESULT_OK) {
+ e = e_all;
+ rrl_result = rrl_all_result;
+ if (isc_log_wouldlog(dns_lctx, DNS_RRL_LOG_DEBUG1)) {
+ make_log_buf(rrl, e,
+ "prefer all-per-second limiting ",
+ NULL, true, qname, false,
+ DNS_RRL_RESULT_OK, resp_result,
+ log_buf, log_buf_len);
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_RRL,
+ DNS_LOGMODULE_REQUEST,
+ DNS_RRL_LOG_DEBUG1, "%s",
+ log_buf);
+ }
+ }
+ }
+
+ if (rrl_result == DNS_RRL_RESULT_OK) {
+ UNLOCK(&rrl->lock);
+ return (DNS_RRL_RESULT_OK);
+ }
+
+ /*
+ * Log occasionally in the rate-limit category.
+ */
+ if ((!e->logged || e->log_secs >= DNS_RRL_MAX_LOG_SECS) &&
+ isc_log_wouldlog(dns_lctx, DNS_RRL_LOG_DROP))
+ {
+ make_log_buf(rrl, e, rrl->log_only ? "would " : NULL,
+ e->logged ? "continue limiting " : "limit ", true,
+ qname, true, DNS_RRL_RESULT_OK, resp_result,
+ log_buf, log_buf_len);
+ if (!e->logged) {
+ e->logged = true;
+ if (++rrl->num_logged <= 1) {
+ rrl->last_logged = e;
+ }
+ }
+ e->log_secs = 0;
+
+ /*
+ * Avoid holding the lock.
+ */
+ if (!wouldlog) {
+ UNLOCK(&rrl->lock);
+ e = NULL;
+ }
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_RRL,
+ DNS_LOGMODULE_REQUEST, DNS_RRL_LOG_DROP, "%s",
+ log_buf);
+ }
+
+ /*
+ * Make a log message for the caller.
+ */
+ if (wouldlog) {
+ make_log_buf(rrl, e,
+ rrl->log_only ? "would rate limit "
+ : "rate limit ",
+ NULL, false, qname, false, rrl_result, resp_result,
+ log_buf, log_buf_len);
+ }
+
+ if (e != NULL) {
+ /*
+ * Do not save the qname unless we might need it for
+ * the ending log message.
+ */
+ if (!e->logged) {
+ free_qname(rrl, e);
+ }
+ UNLOCK(&rrl->lock);
+ }
+
+ return (rrl_result);
+}
+
+void
+dns_rrl_view_destroy(dns_view_t *view) {
+ dns_rrl_t *rrl;
+ dns_rrl_block_t *b;
+ dns_rrl_hash_t *h;
+ char log_buf[DNS_RRL_LOG_BUF_LEN];
+ int i;
+
+ rrl = view->rrl;
+ if (rrl == NULL) {
+ return;
+ }
+ view->rrl = NULL;
+
+ /*
+ * Assume the caller takes care of locking the view and anything else.
+ */
+
+ if (rrl->num_logged > 0) {
+ log_stops(rrl, 0, INT32_MAX, log_buf, sizeof(log_buf));
+ }
+
+ for (i = 0; i < DNS_RRL_QNAMES; ++i) {
+ if (rrl->qnames[i] == NULL) {
+ break;
+ }
+ isc_mem_put(rrl->mctx, rrl->qnames[i], sizeof(*rrl->qnames[i]));
+ }
+
+ if (rrl->exempt != NULL) {
+ dns_acl_detach(&rrl->exempt);
+ }
+
+ isc_mutex_destroy(&rrl->lock);
+
+ while (!ISC_LIST_EMPTY(rrl->blocks)) {
+ b = ISC_LIST_HEAD(rrl->blocks);
+ ISC_LIST_UNLINK(rrl->blocks, b, link);
+ isc_mem_put(rrl->mctx, b, b->size);
+ }
+
+ h = rrl->hash;
+ if (h != NULL) {
+ isc_mem_put(rrl->mctx, h,
+ sizeof(*h) + (h->length - 1) * sizeof(h->bins[0]));
+ }
+
+ h = rrl->old_hash;
+ if (h != NULL) {
+ isc_mem_put(rrl->mctx, h,
+ sizeof(*h) + (h->length - 1) * sizeof(h->bins[0]));
+ }
+
+ isc_mem_putanddetach(&rrl->mctx, rrl, sizeof(*rrl));
+}
+
+isc_result_t
+dns_rrl_init(dns_rrl_t **rrlp, dns_view_t *view, int min_entries) {
+ dns_rrl_t *rrl;
+ isc_result_t result;
+
+ *rrlp = NULL;
+
+ rrl = isc_mem_get(view->mctx, sizeof(*rrl));
+ memset(rrl, 0, sizeof(*rrl));
+ isc_mem_attach(view->mctx, &rrl->mctx);
+ isc_mutex_init(&rrl->lock);
+ isc_stdtime_get(&rrl->ts_bases[0]);
+
+ view->rrl = rrl;
+
+ result = expand_entries(rrl, min_entries);
+ if (result != ISC_R_SUCCESS) {
+ dns_rrl_view_destroy(view);
+ return (result);
+ }
+ result = expand_rrl_hash(rrl, 0);
+ if (result != ISC_R_SUCCESS) {
+ dns_rrl_view_destroy(view);
+ return (result);
+ }
+
+ *rrlp = rrl;
+ return (ISC_R_SUCCESS);
+}
diff --git a/lib/dns/sdb.c b/lib/dns/sdb.c
new file mode 100644
index 0000000..317eeb0
--- /dev/null
+++ b/lib/dns/sdb.c
@@ -0,0 +1,1598 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/*! \file */
+
+#include <inttypes.h>
+#include <stdbool.h>
+#include <string.h>
+
+#include <isc/buffer.h>
+#include <isc/lex.h>
+#include <isc/log.h>
+#include <isc/magic.h>
+#include <isc/mem.h>
+#include <isc/once.h>
+#include <isc/print.h>
+#include <isc/refcount.h>
+#include <isc/region.h>
+#include <isc/result.h>
+#include <isc/util.h>
+
+#include <dns/callbacks.h>
+#include <dns/db.h>
+#include <dns/dbiterator.h>
+#include <dns/fixedname.h>
+#include <dns/log.h>
+#include <dns/rdata.h>
+#include <dns/rdatalist.h>
+#include <dns/rdataset.h>
+#include <dns/rdatasetiter.h>
+#include <dns/rdatatype.h>
+#include <dns/sdb.h>
+#include <dns/types.h>
+
+#include "rdatalist_p.h"
+
+struct dns_sdbimplementation {
+ const dns_sdbmethods_t *methods;
+ void *driverdata;
+ unsigned int flags;
+ isc_mem_t *mctx;
+ isc_mutex_t driverlock;
+ dns_dbimplementation_t *dbimp;
+};
+
+struct dns_sdb {
+ /* Unlocked */
+ dns_db_t common;
+ char *zone;
+ dns_sdbimplementation_t *implementation;
+ void *dbdata;
+
+ /* Atomic */
+ isc_refcount_t references;
+};
+
+struct dns_sdblookup {
+ /* Unlocked */
+ unsigned int magic;
+ dns_sdb_t *sdb;
+ ISC_LIST(dns_rdatalist_t) lists;
+ ISC_LIST(isc_buffer_t) buffers;
+ dns_name_t *name;
+ ISC_LINK(dns_sdblookup_t) link;
+ dns_rdatacallbacks_t callbacks;
+
+ /* Atomic */
+ isc_refcount_t references;
+};
+
+typedef struct dns_sdblookup dns_sdbnode_t;
+
+struct dns_sdballnodes {
+ dns_dbiterator_t common;
+ ISC_LIST(dns_sdbnode_t) nodelist;
+ dns_sdbnode_t *current;
+ dns_sdbnode_t *origin;
+};
+
+typedef dns_sdballnodes_t sdb_dbiterator_t;
+
+typedef struct sdb_rdatasetiter {
+ dns_rdatasetiter_t common;
+ dns_rdatalist_t *current;
+} sdb_rdatasetiter_t;
+
+#define SDB_MAGIC ISC_MAGIC('S', 'D', 'B', '-')
+
+/*%
+ * Note that "impmagic" is not the first four bytes of the struct, so
+ * ISC_MAGIC_VALID cannot be used.
+ */
+#define VALID_SDB(sdb) ((sdb) != NULL && (sdb)->common.impmagic == SDB_MAGIC)
+
+#define SDBLOOKUP_MAGIC ISC_MAGIC('S', 'D', 'B', 'L')
+#define VALID_SDBLOOKUP(sdbl) ISC_MAGIC_VALID(sdbl, SDBLOOKUP_MAGIC)
+#define VALID_SDBNODE(sdbn) VALID_SDBLOOKUP(sdbn)
+
+/* These values are taken from RFC1537 */
+#define SDB_DEFAULT_REFRESH 28800U /* 8 hours */
+#define SDB_DEFAULT_RETRY 7200U /* 2 hours */
+#define SDB_DEFAULT_EXPIRE 604800U /* 7 days */
+#define SDB_DEFAULT_MINIMUM 86400U /* 1 day */
+
+/* This is a reasonable value */
+#define SDB_DEFAULT_TTL (60 * 60 * 24)
+
+#ifdef __COVERITY__
+#define MAYBE_LOCK(sdb) LOCK(&sdb->implementation->driverlock)
+#define MAYBE_UNLOCK(sdb) UNLOCK(&sdb->implementation->driverlock)
+#else /* ifdef __COVERITY__ */
+#define MAYBE_LOCK(sdb) \
+ do { \
+ unsigned int flags = sdb->implementation->flags; \
+ if ((flags & DNS_SDBFLAG_THREADSAFE) == 0) \
+ LOCK(&sdb->implementation->driverlock); \
+ } while (0)
+
+#define MAYBE_UNLOCK(sdb) \
+ do { \
+ unsigned int flags = sdb->implementation->flags; \
+ if ((flags & DNS_SDBFLAG_THREADSAFE) == 0) \
+ UNLOCK(&sdb->implementation->driverlock); \
+ } while (0)
+#endif /* ifdef __COVERITY__ */
+
+static int dummy;
+
+static isc_result_t
+dns_sdb_create(isc_mem_t *mctx, const dns_name_t *origin, dns_dbtype_t type,
+ dns_rdataclass_t rdclass, unsigned int argc, char *argv[],
+ void *driverarg, dns_db_t **dbp);
+
+static isc_result_t
+findrdataset(dns_db_t *db, dns_dbnode_t *node, dns_dbversion_t *version,
+ dns_rdatatype_t type, dns_rdatatype_t covers, isc_stdtime_t now,
+ dns_rdataset_t *rdataset, dns_rdataset_t *sigrdataset);
+
+static isc_result_t
+createnode(dns_sdb_t *sdb, dns_sdbnode_t **nodep);
+
+static void
+destroynode(dns_sdbnode_t *node);
+
+static void
+detachnode(dns_db_t *db, dns_dbnode_t **targetp);
+
+static void
+list_tordataset(dns_rdatalist_t *rdatalist, dns_db_t *db, dns_dbnode_t *node,
+ dns_rdataset_t *rdataset);
+
+static void
+dbiterator_destroy(dns_dbiterator_t **iteratorp);
+static isc_result_t
+dbiterator_first(dns_dbiterator_t *iterator);
+static isc_result_t
+dbiterator_last(dns_dbiterator_t *iterator);
+static isc_result_t
+dbiterator_seek(dns_dbiterator_t *iterator, const dns_name_t *name);
+static isc_result_t
+dbiterator_prev(dns_dbiterator_t *iterator);
+static isc_result_t
+dbiterator_next(dns_dbiterator_t *iterator);
+static isc_result_t
+dbiterator_current(dns_dbiterator_t *iterator, dns_dbnode_t **nodep,
+ dns_name_t *name);
+static isc_result_t
+dbiterator_pause(dns_dbiterator_t *iterator);
+static isc_result_t
+dbiterator_origin(dns_dbiterator_t *iterator, dns_name_t *name);
+
+static dns_dbiteratormethods_t dbiterator_methods = {
+ dbiterator_destroy, dbiterator_first, dbiterator_last,
+ dbiterator_seek, dbiterator_prev, dbiterator_next,
+ dbiterator_current, dbiterator_pause, dbiterator_origin
+};
+
+static void
+rdatasetiter_destroy(dns_rdatasetiter_t **iteratorp);
+static isc_result_t
+rdatasetiter_first(dns_rdatasetiter_t *iterator);
+static isc_result_t
+rdatasetiter_next(dns_rdatasetiter_t *iterator);
+static void
+rdatasetiter_current(dns_rdatasetiter_t *iterator, dns_rdataset_t *rdataset);
+
+static dns_rdatasetitermethods_t rdatasetiter_methods = {
+ rdatasetiter_destroy, rdatasetiter_first, rdatasetiter_next,
+ rdatasetiter_current
+};
+
+/*
+ * Functions used by implementors of simple databases
+ */
+isc_result_t
+dns_sdb_register(const char *drivername, const dns_sdbmethods_t *methods,
+ void *driverdata, unsigned int flags, isc_mem_t *mctx,
+ dns_sdbimplementation_t **sdbimp) {
+ dns_sdbimplementation_t *imp;
+ isc_result_t result;
+
+ REQUIRE(drivername != NULL);
+ REQUIRE(methods != NULL);
+ REQUIRE(methods->lookup != NULL || methods->lookup2 != NULL);
+ REQUIRE(mctx != NULL);
+ REQUIRE(sdbimp != NULL && *sdbimp == NULL);
+ REQUIRE((flags &
+ ~(DNS_SDBFLAG_RELATIVEOWNER | DNS_SDBFLAG_RELATIVERDATA |
+ DNS_SDBFLAG_THREADSAFE | DNS_SDBFLAG_DNS64)) == 0);
+
+ imp = isc_mem_get(mctx, sizeof(dns_sdbimplementation_t));
+ imp->methods = methods;
+ imp->driverdata = driverdata;
+ imp->flags = flags;
+ imp->mctx = NULL;
+ isc_mem_attach(mctx, &imp->mctx);
+ isc_mutex_init(&imp->driverlock);
+
+ imp->dbimp = NULL;
+ result = dns_db_register(drivername, dns_sdb_create, imp, mctx,
+ &imp->dbimp);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup_mutex;
+ }
+ *sdbimp = imp;
+
+ return (ISC_R_SUCCESS);
+
+cleanup_mutex:
+ isc_mutex_destroy(&imp->driverlock);
+ isc_mem_put(mctx, imp, sizeof(dns_sdbimplementation_t));
+ return (result);
+}
+
+void
+dns_sdb_unregister(dns_sdbimplementation_t **sdbimp) {
+ dns_sdbimplementation_t *imp;
+
+ REQUIRE(sdbimp != NULL && *sdbimp != NULL);
+
+ imp = *sdbimp;
+ *sdbimp = NULL;
+ dns_db_unregister(&imp->dbimp);
+ isc_mutex_destroy(&imp->driverlock);
+
+ isc_mem_putanddetach(&imp->mctx, imp, sizeof(dns_sdbimplementation_t));
+}
+
+static unsigned int
+initial_size(unsigned int len) {
+ unsigned int size;
+
+ for (size = 1024; size < (64 * 1024); size *= 2) {
+ if (len < size) {
+ return (size);
+ }
+ }
+ return (65535);
+}
+
+isc_result_t
+dns_sdb_putrdata(dns_sdblookup_t *lookup, dns_rdatatype_t typeval,
+ dns_ttl_t ttl, const unsigned char *rdatap,
+ unsigned int rdlen) {
+ dns_rdatalist_t *rdatalist;
+ dns_rdata_t *rdata;
+ isc_buffer_t *rdatabuf = NULL;
+ isc_mem_t *mctx;
+ isc_region_t region;
+
+ mctx = lookup->sdb->common.mctx;
+
+ rdatalist = ISC_LIST_HEAD(lookup->lists);
+ while (rdatalist != NULL) {
+ if (rdatalist->type == typeval) {
+ break;
+ }
+ rdatalist = ISC_LIST_NEXT(rdatalist, link);
+ }
+
+ if (rdatalist == NULL) {
+ rdatalist = isc_mem_get(mctx, sizeof(dns_rdatalist_t));
+ dns_rdatalist_init(rdatalist);
+ rdatalist->rdclass = lookup->sdb->common.rdclass;
+ rdatalist->type = typeval;
+ rdatalist->ttl = ttl;
+ ISC_LIST_APPEND(lookup->lists, rdatalist, link);
+ } else if (rdatalist->ttl != ttl) {
+ return (DNS_R_BADTTL);
+ }
+
+ rdata = isc_mem_get(mctx, sizeof(dns_rdata_t));
+
+ isc_buffer_allocate(mctx, &rdatabuf, rdlen);
+ DE_CONST(rdatap, region.base);
+ region.length = rdlen;
+ isc_buffer_copyregion(rdatabuf, &region);
+ isc_buffer_usedregion(rdatabuf, &region);
+ dns_rdata_init(rdata);
+ dns_rdata_fromregion(rdata, rdatalist->rdclass, rdatalist->type,
+ &region);
+ ISC_LIST_APPEND(rdatalist->rdata, rdata, link);
+ ISC_LIST_APPEND(lookup->buffers, rdatabuf, link);
+
+ return (ISC_R_SUCCESS);
+}
+
+isc_result_t
+dns_sdb_putrr(dns_sdblookup_t *lookup, const char *type, dns_ttl_t ttl,
+ const char *data) {
+ unsigned int datalen;
+ dns_rdatatype_t typeval;
+ isc_textregion_t r;
+ isc_lex_t *lex = NULL;
+ isc_result_t result;
+ unsigned char *p = NULL;
+ unsigned int size = 0; /* Init to suppress compiler warning */
+ isc_mem_t *mctx;
+ dns_sdbimplementation_t *imp;
+ const dns_name_t *origin;
+ isc_buffer_t b;
+ isc_buffer_t rb;
+
+ REQUIRE(VALID_SDBLOOKUP(lookup));
+ REQUIRE(type != NULL);
+ REQUIRE(data != NULL);
+
+ mctx = lookup->sdb->common.mctx;
+
+ DE_CONST(type, r.base);
+ r.length = strlen(type);
+ result = dns_rdatatype_fromtext(&typeval, &r);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+
+ imp = lookup->sdb->implementation;
+ if ((imp->flags & DNS_SDBFLAG_RELATIVERDATA) != 0) {
+ origin = &lookup->sdb->common.origin;
+ } else {
+ origin = dns_rootname;
+ }
+
+ result = isc_lex_create(mctx, 64, &lex);
+ if (result != ISC_R_SUCCESS) {
+ goto failure;
+ }
+
+ datalen = strlen(data);
+ size = initial_size(datalen);
+ do {
+ isc_buffer_constinit(&b, data, datalen);
+ isc_buffer_add(&b, datalen);
+ result = isc_lex_openbuffer(lex, &b);
+ if (result != ISC_R_SUCCESS) {
+ goto failure;
+ }
+
+ if (size >= 65535) {
+ size = 65535;
+ }
+ p = isc_mem_get(mctx, size);
+ isc_buffer_init(&rb, p, size);
+ result = dns_rdata_fromtext(NULL, lookup->sdb->common.rdclass,
+ typeval, lex, origin, 0, mctx, &rb,
+ &lookup->callbacks);
+ if (result != ISC_R_NOSPACE) {
+ break;
+ }
+
+ /*
+ * Is the RR too big?
+ */
+ if (size >= 65535) {
+ break;
+ }
+ isc_mem_put(mctx, p, size);
+ p = NULL;
+ size *= 2;
+ } while (result == ISC_R_NOSPACE);
+
+ if (result != ISC_R_SUCCESS) {
+ goto failure;
+ }
+
+ result = dns_sdb_putrdata(lookup, typeval, ttl, isc_buffer_base(&rb),
+ isc_buffer_usedlength(&rb));
+failure:
+ if (p != NULL) {
+ isc_mem_put(mctx, p, size);
+ }
+ if (lex != NULL) {
+ isc_lex_destroy(&lex);
+ }
+
+ return (result);
+}
+
+static isc_result_t
+getnode(dns_sdballnodes_t *allnodes, const char *name, dns_sdbnode_t **nodep) {
+ dns_name_t *newname;
+ const dns_name_t *origin;
+ dns_fixedname_t fnewname;
+ dns_sdb_t *sdb = (dns_sdb_t *)allnodes->common.db;
+ dns_sdbimplementation_t *imp = sdb->implementation;
+ dns_sdbnode_t *sdbnode;
+ isc_mem_t *mctx = sdb->common.mctx;
+ isc_buffer_t b;
+ isc_result_t result;
+
+ newname = dns_fixedname_initname(&fnewname);
+
+ if ((imp->flags & DNS_SDBFLAG_RELATIVERDATA) != 0) {
+ origin = &sdb->common.origin;
+ } else {
+ origin = dns_rootname;
+ }
+ isc_buffer_constinit(&b, name, strlen(name));
+ isc_buffer_add(&b, strlen(name));
+
+ result = dns_name_fromtext(newname, &b, origin, 0, NULL);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+
+ if (allnodes->common.relative_names) {
+ /* All names are relative to the root */
+ unsigned int nlabels = dns_name_countlabels(newname);
+ dns_name_getlabelsequence(newname, 0, nlabels - 1, newname);
+ }
+
+ sdbnode = ISC_LIST_HEAD(allnodes->nodelist);
+ if (sdbnode == NULL || !dns_name_equal(sdbnode->name, newname)) {
+ sdbnode = NULL;
+ result = createnode(sdb, &sdbnode);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+ sdbnode->name = isc_mem_get(mctx, sizeof(dns_name_t));
+ dns_name_init(sdbnode->name, NULL);
+ dns_name_dup(newname, mctx, sdbnode->name);
+ ISC_LIST_PREPEND(allnodes->nodelist, sdbnode, link);
+ if (allnodes->origin == NULL &&
+ dns_name_equal(newname, &sdb->common.origin))
+ {
+ allnodes->origin = sdbnode;
+ }
+ }
+ *nodep = sdbnode;
+ return (ISC_R_SUCCESS);
+}
+
+isc_result_t
+dns_sdb_putnamedrr(dns_sdballnodes_t *allnodes, const char *name,
+ const char *type, dns_ttl_t ttl, const char *data) {
+ isc_result_t result;
+ dns_sdbnode_t *sdbnode = NULL;
+ result = getnode(allnodes, name, &sdbnode);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+ return (dns_sdb_putrr(sdbnode, type, ttl, data));
+}
+
+isc_result_t
+dns_sdb_putnamedrdata(dns_sdballnodes_t *allnodes, const char *name,
+ dns_rdatatype_t type, dns_ttl_t ttl, const void *rdata,
+ unsigned int rdlen) {
+ isc_result_t result;
+ dns_sdbnode_t *sdbnode = NULL;
+ result = getnode(allnodes, name, &sdbnode);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+ return (dns_sdb_putrdata(sdbnode, type, ttl, rdata, rdlen));
+}
+
+isc_result_t
+dns_sdb_putsoa(dns_sdblookup_t *lookup, const char *mname, const char *rname,
+ uint32_t serial) {
+ char str[2 * DNS_NAME_MAXTEXT + 5 * (sizeof("2147483647")) + 7];
+ int n;
+
+ REQUIRE(mname != NULL);
+ REQUIRE(rname != NULL);
+
+ n = snprintf(str, sizeof(str), "%s %s %u %u %u %u %u", mname, rname,
+ serial, SDB_DEFAULT_REFRESH, SDB_DEFAULT_RETRY,
+ SDB_DEFAULT_EXPIRE, SDB_DEFAULT_MINIMUM);
+ if (n >= (int)sizeof(str) || n < 0) {
+ return (ISC_R_NOSPACE);
+ }
+ return (dns_sdb_putrr(lookup, "SOA", SDB_DEFAULT_TTL, str));
+}
+
+/*
+ * DB routines
+ */
+
+static void
+attach(dns_db_t *source, dns_db_t **targetp) {
+ dns_sdb_t *sdb = (dns_sdb_t *)source;
+
+ REQUIRE(VALID_SDB(sdb));
+
+ isc_refcount_increment(&sdb->references);
+
+ *targetp = source;
+}
+
+static void
+destroy(dns_sdb_t *sdb) {
+ dns_sdbimplementation_t *imp = sdb->implementation;
+
+ isc_refcount_destroy(&sdb->references);
+
+ if (imp->methods->destroy != NULL) {
+ MAYBE_LOCK(sdb);
+ imp->methods->destroy(sdb->zone, imp->driverdata, &sdb->dbdata);
+ MAYBE_UNLOCK(sdb);
+ }
+
+ isc_mem_free(sdb->common.mctx, sdb->zone);
+
+ sdb->common.magic = 0;
+ sdb->common.impmagic = 0;
+
+ dns_name_free(&sdb->common.origin, sdb->common.mctx);
+
+ isc_mem_putanddetach(&sdb->common.mctx, sdb, sizeof(dns_sdb_t));
+}
+
+static void
+detach(dns_db_t **dbp) {
+ dns_sdb_t *sdb = (dns_sdb_t *)(*dbp);
+
+ REQUIRE(VALID_SDB(sdb));
+
+ *dbp = NULL;
+
+ if (isc_refcount_decrement(&sdb->references) == 1) {
+ destroy(sdb);
+ }
+}
+
+static isc_result_t
+beginload(dns_db_t *db, dns_rdatacallbacks_t *callbacks) {
+ UNUSED(db);
+ UNUSED(callbacks);
+ return (ISC_R_NOTIMPLEMENTED);
+}
+
+static isc_result_t
+endload(dns_db_t *db, dns_rdatacallbacks_t *callbacks) {
+ UNUSED(db);
+ UNUSED(callbacks);
+ return (ISC_R_NOTIMPLEMENTED);
+}
+
+static isc_result_t
+dump(dns_db_t *db, dns_dbversion_t *version, const char *filename,
+ dns_masterformat_t masterformat) {
+ UNUSED(db);
+ UNUSED(version);
+ UNUSED(filename);
+ UNUSED(masterformat);
+ return (ISC_R_NOTIMPLEMENTED);
+}
+
+static void
+currentversion(dns_db_t *db, dns_dbversion_t **versionp) {
+ REQUIRE(versionp != NULL && *versionp == NULL);
+
+ UNUSED(db);
+
+ *versionp = (void *)&dummy;
+ return;
+}
+
+static isc_result_t
+newversion(dns_db_t *db, dns_dbversion_t **versionp) {
+ UNUSED(db);
+ UNUSED(versionp);
+
+ return (ISC_R_NOTIMPLEMENTED);
+}
+
+static void
+attachversion(dns_db_t *db, dns_dbversion_t *source,
+ dns_dbversion_t **targetp) {
+ REQUIRE(source != NULL && source == (void *)&dummy);
+ REQUIRE(targetp != NULL && *targetp == NULL);
+
+ UNUSED(db);
+ *targetp = source;
+ return;
+}
+
+static void
+closeversion(dns_db_t *db, dns_dbversion_t **versionp, bool commit) {
+ REQUIRE(versionp != NULL && *versionp == (void *)&dummy);
+ REQUIRE(!commit);
+
+ UNUSED(db);
+ UNUSED(commit);
+
+ *versionp = NULL;
+}
+
+static isc_result_t
+createnode(dns_sdb_t *sdb, dns_sdbnode_t **nodep) {
+ dns_sdbnode_t *node;
+
+ node = isc_mem_get(sdb->common.mctx, sizeof(dns_sdbnode_t));
+
+ node->sdb = NULL;
+ attach((dns_db_t *)sdb, (dns_db_t **)&node->sdb);
+ ISC_LIST_INIT(node->lists);
+ ISC_LIST_INIT(node->buffers);
+ ISC_LINK_INIT(node, link);
+ node->name = NULL;
+ dns_rdatacallbacks_init(&node->callbacks);
+
+ isc_refcount_init(&node->references, 1);
+
+ node->magic = SDBLOOKUP_MAGIC;
+
+ *nodep = node;
+ return (ISC_R_SUCCESS);
+}
+
+static void
+destroynode(dns_sdbnode_t *node) {
+ dns_rdatalist_t *list;
+ dns_rdata_t *rdata;
+ isc_buffer_t *b;
+ dns_sdb_t *sdb;
+ isc_mem_t *mctx;
+
+ sdb = node->sdb;
+ mctx = sdb->common.mctx;
+
+ while (!ISC_LIST_EMPTY(node->lists)) {
+ list = ISC_LIST_HEAD(node->lists);
+ while (!ISC_LIST_EMPTY(list->rdata)) {
+ rdata = ISC_LIST_HEAD(list->rdata);
+ ISC_LIST_UNLINK(list->rdata, rdata, link);
+ isc_mem_put(mctx, rdata, sizeof(dns_rdata_t));
+ }
+ ISC_LIST_UNLINK(node->lists, list, link);
+ isc_mem_put(mctx, list, sizeof(dns_rdatalist_t));
+ }
+
+ while (!ISC_LIST_EMPTY(node->buffers)) {
+ b = ISC_LIST_HEAD(node->buffers);
+ ISC_LIST_UNLINK(node->buffers, b, link);
+ isc_buffer_free(&b);
+ }
+
+ if (node->name != NULL) {
+ dns_name_free(node->name, mctx);
+ isc_mem_put(mctx, node->name, sizeof(dns_name_t));
+ }
+
+ node->magic = 0;
+ isc_mem_put(mctx, node, sizeof(dns_sdbnode_t));
+ detach((dns_db_t **)(void *)&sdb);
+}
+
+static isc_result_t
+getoriginnode(dns_db_t *db, dns_dbnode_t **nodep) {
+ dns_sdb_t *sdb = (dns_sdb_t *)db;
+ dns_sdbnode_t *node = NULL;
+ isc_result_t result;
+ isc_buffer_t b;
+ char namestr[DNS_NAME_MAXTEXT + 1];
+ dns_sdbimplementation_t *imp;
+ dns_name_t relname;
+ dns_name_t *name;
+
+ REQUIRE(VALID_SDB(sdb));
+ REQUIRE(nodep != NULL && *nodep == NULL);
+
+ imp = sdb->implementation;
+ name = &sdb->common.origin;
+
+ if (imp->methods->lookup2 != NULL) {
+ if ((imp->flags & DNS_SDBFLAG_RELATIVEOWNER) != 0) {
+ dns_name_init(&relname, NULL);
+ name = &relname;
+ }
+ } else {
+ isc_buffer_init(&b, namestr, sizeof(namestr));
+ if ((imp->flags & DNS_SDBFLAG_RELATIVEOWNER) != 0) {
+ dns_name_init(&relname, NULL);
+ result = dns_name_totext(&relname, true, &b);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+ } else {
+ result = dns_name_totext(name, true, &b);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+ }
+ isc_buffer_putuint8(&b, 0);
+ }
+
+ result = createnode(sdb, &node);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+
+ MAYBE_LOCK(sdb);
+ if (imp->methods->lookup2 != NULL) {
+ result = imp->methods->lookup2(&sdb->common.origin, name,
+ sdb->dbdata, node, NULL, NULL);
+ } else {
+ result = imp->methods->lookup(sdb->zone, namestr, sdb->dbdata,
+ node, NULL, NULL);
+ }
+ MAYBE_UNLOCK(sdb);
+ if (result != ISC_R_SUCCESS &&
+ !(result == ISC_R_NOTFOUND && imp->methods->authority != NULL))
+ {
+ destroynode(node);
+ return (result);
+ }
+
+ if (imp->methods->authority != NULL) {
+ MAYBE_LOCK(sdb);
+ result = imp->methods->authority(sdb->zone, sdb->dbdata, node);
+ MAYBE_UNLOCK(sdb);
+ if (result != ISC_R_SUCCESS) {
+ destroynode(node);
+ return (result);
+ }
+ }
+
+ *nodep = node;
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+findnodeext(dns_db_t *db, const dns_name_t *name, bool create,
+ dns_clientinfomethods_t *methods, dns_clientinfo_t *clientinfo,
+ dns_dbnode_t **nodep) {
+ dns_sdb_t *sdb = (dns_sdb_t *)db;
+ dns_sdbnode_t *node = NULL;
+ isc_result_t result;
+ isc_buffer_t b;
+ char namestr[DNS_NAME_MAXTEXT + 1];
+ bool isorigin;
+ dns_sdbimplementation_t *imp;
+ dns_name_t relname;
+ unsigned int labels;
+
+ REQUIRE(VALID_SDB(sdb));
+ REQUIRE(nodep != NULL && *nodep == NULL);
+
+ UNUSED(name);
+ UNUSED(create);
+
+ imp = sdb->implementation;
+
+ isorigin = dns_name_equal(name, &sdb->common.origin);
+
+ if (imp->methods->lookup2 != NULL) {
+ if ((imp->flags & DNS_SDBFLAG_RELATIVEOWNER) != 0) {
+ labels = dns_name_countlabels(name) -
+ dns_name_countlabels(&db->origin);
+ dns_name_init(&relname, NULL);
+ dns_name_getlabelsequence(name, 0, labels, &relname);
+ name = &relname;
+ }
+ } else {
+ isc_buffer_init(&b, namestr, sizeof(namestr));
+ if ((imp->flags & DNS_SDBFLAG_RELATIVEOWNER) != 0) {
+ labels = dns_name_countlabels(name) -
+ dns_name_countlabels(&db->origin);
+ dns_name_init(&relname, NULL);
+ dns_name_getlabelsequence(name, 0, labels, &relname);
+ result = dns_name_totext(&relname, true, &b);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+ } else {
+ result = dns_name_totext(name, true, &b);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+ }
+ isc_buffer_putuint8(&b, 0);
+ }
+
+ result = createnode(sdb, &node);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+
+ MAYBE_LOCK(sdb);
+ if (imp->methods->lookup2 != NULL) {
+ result = imp->methods->lookup2(&sdb->common.origin, name,
+ sdb->dbdata, node, methods,
+ clientinfo);
+ } else {
+ result = imp->methods->lookup(sdb->zone, namestr, sdb->dbdata,
+ node, methods, clientinfo);
+ }
+ MAYBE_UNLOCK(sdb);
+ if (result != ISC_R_SUCCESS && !(result == ISC_R_NOTFOUND && isorigin &&
+ imp->methods->authority != NULL))
+ {
+ destroynode(node);
+ return (result);
+ }
+
+ if (isorigin && imp->methods->authority != NULL) {
+ MAYBE_LOCK(sdb);
+ result = imp->methods->authority(sdb->zone, sdb->dbdata, node);
+ MAYBE_UNLOCK(sdb);
+ if (result != ISC_R_SUCCESS) {
+ destroynode(node);
+ return (result);
+ }
+ }
+
+ *nodep = node;
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+findext(dns_db_t *db, const dns_name_t *name, dns_dbversion_t *version,
+ dns_rdatatype_t type, unsigned int options, isc_stdtime_t now,
+ dns_dbnode_t **nodep, dns_name_t *foundname,
+ dns_clientinfomethods_t *methods, dns_clientinfo_t *clientinfo,
+ dns_rdataset_t *rdataset, dns_rdataset_t *sigrdataset) {
+ dns_sdb_t *sdb = (dns_sdb_t *)db;
+ dns_dbnode_t *node = NULL;
+ dns_fixedname_t fname;
+ dns_rdataset_t xrdataset;
+ dns_name_t *xname;
+ unsigned int nlabels, olabels;
+ isc_result_t result;
+ unsigned int i;
+ unsigned int flags;
+
+ REQUIRE(VALID_SDB(sdb));
+ REQUIRE(nodep == NULL || *nodep == NULL);
+ REQUIRE(version == NULL || version == (void *)&dummy);
+
+ UNUSED(options);
+
+ if (!dns_name_issubdomain(name, &db->origin)) {
+ return (DNS_R_NXDOMAIN);
+ }
+
+ olabels = dns_name_countlabels(&db->origin);
+ nlabels = dns_name_countlabels(name);
+
+ xname = dns_fixedname_initname(&fname);
+
+ if (rdataset == NULL) {
+ dns_rdataset_init(&xrdataset);
+ rdataset = &xrdataset;
+ }
+
+ result = DNS_R_NXDOMAIN;
+ flags = sdb->implementation->flags;
+ i = (flags & DNS_SDBFLAG_DNS64) != 0 ? nlabels : olabels;
+ for (; i <= nlabels; i++) {
+ /*
+ * Look up the next label.
+ */
+ dns_name_getlabelsequence(name, nlabels - i, i, xname);
+ result = findnodeext(db, xname, false, methods, clientinfo,
+ &node);
+ if (result == ISC_R_NOTFOUND) {
+ /*
+ * No data at zone apex?
+ */
+ if (i == olabels) {
+ return (DNS_R_BADDB);
+ }
+ result = DNS_R_NXDOMAIN;
+ continue;
+ }
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+
+ /*
+ * DNS64 zone's don't have DNAME or NS records.
+ */
+ if ((flags & DNS_SDBFLAG_DNS64) != 0) {
+ goto skip;
+ }
+
+ /*
+ * DNS64 zone's don't have DNAME or NS records.
+ */
+ if ((flags & DNS_SDBFLAG_DNS64) != 0) {
+ goto skip;
+ }
+
+ /*
+ * Look for a DNAME at the current label, unless this is
+ * the qname.
+ */
+ if (i < nlabels) {
+ result = findrdataset(db, node, version,
+ dns_rdatatype_dname, 0, now,
+ rdataset, sigrdataset);
+ if (result == ISC_R_SUCCESS) {
+ result = DNS_R_DNAME;
+ break;
+ }
+ }
+
+ /*
+ * Look for an NS at the current label, unless this is the
+ * origin or glue is ok.
+ */
+ if (i != olabels && (options & DNS_DBFIND_GLUEOK) == 0) {
+ result = findrdataset(db, node, version,
+ dns_rdatatype_ns, 0, now,
+ rdataset, sigrdataset);
+ if (result == ISC_R_SUCCESS) {
+ if (i == nlabels && type == dns_rdatatype_any) {
+ result = DNS_R_ZONECUT;
+ dns_rdataset_disassociate(rdataset);
+ if (sigrdataset != NULL &&
+ dns_rdataset_isassociated(
+ sigrdataset))
+ {
+ dns_rdataset_disassociate(
+ sigrdataset);
+ }
+ } else {
+ result = DNS_R_DELEGATION;
+ }
+ break;
+ }
+ }
+
+ /*
+ * If the current name is not the qname, add another label
+ * and try again.
+ */
+ if (i < nlabels) {
+ destroynode(node);
+ node = NULL;
+ continue;
+ }
+
+ skip:
+ /*
+ * If we're looking for ANY, we're done.
+ */
+ if (type == dns_rdatatype_any) {
+ result = ISC_R_SUCCESS;
+ break;
+ }
+
+ /*
+ * Look for the qtype.
+ */
+ result = findrdataset(db, node, version, type, 0, now, rdataset,
+ sigrdataset);
+ if (result == ISC_R_SUCCESS) {
+ break;
+ }
+
+ /*
+ * Look for a CNAME
+ */
+ if (type != dns_rdatatype_cname) {
+ result = findrdataset(db, node, version,
+ dns_rdatatype_cname, 0, now,
+ rdataset, sigrdataset);
+ if (result == ISC_R_SUCCESS) {
+ result = DNS_R_CNAME;
+ break;
+ }
+ }
+
+ result = DNS_R_NXRRSET;
+ break;
+ }
+
+ if (rdataset == &xrdataset && dns_rdataset_isassociated(rdataset)) {
+ dns_rdataset_disassociate(rdataset);
+ }
+
+ if (foundname != NULL) {
+ dns_name_copy(xname, foundname);
+ }
+
+ if (nodep != NULL) {
+ *nodep = node;
+ } else if (node != NULL) {
+ detachnode(db, &node);
+ }
+
+ return (result);
+}
+
+static isc_result_t
+findzonecut(dns_db_t *db, const dns_name_t *name, unsigned int options,
+ isc_stdtime_t now, dns_dbnode_t **nodep, dns_name_t *foundname,
+ dns_name_t *dcname, dns_rdataset_t *rdataset,
+ dns_rdataset_t *sigrdataset) {
+ UNUSED(db);
+ UNUSED(name);
+ UNUSED(options);
+ UNUSED(now);
+ UNUSED(nodep);
+ UNUSED(foundname);
+ UNUSED(dcname);
+ UNUSED(rdataset);
+ UNUSED(sigrdataset);
+
+ return (ISC_R_NOTIMPLEMENTED);
+}
+
+static void
+attachnode(dns_db_t *db, dns_dbnode_t *source, dns_dbnode_t **targetp) {
+ dns_sdb_t *sdb = (dns_sdb_t *)db;
+ dns_sdbnode_t *node = (dns_sdbnode_t *)source;
+
+ REQUIRE(VALID_SDB(sdb));
+
+ UNUSED(sdb);
+
+ isc_refcount_increment(&node->references);
+
+ *targetp = source;
+}
+
+static void
+detachnode(dns_db_t *db, dns_dbnode_t **targetp) {
+ dns_sdb_t *sdb = (dns_sdb_t *)db;
+ dns_sdbnode_t *node;
+
+ REQUIRE(VALID_SDB(sdb));
+ REQUIRE(targetp != NULL && *targetp != NULL);
+
+ UNUSED(sdb);
+
+ node = (dns_sdbnode_t *)(*targetp);
+
+ *targetp = NULL;
+
+ if (isc_refcount_decrement(&node->references) == 1) {
+ destroynode(node);
+ }
+}
+
+static isc_result_t
+expirenode(dns_db_t *db, dns_dbnode_t *node, isc_stdtime_t now) {
+ UNUSED(db);
+ UNUSED(node);
+ UNUSED(now);
+ UNREACHABLE();
+}
+
+static void
+printnode(dns_db_t *db, dns_dbnode_t *node, FILE *out) {
+ UNUSED(db);
+ UNUSED(node);
+ UNUSED(out);
+ return;
+}
+
+static isc_result_t
+createiterator(dns_db_t *db, unsigned int options,
+ dns_dbiterator_t **iteratorp) {
+ dns_sdb_t *sdb = (dns_sdb_t *)db;
+ REQUIRE(VALID_SDB(sdb));
+
+ sdb_dbiterator_t *sdbiter;
+ isc_result_t result;
+ dns_sdbimplementation_t *imp = sdb->implementation;
+
+ if (imp->methods->allnodes == NULL) {
+ return (ISC_R_NOTIMPLEMENTED);
+ }
+
+ if ((options & DNS_DB_NSEC3ONLY) != 0 ||
+ (options & DNS_DB_NONSEC3) != 0)
+ {
+ return (ISC_R_NOTIMPLEMENTED);
+ }
+
+ sdbiter = isc_mem_get(sdb->common.mctx, sizeof(sdb_dbiterator_t));
+
+ sdbiter->common.methods = &dbiterator_methods;
+ sdbiter->common.db = NULL;
+ dns_db_attach(db, &sdbiter->common.db);
+ sdbiter->common.relative_names = ((options & DNS_DB_RELATIVENAMES) !=
+ 0);
+ sdbiter->common.magic = DNS_DBITERATOR_MAGIC;
+ ISC_LIST_INIT(sdbiter->nodelist);
+ sdbiter->current = NULL;
+ sdbiter->origin = NULL;
+
+ MAYBE_LOCK(sdb);
+ result = imp->methods->allnodes(sdb->zone, sdb->dbdata, sdbiter);
+ MAYBE_UNLOCK(sdb);
+ if (result != ISC_R_SUCCESS) {
+ dbiterator_destroy((dns_dbiterator_t **)(void *)&sdbiter);
+ return (result);
+ }
+
+ if (sdbiter->origin != NULL) {
+ ISC_LIST_UNLINK(sdbiter->nodelist, sdbiter->origin, link);
+ ISC_LIST_PREPEND(sdbiter->nodelist, sdbiter->origin, link);
+ }
+
+ *iteratorp = (dns_dbiterator_t *)sdbiter;
+
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+findrdataset(dns_db_t *db, dns_dbnode_t *node, dns_dbversion_t *version,
+ dns_rdatatype_t type, dns_rdatatype_t covers, isc_stdtime_t now,
+ dns_rdataset_t *rdataset, dns_rdataset_t *sigrdataset) {
+ REQUIRE(VALID_SDBNODE(node));
+
+ dns_rdatalist_t *list;
+ dns_sdbnode_t *sdbnode = (dns_sdbnode_t *)node;
+
+ UNUSED(db);
+ UNUSED(version);
+ UNUSED(covers);
+ UNUSED(now);
+ UNUSED(sigrdataset);
+
+ if (type == dns_rdatatype_rrsig) {
+ return (ISC_R_NOTIMPLEMENTED);
+ }
+
+ list = ISC_LIST_HEAD(sdbnode->lists);
+ while (list != NULL) {
+ if (list->type == type) {
+ break;
+ }
+ list = ISC_LIST_NEXT(list, link);
+ }
+ if (list == NULL) {
+ return (ISC_R_NOTFOUND);
+ }
+
+ list_tordataset(list, db, node, rdataset);
+
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+allrdatasets(dns_db_t *db, dns_dbnode_t *node, dns_dbversion_t *version,
+ unsigned int options, isc_stdtime_t now,
+ dns_rdatasetiter_t **iteratorp) {
+ sdb_rdatasetiter_t *iterator;
+
+ REQUIRE(version == NULL || version == &dummy);
+
+ UNUSED(version);
+ UNUSED(now);
+
+ iterator = isc_mem_get(db->mctx, sizeof(sdb_rdatasetiter_t));
+
+ iterator->common.magic = DNS_RDATASETITER_MAGIC;
+ iterator->common.methods = &rdatasetiter_methods;
+ iterator->common.db = db;
+ iterator->common.node = NULL;
+ attachnode(db, node, &iterator->common.node);
+ iterator->common.version = version;
+ iterator->common.options = options;
+ iterator->common.now = now;
+
+ *iteratorp = (dns_rdatasetiter_t *)iterator;
+
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+addrdataset(dns_db_t *db, dns_dbnode_t *node, dns_dbversion_t *version,
+ isc_stdtime_t now, dns_rdataset_t *rdataset, unsigned int options,
+ dns_rdataset_t *addedrdataset) {
+ UNUSED(db);
+ UNUSED(node);
+ UNUSED(version);
+ UNUSED(now);
+ UNUSED(rdataset);
+ UNUSED(options);
+ UNUSED(addedrdataset);
+
+ return (ISC_R_NOTIMPLEMENTED);
+}
+
+static isc_result_t
+subtractrdataset(dns_db_t *db, dns_dbnode_t *node, dns_dbversion_t *version,
+ dns_rdataset_t *rdataset, unsigned int options,
+ dns_rdataset_t *newrdataset) {
+ UNUSED(db);
+ UNUSED(node);
+ UNUSED(version);
+ UNUSED(rdataset);
+ UNUSED(options);
+ UNUSED(newrdataset);
+
+ return (ISC_R_NOTIMPLEMENTED);
+}
+
+static isc_result_t
+deleterdataset(dns_db_t *db, dns_dbnode_t *node, dns_dbversion_t *version,
+ dns_rdatatype_t type, dns_rdatatype_t covers) {
+ UNUSED(db);
+ UNUSED(node);
+ UNUSED(version);
+ UNUSED(type);
+ UNUSED(covers);
+
+ return (ISC_R_NOTIMPLEMENTED);
+}
+
+static bool
+issecure(dns_db_t *db) {
+ UNUSED(db);
+
+ return (false);
+}
+
+static unsigned int
+nodecount(dns_db_t *db, dns_dbtree_t tree) {
+ UNUSED(db);
+ UNUSED(tree);
+
+ return (0);
+}
+
+static bool
+ispersistent(dns_db_t *db) {
+ UNUSED(db);
+ return (true);
+}
+
+static void
+overmem(dns_db_t *db, bool over) {
+ UNUSED(db);
+ UNUSED(over);
+}
+
+static void
+settask(dns_db_t *db, isc_task_t *task) {
+ UNUSED(db);
+ UNUSED(task);
+}
+
+static dns_dbmethods_t sdb_methods = {
+ attach, detach,
+ beginload, endload,
+ dump, currentversion,
+ newversion, attachversion,
+ closeversion, NULL, /* findnode */
+ NULL, /* find */
+ findzonecut, attachnode,
+ detachnode, expirenode,
+ printnode, createiterator,
+ findrdataset, allrdatasets,
+ addrdataset, subtractrdataset,
+ deleterdataset, issecure,
+ nodecount, ispersistent,
+ overmem, settask,
+ getoriginnode, /* getoriginnode */
+ NULL, /* transfernode */
+ NULL, /* getnsec3parameters */
+ NULL, /* findnsec3node */
+ NULL, /* setsigningtime */
+ NULL, /* getsigningtime */
+ NULL, /* resigned */
+ NULL, /* isdnssec */
+ NULL, /* getrrsetstats */
+ NULL, /* rpz_attach */
+ NULL, /* rpz_ready */
+ findnodeext, findext,
+ NULL, /* setcachestats */
+ NULL, /* hashsize */
+ NULL, /* nodefullname */
+ NULL, /* getsize */
+ NULL, /* setservestalettl */
+ NULL, /* getservestalettl */
+ NULL, /* setservestalerefresh */
+ NULL, /* getservestalerefresh */
+ NULL, /* setgluecachestats */
+};
+
+static isc_result_t
+dns_sdb_create(isc_mem_t *mctx, const dns_name_t *origin, dns_dbtype_t type,
+ dns_rdataclass_t rdclass, unsigned int argc, char *argv[],
+ void *driverarg, dns_db_t **dbp) {
+ dns_sdb_t *sdb;
+ isc_result_t result;
+ char zonestr[DNS_NAME_MAXTEXT + 1];
+ isc_buffer_t b;
+ dns_sdbimplementation_t *imp;
+
+ REQUIRE(driverarg != NULL);
+
+ imp = driverarg;
+
+ if (type != dns_dbtype_zone) {
+ return (ISC_R_NOTIMPLEMENTED);
+ }
+
+ sdb = isc_mem_get(mctx, sizeof(dns_sdb_t));
+ memset(sdb, 0, sizeof(dns_sdb_t));
+
+ dns_name_init(&sdb->common.origin, NULL);
+ sdb->common.attributes = 0;
+ sdb->common.methods = &sdb_methods;
+ sdb->common.rdclass = rdclass;
+ sdb->common.mctx = NULL;
+ sdb->implementation = imp;
+
+ isc_mem_attach(mctx, &sdb->common.mctx);
+
+ result = dns_name_dupwithoffsets(origin, mctx, &sdb->common.origin);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup_lock;
+ }
+
+ isc_buffer_init(&b, zonestr, sizeof(zonestr));
+ result = dns_name_totext(origin, true, &b);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup_origin;
+ }
+ isc_buffer_putuint8(&b, 0);
+
+ sdb->zone = isc_mem_strdup(mctx, zonestr);
+
+ sdb->dbdata = NULL;
+ if (imp->methods->create != NULL) {
+ MAYBE_LOCK(sdb);
+ result = imp->methods->create(sdb->zone, argc, argv,
+ imp->driverdata, &sdb->dbdata);
+ MAYBE_UNLOCK(sdb);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup_zonestr;
+ }
+ }
+
+ isc_refcount_init(&sdb->references, 1);
+
+ sdb->common.magic = DNS_DB_MAGIC;
+ sdb->common.impmagic = SDB_MAGIC;
+
+ *dbp = (dns_db_t *)sdb;
+
+ return (ISC_R_SUCCESS);
+
+cleanup_zonestr:
+ isc_mem_free(mctx, sdb->zone);
+cleanup_origin:
+ dns_name_free(&sdb->common.origin, mctx);
+cleanup_lock:
+ isc_mem_putanddetach(&mctx, sdb, sizeof(dns_sdb_t));
+
+ return (result);
+}
+
+/*
+ * Rdataset Methods
+ */
+
+static void
+disassociate(dns_rdataset_t *rdataset) {
+ dns_dbnode_t *node = rdataset->private5;
+ dns_sdbnode_t *sdbnode = (dns_sdbnode_t *)node;
+ dns_db_t *db = (dns_db_t *)sdbnode->sdb;
+
+ detachnode(db, &node);
+ isc__rdatalist_disassociate(rdataset);
+}
+
+static void
+rdataset_clone(dns_rdataset_t *source, dns_rdataset_t *target) {
+ dns_dbnode_t *node = source->private5;
+ dns_sdbnode_t *sdbnode = (dns_sdbnode_t *)node;
+ dns_db_t *db = (dns_db_t *)sdbnode->sdb;
+ dns_dbnode_t *tempdb = NULL;
+
+ isc__rdatalist_clone(source, target);
+ attachnode(db, node, &tempdb);
+ source->private5 = tempdb;
+}
+
+static dns_rdatasetmethods_t sdb_rdataset_methods = {
+ disassociate,
+ isc__rdatalist_first,
+ isc__rdatalist_next,
+ isc__rdatalist_current,
+ rdataset_clone,
+ isc__rdatalist_count,
+ isc__rdatalist_addnoqname,
+ isc__rdatalist_getnoqname,
+ NULL, /* addclosest */
+ NULL, /* getclosest */
+ NULL, /* settrust */
+ NULL, /* expire */
+ NULL, /* clearprefetch */
+ NULL, /* setownercase */
+ NULL, /* getownercase */
+ NULL /* addglue */
+};
+
+static void
+list_tordataset(dns_rdatalist_t *rdatalist, dns_db_t *db, dns_dbnode_t *node,
+ dns_rdataset_t *rdataset) {
+ /*
+ * The sdb rdataset is an rdatalist with some additions.
+ * - private1 & private2 are used by the rdatalist.
+ * - private3 & private 4 are unused.
+ * - private5 is the node.
+ */
+
+ /* This should never fail. */
+ RUNTIME_CHECK(dns_rdatalist_tordataset(rdatalist, rdataset) ==
+ ISC_R_SUCCESS);
+
+ rdataset->methods = &sdb_rdataset_methods;
+ dns_db_attachnode(db, node, &rdataset->private5);
+}
+
+/*
+ * Database Iterator Methods
+ */
+static void
+dbiterator_destroy(dns_dbiterator_t **iteratorp) {
+ sdb_dbiterator_t *sdbiter = (sdb_dbiterator_t *)(*iteratorp);
+ dns_sdb_t *sdb = (dns_sdb_t *)sdbiter->common.db;
+
+ while (!ISC_LIST_EMPTY(sdbiter->nodelist)) {
+ dns_sdbnode_t *node;
+ node = ISC_LIST_HEAD(sdbiter->nodelist);
+ ISC_LIST_UNLINK(sdbiter->nodelist, node, link);
+ destroynode(node);
+ }
+
+ dns_db_detach(&sdbiter->common.db);
+ isc_mem_put(sdb->common.mctx, sdbiter, sizeof(sdb_dbiterator_t));
+
+ *iteratorp = NULL;
+}
+
+static isc_result_t
+dbiterator_first(dns_dbiterator_t *iterator) {
+ sdb_dbiterator_t *sdbiter = (sdb_dbiterator_t *)iterator;
+
+ sdbiter->current = ISC_LIST_HEAD(sdbiter->nodelist);
+ if (sdbiter->current == NULL) {
+ return (ISC_R_NOMORE);
+ } else {
+ return (ISC_R_SUCCESS);
+ }
+}
+
+static isc_result_t
+dbiterator_last(dns_dbiterator_t *iterator) {
+ sdb_dbiterator_t *sdbiter = (sdb_dbiterator_t *)iterator;
+
+ sdbiter->current = ISC_LIST_TAIL(sdbiter->nodelist);
+ if (sdbiter->current == NULL) {
+ return (ISC_R_NOMORE);
+ } else {
+ return (ISC_R_SUCCESS);
+ }
+}
+
+static isc_result_t
+dbiterator_seek(dns_dbiterator_t *iterator, const dns_name_t *name) {
+ sdb_dbiterator_t *sdbiter = (sdb_dbiterator_t *)iterator;
+
+ sdbiter->current = ISC_LIST_HEAD(sdbiter->nodelist);
+ while (sdbiter->current != NULL) {
+ if (dns_name_equal(sdbiter->current->name, name)) {
+ return (ISC_R_SUCCESS);
+ }
+ sdbiter->current = ISC_LIST_NEXT(sdbiter->current, link);
+ }
+ return (ISC_R_NOTFOUND);
+}
+
+static isc_result_t
+dbiterator_prev(dns_dbiterator_t *iterator) {
+ sdb_dbiterator_t *sdbiter = (sdb_dbiterator_t *)iterator;
+
+ sdbiter->current = ISC_LIST_PREV(sdbiter->current, link);
+ if (sdbiter->current == NULL) {
+ return (ISC_R_NOMORE);
+ } else {
+ return (ISC_R_SUCCESS);
+ }
+}
+
+static isc_result_t
+dbiterator_next(dns_dbiterator_t *iterator) {
+ sdb_dbiterator_t *sdbiter = (sdb_dbiterator_t *)iterator;
+
+ sdbiter->current = ISC_LIST_NEXT(sdbiter->current, link);
+ if (sdbiter->current == NULL) {
+ return (ISC_R_NOMORE);
+ } else {
+ return (ISC_R_SUCCESS);
+ }
+}
+
+static isc_result_t
+dbiterator_current(dns_dbiterator_t *iterator, dns_dbnode_t **nodep,
+ dns_name_t *name) {
+ sdb_dbiterator_t *sdbiter = (sdb_dbiterator_t *)iterator;
+
+ attachnode(iterator->db, sdbiter->current, nodep);
+ if (name != NULL) {
+ dns_name_copy(sdbiter->current->name, name);
+ return (ISC_R_SUCCESS);
+ }
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+dbiterator_pause(dns_dbiterator_t *iterator) {
+ UNUSED(iterator);
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+dbiterator_origin(dns_dbiterator_t *iterator, dns_name_t *name) {
+ UNUSED(iterator);
+ dns_name_copy(dns_rootname, name);
+ return (ISC_R_SUCCESS);
+}
+
+/*
+ * Rdataset Iterator Methods
+ */
+
+static void
+rdatasetiter_destroy(dns_rdatasetiter_t **iteratorp) {
+ sdb_rdatasetiter_t *sdbiterator = (sdb_rdatasetiter_t *)(*iteratorp);
+ detachnode(sdbiterator->common.db, &sdbiterator->common.node);
+ isc_mem_put(sdbiterator->common.db->mctx, sdbiterator,
+ sizeof(sdb_rdatasetiter_t));
+ *iteratorp = NULL;
+}
+
+static isc_result_t
+rdatasetiter_first(dns_rdatasetiter_t *iterator) {
+ sdb_rdatasetiter_t *sdbiterator = (sdb_rdatasetiter_t *)iterator;
+ dns_sdbnode_t *sdbnode = (dns_sdbnode_t *)iterator->node;
+
+ if (ISC_LIST_EMPTY(sdbnode->lists)) {
+ return (ISC_R_NOMORE);
+ }
+ sdbiterator->current = ISC_LIST_HEAD(sdbnode->lists);
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+rdatasetiter_next(dns_rdatasetiter_t *iterator) {
+ sdb_rdatasetiter_t *sdbiterator = (sdb_rdatasetiter_t *)iterator;
+
+ sdbiterator->current = ISC_LIST_NEXT(sdbiterator->current, link);
+ if (sdbiterator->current == NULL) {
+ return (ISC_R_NOMORE);
+ } else {
+ return (ISC_R_SUCCESS);
+ }
+}
+
+static void
+rdatasetiter_current(dns_rdatasetiter_t *iterator, dns_rdataset_t *rdataset) {
+ sdb_rdatasetiter_t *sdbiterator = (sdb_rdatasetiter_t *)iterator;
+
+ list_tordataset(sdbiterator->current, iterator->db, iterator->node,
+ rdataset);
+}
diff --git a/lib/dns/sdlz.c b/lib/dns/sdlz.c
new file mode 100644
index 0000000..7ab08f6
--- /dev/null
+++ b/lib/dns/sdlz.c
@@ -0,0 +1,2086 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0 AND ISC
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/*
+ * Copyright (C) 2002 Stichting NLnet, Netherlands, stichting@nlnet.nl.
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the
+ * above copyright notice and this permission notice appear in all
+ * copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND STICHTING NLNET
+ * DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
+ * STICHTING NLNET BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR
+ * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
+ * OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+ * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE
+ * USE OR PERFORMANCE OF THIS SOFTWARE.
+ *
+ * The development of Dynamically Loadable Zones (DLZ) for Bind 9 was
+ * conceived and contributed by Rob Butler.
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the
+ * above copyright notice and this permission notice appear in all
+ * copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND ROB BUTLER
+ * DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
+ * ROB BUTLER BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR
+ * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
+ * OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+ * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE
+ * USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+/*! \file */
+
+#include <inttypes.h>
+#include <stdbool.h>
+#include <string.h>
+
+#include <isc/buffer.h>
+#include <isc/lex.h>
+#include <isc/log.h>
+#include <isc/magic.h>
+#include <isc/mem.h>
+#include <isc/once.h>
+#include <isc/print.h>
+#include <isc/region.h>
+#include <isc/result.h>
+#include <isc/rwlock.h>
+#include <isc/string.h>
+#include <isc/util.h>
+
+#include <dns/callbacks.h>
+#include <dns/db.h>
+#include <dns/dbiterator.h>
+#include <dns/dlz.h>
+#include <dns/fixedname.h>
+#include <dns/log.h>
+#include <dns/master.h>
+#include <dns/rdata.h>
+#include <dns/rdatalist.h>
+#include <dns/rdataset.h>
+#include <dns/rdatasetiter.h>
+#include <dns/rdatatype.h>
+#include <dns/sdlz.h>
+#include <dns/types.h>
+
+#include "rdatalist_p.h"
+
+/*
+ * Private Types
+ */
+
+struct dns_sdlzimplementation {
+ const dns_sdlzmethods_t *methods;
+ isc_mem_t *mctx;
+ void *driverarg;
+ unsigned int flags;
+ isc_mutex_t driverlock;
+ dns_dlzimplementation_t *dlz_imp;
+};
+
+struct dns_sdlz_db {
+ /* Unlocked */
+ dns_db_t common;
+ void *dbdata;
+ dns_sdlzimplementation_t *dlzimp;
+
+ /* Atomic */
+ isc_refcount_t references;
+
+ /* Locked */
+ dns_dbversion_t *future_version;
+ int dummy_version;
+};
+
+struct dns_sdlzlookup {
+ /* Unlocked */
+ unsigned int magic;
+ dns_sdlz_db_t *sdlz;
+ ISC_LIST(dns_rdatalist_t) lists;
+ ISC_LIST(isc_buffer_t) buffers;
+ dns_name_t *name;
+ ISC_LINK(dns_sdlzlookup_t) link;
+ dns_rdatacallbacks_t callbacks;
+
+ /* Atomic */
+ isc_refcount_t references;
+};
+
+typedef struct dns_sdlzlookup dns_sdlznode_t;
+
+struct dns_sdlzallnodes {
+ dns_dbiterator_t common;
+ ISC_LIST(dns_sdlznode_t) nodelist;
+ dns_sdlznode_t *current;
+ dns_sdlznode_t *origin;
+};
+
+typedef dns_sdlzallnodes_t sdlz_dbiterator_t;
+
+typedef struct sdlz_rdatasetiter {
+ dns_rdatasetiter_t common;
+ dns_rdatalist_t *current;
+} sdlz_rdatasetiter_t;
+
+#define SDLZDB_MAGIC ISC_MAGIC('D', 'L', 'Z', 'S')
+
+/*
+ * Note that "impmagic" is not the first four bytes of the struct, so
+ * ISC_MAGIC_VALID cannot be used.
+ */
+
+#define VALID_SDLZDB(sdlzdb) \
+ ((sdlzdb) != NULL && (sdlzdb)->common.impmagic == SDLZDB_MAGIC)
+
+#define SDLZLOOKUP_MAGIC ISC_MAGIC('D', 'L', 'Z', 'L')
+#define VALID_SDLZLOOKUP(sdlzl) ISC_MAGIC_VALID(sdlzl, SDLZLOOKUP_MAGIC)
+#define VALID_SDLZNODE(sdlzn) VALID_SDLZLOOKUP(sdlzn)
+
+/* These values are taken from RFC 1537 */
+#define SDLZ_DEFAULT_REFRESH 28800U /* 8 hours */
+#define SDLZ_DEFAULT_RETRY 7200U /* 2 hours */
+#define SDLZ_DEFAULT_EXPIRE 604800U /* 7 days */
+#define SDLZ_DEFAULT_MINIMUM 86400U /* 1 day */
+
+/* This is a reasonable value */
+#define SDLZ_DEFAULT_TTL (60 * 60 * 24)
+
+#ifdef __COVERITY__
+#define MAYBE_LOCK(imp) LOCK(&imp->driverlock)
+#define MAYBE_UNLOCK(imp) UNLOCK(&imp->driverlock)
+#else /* ifdef __COVERITY__ */
+#define MAYBE_LOCK(imp) \
+ do { \
+ unsigned int flags = imp->flags; \
+ if ((flags & DNS_SDLZFLAG_THREADSAFE) == 0) \
+ LOCK(&imp->driverlock); \
+ } while (0)
+
+#define MAYBE_UNLOCK(imp) \
+ do { \
+ unsigned int flags = imp->flags; \
+ if ((flags & DNS_SDLZFLAG_THREADSAFE) == 0) \
+ UNLOCK(&imp->driverlock); \
+ } while (0)
+#endif /* ifdef __COVERITY__ */
+
+/*
+ * Forward references.
+ */
+static isc_result_t
+getnodedata(dns_db_t *db, const dns_name_t *name, bool create,
+ unsigned int options, dns_clientinfomethods_t *methods,
+ dns_clientinfo_t *clientinfo, dns_dbnode_t **nodep);
+
+static void
+list_tordataset(dns_rdatalist_t *rdatalist, dns_db_t *db, dns_dbnode_t *node,
+ dns_rdataset_t *rdataset);
+
+static void
+detachnode(dns_db_t *db, dns_dbnode_t **targetp);
+
+static void
+dbiterator_destroy(dns_dbiterator_t **iteratorp);
+static isc_result_t
+dbiterator_first(dns_dbiterator_t *iterator);
+static isc_result_t
+dbiterator_last(dns_dbiterator_t *iterator);
+static isc_result_t
+dbiterator_seek(dns_dbiterator_t *iterator, const dns_name_t *name);
+static isc_result_t
+dbiterator_prev(dns_dbiterator_t *iterator);
+static isc_result_t
+dbiterator_next(dns_dbiterator_t *iterator);
+static isc_result_t
+dbiterator_current(dns_dbiterator_t *iterator, dns_dbnode_t **nodep,
+ dns_name_t *name);
+static isc_result_t
+dbiterator_pause(dns_dbiterator_t *iterator);
+static isc_result_t
+dbiterator_origin(dns_dbiterator_t *iterator, dns_name_t *name);
+
+static dns_dbiteratormethods_t dbiterator_methods = {
+ dbiterator_destroy, dbiterator_first, dbiterator_last,
+ dbiterator_seek, dbiterator_prev, dbiterator_next,
+ dbiterator_current, dbiterator_pause, dbiterator_origin
+};
+
+/*
+ * Utility functions
+ */
+
+/*
+ * Log a message at the given level
+ */
+static void
+sdlz_log(int level, const char *fmt, ...) {
+ va_list ap;
+ va_start(ap, fmt);
+ isc_log_vwrite(dns_lctx, DNS_LOGCATEGORY_DATABASE, DNS_LOGMODULE_DLZ,
+ ISC_LOG_DEBUG(level), fmt, ap);
+ va_end(ap);
+}
+
+/*% Converts the input string to lowercase, in place. */
+static void
+dns_sdlz_tolower(char *str) {
+ unsigned int len = strlen(str);
+ unsigned int i;
+
+ for (i = 0; i < len; i++) {
+ if (str[i] >= 'A' && str[i] <= 'Z') {
+ str[i] += 32;
+ }
+ }
+}
+
+static unsigned int
+initial_size(const char *data) {
+ unsigned int len = (strlen(data) / 64) + 1;
+ return (len * 64 + 64);
+}
+
+/*
+ * Rdataset Iterator Methods. These methods were "borrowed" from the SDB
+ * driver interface. See the SDB driver interface documentation for more info.
+ */
+
+static void
+rdatasetiter_destroy(dns_rdatasetiter_t **iteratorp) {
+ sdlz_rdatasetiter_t *sdlziterator = (sdlz_rdatasetiter_t *)(*iteratorp);
+
+ detachnode(sdlziterator->common.db, &sdlziterator->common.node);
+ isc_mem_put(sdlziterator->common.db->mctx, sdlziterator,
+ sizeof(sdlz_rdatasetiter_t));
+ *iteratorp = NULL;
+}
+
+static isc_result_t
+rdatasetiter_first(dns_rdatasetiter_t *iterator) {
+ sdlz_rdatasetiter_t *sdlziterator = (sdlz_rdatasetiter_t *)iterator;
+ dns_sdlznode_t *sdlznode = (dns_sdlznode_t *)iterator->node;
+
+ if (ISC_LIST_EMPTY(sdlznode->lists)) {
+ return (ISC_R_NOMORE);
+ }
+ sdlziterator->current = ISC_LIST_HEAD(sdlznode->lists);
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+rdatasetiter_next(dns_rdatasetiter_t *iterator) {
+ sdlz_rdatasetiter_t *sdlziterator = (sdlz_rdatasetiter_t *)iterator;
+
+ sdlziterator->current = ISC_LIST_NEXT(sdlziterator->current, link);
+ if (sdlziterator->current == NULL) {
+ return (ISC_R_NOMORE);
+ } else {
+ return (ISC_R_SUCCESS);
+ }
+}
+
+static void
+rdatasetiter_current(dns_rdatasetiter_t *iterator, dns_rdataset_t *rdataset) {
+ sdlz_rdatasetiter_t *sdlziterator = (sdlz_rdatasetiter_t *)iterator;
+
+ list_tordataset(sdlziterator->current, iterator->db, iterator->node,
+ rdataset);
+}
+
+static dns_rdatasetitermethods_t rdatasetiter_methods = {
+ rdatasetiter_destroy, rdatasetiter_first, rdatasetiter_next,
+ rdatasetiter_current
+};
+
+/*
+ * DB routines. These methods were "borrowed" from the SDB driver interface.
+ * See the SDB driver interface documentation for more info.
+ */
+
+static void
+attach(dns_db_t *source, dns_db_t **targetp) {
+ dns_sdlz_db_t *sdlz = (dns_sdlz_db_t *)source;
+
+ REQUIRE(VALID_SDLZDB(sdlz));
+
+ isc_refcount_increment(&sdlz->references);
+
+ *targetp = source;
+}
+
+static void
+destroy(dns_sdlz_db_t *sdlz) {
+ sdlz->common.magic = 0;
+ sdlz->common.impmagic = 0;
+
+ dns_name_free(&sdlz->common.origin, sdlz->common.mctx);
+
+ isc_refcount_destroy(&sdlz->references);
+ isc_mem_putanddetach(&sdlz->common.mctx, sdlz, sizeof(dns_sdlz_db_t));
+}
+
+static void
+detach(dns_db_t **dbp) {
+ dns_sdlz_db_t *sdlz = (dns_sdlz_db_t *)(*dbp);
+
+ REQUIRE(VALID_SDLZDB(sdlz));
+
+ *dbp = NULL;
+
+ if (isc_refcount_decrement(&sdlz->references) == 1) {
+ destroy(sdlz);
+ }
+}
+
+static isc_result_t
+beginload(dns_db_t *db, dns_rdatacallbacks_t *callbacks) {
+ UNUSED(db);
+ UNUSED(callbacks);
+ return (ISC_R_NOTIMPLEMENTED);
+}
+
+static isc_result_t
+endload(dns_db_t *db, dns_rdatacallbacks_t *callbacks) {
+ UNUSED(db);
+ UNUSED(callbacks);
+ return (ISC_R_NOTIMPLEMENTED);
+}
+
+static isc_result_t
+dump(dns_db_t *db, dns_dbversion_t *version, const char *filename,
+ dns_masterformat_t masterformat) {
+ UNUSED(db);
+ UNUSED(version);
+ UNUSED(filename);
+ UNUSED(masterformat);
+ return (ISC_R_NOTIMPLEMENTED);
+}
+
+static void
+currentversion(dns_db_t *db, dns_dbversion_t **versionp) {
+ dns_sdlz_db_t *sdlz = (dns_sdlz_db_t *)db;
+ REQUIRE(VALID_SDLZDB(sdlz));
+ REQUIRE(versionp != NULL && *versionp == NULL);
+
+ *versionp = (void *)&sdlz->dummy_version;
+ return;
+}
+
+static isc_result_t
+newversion(dns_db_t *db, dns_dbversion_t **versionp) {
+ dns_sdlz_db_t *sdlz = (dns_sdlz_db_t *)db;
+ char origin[DNS_NAME_MAXTEXT + 1];
+ isc_result_t result;
+
+ REQUIRE(VALID_SDLZDB(sdlz));
+
+ if (sdlz->dlzimp->methods->newversion == NULL) {
+ return (ISC_R_NOTIMPLEMENTED);
+ }
+
+ dns_name_format(&sdlz->common.origin, origin, sizeof(origin));
+
+ result = sdlz->dlzimp->methods->newversion(
+ origin, sdlz->dlzimp->driverarg, sdlz->dbdata, versionp);
+ if (result != ISC_R_SUCCESS) {
+ sdlz_log(ISC_LOG_ERROR,
+ "sdlz newversion on origin %s failed : %s", origin,
+ isc_result_totext(result));
+ return (result);
+ }
+
+ sdlz->future_version = *versionp;
+ return (ISC_R_SUCCESS);
+}
+
+static void
+attachversion(dns_db_t *db, dns_dbversion_t *source,
+ dns_dbversion_t **targetp) {
+ dns_sdlz_db_t *sdlz = (dns_sdlz_db_t *)db;
+
+ REQUIRE(VALID_SDLZDB(sdlz));
+ REQUIRE(source != NULL && source == (void *)&sdlz->dummy_version);
+
+ *targetp = source;
+}
+
+static void
+closeversion(dns_db_t *db, dns_dbversion_t **versionp, bool commit) {
+ dns_sdlz_db_t *sdlz = (dns_sdlz_db_t *)db;
+ char origin[DNS_NAME_MAXTEXT + 1];
+
+ REQUIRE(VALID_SDLZDB(sdlz));
+ REQUIRE(versionp != NULL);
+
+ if (*versionp == (void *)&sdlz->dummy_version) {
+ *versionp = NULL;
+ return;
+ }
+
+ REQUIRE(*versionp == sdlz->future_version);
+ REQUIRE(sdlz->dlzimp->methods->closeversion != NULL);
+
+ dns_name_format(&sdlz->common.origin, origin, sizeof(origin));
+
+ sdlz->dlzimp->methods->closeversion(origin, commit,
+ sdlz->dlzimp->driverarg,
+ sdlz->dbdata, versionp);
+ if (*versionp != NULL) {
+ sdlz_log(ISC_LOG_ERROR, "sdlz closeversion on origin %s failed",
+ origin);
+ }
+
+ sdlz->future_version = NULL;
+}
+
+static isc_result_t
+createnode(dns_sdlz_db_t *sdlz, dns_sdlznode_t **nodep) {
+ dns_sdlznode_t *node;
+
+ node = isc_mem_get(sdlz->common.mctx, sizeof(dns_sdlznode_t));
+
+ node->sdlz = NULL;
+ attach((dns_db_t *)sdlz, (dns_db_t **)&node->sdlz);
+ ISC_LIST_INIT(node->lists);
+ ISC_LIST_INIT(node->buffers);
+ ISC_LINK_INIT(node, link);
+ node->name = NULL;
+ dns_rdatacallbacks_init(&node->callbacks);
+
+ isc_refcount_init(&node->references, 1);
+ node->magic = SDLZLOOKUP_MAGIC;
+
+ *nodep = node;
+ return (ISC_R_SUCCESS);
+}
+
+static void
+destroynode(dns_sdlznode_t *node) {
+ dns_rdatalist_t *list;
+ dns_rdata_t *rdata;
+ isc_buffer_t *b;
+ dns_sdlz_db_t *sdlz;
+ dns_db_t *db;
+ isc_mem_t *mctx;
+
+ isc_refcount_destroy(&node->references);
+
+ sdlz = node->sdlz;
+ mctx = sdlz->common.mctx;
+
+ while (!ISC_LIST_EMPTY(node->lists)) {
+ list = ISC_LIST_HEAD(node->lists);
+ while (!ISC_LIST_EMPTY(list->rdata)) {
+ rdata = ISC_LIST_HEAD(list->rdata);
+ ISC_LIST_UNLINK(list->rdata, rdata, link);
+ isc_mem_put(mctx, rdata, sizeof(dns_rdata_t));
+ }
+ ISC_LIST_UNLINK(node->lists, list, link);
+ isc_mem_put(mctx, list, sizeof(dns_rdatalist_t));
+ }
+
+ while (!ISC_LIST_EMPTY(node->buffers)) {
+ b = ISC_LIST_HEAD(node->buffers);
+ ISC_LIST_UNLINK(node->buffers, b, link);
+ isc_buffer_free(&b);
+ }
+
+ if (node->name != NULL) {
+ dns_name_free(node->name, mctx);
+ isc_mem_put(mctx, node->name, sizeof(dns_name_t));
+ }
+
+ node->magic = 0;
+ isc_mem_put(mctx, node, sizeof(dns_sdlznode_t));
+ db = &sdlz->common;
+ detach(&db);
+}
+
+static isc_result_t
+getnodedata(dns_db_t *db, const dns_name_t *name, bool create,
+ unsigned int options, dns_clientinfomethods_t *methods,
+ dns_clientinfo_t *clientinfo, dns_dbnode_t **nodep) {
+ dns_sdlz_db_t *sdlz = (dns_sdlz_db_t *)db;
+ dns_sdlznode_t *node = NULL;
+ isc_result_t result;
+ isc_buffer_t b;
+ char namestr[DNS_NAME_MAXTEXT + 1];
+ isc_buffer_t b2;
+ char zonestr[DNS_NAME_MAXTEXT + 1];
+ bool isorigin;
+ dns_sdlzauthorityfunc_t authority;
+
+ REQUIRE(VALID_SDLZDB(sdlz));
+ REQUIRE(nodep != NULL && *nodep == NULL);
+
+ if (sdlz->dlzimp->methods->newversion == NULL) {
+ REQUIRE(!create);
+ }
+
+ isc_buffer_init(&b, namestr, sizeof(namestr));
+ if ((sdlz->dlzimp->flags & DNS_SDLZFLAG_RELATIVEOWNER) != 0) {
+ dns_name_t relname;
+ unsigned int labels;
+
+ labels = dns_name_countlabels(name) -
+ dns_name_countlabels(&sdlz->common.origin);
+ dns_name_init(&relname, NULL);
+ dns_name_getlabelsequence(name, 0, labels, &relname);
+ result = dns_name_totext(&relname, true, &b);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+ } else {
+ result = dns_name_totext(name, true, &b);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+ }
+ isc_buffer_putuint8(&b, 0);
+
+ isc_buffer_init(&b2, zonestr, sizeof(zonestr));
+ result = dns_name_totext(&sdlz->common.origin, true, &b2);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+ isc_buffer_putuint8(&b2, 0);
+
+ result = createnode(sdlz, &node);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+
+ isorigin = dns_name_equal(name, &sdlz->common.origin);
+
+ /* make sure strings are always lowercase */
+ dns_sdlz_tolower(zonestr);
+ dns_sdlz_tolower(namestr);
+
+ MAYBE_LOCK(sdlz->dlzimp);
+
+ /* try to lookup the host (namestr) */
+ result = sdlz->dlzimp->methods->lookup(
+ zonestr, namestr, sdlz->dlzimp->driverarg, sdlz->dbdata, node,
+ methods, clientinfo);
+
+ /*
+ * If the name was not found and DNS_DBFIND_NOWILD is not
+ * set, then we try to find a wildcard entry.
+ *
+ * If DNS_DBFIND_NOZONECUT is set and there are multiple
+ * levels between the host and the zone origin, we also look
+ * for wildcards at each level.
+ */
+ if (result == ISC_R_NOTFOUND && !create &&
+ (options & DNS_DBFIND_NOWILD) == 0)
+ {
+ unsigned int i, dlabels, nlabels;
+
+ nlabels = dns_name_countlabels(name);
+ dlabels = nlabels - dns_name_countlabels(&sdlz->common.origin);
+ for (i = 0; i < dlabels; i++) {
+ char wildstr[DNS_NAME_MAXTEXT + 1];
+ dns_fixedname_t fixed;
+ const dns_name_t *wild;
+
+ dns_fixedname_init(&fixed);
+ if (i == dlabels - 1) {
+ wild = dns_wildcardname;
+ } else {
+ dns_name_t *fname;
+ fname = dns_fixedname_name(&fixed);
+ dns_name_getlabelsequence(
+ name, i + 1, dlabels - i - 1, fname);
+ result = dns_name_concatenate(
+ dns_wildcardname, fname, fname, NULL);
+ if (result != ISC_R_SUCCESS) {
+ MAYBE_UNLOCK(sdlz->dlzimp);
+ return (result);
+ }
+ wild = fname;
+ }
+
+ isc_buffer_init(&b, wildstr, sizeof(wildstr));
+ result = dns_name_totext(wild, true, &b);
+ if (result != ISC_R_SUCCESS) {
+ MAYBE_UNLOCK(sdlz->dlzimp);
+ return (result);
+ }
+ isc_buffer_putuint8(&b, 0);
+
+ result = sdlz->dlzimp->methods->lookup(
+ zonestr, wildstr, sdlz->dlzimp->driverarg,
+ sdlz->dbdata, node, methods, clientinfo);
+ if (result == ISC_R_SUCCESS) {
+ break;
+ }
+ }
+ }
+
+ MAYBE_UNLOCK(sdlz->dlzimp);
+
+ if (result == ISC_R_NOTFOUND && (isorigin || create)) {
+ result = ISC_R_SUCCESS;
+ }
+
+ if (result != ISC_R_SUCCESS) {
+ isc_refcount_decrementz(&node->references);
+ destroynode(node);
+ return (result);
+ }
+
+ if (isorigin && sdlz->dlzimp->methods->authority != NULL) {
+ MAYBE_LOCK(sdlz->dlzimp);
+ authority = sdlz->dlzimp->methods->authority;
+ result = (*authority)(zonestr, sdlz->dlzimp->driverarg,
+ sdlz->dbdata, node);
+ MAYBE_UNLOCK(sdlz->dlzimp);
+ if (result != ISC_R_SUCCESS && result != ISC_R_NOTIMPLEMENTED) {
+ isc_refcount_decrementz(&node->references);
+ destroynode(node);
+ return (result);
+ }
+ }
+
+ if (node->name == NULL) {
+ node->name = isc_mem_get(sdlz->common.mctx, sizeof(dns_name_t));
+ dns_name_init(node->name, NULL);
+ dns_name_dup(name, sdlz->common.mctx, node->name);
+ }
+
+ *nodep = node;
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+findnodeext(dns_db_t *db, const dns_name_t *name, bool create,
+ dns_clientinfomethods_t *methods, dns_clientinfo_t *clientinfo,
+ dns_dbnode_t **nodep) {
+ return (getnodedata(db, name, create, 0, methods, clientinfo, nodep));
+}
+
+static isc_result_t
+findnode(dns_db_t *db, const dns_name_t *name, bool create,
+ dns_dbnode_t **nodep) {
+ return (getnodedata(db, name, create, 0, NULL, NULL, nodep));
+}
+
+static isc_result_t
+findzonecut(dns_db_t *db, const dns_name_t *name, unsigned int options,
+ isc_stdtime_t now, dns_dbnode_t **nodep, dns_name_t *foundname,
+ dns_name_t *dcname, dns_rdataset_t *rdataset,
+ dns_rdataset_t *sigrdataset) {
+ UNUSED(db);
+ UNUSED(name);
+ UNUSED(options);
+ UNUSED(now);
+ UNUSED(nodep);
+ UNUSED(foundname);
+ UNUSED(dcname);
+ UNUSED(rdataset);
+ UNUSED(sigrdataset);
+
+ return (ISC_R_NOTIMPLEMENTED);
+}
+
+static void
+attachnode(dns_db_t *db, dns_dbnode_t *source, dns_dbnode_t **targetp) {
+ dns_sdlz_db_t *sdlz = (dns_sdlz_db_t *)db;
+ dns_sdlznode_t *node = (dns_sdlznode_t *)source;
+
+ REQUIRE(VALID_SDLZDB(sdlz));
+
+ UNUSED(sdlz);
+
+ isc_refcount_increment(&node->references);
+
+ *targetp = source;
+}
+
+static void
+detachnode(dns_db_t *db, dns_dbnode_t **targetp) {
+ dns_sdlz_db_t *sdlz = (dns_sdlz_db_t *)db;
+ dns_sdlznode_t *node;
+
+ REQUIRE(VALID_SDLZDB(sdlz));
+ REQUIRE(targetp != NULL && *targetp != NULL);
+
+ UNUSED(sdlz);
+
+ node = (dns_sdlznode_t *)(*targetp);
+ *targetp = NULL;
+
+ if (isc_refcount_decrement(&node->references) == 1) {
+ destroynode(node);
+ }
+}
+
+static isc_result_t
+expirenode(dns_db_t *db, dns_dbnode_t *node, isc_stdtime_t now) {
+ UNUSED(db);
+ UNUSED(node);
+ UNUSED(now);
+ UNREACHABLE();
+}
+
+static void
+printnode(dns_db_t *db, dns_dbnode_t *node, FILE *out) {
+ UNUSED(db);
+ UNUSED(node);
+ UNUSED(out);
+ return;
+}
+
+static isc_result_t
+createiterator(dns_db_t *db, unsigned int options,
+ dns_dbiterator_t **iteratorp) {
+ dns_sdlz_db_t *sdlz = (dns_sdlz_db_t *)db;
+ sdlz_dbiterator_t *sdlziter;
+ isc_result_t result;
+ isc_buffer_t b;
+ char zonestr[DNS_NAME_MAXTEXT + 1];
+
+ REQUIRE(VALID_SDLZDB(sdlz));
+
+ if (sdlz->dlzimp->methods->allnodes == NULL) {
+ return (ISC_R_NOTIMPLEMENTED);
+ }
+
+ if ((options & DNS_DB_NSEC3ONLY) != 0 ||
+ (options & DNS_DB_NONSEC3) != 0)
+ {
+ return (ISC_R_NOTIMPLEMENTED);
+ }
+
+ isc_buffer_init(&b, zonestr, sizeof(zonestr));
+ result = dns_name_totext(&sdlz->common.origin, true, &b);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+ isc_buffer_putuint8(&b, 0);
+
+ sdlziter = isc_mem_get(sdlz->common.mctx, sizeof(sdlz_dbiterator_t));
+
+ sdlziter->common.methods = &dbiterator_methods;
+ sdlziter->common.db = NULL;
+ dns_db_attach(db, &sdlziter->common.db);
+ sdlziter->common.relative_names = ((options & DNS_DB_RELATIVENAMES) !=
+ 0);
+ sdlziter->common.magic = DNS_DBITERATOR_MAGIC;
+ ISC_LIST_INIT(sdlziter->nodelist);
+ sdlziter->current = NULL;
+ sdlziter->origin = NULL;
+
+ /* make sure strings are always lowercase */
+ dns_sdlz_tolower(zonestr);
+
+ MAYBE_LOCK(sdlz->dlzimp);
+ result = sdlz->dlzimp->methods->allnodes(
+ zonestr, sdlz->dlzimp->driverarg, sdlz->dbdata, sdlziter);
+ MAYBE_UNLOCK(sdlz->dlzimp);
+ if (result != ISC_R_SUCCESS) {
+ dns_dbiterator_t *iter = &sdlziter->common;
+ dbiterator_destroy(&iter);
+ return (result);
+ }
+
+ if (sdlziter->origin != NULL) {
+ ISC_LIST_UNLINK(sdlziter->nodelist, sdlziter->origin, link);
+ ISC_LIST_PREPEND(sdlziter->nodelist, sdlziter->origin, link);
+ }
+
+ *iteratorp = (dns_dbiterator_t *)sdlziter;
+
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+findrdataset(dns_db_t *db, dns_dbnode_t *node, dns_dbversion_t *version,
+ dns_rdatatype_t type, dns_rdatatype_t covers, isc_stdtime_t now,
+ dns_rdataset_t *rdataset, dns_rdataset_t *sigrdataset) {
+ REQUIRE(VALID_SDLZNODE(node));
+ dns_rdatalist_t *list;
+ dns_sdlznode_t *sdlznode = (dns_sdlznode_t *)node;
+
+ UNUSED(db);
+ UNUSED(version);
+ UNUSED(covers);
+ UNUSED(now);
+ UNUSED(sigrdataset);
+
+ if (type == dns_rdatatype_sig || type == dns_rdatatype_rrsig) {
+ return (ISC_R_NOTIMPLEMENTED);
+ }
+
+ list = ISC_LIST_HEAD(sdlznode->lists);
+ while (list != NULL) {
+ if (list->type == type) {
+ break;
+ }
+ list = ISC_LIST_NEXT(list, link);
+ }
+ if (list == NULL) {
+ return (ISC_R_NOTFOUND);
+ }
+
+ list_tordataset(list, db, node, rdataset);
+
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+findext(dns_db_t *db, const dns_name_t *name, dns_dbversion_t *version,
+ dns_rdatatype_t type, unsigned int options, isc_stdtime_t now,
+ dns_dbnode_t **nodep, dns_name_t *foundname,
+ dns_clientinfomethods_t *methods, dns_clientinfo_t *clientinfo,
+ dns_rdataset_t *rdataset, dns_rdataset_t *sigrdataset) {
+ dns_sdlz_db_t *sdlz = (dns_sdlz_db_t *)db;
+ dns_dbnode_t *node = NULL;
+ dns_fixedname_t fname;
+ dns_rdataset_t xrdataset;
+ dns_name_t *xname;
+ unsigned int nlabels, olabels;
+ isc_result_t result;
+ unsigned int i;
+
+ REQUIRE(VALID_SDLZDB(sdlz));
+ REQUIRE(nodep == NULL || *nodep == NULL);
+ REQUIRE(version == NULL || version == (void *)&sdlz->dummy_version ||
+ version == sdlz->future_version);
+
+ UNUSED(sdlz);
+
+ if (!dns_name_issubdomain(name, &db->origin)) {
+ return (DNS_R_NXDOMAIN);
+ }
+
+ olabels = dns_name_countlabels(&db->origin);
+ nlabels = dns_name_countlabels(name);
+
+ xname = dns_fixedname_initname(&fname);
+
+ if (rdataset == NULL) {
+ dns_rdataset_init(&xrdataset);
+ rdataset = &xrdataset;
+ }
+
+ result = DNS_R_NXDOMAIN;
+
+ /*
+ * If we're not walking down searching for zone
+ * cuts, we can cut straight to the chase
+ */
+ if ((options & DNS_DBFIND_NOZONECUT) != 0) {
+ i = nlabels;
+ goto search;
+ }
+
+ for (i = olabels; i <= nlabels; i++) {
+ search:
+ /*
+ * Look up the next label.
+ */
+ dns_name_getlabelsequence(name, nlabels - i, i, xname);
+ result = getnodedata(db, xname, false, options, methods,
+ clientinfo, &node);
+ if (result == ISC_R_NOTFOUND) {
+ result = DNS_R_NXDOMAIN;
+ continue;
+ } else if (result != ISC_R_SUCCESS) {
+ break;
+ }
+
+ /*
+ * Look for a DNAME at the current label, unless this is
+ * the qname.
+ */
+ if (i < nlabels) {
+ result = findrdataset(db, node, version,
+ dns_rdatatype_dname, 0, now,
+ rdataset, sigrdataset);
+ if (result == ISC_R_SUCCESS) {
+ result = DNS_R_DNAME;
+ break;
+ }
+ }
+
+ /*
+ * Look for an NS at the current label, unless this is the
+ * origin, glue is ok, or there are known to be no zone cuts.
+ */
+ if (i != olabels && (options & DNS_DBFIND_GLUEOK) == 0 &&
+ (options & DNS_DBFIND_NOZONECUT) == 0)
+ {
+ result = findrdataset(db, node, version,
+ dns_rdatatype_ns, 0, now,
+ rdataset, sigrdataset);
+
+ if (result == ISC_R_SUCCESS && i == nlabels &&
+ type == dns_rdatatype_any)
+ {
+ result = DNS_R_ZONECUT;
+ dns_rdataset_disassociate(rdataset);
+ if (sigrdataset != NULL &&
+ dns_rdataset_isassociated(sigrdataset))
+ {
+ dns_rdataset_disassociate(sigrdataset);
+ }
+ break;
+ } else if (result == ISC_R_SUCCESS) {
+ result = DNS_R_DELEGATION;
+ break;
+ }
+ }
+
+ /*
+ * If the current name is not the qname, add another label
+ * and try again.
+ */
+ if (i < nlabels) {
+ detachnode(db, &node);
+ node = NULL;
+ continue;
+ }
+
+ /*
+ * If we're looking for ANY, we're done.
+ */
+ if (type == dns_rdatatype_any) {
+ result = ISC_R_SUCCESS;
+ break;
+ }
+
+ /*
+ * Look for the qtype.
+ */
+ result = findrdataset(db, node, version, type, 0, now, rdataset,
+ sigrdataset);
+ if (result == ISC_R_SUCCESS) {
+ break;
+ }
+
+ /*
+ * Look for a CNAME
+ */
+ if (type != dns_rdatatype_cname) {
+ result = findrdataset(db, node, version,
+ dns_rdatatype_cname, 0, now,
+ rdataset, sigrdataset);
+ if (result == ISC_R_SUCCESS) {
+ result = DNS_R_CNAME;
+ break;
+ }
+ }
+
+ result = DNS_R_NXRRSET;
+ break;
+ }
+
+ if (rdataset == &xrdataset && dns_rdataset_isassociated(rdataset)) {
+ dns_rdataset_disassociate(rdataset);
+ }
+
+ if (foundname != NULL) {
+ dns_name_copy(xname, foundname);
+ }
+
+ if (nodep != NULL) {
+ *nodep = node;
+ } else if (node != NULL) {
+ detachnode(db, &node);
+ }
+
+ return (result);
+}
+
+static isc_result_t
+find(dns_db_t *db, const dns_name_t *name, dns_dbversion_t *version,
+ dns_rdatatype_t type, unsigned int options, isc_stdtime_t now,
+ dns_dbnode_t **nodep, dns_name_t *foundname, dns_rdataset_t *rdataset,
+ dns_rdataset_t *sigrdataset) {
+ return (findext(db, name, version, type, options, now, nodep, foundname,
+ NULL, NULL, rdataset, sigrdataset));
+}
+
+static isc_result_t
+allrdatasets(dns_db_t *db, dns_dbnode_t *node, dns_dbversion_t *version,
+ unsigned int options, isc_stdtime_t now,
+ dns_rdatasetiter_t **iteratorp) {
+ dns_sdlz_db_t *sdlz = (dns_sdlz_db_t *)db;
+ sdlz_rdatasetiter_t *iterator;
+
+ REQUIRE(VALID_SDLZDB(sdlz));
+
+ REQUIRE(version == NULL || version == (void *)&sdlz->dummy_version ||
+ version == sdlz->future_version);
+
+ UNUSED(version);
+ UNUSED(now);
+
+ iterator = isc_mem_get(db->mctx, sizeof(sdlz_rdatasetiter_t));
+
+ iterator->common.magic = DNS_RDATASETITER_MAGIC;
+ iterator->common.methods = &rdatasetiter_methods;
+ iterator->common.db = db;
+ iterator->common.node = NULL;
+ attachnode(db, node, &iterator->common.node);
+ iterator->common.version = version;
+ iterator->common.options = options;
+ iterator->common.now = now;
+
+ *iteratorp = (dns_rdatasetiter_t *)iterator;
+
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+modrdataset(dns_db_t *db, dns_dbnode_t *node, dns_dbversion_t *version,
+ dns_rdataset_t *rdataset, unsigned int options,
+ dns_sdlzmodrdataset_t mod_function) {
+ dns_sdlz_db_t *sdlz = (dns_sdlz_db_t *)db;
+ dns_master_style_t *style = NULL;
+ isc_result_t result;
+ isc_buffer_t *buffer = NULL;
+ isc_mem_t *mctx;
+ dns_sdlznode_t *sdlznode;
+ char *rdatastr = NULL;
+ char name[DNS_NAME_MAXTEXT + 1];
+
+ REQUIRE(VALID_SDLZDB(sdlz));
+
+ if (mod_function == NULL) {
+ return (ISC_R_NOTIMPLEMENTED);
+ }
+
+ sdlznode = (dns_sdlznode_t *)node;
+
+ UNUSED(options);
+
+ dns_name_format(sdlznode->name, name, sizeof(name));
+
+ mctx = sdlz->common.mctx;
+
+ isc_buffer_allocate(mctx, &buffer, 1024);
+
+ result = dns_master_stylecreate(&style, 0, 0, 0, 0, 0, 0, 1, 0xffffffff,
+ mctx);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+
+ result = dns_master_rdatasettotext(sdlznode->name, rdataset, style,
+ NULL, buffer);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+
+ if (isc_buffer_usedlength(buffer) < 1) {
+ result = ISC_R_BADADDRESSFORM;
+ goto cleanup;
+ }
+
+ rdatastr = isc_buffer_base(buffer);
+ if (rdatastr == NULL) {
+ result = ISC_R_NOMEMORY;
+ goto cleanup;
+ }
+ rdatastr[isc_buffer_usedlength(buffer) - 1] = 0;
+
+ MAYBE_LOCK(sdlz->dlzimp);
+ result = mod_function(name, rdatastr, sdlz->dlzimp->driverarg,
+ sdlz->dbdata, version);
+ MAYBE_UNLOCK(sdlz->dlzimp);
+
+cleanup:
+ isc_buffer_free(&buffer);
+ if (style != NULL) {
+ dns_master_styledestroy(&style, mctx);
+ }
+
+ return (result);
+}
+
+static isc_result_t
+addrdataset(dns_db_t *db, dns_dbnode_t *node, dns_dbversion_t *version,
+ isc_stdtime_t now, dns_rdataset_t *rdataset, unsigned int options,
+ dns_rdataset_t *addedrdataset) {
+ dns_sdlz_db_t *sdlz = (dns_sdlz_db_t *)db;
+ isc_result_t result;
+
+ UNUSED(now);
+ UNUSED(addedrdataset);
+ REQUIRE(VALID_SDLZDB(sdlz));
+
+ if (sdlz->dlzimp->methods->addrdataset == NULL) {
+ return (ISC_R_NOTIMPLEMENTED);
+ }
+
+ result = modrdataset(db, node, version, rdataset, options,
+ sdlz->dlzimp->methods->addrdataset);
+ return (result);
+}
+
+static isc_result_t
+subtractrdataset(dns_db_t *db, dns_dbnode_t *node, dns_dbversion_t *version,
+ dns_rdataset_t *rdataset, unsigned int options,
+ dns_rdataset_t *newrdataset) {
+ dns_sdlz_db_t *sdlz = (dns_sdlz_db_t *)db;
+ isc_result_t result;
+
+ UNUSED(newrdataset);
+ REQUIRE(VALID_SDLZDB(sdlz));
+
+ if (sdlz->dlzimp->methods->subtractrdataset == NULL) {
+ return (ISC_R_NOTIMPLEMENTED);
+ }
+
+ result = modrdataset(db, node, version, rdataset, options,
+ sdlz->dlzimp->methods->subtractrdataset);
+ return (result);
+}
+
+static isc_result_t
+deleterdataset(dns_db_t *db, dns_dbnode_t *node, dns_dbversion_t *version,
+ dns_rdatatype_t type, dns_rdatatype_t covers) {
+ dns_sdlz_db_t *sdlz = (dns_sdlz_db_t *)db;
+ char name[DNS_NAME_MAXTEXT + 1];
+ char b_type[DNS_RDATATYPE_FORMATSIZE];
+ dns_sdlznode_t *sdlznode;
+ isc_result_t result;
+
+ UNUSED(covers);
+
+ REQUIRE(VALID_SDLZDB(sdlz));
+
+ if (sdlz->dlzimp->methods->delrdataset == NULL) {
+ return (ISC_R_NOTIMPLEMENTED);
+ }
+
+ sdlznode = (dns_sdlznode_t *)node;
+ dns_name_format(sdlznode->name, name, sizeof(name));
+ dns_rdatatype_format(type, b_type, sizeof(b_type));
+
+ MAYBE_LOCK(sdlz->dlzimp);
+ result = sdlz->dlzimp->methods->delrdataset(
+ name, b_type, sdlz->dlzimp->driverarg, sdlz->dbdata, version);
+ MAYBE_UNLOCK(sdlz->dlzimp);
+
+ return (result);
+}
+
+static bool
+issecure(dns_db_t *db) {
+ UNUSED(db);
+
+ return (false);
+}
+
+static unsigned int
+nodecount(dns_db_t *db, dns_dbtree_t tree) {
+ UNUSED(db);
+ UNUSED(tree);
+
+ return (0);
+}
+
+static bool
+ispersistent(dns_db_t *db) {
+ UNUSED(db);
+ return (true);
+}
+
+static void
+overmem(dns_db_t *db, bool over) {
+ UNUSED(db);
+ UNUSED(over);
+}
+
+static void
+settask(dns_db_t *db, isc_task_t *task) {
+ UNUSED(db);
+ UNUSED(task);
+}
+
+/*
+ * getoriginnode() is used by the update code to find the
+ * dns_rdatatype_dnskey record for a zone
+ */
+static isc_result_t
+getoriginnode(dns_db_t *db, dns_dbnode_t **nodep) {
+ dns_sdlz_db_t *sdlz = (dns_sdlz_db_t *)db;
+ isc_result_t result;
+
+ REQUIRE(VALID_SDLZDB(sdlz));
+ if (sdlz->dlzimp->methods->newversion == NULL) {
+ return (ISC_R_NOTIMPLEMENTED);
+ }
+
+ result = getnodedata(db, &sdlz->common.origin, false, 0, NULL, NULL,
+ nodep);
+ if (result != ISC_R_SUCCESS) {
+ sdlz_log(ISC_LOG_ERROR, "sdlz getoriginnode failed: %s",
+ isc_result_totext(result));
+ }
+ return (result);
+}
+
+static dns_dbmethods_t sdlzdb_methods = {
+ attach, detach, beginload,
+ endload, dump, currentversion,
+ newversion, attachversion, closeversion,
+ findnode, find, findzonecut,
+ attachnode, detachnode, expirenode,
+ printnode, createiterator, findrdataset,
+ allrdatasets, addrdataset, subtractrdataset,
+ deleterdataset, issecure, nodecount,
+ ispersistent, overmem, settask,
+ getoriginnode, NULL, /* transfernode */
+ NULL, /* getnsec3parameters */
+ NULL, /* findnsec3node */
+ NULL, /* setsigningtime */
+ NULL, /* getsigningtime */
+ NULL, /* resigned */
+ NULL, /* isdnssec */
+ NULL, /* getrrsetstats */
+ NULL, /* rpz_attach */
+ NULL, /* rpz_ready */
+ findnodeext, findext, NULL, /* setcachestats */
+ NULL, /* hashsize */
+ NULL, /* nodefullname */
+ NULL, /* getsize */
+ NULL, /* setservestalettl */
+ NULL, /* getservestalettl */
+ NULL, /* setservestalerefresh */
+ NULL, /* getservestalerefresh */
+ NULL, /* setgluecachestats */
+};
+
+/*
+ * Database Iterator Methods. These methods were "borrowed" from the SDB
+ * driver interface. See the SDB driver interface documentation for more info.
+ */
+
+static void
+dbiterator_destroy(dns_dbiterator_t **iteratorp) {
+ sdlz_dbiterator_t *sdlziter = (sdlz_dbiterator_t *)(*iteratorp);
+ dns_sdlz_db_t *sdlz = (dns_sdlz_db_t *)sdlziter->common.db;
+
+ while (!ISC_LIST_EMPTY(sdlziter->nodelist)) {
+ dns_sdlznode_t *node;
+ node = ISC_LIST_HEAD(sdlziter->nodelist);
+ ISC_LIST_UNLINK(sdlziter->nodelist, node, link);
+ isc_refcount_decrementz(&node->references);
+ destroynode(node);
+ }
+
+ dns_db_detach(&sdlziter->common.db);
+ isc_mem_put(sdlz->common.mctx, sdlziter, sizeof(sdlz_dbiterator_t));
+
+ *iteratorp = NULL;
+}
+
+static isc_result_t
+dbiterator_first(dns_dbiterator_t *iterator) {
+ sdlz_dbiterator_t *sdlziter = (sdlz_dbiterator_t *)iterator;
+
+ sdlziter->current = ISC_LIST_HEAD(sdlziter->nodelist);
+ if (sdlziter->current == NULL) {
+ return (ISC_R_NOMORE);
+ } else {
+ return (ISC_R_SUCCESS);
+ }
+}
+
+static isc_result_t
+dbiterator_last(dns_dbiterator_t *iterator) {
+ sdlz_dbiterator_t *sdlziter = (sdlz_dbiterator_t *)iterator;
+
+ sdlziter->current = ISC_LIST_TAIL(sdlziter->nodelist);
+ if (sdlziter->current == NULL) {
+ return (ISC_R_NOMORE);
+ } else {
+ return (ISC_R_SUCCESS);
+ }
+}
+
+static isc_result_t
+dbiterator_seek(dns_dbiterator_t *iterator, const dns_name_t *name) {
+ sdlz_dbiterator_t *sdlziter = (sdlz_dbiterator_t *)iterator;
+
+ sdlziter->current = ISC_LIST_HEAD(sdlziter->nodelist);
+ while (sdlziter->current != NULL) {
+ if (dns_name_equal(sdlziter->current->name, name)) {
+ return (ISC_R_SUCCESS);
+ }
+ sdlziter->current = ISC_LIST_NEXT(sdlziter->current, link);
+ }
+ return (ISC_R_NOTFOUND);
+}
+
+static isc_result_t
+dbiterator_prev(dns_dbiterator_t *iterator) {
+ sdlz_dbiterator_t *sdlziter = (sdlz_dbiterator_t *)iterator;
+
+ sdlziter->current = ISC_LIST_PREV(sdlziter->current, link);
+ if (sdlziter->current == NULL) {
+ return (ISC_R_NOMORE);
+ } else {
+ return (ISC_R_SUCCESS);
+ }
+}
+
+static isc_result_t
+dbiterator_next(dns_dbiterator_t *iterator) {
+ sdlz_dbiterator_t *sdlziter = (sdlz_dbiterator_t *)iterator;
+
+ sdlziter->current = ISC_LIST_NEXT(sdlziter->current, link);
+ if (sdlziter->current == NULL) {
+ return (ISC_R_NOMORE);
+ } else {
+ return (ISC_R_SUCCESS);
+ }
+}
+
+static isc_result_t
+dbiterator_current(dns_dbiterator_t *iterator, dns_dbnode_t **nodep,
+ dns_name_t *name) {
+ sdlz_dbiterator_t *sdlziter = (sdlz_dbiterator_t *)iterator;
+
+ attachnode(iterator->db, sdlziter->current, nodep);
+ if (name != NULL) {
+ dns_name_copy(sdlziter->current->name, name);
+ return (ISC_R_SUCCESS);
+ }
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+dbiterator_pause(dns_dbiterator_t *iterator) {
+ UNUSED(iterator);
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+dbiterator_origin(dns_dbiterator_t *iterator, dns_name_t *name) {
+ UNUSED(iterator);
+ dns_name_copy(dns_rootname, name);
+ return (ISC_R_SUCCESS);
+}
+
+/*
+ * Rdataset Methods. These methods were "borrowed" from the SDB driver
+ * interface. See the SDB driver interface documentation for more info.
+ */
+
+static void
+disassociate(dns_rdataset_t *rdataset) {
+ dns_dbnode_t *node = rdataset->private5;
+ dns_sdlznode_t *sdlznode = (dns_sdlznode_t *)node;
+ dns_db_t *db = (dns_db_t *)sdlznode->sdlz;
+
+ detachnode(db, &node);
+ isc__rdatalist_disassociate(rdataset);
+}
+
+static void
+rdataset_clone(dns_rdataset_t *source, dns_rdataset_t *target) {
+ dns_dbnode_t *node = source->private5;
+ dns_sdlznode_t *sdlznode = (dns_sdlznode_t *)node;
+ dns_db_t *db = (dns_db_t *)sdlznode->sdlz;
+ dns_dbnode_t *tempdb = NULL;
+
+ isc__rdatalist_clone(source, target);
+ attachnode(db, node, &tempdb);
+ source->private5 = tempdb;
+}
+
+static dns_rdatasetmethods_t rdataset_methods = {
+ disassociate,
+ isc__rdatalist_first,
+ isc__rdatalist_next,
+ isc__rdatalist_current,
+ rdataset_clone,
+ isc__rdatalist_count,
+ isc__rdatalist_addnoqname,
+ isc__rdatalist_getnoqname,
+ NULL, /* addclosest */
+ NULL, /* getclosest */
+ NULL, /* settrust */
+ NULL, /* expire */
+ NULL, /* clearprefetch */
+ NULL, /* setownercase */
+ NULL, /* getownercase */
+ NULL /* addglue */
+};
+
+static void
+list_tordataset(dns_rdatalist_t *rdatalist, dns_db_t *db, dns_dbnode_t *node,
+ dns_rdataset_t *rdataset) {
+ /*
+ * The sdlz rdataset is an rdatalist with some additions.
+ * - private1 & private2 are used by the rdatalist.
+ * - private3 & private 4 are unused.
+ * - private5 is the node.
+ */
+
+ /* This should never fail. */
+ RUNTIME_CHECK(dns_rdatalist_tordataset(rdatalist, rdataset) ==
+ ISC_R_SUCCESS);
+
+ rdataset->methods = &rdataset_methods;
+ dns_db_attachnode(db, node, &rdataset->private5);
+}
+
+/*
+ * SDLZ core methods. This is the core of the new DLZ functionality.
+ */
+
+/*%
+ * Build a 'bind' database driver structure to be returned by
+ * either the find zone or the allow zone transfer method.
+ * This method is only available in this source file, it is
+ * not made available anywhere else.
+ */
+
+static isc_result_t
+dns_sdlzcreateDBP(isc_mem_t *mctx, void *driverarg, void *dbdata,
+ const dns_name_t *name, dns_rdataclass_t rdclass,
+ dns_db_t **dbp) {
+ isc_result_t result;
+ dns_sdlz_db_t *sdlzdb;
+ dns_sdlzimplementation_t *imp;
+
+ /* check that things are as we expect */
+ REQUIRE(dbp != NULL && *dbp == NULL);
+ REQUIRE(name != NULL);
+
+ imp = (dns_sdlzimplementation_t *)driverarg;
+
+ /* allocate and zero memory for driver structure */
+ sdlzdb = isc_mem_get(mctx, sizeof(dns_sdlz_db_t));
+ memset(sdlzdb, 0, sizeof(dns_sdlz_db_t));
+
+ /* initialize and set origin */
+ dns_name_init(&sdlzdb->common.origin, NULL);
+ result = dns_name_dupwithoffsets(name, mctx, &sdlzdb->common.origin);
+ if (result != ISC_R_SUCCESS) {
+ goto mem_cleanup;
+ }
+
+ /* set the rest of the database structure attributes */
+ sdlzdb->dlzimp = imp;
+ sdlzdb->common.methods = &sdlzdb_methods;
+ sdlzdb->common.attributes = 0;
+ sdlzdb->common.rdclass = rdclass;
+ sdlzdb->common.mctx = NULL;
+ sdlzdb->dbdata = dbdata;
+ isc_refcount_init(&sdlzdb->references, 1);
+
+ /* attach to the memory context */
+ isc_mem_attach(mctx, &sdlzdb->common.mctx);
+
+ /* mark structure as valid */
+ sdlzdb->common.magic = DNS_DB_MAGIC;
+ sdlzdb->common.impmagic = SDLZDB_MAGIC;
+ *dbp = (dns_db_t *)sdlzdb;
+
+ return (result);
+mem_cleanup:
+ isc_mem_put(mctx, sdlzdb, sizeof(dns_sdlz_db_t));
+ return (result);
+}
+
+static isc_result_t
+dns_sdlzallowzonexfr(void *driverarg, void *dbdata, isc_mem_t *mctx,
+ dns_rdataclass_t rdclass, const dns_name_t *name,
+ const isc_sockaddr_t *clientaddr, dns_db_t **dbp) {
+ isc_buffer_t b;
+ isc_buffer_t b2;
+ char namestr[DNS_NAME_MAXTEXT + 1];
+ char clientstr[(sizeof "xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:255.255.255."
+ "255") +
+ 1];
+ isc_netaddr_t netaddr;
+ isc_result_t result;
+ dns_sdlzimplementation_t *imp;
+
+ /*
+ * Perform checks to make sure data is as we expect it to be.
+ */
+ REQUIRE(driverarg != NULL);
+ REQUIRE(name != NULL);
+ REQUIRE(clientaddr != NULL);
+ REQUIRE(dbp != NULL && *dbp == NULL);
+
+ imp = (dns_sdlzimplementation_t *)driverarg;
+
+ /* Convert DNS name to ascii text */
+ isc_buffer_init(&b, namestr, sizeof(namestr));
+ result = dns_name_totext(name, true, &b);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+ isc_buffer_putuint8(&b, 0);
+
+ /* convert client address to ascii text */
+ isc_buffer_init(&b2, clientstr, sizeof(clientstr));
+ isc_netaddr_fromsockaddr(&netaddr, clientaddr);
+ result = isc_netaddr_totext(&netaddr, &b2);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+ isc_buffer_putuint8(&b2, 0);
+
+ /* make sure strings are always lowercase */
+ dns_sdlz_tolower(namestr);
+ dns_sdlz_tolower(clientstr);
+
+ /* Call SDLZ driver's find zone method */
+ if (imp->methods->allowzonexfr != NULL) {
+ isc_result_t rresult = ISC_R_SUCCESS;
+
+ MAYBE_LOCK(imp);
+ result = imp->methods->allowzonexfr(imp->driverarg, dbdata,
+ namestr, clientstr);
+ MAYBE_UNLOCK(imp);
+ /*
+ * if zone is supported and transfers are (or might be)
+ * allowed, build a 'bind' database driver
+ */
+ if (result == ISC_R_SUCCESS || result == ISC_R_DEFAULT) {
+ rresult = dns_sdlzcreateDBP(mctx, driverarg, dbdata,
+ name, rdclass, dbp);
+ }
+ if (rresult != ISC_R_SUCCESS) {
+ result = rresult;
+ }
+ return (result);
+ }
+
+ return (ISC_R_NOTIMPLEMENTED);
+}
+
+static isc_result_t
+dns_sdlzcreate(isc_mem_t *mctx, const char *dlzname, unsigned int argc,
+ char *argv[], void *driverarg, void **dbdata) {
+ dns_sdlzimplementation_t *imp;
+ isc_result_t result = ISC_R_NOTFOUND;
+
+ /* Write debugging message to log */
+ sdlz_log(ISC_LOG_DEBUG(2), "Loading SDLZ driver.");
+
+ /*
+ * Performs checks to make sure data is as we expect it to be.
+ */
+ REQUIRE(driverarg != NULL);
+ REQUIRE(dlzname != NULL);
+ REQUIRE(dbdata != NULL);
+ UNUSED(mctx);
+
+ imp = driverarg;
+
+ /* If the create method exists, call it. */
+ if (imp->methods->create != NULL) {
+ MAYBE_LOCK(imp);
+ result = imp->methods->create(dlzname, argc, argv,
+ imp->driverarg, dbdata);
+ MAYBE_UNLOCK(imp);
+ }
+
+ /* Write debugging message to log */
+ if (result == ISC_R_SUCCESS) {
+ sdlz_log(ISC_LOG_DEBUG(2), "SDLZ driver loaded successfully.");
+ } else {
+ sdlz_log(ISC_LOG_ERROR, "SDLZ driver failed to load.");
+ }
+
+ return (result);
+}
+
+static void
+dns_sdlzdestroy(void *driverdata, void **dbdata) {
+ dns_sdlzimplementation_t *imp;
+
+ /* Write debugging message to log */
+ sdlz_log(ISC_LOG_DEBUG(2), "Unloading SDLZ driver.");
+
+ imp = driverdata;
+
+ /* If the destroy method exists, call it. */
+ if (imp->methods->destroy != NULL) {
+ MAYBE_LOCK(imp);
+ imp->methods->destroy(imp->driverarg, dbdata);
+ MAYBE_UNLOCK(imp);
+ }
+}
+
+static isc_result_t
+dns_sdlzfindzone(void *driverarg, void *dbdata, isc_mem_t *mctx,
+ dns_rdataclass_t rdclass, const dns_name_t *name,
+ dns_clientinfomethods_t *methods, dns_clientinfo_t *clientinfo,
+ dns_db_t **dbp) {
+ isc_buffer_t b;
+ char namestr[DNS_NAME_MAXTEXT + 1];
+ isc_result_t result;
+ dns_sdlzimplementation_t *imp;
+
+ /*
+ * Perform checks to make sure data is as we expect it to be.
+ */
+ REQUIRE(driverarg != NULL);
+ REQUIRE(name != NULL);
+ REQUIRE(dbp != NULL && *dbp == NULL);
+
+ imp = (dns_sdlzimplementation_t *)driverarg;
+
+ /* Convert DNS name to ascii text */
+ isc_buffer_init(&b, namestr, sizeof(namestr));
+ result = dns_name_totext(name, true, &b);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+ isc_buffer_putuint8(&b, 0);
+
+ /* make sure strings are always lowercase */
+ dns_sdlz_tolower(namestr);
+
+ /* Call SDLZ driver's find zone method */
+ MAYBE_LOCK(imp);
+ result = imp->methods->findzone(imp->driverarg, dbdata, namestr,
+ methods, clientinfo);
+ MAYBE_UNLOCK(imp);
+
+ /*
+ * if zone is supported build a 'bind' database driver
+ * structure to return
+ */
+ if (result == ISC_R_SUCCESS) {
+ result = dns_sdlzcreateDBP(mctx, driverarg, dbdata, name,
+ rdclass, dbp);
+ }
+
+ return (result);
+}
+
+static isc_result_t
+dns_sdlzconfigure(void *driverarg, void *dbdata, dns_view_t *view,
+ dns_dlzdb_t *dlzdb) {
+ isc_result_t result;
+ dns_sdlzimplementation_t *imp;
+
+ REQUIRE(driverarg != NULL);
+
+ imp = (dns_sdlzimplementation_t *)driverarg;
+
+ /* Call SDLZ driver's configure method */
+ if (imp->methods->configure != NULL) {
+ MAYBE_LOCK(imp);
+ result = imp->methods->configure(view, dlzdb, imp->driverarg,
+ dbdata);
+ MAYBE_UNLOCK(imp);
+ } else {
+ result = ISC_R_SUCCESS;
+ }
+
+ return (result);
+}
+
+static bool
+dns_sdlzssumatch(const dns_name_t *signer, const dns_name_t *name,
+ const isc_netaddr_t *tcpaddr, dns_rdatatype_t type,
+ const dst_key_t *key, void *driverarg, void *dbdata) {
+ dns_sdlzimplementation_t *imp;
+ char b_signer[DNS_NAME_FORMATSIZE];
+ char b_name[DNS_NAME_FORMATSIZE];
+ char b_addr[ISC_NETADDR_FORMATSIZE];
+ char b_type[DNS_RDATATYPE_FORMATSIZE];
+ char b_key[DST_KEY_FORMATSIZE];
+ isc_buffer_t *tkey_token = NULL;
+ isc_region_t token_region = { NULL, 0 };
+ uint32_t token_len = 0;
+ bool ret;
+
+ REQUIRE(driverarg != NULL);
+
+ imp = (dns_sdlzimplementation_t *)driverarg;
+ if (imp->methods->ssumatch == NULL) {
+ return (false);
+ }
+
+ /*
+ * Format the request elements. sdlz operates on strings, not
+ * structures
+ */
+ if (signer != NULL) {
+ dns_name_format(signer, b_signer, sizeof(b_signer));
+ } else {
+ b_signer[0] = 0;
+ }
+
+ dns_name_format(name, b_name, sizeof(b_name));
+
+ if (tcpaddr != NULL) {
+ isc_netaddr_format(tcpaddr, b_addr, sizeof(b_addr));
+ } else {
+ b_addr[0] = 0;
+ }
+
+ dns_rdatatype_format(type, b_type, sizeof(b_type));
+
+ if (key != NULL) {
+ dst_key_format(key, b_key, sizeof(b_key));
+ tkey_token = dst_key_tkeytoken(key);
+ } else {
+ b_key[0] = 0;
+ }
+
+ if (tkey_token != NULL) {
+ isc_buffer_region(tkey_token, &token_region);
+ token_len = token_region.length;
+ }
+
+ MAYBE_LOCK(imp);
+ ret = imp->methods->ssumatch(b_signer, b_name, b_addr, b_type, b_key,
+ token_len,
+ token_len != 0 ? token_region.base : NULL,
+ imp->driverarg, dbdata);
+ MAYBE_UNLOCK(imp);
+ return (ret);
+}
+
+static dns_dlzmethods_t sdlzmethods = { dns_sdlzcreate, dns_sdlzdestroy,
+ dns_sdlzfindzone, dns_sdlzallowzonexfr,
+ dns_sdlzconfigure, dns_sdlzssumatch };
+
+/*
+ * Public functions.
+ */
+
+isc_result_t
+dns_sdlz_putrr(dns_sdlzlookup_t *lookup, const char *type, dns_ttl_t ttl,
+ const char *data) {
+ dns_rdatalist_t *rdatalist;
+ dns_rdata_t *rdata;
+ dns_rdatatype_t typeval;
+ isc_consttextregion_t r;
+ isc_buffer_t b;
+ isc_buffer_t *rdatabuf = NULL;
+ isc_lex_t *lex;
+ isc_result_t result;
+ unsigned int size;
+ isc_mem_t *mctx;
+ const dns_name_t *origin;
+
+ REQUIRE(VALID_SDLZLOOKUP(lookup));
+ REQUIRE(type != NULL);
+ REQUIRE(data != NULL);
+
+ mctx = lookup->sdlz->common.mctx;
+
+ r.base = type;
+ r.length = strlen(type);
+ result = dns_rdatatype_fromtext(&typeval, (void *)&r);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+
+ rdatalist = ISC_LIST_HEAD(lookup->lists);
+ while (rdatalist != NULL) {
+ if (rdatalist->type == typeval) {
+ break;
+ }
+ rdatalist = ISC_LIST_NEXT(rdatalist, link);
+ }
+
+ if (rdatalist == NULL) {
+ rdatalist = isc_mem_get(mctx, sizeof(dns_rdatalist_t));
+ dns_rdatalist_init(rdatalist);
+ rdatalist->rdclass = lookup->sdlz->common.rdclass;
+ rdatalist->type = typeval;
+ rdatalist->ttl = ttl;
+ ISC_LIST_APPEND(lookup->lists, rdatalist, link);
+ } else if (rdatalist->ttl > ttl) {
+ /*
+ * BIND9 doesn't enforce all RRs in an RRset
+ * having the same TTL, as per RFC 2136,
+ * section 7.12. If a DLZ backend has
+ * different TTLs, then the best
+ * we can do is return the lowest.
+ */
+ rdatalist->ttl = ttl;
+ }
+
+ rdata = isc_mem_get(mctx, sizeof(dns_rdata_t));
+ dns_rdata_init(rdata);
+
+ if ((lookup->sdlz->dlzimp->flags & DNS_SDLZFLAG_RELATIVERDATA) != 0) {
+ origin = &lookup->sdlz->common.origin;
+ } else {
+ origin = dns_rootname;
+ }
+
+ lex = NULL;
+ result = isc_lex_create(mctx, 64, &lex);
+ if (result != ISC_R_SUCCESS) {
+ goto failure;
+ }
+
+ size = initial_size(data);
+ do {
+ isc_buffer_constinit(&b, data, strlen(data));
+ isc_buffer_add(&b, strlen(data));
+
+ result = isc_lex_openbuffer(lex, &b);
+ if (result != ISC_R_SUCCESS) {
+ goto failure;
+ }
+
+ rdatabuf = NULL;
+ isc_buffer_allocate(mctx, &rdatabuf, size);
+
+ result = dns_rdata_fromtext(rdata, rdatalist->rdclass,
+ rdatalist->type, lex, origin, false,
+ mctx, rdatabuf, &lookup->callbacks);
+ if (result != ISC_R_SUCCESS) {
+ isc_buffer_free(&rdatabuf);
+ }
+ if (size >= 65535) {
+ break;
+ }
+ size *= 2;
+ if (size >= 65535) {
+ size = 65535;
+ }
+ } while (result == ISC_R_NOSPACE);
+
+ if (result != ISC_R_SUCCESS) {
+ result = DNS_R_SERVFAIL;
+ goto failure;
+ }
+
+ ISC_LIST_APPEND(rdatalist->rdata, rdata, link);
+ ISC_LIST_APPEND(lookup->buffers, rdatabuf, link);
+
+ if (lex != NULL) {
+ isc_lex_destroy(&lex);
+ }
+
+ return (ISC_R_SUCCESS);
+
+failure:
+ if (rdatabuf != NULL) {
+ isc_buffer_free(&rdatabuf);
+ }
+ if (lex != NULL) {
+ isc_lex_destroy(&lex);
+ }
+ isc_mem_put(mctx, rdata, sizeof(dns_rdata_t));
+
+ return (result);
+}
+
+isc_result_t
+dns_sdlz_putnamedrr(dns_sdlzallnodes_t *allnodes, const char *name,
+ const char *type, dns_ttl_t ttl, const char *data) {
+ dns_name_t *newname;
+ const dns_name_t *origin;
+ dns_fixedname_t fnewname;
+ dns_sdlz_db_t *sdlz = (dns_sdlz_db_t *)allnodes->common.db;
+ dns_sdlznode_t *sdlznode;
+ isc_mem_t *mctx = sdlz->common.mctx;
+ isc_buffer_t b;
+ isc_result_t result;
+
+ newname = dns_fixedname_initname(&fnewname);
+
+ if ((sdlz->dlzimp->flags & DNS_SDLZFLAG_RELATIVERDATA) != 0) {
+ origin = &sdlz->common.origin;
+ } else {
+ origin = dns_rootname;
+ }
+ isc_buffer_constinit(&b, name, strlen(name));
+ isc_buffer_add(&b, strlen(name));
+
+ result = dns_name_fromtext(newname, &b, origin, 0, NULL);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+
+ if (allnodes->common.relative_names) {
+ /* All names are relative to the root */
+ unsigned int nlabels = dns_name_countlabels(newname);
+ dns_name_getlabelsequence(newname, 0, nlabels - 1, newname);
+ }
+
+ sdlznode = ISC_LIST_HEAD(allnodes->nodelist);
+ if (sdlznode == NULL || !dns_name_equal(sdlznode->name, newname)) {
+ sdlznode = NULL;
+ result = createnode(sdlz, &sdlznode);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+ sdlznode->name = isc_mem_get(mctx, sizeof(dns_name_t));
+ dns_name_init(sdlznode->name, NULL);
+ dns_name_dup(newname, mctx, sdlznode->name);
+ ISC_LIST_PREPEND(allnodes->nodelist, sdlznode, link);
+ if (allnodes->origin == NULL &&
+ dns_name_equal(newname, &sdlz->common.origin))
+ {
+ allnodes->origin = sdlznode;
+ }
+ }
+ return (dns_sdlz_putrr(sdlznode, type, ttl, data));
+}
+
+isc_result_t
+dns_sdlz_putsoa(dns_sdlzlookup_t *lookup, const char *mname, const char *rname,
+ uint32_t serial) {
+ char str[2 * DNS_NAME_MAXTEXT + 5 * (sizeof("2147483647")) + 7];
+ int n;
+
+ REQUIRE(mname != NULL);
+ REQUIRE(rname != NULL);
+
+ n = snprintf(str, sizeof str, "%s %s %u %u %u %u %u", mname, rname,
+ serial, SDLZ_DEFAULT_REFRESH, SDLZ_DEFAULT_RETRY,
+ SDLZ_DEFAULT_EXPIRE, SDLZ_DEFAULT_MINIMUM);
+ if (n >= (int)sizeof(str) || n < 0) {
+ return (ISC_R_NOSPACE);
+ }
+ return (dns_sdlz_putrr(lookup, "SOA", SDLZ_DEFAULT_TTL, str));
+}
+
+isc_result_t
+dns_sdlzregister(const char *drivername, const dns_sdlzmethods_t *methods,
+ void *driverarg, unsigned int flags, isc_mem_t *mctx,
+ dns_sdlzimplementation_t **sdlzimp) {
+ dns_sdlzimplementation_t *imp;
+ isc_result_t result;
+
+ /*
+ * Performs checks to make sure data is as we expect it to be.
+ */
+ REQUIRE(drivername != NULL);
+ REQUIRE(methods != NULL);
+ REQUIRE(methods->findzone != NULL);
+ REQUIRE(methods->lookup != NULL);
+ REQUIRE(mctx != NULL);
+ REQUIRE(sdlzimp != NULL && *sdlzimp == NULL);
+ REQUIRE((flags &
+ ~(DNS_SDLZFLAG_RELATIVEOWNER | DNS_SDLZFLAG_RELATIVERDATA |
+ DNS_SDLZFLAG_THREADSAFE)) == 0);
+
+ /* Write debugging message to log */
+ sdlz_log(ISC_LOG_DEBUG(2), "Registering SDLZ driver '%s'", drivername);
+
+ /*
+ * Allocate memory for a sdlz_implementation object. Error if
+ * we cannot.
+ */
+ imp = isc_mem_get(mctx, sizeof(dns_sdlzimplementation_t));
+
+ /* Make sure memory region is set to all 0's */
+ memset(imp, 0, sizeof(dns_sdlzimplementation_t));
+
+ /* Store the data passed into this method */
+ imp->methods = methods;
+ imp->driverarg = driverarg;
+ imp->flags = flags;
+ imp->mctx = NULL;
+
+ /* attach the new sdlz_implementation object to a memory context */
+ isc_mem_attach(mctx, &imp->mctx);
+
+ /*
+ * initialize the driver lock, error if we cannot
+ * (used if a driver does not support multiple threads)
+ */
+ isc_mutex_init(&imp->driverlock);
+
+ imp->dlz_imp = NULL;
+
+ /*
+ * register the DLZ driver. Pass in our "extra" sdlz information as
+ * a driverarg. (that's why we stored the passed in driver arg in our
+ * sdlz_implementation structure) Also, store the dlz_implementation
+ * structure in our sdlz_implementation.
+ */
+ result = dns_dlzregister(drivername, &sdlzmethods, imp, mctx,
+ &imp->dlz_imp);
+
+ /* if registration fails, cleanup and get outta here. */
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup_mutex;
+ }
+
+ *sdlzimp = imp;
+
+ return (ISC_R_SUCCESS);
+
+cleanup_mutex:
+ /* destroy the driver lock, we don't need it anymore */
+ isc_mutex_destroy(&imp->driverlock);
+
+ /*
+ * return the memory back to the available memory pool and
+ * remove it from the memory context.
+ */
+ isc_mem_putanddetach(&imp->mctx, imp, sizeof(dns_sdlzimplementation_t));
+ return (result);
+}
+
+void
+dns_sdlzunregister(dns_sdlzimplementation_t **sdlzimp) {
+ dns_sdlzimplementation_t *imp;
+
+ /* Write debugging message to log */
+ sdlz_log(ISC_LOG_DEBUG(2), "Unregistering SDLZ driver.");
+
+ /*
+ * Performs checks to make sure data is as we expect it to be.
+ */
+ REQUIRE(sdlzimp != NULL && *sdlzimp != NULL);
+
+ imp = *sdlzimp;
+ *sdlzimp = NULL;
+
+ /* Unregister the DLZ driver implementation */
+ dns_dlzunregister(&imp->dlz_imp);
+
+ /* destroy the driver lock, we don't need it anymore */
+ isc_mutex_destroy(&imp->driverlock);
+
+ /*
+ * return the memory back to the available memory pool and
+ * remove it from the memory context.
+ */
+ isc_mem_putanddetach(&imp->mctx, imp, sizeof(dns_sdlzimplementation_t));
+}
+
+isc_result_t
+dns_sdlz_setdb(dns_dlzdb_t *dlzdatabase, dns_rdataclass_t rdclass,
+ const dns_name_t *name, dns_db_t **dbp) {
+ isc_result_t result;
+
+ result = dns_sdlzcreateDBP(dlzdatabase->mctx,
+ dlzdatabase->implementation->driverarg,
+ dlzdatabase->dbdata, name, rdclass, dbp);
+ return (result);
+}
diff --git a/lib/dns/soa.c b/lib/dns/soa.c
new file mode 100644
index 0000000..ca5ca88
--- /dev/null
+++ b/lib/dns/soa.c
@@ -0,0 +1,137 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/*! \file */
+
+#include <inttypes.h>
+#include <string.h>
+
+#include <isc/buffer.h>
+#include <isc/util.h>
+
+#include <dns/rdata.h>
+#include <dns/rdatastruct.h>
+#include <dns/soa.h>
+
+static uint32_t
+decode_uint32(unsigned char *p) {
+ return (((uint32_t)p[0] << 24) + ((uint32_t)p[1] << 16) +
+ ((uint32_t)p[2] << 8) + ((uint32_t)p[3] << 0));
+}
+
+static void
+encode_uint32(uint32_t val, unsigned char *p) {
+ p[0] = (uint8_t)(val >> 24);
+ p[1] = (uint8_t)(val >> 16);
+ p[2] = (uint8_t)(val >> 8);
+ p[3] = (uint8_t)(val >> 0);
+}
+
+static uint32_t
+soa_get(dns_rdata_t *rdata, int offset) {
+ INSIST(rdata->type == dns_rdatatype_soa);
+ /*
+ * Locate the field within the SOA RDATA based
+ * on its position relative to the end of the data.
+ *
+ * This is a bit of a kludge, but the alternative approach of
+ * using dns_rdata_tostruct() and dns_rdata_fromstruct() would
+ * involve a lot of unnecessary work (like building domain
+ * names and allocating temporary memory) when all we really
+ * want to do is to get 32 bits of fixed-sized data.
+ */
+ INSIST(rdata->length >= 20);
+ INSIST(offset >= 0 && offset <= 16);
+ return (decode_uint32(rdata->data + rdata->length - 20 + offset));
+}
+
+isc_result_t
+dns_soa_buildrdata(const dns_name_t *origin, const dns_name_t *contact,
+ dns_rdataclass_t rdclass, uint32_t serial, uint32_t refresh,
+ uint32_t retry, uint32_t expire, uint32_t minimum,
+ unsigned char *buffer, dns_rdata_t *rdata) {
+ dns_rdata_soa_t soa;
+ isc_buffer_t rdatabuf;
+
+ REQUIRE(origin != NULL);
+ REQUIRE(contact != NULL);
+
+ memset(buffer, 0, DNS_SOA_BUFFERSIZE);
+ isc_buffer_init(&rdatabuf, buffer, DNS_SOA_BUFFERSIZE);
+
+ soa.common.rdtype = dns_rdatatype_soa;
+ soa.common.rdclass = rdclass;
+ soa.mctx = NULL;
+ soa.serial = serial;
+ soa.refresh = refresh;
+ soa.retry = retry;
+ soa.expire = expire;
+ soa.minimum = minimum;
+ dns_name_init(&soa.origin, NULL);
+ dns_name_clone(origin, &soa.origin);
+ dns_name_init(&soa.contact, NULL);
+ dns_name_clone(contact, &soa.contact);
+
+ return (dns_rdata_fromstruct(rdata, rdclass, dns_rdatatype_soa, &soa,
+ &rdatabuf));
+}
+
+uint32_t
+dns_soa_getserial(dns_rdata_t *rdata) {
+ return (soa_get(rdata, 0));
+}
+uint32_t
+dns_soa_getrefresh(dns_rdata_t *rdata) {
+ return (soa_get(rdata, 4));
+}
+uint32_t
+dns_soa_getretry(dns_rdata_t *rdata) {
+ return (soa_get(rdata, 8));
+}
+uint32_t
+dns_soa_getexpire(dns_rdata_t *rdata) {
+ return (soa_get(rdata, 12));
+}
+uint32_t
+dns_soa_getminimum(dns_rdata_t *rdata) {
+ return (soa_get(rdata, 16));
+}
+
+static void
+soa_set(dns_rdata_t *rdata, uint32_t val, int offset) {
+ INSIST(rdata->type == dns_rdatatype_soa);
+ INSIST(rdata->length >= 20);
+ INSIST(offset >= 0 && offset <= 16);
+ encode_uint32(val, rdata->data + rdata->length - 20 + offset);
+}
+
+void
+dns_soa_setserial(uint32_t val, dns_rdata_t *rdata) {
+ soa_set(rdata, val, 0);
+}
+void
+dns_soa_setrefresh(uint32_t val, dns_rdata_t *rdata) {
+ soa_set(rdata, val, 4);
+}
+void
+dns_soa_setretry(uint32_t val, dns_rdata_t *rdata) {
+ soa_set(rdata, val, 8);
+}
+void
+dns_soa_setexpire(uint32_t val, dns_rdata_t *rdata) {
+ soa_set(rdata, val, 12);
+}
+void
+dns_soa_setminimum(uint32_t val, dns_rdata_t *rdata) {
+ soa_set(rdata, val, 16);
+}
diff --git a/lib/dns/ssu.c b/lib/dns/ssu.c
new file mode 100644
index 0000000..581cdcd
--- /dev/null
+++ b/lib/dns/ssu.c
@@ -0,0 +1,715 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/*! \file */
+
+#include <stdbool.h>
+
+#include <isc/magic.h>
+#include <isc/mem.h>
+#include <isc/netaddr.h>
+#include <isc/print.h>
+#include <isc/refcount.h>
+#include <isc/result.h>
+#include <isc/string.h>
+#include <isc/util.h>
+
+#include <dns/dlz.h>
+#include <dns/fixedname.h>
+#include <dns/name.h>
+#include <dns/ssu.h>
+
+#include <dst/dst.h>
+#include <dst/gssapi.h>
+
+#define SSUTABLEMAGIC ISC_MAGIC('S', 'S', 'U', 'T')
+#define VALID_SSUTABLE(table) ISC_MAGIC_VALID(table, SSUTABLEMAGIC)
+
+#define SSURULEMAGIC ISC_MAGIC('S', 'S', 'U', 'R')
+#define VALID_SSURULE(table) ISC_MAGIC_VALID(table, SSURULEMAGIC)
+
+struct dns_ssurule {
+ unsigned int magic;
+ bool grant; /*%< is this a grant or a deny? */
+ dns_ssumatchtype_t matchtype; /*%< which type of pattern match? */
+ dns_name_t *identity; /*%< the identity to match */
+ dns_name_t *name; /*%< the name being updated */
+ unsigned int ntypes; /*%< number of data types covered */
+ dns_ssuruletype_t *types; /*%< the data types. Can include */
+ /* ANY. if NULL, defaults to all */
+ /* types except SIG, SOA, and NS */
+ ISC_LINK(dns_ssurule_t) link;
+};
+
+struct dns_ssutable {
+ unsigned int magic;
+ isc_mem_t *mctx;
+ isc_refcount_t references;
+ dns_dlzdb_t *dlzdatabase;
+ ISC_LIST(dns_ssurule_t) rules;
+};
+
+void
+dns_ssutable_create(isc_mem_t *mctx, dns_ssutable_t **tablep) {
+ dns_ssutable_t *table;
+
+ REQUIRE(tablep != NULL && *tablep == NULL);
+ REQUIRE(mctx != NULL);
+
+ table = isc_mem_get(mctx, sizeof(dns_ssutable_t));
+ isc_refcount_init(&table->references, 1);
+ table->mctx = NULL;
+ isc_mem_attach(mctx, &table->mctx);
+ ISC_LIST_INIT(table->rules);
+ table->magic = SSUTABLEMAGIC;
+ *tablep = table;
+}
+
+static void
+destroy(dns_ssutable_t *table) {
+ isc_mem_t *mctx;
+
+ REQUIRE(VALID_SSUTABLE(table));
+
+ mctx = table->mctx;
+ while (!ISC_LIST_EMPTY(table->rules)) {
+ dns_ssurule_t *rule = ISC_LIST_HEAD(table->rules);
+ if (rule->identity != NULL) {
+ dns_name_free(rule->identity, mctx);
+ isc_mem_put(mctx, rule->identity,
+ sizeof(*rule->identity));
+ }
+ if (rule->name != NULL) {
+ dns_name_free(rule->name, mctx);
+ isc_mem_put(mctx, rule->name, sizeof(*rule->name));
+ }
+ if (rule->types != NULL) {
+ isc_mem_put(mctx, rule->types,
+ rule->ntypes * sizeof(*rule->types));
+ }
+ ISC_LIST_UNLINK(table->rules, rule, link);
+ rule->magic = 0;
+ isc_mem_put(mctx, rule, sizeof(dns_ssurule_t));
+ }
+ isc_refcount_destroy(&table->references);
+ table->magic = 0;
+ isc_mem_putanddetach(&table->mctx, table, sizeof(dns_ssutable_t));
+}
+
+void
+dns_ssutable_attach(dns_ssutable_t *source, dns_ssutable_t **targetp) {
+ REQUIRE(VALID_SSUTABLE(source));
+ REQUIRE(targetp != NULL && *targetp == NULL);
+
+ isc_refcount_increment(&source->references);
+
+ *targetp = source;
+}
+
+void
+dns_ssutable_detach(dns_ssutable_t **tablep) {
+ dns_ssutable_t *table;
+
+ REQUIRE(tablep != NULL);
+ table = *tablep;
+ *tablep = NULL;
+ REQUIRE(VALID_SSUTABLE(table));
+
+ if (isc_refcount_decrement(&table->references) == 1) {
+ destroy(table);
+ }
+}
+
+void
+dns_ssutable_addrule(dns_ssutable_t *table, bool grant,
+ const dns_name_t *identity, dns_ssumatchtype_t matchtype,
+ const dns_name_t *name, unsigned int ntypes,
+ dns_ssuruletype_t *types) {
+ dns_ssurule_t *rule;
+ isc_mem_t *mctx;
+
+ REQUIRE(VALID_SSUTABLE(table));
+ REQUIRE(dns_name_isabsolute(identity));
+ REQUIRE(dns_name_isabsolute(name));
+ REQUIRE(matchtype <= dns_ssumatchtype_max);
+ if (matchtype == dns_ssumatchtype_wildcard) {
+ REQUIRE(dns_name_iswildcard(name));
+ }
+ if (ntypes > 0) {
+ REQUIRE(types != NULL);
+ }
+
+ mctx = table->mctx;
+ rule = isc_mem_get(mctx, sizeof(*rule));
+
+ rule->identity = NULL;
+ rule->name = NULL;
+ rule->types = NULL;
+
+ rule->grant = grant;
+
+ rule->identity = isc_mem_get(mctx, sizeof(*rule->identity));
+ dns_name_init(rule->identity, NULL);
+ dns_name_dup(identity, mctx, rule->identity);
+
+ rule->name = isc_mem_get(mctx, sizeof(*rule->name));
+ dns_name_init(rule->name, NULL);
+ dns_name_dup(name, mctx, rule->name);
+
+ rule->matchtype = matchtype;
+
+ rule->ntypes = ntypes;
+ if (ntypes > 0) {
+ rule->types = isc_mem_get(mctx, ntypes * sizeof(*rule->types));
+ memmove(rule->types, types, ntypes * sizeof(*rule->types));
+ } else {
+ rule->types = NULL;
+ }
+
+ rule->magic = SSURULEMAGIC;
+ ISC_LIST_INITANDAPPEND(table->rules, rule, link);
+}
+
+static bool
+isusertype(dns_rdatatype_t type) {
+ return (type != dns_rdatatype_ns && type != dns_rdatatype_soa &&
+ type != dns_rdatatype_rrsig);
+}
+
+static void
+reverse_from_address(dns_name_t *tcpself, const isc_netaddr_t *tcpaddr) {
+ char buf[16 * 4 + sizeof("IP6.ARPA.")];
+ isc_result_t result;
+ const unsigned char *ap;
+ isc_buffer_t b;
+ unsigned long l;
+
+ switch (tcpaddr->family) {
+ case AF_INET:
+ l = ntohl(tcpaddr->type.in.s_addr);
+ result = snprintf(buf, sizeof(buf),
+ "%lu.%lu.%lu.%lu.IN-ADDR.ARPA.",
+ (l >> 0) & 0xff, (l >> 8) & 0xff,
+ (l >> 16) & 0xff, (l >> 24) & 0xff);
+ RUNTIME_CHECK(result < sizeof(buf));
+ break;
+ case AF_INET6:
+ ap = tcpaddr->type.in6.s6_addr;
+ result = snprintf(
+ buf, sizeof(buf),
+ "%x.%x.%x.%x.%x.%x.%x.%x."
+ "%x.%x.%x.%x.%x.%x.%x.%x."
+ "%x.%x.%x.%x.%x.%x.%x.%x."
+ "%x.%x.%x.%x.%x.%x.%x.%x."
+ "IP6.ARPA.",
+ ap[15] & 0x0f, (ap[15] >> 4) & 0x0f, ap[14] & 0x0f,
+ (ap[14] >> 4) & 0x0f, ap[13] & 0x0f,
+ (ap[13] >> 4) & 0x0f, ap[12] & 0x0f,
+ (ap[12] >> 4) & 0x0f, ap[11] & 0x0f,
+ (ap[11] >> 4) & 0x0f, ap[10] & 0x0f,
+ (ap[10] >> 4) & 0x0f, ap[9] & 0x0f, (ap[9] >> 4) & 0x0f,
+ ap[8] & 0x0f, (ap[8] >> 4) & 0x0f, ap[7] & 0x0f,
+ (ap[7] >> 4) & 0x0f, ap[6] & 0x0f, (ap[6] >> 4) & 0x0f,
+ ap[5] & 0x0f, (ap[5] >> 4) & 0x0f, ap[4] & 0x0f,
+ (ap[4] >> 4) & 0x0f, ap[3] & 0x0f, (ap[3] >> 4) & 0x0f,
+ ap[2] & 0x0f, (ap[2] >> 4) & 0x0f, ap[1] & 0x0f,
+ (ap[1] >> 4) & 0x0f, ap[0] & 0x0f, (ap[0] >> 4) & 0x0f);
+ RUNTIME_CHECK(result < sizeof(buf));
+ break;
+ default:
+ UNREACHABLE();
+ }
+ isc_buffer_init(&b, buf, strlen(buf));
+ isc_buffer_add(&b, strlen(buf));
+ result = dns_name_fromtext(tcpself, &b, dns_rootname, 0, NULL);
+ RUNTIME_CHECK(result == ISC_R_SUCCESS);
+}
+
+static void
+stf_from_address(dns_name_t *stfself, const isc_netaddr_t *tcpaddr) {
+ char buf[sizeof("X.X.X.X.Y.Y.Y.Y.2.0.0.2.IP6.ARPA.")];
+ isc_result_t result;
+ const unsigned char *ap;
+ isc_buffer_t b;
+ unsigned long l;
+
+ switch (tcpaddr->family) {
+ case AF_INET:
+ l = ntohl(tcpaddr->type.in.s_addr);
+ result = snprintf(buf, sizeof(buf),
+ "%lx.%lx.%lx.%lx.%lx.%lx.%lx.%lx"
+ "2.0.0.2.IP6.ARPA.",
+ l & 0xf, (l >> 4) & 0xf, (l >> 8) & 0xf,
+ (l >> 12) & 0xf, (l >> 16) & 0xf,
+ (l >> 20) & 0xf, (l >> 24) & 0xf,
+ (l >> 28) & 0xf);
+ RUNTIME_CHECK(result < sizeof(buf));
+ break;
+ case AF_INET6:
+ ap = tcpaddr->type.in6.s6_addr;
+ result = snprintf(
+ buf, sizeof(buf),
+ "%x.%x.%x.%x.%x.%x.%x.%x."
+ "%x.%x.%x.%x.IP6.ARPA.",
+ ap[5] & 0x0f, (ap[5] >> 4) & 0x0f, ap[4] & 0x0f,
+ (ap[4] >> 4) & 0x0f, ap[3] & 0x0f, (ap[3] >> 4) & 0x0f,
+ ap[2] & 0x0f, (ap[2] >> 4) & 0x0f, ap[1] & 0x0f,
+ (ap[1] >> 4) & 0x0f, ap[0] & 0x0f, (ap[0] >> 4) & 0x0f);
+ RUNTIME_CHECK(result < sizeof(buf));
+ break;
+ default:
+ UNREACHABLE();
+ }
+ isc_buffer_init(&b, buf, strlen(buf));
+ isc_buffer_add(&b, strlen(buf));
+ result = dns_name_fromtext(stfself, &b, dns_rootname, 0, NULL);
+ RUNTIME_CHECK(result == ISC_R_SUCCESS);
+}
+
+bool
+dns_ssutable_checkrules(dns_ssutable_t *table, const dns_name_t *signer,
+ const dns_name_t *name, const isc_netaddr_t *addr,
+ bool tcp, dns_aclenv_t *env, dns_rdatatype_t type,
+ const dns_name_t *target, const dst_key_t *key,
+ const dns_ssurule_t **rulep) {
+ dns_fixedname_t fixed;
+ dns_name_t *stfself;
+ dns_name_t *tcpself;
+ dns_name_t *wildcard;
+ dns_ssurule_t *rule;
+ const dns_name_t *tname;
+ int match;
+ isc_result_t result;
+ unsigned int i;
+
+ REQUIRE(VALID_SSUTABLE(table));
+ REQUIRE(signer == NULL || dns_name_isabsolute(signer));
+ REQUIRE(dns_name_isabsolute(name));
+ REQUIRE(addr == NULL || env != NULL);
+
+ if (signer == NULL && addr == NULL) {
+ return (false);
+ }
+
+ for (rule = ISC_LIST_HEAD(table->rules); rule != NULL;
+ rule = ISC_LIST_NEXT(rule, link))
+ {
+ switch (rule->matchtype) {
+ case dns_ssumatchtype_local:
+ case dns_ssumatchtype_name:
+ case dns_ssumatchtype_self:
+ case dns_ssumatchtype_selfsub:
+ case dns_ssumatchtype_selfwild:
+ case dns_ssumatchtype_subdomain:
+ case dns_ssumatchtype_wildcard:
+ if (signer == NULL) {
+ continue;
+ }
+ if (dns_name_iswildcard(rule->identity)) {
+ if (!dns_name_matcheswildcard(signer,
+ rule->identity))
+ {
+ continue;
+ }
+ } else {
+ if (!dns_name_equal(signer, rule->identity)) {
+ continue;
+ }
+ }
+ break;
+ case dns_ssumatchtype_selfkrb5:
+ case dns_ssumatchtype_selfms:
+ case dns_ssumatchtype_selfsubkrb5:
+ case dns_ssumatchtype_selfsubms:
+ case dns_ssumatchtype_subdomainkrb5:
+ case dns_ssumatchtype_subdomainms:
+ case dns_ssumatchtype_subdomainselfkrb5rhs:
+ case dns_ssumatchtype_subdomainselfmsrhs:
+ if (signer == NULL) {
+ continue;
+ }
+ break;
+ case dns_ssumatchtype_tcpself:
+ case dns_ssumatchtype_6to4self:
+ if (!tcp || addr == NULL) {
+ continue;
+ }
+ break;
+ case dns_ssumatchtype_external:
+ case dns_ssumatchtype_dlz:
+ break;
+ }
+
+ switch (rule->matchtype) {
+ case dns_ssumatchtype_name:
+ if (!dns_name_equal(name, rule->name)) {
+ continue;
+ }
+ break;
+ case dns_ssumatchtype_subdomain:
+ if (!dns_name_issubdomain(name, rule->name)) {
+ continue;
+ }
+ break;
+ case dns_ssumatchtype_local:
+ if (addr == NULL) {
+ continue;
+ }
+ if (!dns_name_issubdomain(name, rule->name)) {
+ continue;
+ }
+ RWLOCK(&env->rwlock, isc_rwlocktype_read);
+ dns_acl_match(addr, NULL, env->localhost, NULL, &match,
+ NULL);
+ RWUNLOCK(&env->rwlock, isc_rwlocktype_read);
+ if (match == 0) {
+ if (signer != NULL) {
+ isc_log_write(dns_lctx,
+ DNS_LOGCATEGORY_GENERAL,
+ DNS_LOGMODULE_SSU,
+ ISC_LOG_WARNING,
+ "update-policy local: "
+ "match on session "
+ "key not from "
+ "localhost");
+ }
+ continue;
+ }
+ break;
+ case dns_ssumatchtype_wildcard:
+ if (!dns_name_matcheswildcard(name, rule->name)) {
+ continue;
+ }
+ break;
+ case dns_ssumatchtype_self:
+ if (!dns_name_equal(signer, name)) {
+ continue;
+ }
+ break;
+ case dns_ssumatchtype_selfsub:
+ if (!dns_name_issubdomain(name, signer)) {
+ continue;
+ }
+ break;
+ case dns_ssumatchtype_selfwild:
+ wildcard = dns_fixedname_initname(&fixed);
+ result = dns_name_concatenate(dns_wildcardname, signer,
+ wildcard, NULL);
+ if (result != ISC_R_SUCCESS) {
+ continue;
+ }
+ if (!dns_name_matcheswildcard(name, wildcard)) {
+ continue;
+ }
+ break;
+ case dns_ssumatchtype_selfkrb5:
+ if (dst_gssapi_identitymatchesrealmkrb5(
+ signer, name, rule->identity, false))
+ {
+ break;
+ }
+ continue;
+ case dns_ssumatchtype_selfms:
+ if (dst_gssapi_identitymatchesrealmms(
+ signer, name, rule->identity, false))
+ {
+ break;
+ }
+ continue;
+ case dns_ssumatchtype_selfsubkrb5:
+ if (dst_gssapi_identitymatchesrealmkrb5(
+ signer, name, rule->identity, true))
+ {
+ break;
+ }
+ continue;
+ case dns_ssumatchtype_selfsubms:
+ if (dst_gssapi_identitymatchesrealmms(
+ signer, name, rule->identity, true))
+ {
+ break;
+ }
+ continue;
+ case dns_ssumatchtype_subdomainkrb5:
+ case dns_ssumatchtype_subdomainselfkrb5rhs:
+ if (!dns_name_issubdomain(name, rule->name)) {
+ continue;
+ }
+ tname = NULL;
+ switch (rule->matchtype) {
+ case dns_ssumatchtype_subdomainselfkrb5rhs:
+ if (type == dns_rdatatype_ptr) {
+ tname = target;
+ }
+ if (type == dns_rdatatype_srv) {
+ tname = target;
+ }
+ break;
+ default:
+ break;
+ }
+ if (dst_gssapi_identitymatchesrealmkrb5(
+ signer, tname, rule->identity, false))
+ {
+ break;
+ }
+ continue;
+ case dns_ssumatchtype_subdomainms:
+ case dns_ssumatchtype_subdomainselfmsrhs:
+ if (!dns_name_issubdomain(name, rule->name)) {
+ continue;
+ }
+ tname = NULL;
+ switch (rule->matchtype) {
+ case dns_ssumatchtype_subdomainselfmsrhs:
+ if (type == dns_rdatatype_ptr) {
+ tname = target;
+ }
+ if (type == dns_rdatatype_srv) {
+ tname = target;
+ }
+ break;
+ default:
+ break;
+ }
+ if (dst_gssapi_identitymatchesrealmms(
+ signer, tname, rule->identity, false))
+ {
+ break;
+ }
+ continue;
+ case dns_ssumatchtype_tcpself:
+ tcpself = dns_fixedname_initname(&fixed);
+ reverse_from_address(tcpself, addr);
+ if (dns_name_iswildcard(rule->identity)) {
+ if (!dns_name_matcheswildcard(tcpself,
+ rule->identity))
+ {
+ continue;
+ }
+ } else {
+ if (!dns_name_equal(tcpself, rule->identity)) {
+ continue;
+ }
+ }
+ if (!dns_name_equal(tcpself, name)) {
+ continue;
+ }
+ break;
+ case dns_ssumatchtype_6to4self:
+ stfself = dns_fixedname_initname(&fixed);
+ stf_from_address(stfself, addr);
+ if (dns_name_iswildcard(rule->identity)) {
+ if (!dns_name_matcheswildcard(stfself,
+ rule->identity))
+ {
+ continue;
+ }
+ } else {
+ if (!dns_name_equal(stfself, rule->identity)) {
+ continue;
+ }
+ }
+ if (!dns_name_equal(stfself, name)) {
+ continue;
+ }
+ break;
+ case dns_ssumatchtype_external:
+ if (!dns_ssu_external_match(rule->identity, signer,
+ name, addr, type, key,
+ table->mctx))
+ {
+ continue;
+ }
+ break;
+ case dns_ssumatchtype_dlz:
+ if (!dns_dlz_ssumatch(table->dlzdatabase, signer, name,
+ addr, type, key))
+ {
+ continue;
+ }
+ break;
+ }
+
+ if (rule->ntypes == 0) {
+ /*
+ * If this is a DLZ rule, then the DLZ ssu
+ * checks will have already checked the type.
+ */
+ if (rule->matchtype != dns_ssumatchtype_dlz &&
+ !isusertype(type))
+ {
+ continue;
+ }
+ } else {
+ for (i = 0; i < rule->ntypes; i++) {
+ if (rule->types[i].type == dns_rdatatype_any ||
+ rule->types[i].type == type)
+ {
+ break;
+ }
+ }
+ if (i == rule->ntypes) {
+ continue;
+ }
+ }
+ if (rule->grant && rulep != NULL) {
+ *rulep = rule;
+ }
+ return (rule->grant);
+ }
+
+ return (false);
+}
+
+bool
+dns_ssurule_isgrant(const dns_ssurule_t *rule) {
+ REQUIRE(VALID_SSURULE(rule));
+ return (rule->grant);
+}
+
+dns_name_t *
+dns_ssurule_identity(const dns_ssurule_t *rule) {
+ REQUIRE(VALID_SSURULE(rule));
+ return (rule->identity);
+}
+
+unsigned int
+dns_ssurule_matchtype(const dns_ssurule_t *rule) {
+ REQUIRE(VALID_SSURULE(rule));
+ return (rule->matchtype);
+}
+
+dns_name_t *
+dns_ssurule_name(const dns_ssurule_t *rule) {
+ REQUIRE(VALID_SSURULE(rule));
+ return (rule->name);
+}
+
+unsigned int
+dns_ssurule_types(const dns_ssurule_t *rule, dns_ssuruletype_t **types) {
+ REQUIRE(VALID_SSURULE(rule));
+ REQUIRE(types != NULL && *types != NULL);
+ *types = rule->types;
+ return (rule->ntypes);
+}
+
+unsigned int
+dns_ssurule_max(const dns_ssurule_t *rule, dns_rdatatype_t type) {
+ unsigned int i;
+ unsigned int max = 0;
+
+ REQUIRE(VALID_SSURULE(rule));
+
+ for (i = 0; i < rule->ntypes; i++) {
+ if (rule->types[i].type == dns_rdatatype_any) {
+ max = rule->types[i].max;
+ }
+ if (rule->types[i].type == type) {
+ return (rule->types[i].max);
+ }
+ }
+ return (max);
+}
+
+isc_result_t
+dns_ssutable_firstrule(const dns_ssutable_t *table, dns_ssurule_t **rule) {
+ REQUIRE(VALID_SSUTABLE(table));
+ REQUIRE(rule != NULL && *rule == NULL);
+ *rule = ISC_LIST_HEAD(table->rules);
+ return (*rule != NULL ? ISC_R_SUCCESS : ISC_R_NOMORE);
+}
+
+isc_result_t
+dns_ssutable_nextrule(dns_ssurule_t *rule, dns_ssurule_t **nextrule) {
+ REQUIRE(VALID_SSURULE(rule));
+ REQUIRE(nextrule != NULL && *nextrule == NULL);
+ *nextrule = ISC_LIST_NEXT(rule, link);
+ return (*nextrule != NULL ? ISC_R_SUCCESS : ISC_R_NOMORE);
+}
+
+/*
+ * Create a specialised SSU table that points at an external DLZ database
+ */
+void
+dns_ssutable_createdlz(isc_mem_t *mctx, dns_ssutable_t **tablep,
+ dns_dlzdb_t *dlzdatabase) {
+ dns_ssurule_t *rule;
+ dns_ssutable_t *table = NULL;
+
+ REQUIRE(tablep != NULL && *tablep == NULL);
+
+ dns_ssutable_create(mctx, &table);
+
+ table->dlzdatabase = dlzdatabase;
+
+ rule = isc_mem_get(table->mctx, sizeof(dns_ssurule_t));
+
+ rule->identity = NULL;
+ rule->name = NULL;
+ rule->grant = true;
+ rule->matchtype = dns_ssumatchtype_dlz;
+ rule->ntypes = 0;
+ rule->types = NULL;
+ rule->magic = SSURULEMAGIC;
+
+ ISC_LIST_INITANDAPPEND(table->rules, rule, link);
+ *tablep = table;
+}
+
+isc_result_t
+dns_ssu_mtypefromstring(const char *str, dns_ssumatchtype_t *mtype) {
+ REQUIRE(str != NULL);
+ REQUIRE(mtype != NULL);
+
+ if (strcasecmp(str, "name") == 0) {
+ *mtype = dns_ssumatchtype_name;
+ } else if (strcasecmp(str, "subdomain") == 0) {
+ *mtype = dns_ssumatchtype_subdomain;
+ } else if (strcasecmp(str, "wildcard") == 0) {
+ *mtype = dns_ssumatchtype_wildcard;
+ } else if (strcasecmp(str, "self") == 0) {
+ *mtype = dns_ssumatchtype_self;
+ } else if (strcasecmp(str, "selfsub") == 0) {
+ *mtype = dns_ssumatchtype_selfsub;
+ } else if (strcasecmp(str, "selfwild") == 0) {
+ *mtype = dns_ssumatchtype_selfwild;
+ } else if (strcasecmp(str, "ms-self") == 0) {
+ *mtype = dns_ssumatchtype_selfms;
+ } else if (strcasecmp(str, "ms-selfsub") == 0) {
+ *mtype = dns_ssumatchtype_selfsubms;
+ } else if (strcasecmp(str, "krb5-self") == 0) {
+ *mtype = dns_ssumatchtype_selfkrb5;
+ } else if (strcasecmp(str, "krb5-selfsub") == 0) {
+ *mtype = dns_ssumatchtype_selfsubkrb5;
+ } else if (strcasecmp(str, "ms-subdomain") == 0) {
+ *mtype = dns_ssumatchtype_subdomainms;
+ } else if (strcasecmp(str, "ms-subdomain-self-rhs") == 0) {
+ *mtype = dns_ssumatchtype_subdomainselfmsrhs;
+ } else if (strcasecmp(str, "krb5-subdomain") == 0) {
+ *mtype = dns_ssumatchtype_subdomainkrb5;
+ } else if (strcasecmp(str, "krb5-subdomain-self-rhs") == 0) {
+ *mtype = dns_ssumatchtype_subdomainselfkrb5rhs;
+ } else if (strcasecmp(str, "tcp-self") == 0) {
+ *mtype = dns_ssumatchtype_tcpself;
+ } else if (strcasecmp(str, "6to4-self") == 0) {
+ *mtype = dns_ssumatchtype_6to4self;
+ } else if (strcasecmp(str, "zonesub") == 0) {
+ *mtype = dns_ssumatchtype_subdomain;
+ } else if (strcasecmp(str, "external") == 0) {
+ *mtype = dns_ssumatchtype_external;
+ } else {
+ return (ISC_R_NOTFOUND);
+ }
+ return (ISC_R_SUCCESS);
+}
diff --git a/lib/dns/ssu_external.c b/lib/dns/ssu_external.c
new file mode 100644
index 0000000..905bbb7
--- /dev/null
+++ b/lib/dns/ssu_external.c
@@ -0,0 +1,255 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/*
+ * This implements external update-policy rules. This allows permission
+ * to update a zone to be checked by consulting an external daemon (e.g.,
+ * kerberos).
+ */
+
+#include <errno.h>
+#include <inttypes.h>
+#include <stdbool.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <unistd.h>
+
+#include <isc/magic.h>
+#include <isc/mem.h>
+#include <isc/netaddr.h>
+#include <isc/print.h>
+#include <isc/result.h>
+#include <isc/strerr.h>
+#include <isc/string.h>
+#include <isc/util.h>
+
+#include <dns/fixedname.h>
+#include <dns/log.h>
+#include <dns/name.h>
+#include <dns/rdatatype.h>
+#include <dns/ssu.h>
+
+#include <dst/dst.h>
+
+static void
+ssu_e_log(int level, const char *fmt, ...) {
+ va_list ap;
+
+ va_start(ap, fmt);
+ isc_log_vwrite(dns_lctx, DNS_LOGCATEGORY_SECURITY, DNS_LOGMODULE_ZONE,
+ ISC_LOG_DEBUG(level), fmt, ap);
+ va_end(ap);
+}
+
+/*
+ * Connect to a UNIX domain socket.
+ */
+static int
+ux_socket_connect(const char *path) {
+ int fd = -1;
+ struct sockaddr_un addr;
+
+ REQUIRE(path != NULL);
+
+ if (strlen(path) > sizeof(addr.sun_path)) {
+ ssu_e_log(3,
+ "ssu_external: socket path '%s' "
+ "longer than system maximum %zu",
+ path, sizeof(addr.sun_path));
+ return (-1);
+ }
+
+ memset(&addr, 0, sizeof(addr));
+ addr.sun_family = AF_UNIX;
+ strlcpy(addr.sun_path, path, sizeof(addr.sun_path));
+
+ fd = socket(AF_UNIX, SOCK_STREAM, 0);
+ if (fd == -1) {
+ char strbuf[ISC_STRERRORSIZE];
+ strerror_r(errno, strbuf, sizeof(strbuf));
+ ssu_e_log(3, "ssu_external: unable to create socket - %s",
+ strbuf);
+ return (-1);
+ }
+
+ if (connect(fd, (struct sockaddr *)&addr, sizeof(addr)) == -1) {
+ char strbuf[ISC_STRERRORSIZE];
+ strerror_r(errno, strbuf, sizeof(strbuf));
+ ssu_e_log(3,
+ "ssu_external: unable to connect to "
+ "socket '%s' - %s",
+ path, strbuf);
+ close(fd);
+ return (-1);
+ }
+ return (fd);
+}
+
+/* Change this version if you update the format of the request */
+#define SSU_EXTERNAL_VERSION 1
+
+/*
+ * Perform an update-policy rule check against an external application
+ * over a socket.
+ *
+ * This currently only supports local: for unix domain datagram sockets.
+ *
+ * Note that by using a datagram socket and creating a new socket each
+ * time we avoid the need for locking and allow for parallel access to
+ * the authorization server.
+ */
+bool
+dns_ssu_external_match(const dns_name_t *identity, const dns_name_t *signer,
+ const dns_name_t *name, const isc_netaddr_t *tcpaddr,
+ dns_rdatatype_t type, const dst_key_t *key,
+ isc_mem_t *mctx) {
+ char b_identity[DNS_NAME_FORMATSIZE];
+ char b_signer[DNS_NAME_FORMATSIZE];
+ char b_name[DNS_NAME_FORMATSIZE];
+ char b_addr[ISC_NETADDR_FORMATSIZE];
+ char b_type[DNS_RDATATYPE_FORMATSIZE];
+ char b_key[DST_KEY_FORMATSIZE];
+ isc_buffer_t *tkey_token = NULL;
+ int fd;
+ const char *sock_path;
+ unsigned int req_len;
+ isc_region_t token_region = { NULL, 0 };
+ unsigned char *data;
+ isc_buffer_t buf;
+ uint32_t token_len = 0;
+ uint32_t reply;
+ ssize_t ret;
+
+ /* The identity contains local:/path/to/socket */
+ dns_name_format(identity, b_identity, sizeof(b_identity));
+
+ /* For now only local: is supported */
+ if (strncmp(b_identity, "local:", 6) != 0) {
+ ssu_e_log(3, "ssu_external: invalid socket path '%s'",
+ b_identity);
+ return (false);
+ }
+ sock_path = &b_identity[6];
+
+ fd = ux_socket_connect(sock_path);
+ if (fd == -1) {
+ return (false);
+ }
+
+ if (key != NULL) {
+ dst_key_format(key, b_key, sizeof(b_key));
+ tkey_token = dst_key_tkeytoken(key);
+ } else {
+ b_key[0] = 0;
+ }
+
+ if (tkey_token != NULL) {
+ isc_buffer_region(tkey_token, &token_region);
+ token_len = token_region.length;
+ }
+
+ /* Format the request elements */
+ if (signer != NULL) {
+ dns_name_format(signer, b_signer, sizeof(b_signer));
+ } else {
+ b_signer[0] = 0;
+ }
+
+ dns_name_format(name, b_name, sizeof(b_name));
+
+ if (tcpaddr != NULL) {
+ isc_netaddr_format(tcpaddr, b_addr, sizeof(b_addr));
+ } else {
+ b_addr[0] = 0;
+ }
+
+ dns_rdatatype_format(type, b_type, sizeof(b_type));
+
+ /* Work out how big the request will be */
+ req_len = sizeof(uint32_t) + /* Format version */
+ sizeof(uint32_t) + /* Length */
+ strlen(b_signer) + 1 + /* Signer */
+ strlen(b_name) + 1 + /* Name */
+ strlen(b_addr) + 1 + /* Address */
+ strlen(b_type) + 1 + /* Type */
+ strlen(b_key) + 1 + /* Key */
+ sizeof(uint32_t) + /* tkey_token length */
+ token_len; /* tkey_token */
+
+ /* format the buffer */
+ data = isc_mem_allocate(mctx, req_len);
+
+ isc_buffer_init(&buf, data, req_len);
+ isc_buffer_putuint32(&buf, SSU_EXTERNAL_VERSION);
+ isc_buffer_putuint32(&buf, req_len);
+
+ /* Strings must be null-terminated */
+ isc_buffer_putstr(&buf, b_signer);
+ isc_buffer_putuint8(&buf, 0);
+ isc_buffer_putstr(&buf, b_name);
+ isc_buffer_putuint8(&buf, 0);
+ isc_buffer_putstr(&buf, b_addr);
+ isc_buffer_putuint8(&buf, 0);
+ isc_buffer_putstr(&buf, b_type);
+ isc_buffer_putuint8(&buf, 0);
+ isc_buffer_putstr(&buf, b_key);
+ isc_buffer_putuint8(&buf, 0);
+
+ isc_buffer_putuint32(&buf, token_len);
+ if (tkey_token && token_len != 0) {
+ isc_buffer_putmem(&buf, token_region.base, token_len);
+ }
+
+ ENSURE(isc_buffer_availablelength(&buf) == 0);
+
+ /* Send the request */
+ ret = write(fd, data, req_len);
+ isc_mem_free(mctx, data);
+ if (ret != (ssize_t)req_len) {
+ char strbuf[ISC_STRERRORSIZE];
+ strerror_r(errno, strbuf, sizeof(strbuf));
+ ssu_e_log(3, "ssu_external: unable to send request - %s",
+ strbuf);
+ close(fd);
+ return (false);
+ }
+
+ /* Receive the reply */
+ ret = read(fd, &reply, sizeof(uint32_t));
+ if (ret != (ssize_t)sizeof(uint32_t)) {
+ char strbuf[ISC_STRERRORSIZE];
+ strerror_r(errno, strbuf, sizeof(strbuf));
+ ssu_e_log(3, "ssu_external: unable to receive reply - %s",
+ strbuf);
+ close(fd);
+ return (false);
+ }
+
+ close(fd);
+
+ reply = ntohl(reply);
+
+ if (reply == 0) {
+ ssu_e_log(3, "ssu_external: denied external auth for '%s'",
+ b_name);
+ return (false);
+ } else if (reply == 1) {
+ ssu_e_log(3, "ssu_external: allowed external auth for '%s'",
+ b_name);
+ return (true);
+ }
+
+ ssu_e_log(3, "ssu_external: invalid reply 0x%08x", reply);
+
+ return (false);
+}
diff --git a/lib/dns/stats.c b/lib/dns/stats.c
new file mode 100644
index 0000000..390a397
--- /dev/null
+++ b/lib/dns/stats.c
@@ -0,0 +1,653 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/*! \file */
+
+#include <inttypes.h>
+#include <stdbool.h>
+
+#include <isc/magic.h>
+#include <isc/mem.h>
+#include <isc/refcount.h>
+#include <isc/stats.h>
+#include <isc/util.h>
+
+#include <dns/log.h>
+#include <dns/opcode.h>
+#include <dns/rdatatype.h>
+#include <dns/stats.h>
+
+#define DNS_STATS_MAGIC ISC_MAGIC('D', 's', 't', 't')
+#define DNS_STATS_VALID(x) ISC_MAGIC_VALID(x, DNS_STATS_MAGIC)
+
+/*%
+ * Statistics types.
+ */
+typedef enum {
+ dns_statstype_general = 0,
+ dns_statstype_rdtype = 1,
+ dns_statstype_rdataset = 2,
+ dns_statstype_opcode = 3,
+ dns_statstype_rcode = 4,
+ dns_statstype_dnssec = 5
+} dns_statstype_t;
+
+/*%
+ * It doesn't make sense to have 2^16 counters for all possible types since
+ * most of them won't be used. We have counters for the first 256 types.
+ *
+ * A rdtypecounter is now 8 bits for RRtypes and 3 bits for flags:
+ *
+ * 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
+ * +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+ * | | | | | | S |NX| RRType |
+ * +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+ *
+ * If the 8 bits for RRtype are all zero, this is an Other RRtype.
+ */
+#define RDTYPECOUNTER_MAXTYPE 0x00ff
+
+/*
+ *
+ * Bit 7 is the NXRRSET (NX) flag and indicates whether this is a
+ * positive (0) or a negative (1) RRset.
+ */
+#define RDTYPECOUNTER_NXRRSET 0x0100
+
+/*
+ * Then bit 5 and 6 mostly tell you if this counter is for an active,
+ * stale, or ancient RRtype:
+ *
+ * S = 0 (0b00) means Active
+ * S = 1 (0b01) means Stale
+ * S = 2 (0b10) means Ancient
+ *
+ * Since a counter cannot be stale and ancient at the same time, we
+ * treat S = 0b11 as a special case to deal with NXDOMAIN counters.
+ */
+#define RDTYPECOUNTER_STALE (1 << 9)
+#define RDTYPECOUNTER_ANCIENT (1 << 10)
+#define RDTYPECOUNTER_NXDOMAIN ((1 << 9) | (1 << 10))
+
+/*
+ * S = 0b11 indicates an NXDOMAIN counter and in this case the RRtype
+ * field signals the expiry of this cached item:
+ *
+ * RRType = 0 (0b00) means Active
+ * RRType = 1 (0b01) means Stale
+ * RRType = 2 (0b02) means Ancient
+ *
+ */
+#define RDTYPECOUNTER_NXDOMAIN_STALE 1
+#define RDTYPECOUNTER_NXDOMAIN_ANCIENT 2
+
+/*
+ * The maximum value for rdtypecounter is for an ancient NXDOMAIN.
+ */
+#define RDTYPECOUNTER_MAXVAL 0x0602
+
+/*
+ * DNSSEC sign statistics.
+ *
+ * Per key we maintain 3 counters. The first is actually no counter but
+ * a key id reference. The second is the number of signatures the key created.
+ * The third is the number of signatures refreshed by the key.
+ */
+
+/* Maximum number of keys to keep track of for DNSSEC signing statistics. */
+static int dnssecsign_num_keys = 4;
+static int dnssecsign_block_size = 3;
+/* Key id mask */
+#define DNSSECSIGNSTATS_KEY_ID_MASK 0x0000FFFF
+
+struct dns_stats {
+ unsigned int magic;
+ dns_statstype_t type;
+ isc_mem_t *mctx;
+ isc_stats_t *counters;
+ isc_refcount_t references;
+};
+
+typedef struct rdatadumparg {
+ dns_rdatatypestats_dumper_t fn;
+ void *arg;
+} rdatadumparg_t;
+
+typedef struct opcodedumparg {
+ dns_opcodestats_dumper_t fn;
+ void *arg;
+} opcodedumparg_t;
+
+typedef struct rcodedumparg {
+ dns_rcodestats_dumper_t fn;
+ void *arg;
+} rcodedumparg_t;
+typedef struct dnssecsigndumparg {
+ dns_dnssecsignstats_dumper_t fn;
+ void *arg;
+} dnssecsigndumparg_t;
+
+void
+dns_stats_attach(dns_stats_t *stats, dns_stats_t **statsp) {
+ REQUIRE(DNS_STATS_VALID(stats));
+ REQUIRE(statsp != NULL && *statsp == NULL);
+
+ isc_refcount_increment(&stats->references);
+
+ *statsp = stats;
+}
+
+void
+dns_stats_detach(dns_stats_t **statsp) {
+ dns_stats_t *stats;
+
+ REQUIRE(statsp != NULL && DNS_STATS_VALID(*statsp));
+
+ stats = *statsp;
+ *statsp = NULL;
+
+ if (isc_refcount_decrement(&stats->references) == 1) {
+ isc_refcount_destroy(&stats->references);
+ isc_stats_detach(&stats->counters);
+ isc_mem_putanddetach(&stats->mctx, stats, sizeof(*stats));
+ }
+}
+
+/*%
+ * Create methods
+ */
+static isc_result_t
+create_stats(isc_mem_t *mctx, dns_statstype_t type, int ncounters,
+ dns_stats_t **statsp) {
+ dns_stats_t *stats;
+ isc_result_t result;
+
+ stats = isc_mem_get(mctx, sizeof(*stats));
+
+ stats->counters = NULL;
+ isc_refcount_init(&stats->references, 1);
+
+ result = isc_stats_create(mctx, &stats->counters, ncounters);
+ if (result != ISC_R_SUCCESS) {
+ goto clean_mutex;
+ }
+
+ stats->magic = DNS_STATS_MAGIC;
+ stats->type = type;
+ stats->mctx = NULL;
+ isc_mem_attach(mctx, &stats->mctx);
+ *statsp = stats;
+
+ return (ISC_R_SUCCESS);
+
+clean_mutex:
+ isc_mem_put(mctx, stats, sizeof(*stats));
+
+ return (result);
+}
+
+isc_result_t
+dns_generalstats_create(isc_mem_t *mctx, dns_stats_t **statsp, int ncounters) {
+ REQUIRE(statsp != NULL && *statsp == NULL);
+
+ return (create_stats(mctx, dns_statstype_general, ncounters, statsp));
+}
+
+isc_result_t
+dns_rdatatypestats_create(isc_mem_t *mctx, dns_stats_t **statsp) {
+ REQUIRE(statsp != NULL && *statsp == NULL);
+
+ /*
+ * Create rdtype statistics for the first 255 RRtypes,
+ * plus one additional for other RRtypes.
+ */
+ return (create_stats(mctx, dns_statstype_rdtype,
+ (RDTYPECOUNTER_MAXTYPE + 1), statsp));
+}
+
+isc_result_t
+dns_rdatasetstats_create(isc_mem_t *mctx, dns_stats_t **statsp) {
+ REQUIRE(statsp != NULL && *statsp == NULL);
+
+ return (create_stats(mctx, dns_statstype_rdataset,
+ (RDTYPECOUNTER_MAXVAL + 1), statsp));
+}
+
+isc_result_t
+dns_opcodestats_create(isc_mem_t *mctx, dns_stats_t **statsp) {
+ REQUIRE(statsp != NULL && *statsp == NULL);
+
+ return (create_stats(mctx, dns_statstype_opcode, 16, statsp));
+}
+
+isc_result_t
+dns_rcodestats_create(isc_mem_t *mctx, dns_stats_t **statsp) {
+ REQUIRE(statsp != NULL && *statsp == NULL);
+
+ return (create_stats(mctx, dns_statstype_rcode, dns_rcode_badcookie + 1,
+ statsp));
+}
+
+isc_result_t
+dns_dnssecsignstats_create(isc_mem_t *mctx, dns_stats_t **statsp) {
+ REQUIRE(statsp != NULL && *statsp == NULL);
+
+ /*
+ * Create two counters per key, one is the key id, the other two are
+ * the actual counters for creating and refreshing signatures.
+ */
+ return (create_stats(mctx, dns_statstype_dnssec,
+ dnssecsign_num_keys * dnssecsign_block_size,
+ statsp));
+}
+
+/*%
+ * Increment/Decrement methods
+ */
+void
+dns_generalstats_increment(dns_stats_t *stats, isc_statscounter_t counter) {
+ REQUIRE(DNS_STATS_VALID(stats) && stats->type == dns_statstype_general);
+
+ isc_stats_increment(stats->counters, counter);
+}
+
+static isc_statscounter_t
+rdatatype2counter(dns_rdatatype_t type) {
+ if (type > (dns_rdatatype_t)RDTYPECOUNTER_MAXTYPE) {
+ return (0);
+ }
+ return ((isc_statscounter_t)type);
+}
+
+void
+dns_rdatatypestats_increment(dns_stats_t *stats, dns_rdatatype_t type) {
+ isc_statscounter_t counter;
+
+ REQUIRE(DNS_STATS_VALID(stats) && stats->type == dns_statstype_rdtype);
+
+ counter = rdatatype2counter(type);
+ isc_stats_increment(stats->counters, counter);
+}
+
+static void
+update_rdatasetstats(dns_stats_t *stats, dns_rdatastatstype_t rrsettype,
+ bool increment) {
+ isc_statscounter_t counter;
+
+ if ((DNS_RDATASTATSTYPE_ATTR(rrsettype) &
+ DNS_RDATASTATSTYPE_ATTR_NXDOMAIN) != 0)
+ {
+ counter = RDTYPECOUNTER_NXDOMAIN;
+
+ /*
+ * This is an NXDOMAIN counter, save the expiry value
+ * (active, stale, or ancient) value in the RRtype part.
+ */
+ if ((DNS_RDATASTATSTYPE_ATTR(rrsettype) &
+ DNS_RDATASTATSTYPE_ATTR_ANCIENT) != 0)
+ {
+ counter |= RDTYPECOUNTER_NXDOMAIN_ANCIENT;
+ } else if ((DNS_RDATASTATSTYPE_ATTR(rrsettype) &
+ DNS_RDATASTATSTYPE_ATTR_STALE) != 0)
+ {
+ counter += RDTYPECOUNTER_NXDOMAIN_STALE;
+ }
+ } else {
+ counter = rdatatype2counter(DNS_RDATASTATSTYPE_BASE(rrsettype));
+
+ if ((DNS_RDATASTATSTYPE_ATTR(rrsettype) &
+ DNS_RDATASTATSTYPE_ATTR_NXRRSET) != 0)
+ {
+ counter |= RDTYPECOUNTER_NXRRSET;
+ }
+
+ if ((DNS_RDATASTATSTYPE_ATTR(rrsettype) &
+ DNS_RDATASTATSTYPE_ATTR_ANCIENT) != 0)
+ {
+ counter |= RDTYPECOUNTER_ANCIENT;
+ } else if ((DNS_RDATASTATSTYPE_ATTR(rrsettype) &
+ DNS_RDATASTATSTYPE_ATTR_STALE) != 0)
+ {
+ counter |= RDTYPECOUNTER_STALE;
+ }
+ }
+
+ if (increment) {
+ isc_stats_increment(stats->counters, counter);
+ } else {
+ isc_stats_decrement(stats->counters, counter);
+ }
+}
+
+void
+dns_rdatasetstats_increment(dns_stats_t *stats,
+ dns_rdatastatstype_t rrsettype) {
+ REQUIRE(DNS_STATS_VALID(stats) &&
+ stats->type == dns_statstype_rdataset);
+
+ update_rdatasetstats(stats, rrsettype, true);
+}
+
+void
+dns_rdatasetstats_decrement(dns_stats_t *stats,
+ dns_rdatastatstype_t rrsettype) {
+ REQUIRE(DNS_STATS_VALID(stats) &&
+ stats->type == dns_statstype_rdataset);
+
+ update_rdatasetstats(stats, rrsettype, false);
+}
+
+void
+dns_opcodestats_increment(dns_stats_t *stats, dns_opcode_t code) {
+ REQUIRE(DNS_STATS_VALID(stats) && stats->type == dns_statstype_opcode);
+
+ isc_stats_increment(stats->counters, (isc_statscounter_t)code);
+}
+
+void
+dns_rcodestats_increment(dns_stats_t *stats, dns_rcode_t code) {
+ REQUIRE(DNS_STATS_VALID(stats) && stats->type == dns_statstype_rcode);
+
+ if (code <= dns_rcode_badcookie) {
+ isc_stats_increment(stats->counters, (isc_statscounter_t)code);
+ }
+}
+
+void
+dns_dnssecsignstats_increment(dns_stats_t *stats, dns_keytag_t id, uint8_t alg,
+ dnssecsignstats_type_t operation) {
+ uint32_t kval;
+ int num_keys = isc_stats_ncounters(stats->counters) /
+ dnssecsign_block_size;
+
+ REQUIRE(DNS_STATS_VALID(stats) && stats->type == dns_statstype_dnssec);
+
+ /* Shift algorithm in front of key tag, which is 16 bits */
+ kval = (uint32_t)(alg << 16 | id);
+
+ /* Look up correct counter. */
+ for (int i = 0; i < num_keys; i++) {
+ int idx = i * dnssecsign_block_size;
+ uint32_t counter = isc_stats_get_counter(stats->counters, idx);
+ if (counter == kval) {
+ /* Match */
+ isc_stats_increment(stats->counters, (idx + operation));
+ return;
+ }
+ }
+
+ /* No match found. Store key in unused slot. */
+ for (int i = 0; i < num_keys; i++) {
+ int idx = i * dnssecsign_block_size;
+ uint32_t counter = isc_stats_get_counter(stats->counters, idx);
+ if (counter == 0) {
+ isc_stats_set(stats->counters, kval, idx);
+ isc_stats_increment(stats->counters, (idx + operation));
+ return;
+ }
+ }
+
+ /* No room, grow stats storage. */
+ isc_stats_resize(&stats->counters,
+ (num_keys * dnssecsign_block_size * 2));
+
+ /* Reset counters for new key (new index, nidx). */
+ int nidx = num_keys * dnssecsign_block_size;
+ isc_stats_set(stats->counters, kval, nidx);
+ isc_stats_set(stats->counters, 0, (nidx + dns_dnssecsignstats_sign));
+ isc_stats_set(stats->counters, 0, (nidx + dns_dnssecsignstats_refresh));
+
+ /* And increment the counter for the given operation. */
+ isc_stats_increment(stats->counters, (nidx + operation));
+}
+
+void
+dns_dnssecsignstats_clear(dns_stats_t *stats, dns_keytag_t id, uint8_t alg) {
+ uint32_t kval;
+ int num_keys = isc_stats_ncounters(stats->counters) /
+ dnssecsign_block_size;
+
+ REQUIRE(DNS_STATS_VALID(stats) && stats->type == dns_statstype_dnssec);
+
+ /* Shift algorithm in front of key tag, which is 16 bits */
+ kval = (uint32_t)(alg << 16 | id);
+
+ /* Look up correct counter. */
+ for (int i = 0; i < num_keys; i++) {
+ int idx = i * dnssecsign_block_size;
+ uint32_t counter = isc_stats_get_counter(stats->counters, idx);
+ if (counter == kval) {
+ /* Match */
+ isc_stats_set(stats->counters, 0, idx);
+ isc_stats_set(stats->counters, 0,
+ (idx + dns_dnssecsignstats_sign));
+ isc_stats_set(stats->counters, 0,
+ (idx + dns_dnssecsignstats_refresh));
+ return;
+ }
+ }
+}
+
+/*%
+ * Dump methods
+ */
+void
+dns_generalstats_dump(dns_stats_t *stats, dns_generalstats_dumper_t dump_fn,
+ void *arg, unsigned int options) {
+ REQUIRE(DNS_STATS_VALID(stats) && stats->type == dns_statstype_general);
+
+ isc_stats_dump(stats->counters, (isc_stats_dumper_t)dump_fn, arg,
+ options);
+}
+
+static void
+dump_rdentry(int rdcounter, uint64_t value, dns_rdatastatstype_t attributes,
+ dns_rdatatypestats_dumper_t dump_fn, void *arg) {
+ dns_rdatatype_t rdtype = dns_rdatatype_none; /* sentinel */
+ dns_rdatastatstype_t type;
+
+ if ((rdcounter & RDTYPECOUNTER_MAXTYPE) == 0) {
+ attributes |= DNS_RDATASTATSTYPE_ATTR_OTHERTYPE;
+ } else {
+ rdtype = (dns_rdatatype_t)(rdcounter & RDTYPECOUNTER_MAXTYPE);
+ }
+ type = DNS_RDATASTATSTYPE_VALUE((dns_rdatastatstype_t)rdtype,
+ attributes);
+ dump_fn(type, value, arg);
+}
+
+static void
+rdatatype_dumpcb(isc_statscounter_t counter, uint64_t value, void *arg) {
+ rdatadumparg_t *rdatadumparg = arg;
+
+ dump_rdentry(counter, value, 0, rdatadumparg->fn, rdatadumparg->arg);
+}
+
+void
+dns_rdatatypestats_dump(dns_stats_t *stats, dns_rdatatypestats_dumper_t dump_fn,
+ void *arg0, unsigned int options) {
+ rdatadumparg_t arg;
+ REQUIRE(DNS_STATS_VALID(stats) && stats->type == dns_statstype_rdtype);
+
+ arg.fn = dump_fn;
+ arg.arg = arg0;
+ isc_stats_dump(stats->counters, rdatatype_dumpcb, &arg, options);
+}
+
+static void
+rdataset_dumpcb(isc_statscounter_t counter, uint64_t value, void *arg) {
+ rdatadumparg_t *rdatadumparg = arg;
+ unsigned int attributes = 0;
+
+ if ((counter & RDTYPECOUNTER_NXDOMAIN) == RDTYPECOUNTER_NXDOMAIN) {
+ attributes |= DNS_RDATASTATSTYPE_ATTR_NXDOMAIN;
+
+ /*
+ * This is an NXDOMAIN counter, check the RRtype part for the
+ * expiry value (active, stale, or ancient).
+ */
+ if ((counter & RDTYPECOUNTER_MAXTYPE) ==
+ RDTYPECOUNTER_NXDOMAIN_STALE)
+ {
+ attributes |= DNS_RDATASTATSTYPE_ATTR_STALE;
+ } else if ((counter & RDTYPECOUNTER_MAXTYPE) ==
+ RDTYPECOUNTER_NXDOMAIN_ANCIENT)
+ {
+ attributes |= DNS_RDATASTATSTYPE_ATTR_ANCIENT;
+ }
+ } else {
+ if ((counter & RDTYPECOUNTER_MAXTYPE) == 0) {
+ attributes |= DNS_RDATASTATSTYPE_ATTR_OTHERTYPE;
+ }
+ if ((counter & RDTYPECOUNTER_NXRRSET) != 0) {
+ attributes |= DNS_RDATASTATSTYPE_ATTR_NXRRSET;
+ }
+
+ if ((counter & RDTYPECOUNTER_STALE) != 0) {
+ attributes |= DNS_RDATASTATSTYPE_ATTR_STALE;
+ } else if ((counter & RDTYPECOUNTER_ANCIENT) != 0) {
+ attributes |= DNS_RDATASTATSTYPE_ATTR_ANCIENT;
+ }
+ }
+
+ dump_rdentry(counter, value, attributes, rdatadumparg->fn,
+ rdatadumparg->arg);
+}
+
+void
+dns_rdatasetstats_dump(dns_stats_t *stats, dns_rdatatypestats_dumper_t dump_fn,
+ void *arg0, unsigned int options) {
+ rdatadumparg_t arg;
+
+ REQUIRE(DNS_STATS_VALID(stats) &&
+ stats->type == dns_statstype_rdataset);
+
+ arg.fn = dump_fn;
+ arg.arg = arg0;
+ isc_stats_dump(stats->counters, rdataset_dumpcb, &arg, options);
+}
+
+static void
+dnssec_dumpcb(isc_statscounter_t counter, uint64_t value, void *arg) {
+ dnssecsigndumparg_t *dnssecarg = arg;
+
+ dnssecarg->fn((dns_keytag_t)counter, value, dnssecarg->arg);
+}
+
+static void
+dnssec_statsdump(isc_stats_t *stats, dnssecsignstats_type_t operation,
+ isc_stats_dumper_t dump_fn, void *arg, unsigned int options) {
+ int i, num_keys;
+
+ num_keys = isc_stats_ncounters(stats) / dnssecsign_block_size;
+ for (i = 0; i < num_keys; i++) {
+ int idx = dnssecsign_block_size * i;
+ uint32_t kval, val;
+ dns_keytag_t id;
+
+ kval = isc_stats_get_counter(stats, idx);
+ if (kval == 0) {
+ continue;
+ }
+
+ val = isc_stats_get_counter(stats, (idx + operation));
+ if ((options & ISC_STATSDUMP_VERBOSE) == 0 && val == 0) {
+ continue;
+ }
+
+ id = (dns_keytag_t)kval & DNSSECSIGNSTATS_KEY_ID_MASK;
+
+ dump_fn((isc_statscounter_t)id, val, arg);
+ }
+}
+
+void
+dns_dnssecsignstats_dump(dns_stats_t *stats, dnssecsignstats_type_t operation,
+ dns_dnssecsignstats_dumper_t dump_fn, void *arg0,
+ unsigned int options) {
+ dnssecsigndumparg_t arg;
+
+ REQUIRE(DNS_STATS_VALID(stats) && stats->type == dns_statstype_dnssec);
+
+ arg.fn = dump_fn;
+ arg.arg = arg0;
+
+ dnssec_statsdump(stats->counters, operation, dnssec_dumpcb, &arg,
+ options);
+}
+
+static void
+opcode_dumpcb(isc_statscounter_t counter, uint64_t value, void *arg) {
+ opcodedumparg_t *opcodearg = arg;
+
+ opcodearg->fn((dns_opcode_t)counter, value, opcodearg->arg);
+}
+
+static void
+rcode_dumpcb(isc_statscounter_t counter, uint64_t value, void *arg) {
+ rcodedumparg_t *rcodearg = arg;
+
+ rcodearg->fn((dns_rcode_t)counter, value, rcodearg->arg);
+}
+
+void
+dns_opcodestats_dump(dns_stats_t *stats, dns_opcodestats_dumper_t dump_fn,
+ void *arg0, unsigned int options) {
+ opcodedumparg_t arg;
+
+ REQUIRE(DNS_STATS_VALID(stats) && stats->type == dns_statstype_opcode);
+
+ arg.fn = dump_fn;
+ arg.arg = arg0;
+ isc_stats_dump(stats->counters, opcode_dumpcb, &arg, options);
+}
+
+void
+dns_rcodestats_dump(dns_stats_t *stats, dns_rcodestats_dumper_t dump_fn,
+ void *arg0, unsigned int options) {
+ rcodedumparg_t arg;
+
+ REQUIRE(DNS_STATS_VALID(stats) && stats->type == dns_statstype_rcode);
+
+ arg.fn = dump_fn;
+ arg.arg = arg0;
+ isc_stats_dump(stats->counters, rcode_dumpcb, &arg, options);
+}
+
+/***
+ *** Obsolete variables and functions follow:
+ ***/
+const char *dns_statscounter_names[DNS_STATS_NCOUNTERS] = {
+ "success", "referral", "nxrrset", "nxdomain",
+ "recursion", "failure", "duplicate", "dropped"
+};
+
+isc_result_t
+dns_stats_alloccounters(isc_mem_t *mctx, uint64_t **ctrp) {
+ int i;
+ uint64_t *p = isc_mem_get(mctx, DNS_STATS_NCOUNTERS * sizeof(uint64_t));
+ if (p == NULL) {
+ return (ISC_R_NOMEMORY);
+ }
+ for (i = 0; i < DNS_STATS_NCOUNTERS; i++) {
+ p[i] = 0;
+ }
+ *ctrp = p;
+ return (ISC_R_SUCCESS);
+}
+
+void
+dns_stats_freecounters(isc_mem_t *mctx, uint64_t **ctrp) {
+ isc_mem_put(mctx, *ctrp, DNS_STATS_NCOUNTERS * sizeof(uint64_t));
+ *ctrp = NULL;
+}
diff --git a/lib/dns/time.c b/lib/dns/time.c
new file mode 100644
index 0000000..d110ace
--- /dev/null
+++ b/lib/dns/time.c
@@ -0,0 +1,216 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/*! \file */
+
+#include <ctype.h>
+#include <inttypes.h>
+#include <stdio.h>
+#include <time.h>
+
+#include <isc/print.h>
+#include <isc/region.h>
+#include <isc/result.h>
+#include <isc/serial.h>
+#include <isc/stdtime.h>
+#include <isc/string.h> /* Required for HP/UX (and others?) */
+#include <isc/util.h>
+
+#include <dns/time.h>
+
+static const int days[12] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
+
+isc_result_t
+dns_time64_totext(int64_t t, isc_buffer_t *target) {
+ struct tm tm;
+ char buf[sizeof("!!!!!!YYYY!!!!!!!!MM!!!!!!!!DD!!!!!!!!HH!!!!!!!!MM!!!!"
+ "!!!!SS")];
+ int secs;
+ unsigned int l;
+ isc_region_t region;
+
+/*
+ * Warning. Do NOT use arguments with side effects with these macros.
+ */
+#define is_leap(y) ((((y) % 4) == 0 && ((y) % 100) != 0) || ((y) % 400) == 0)
+#define year_secs(y) ((is_leap(y) ? 366 : 365) * 86400)
+#define month_secs(m, y) ((days[m] + ((m == 1 && is_leap(y)) ? 1 : 0)) * 86400)
+
+ tm.tm_year = 70;
+ while (t < 0) {
+ if (tm.tm_year == 0) {
+ return (ISC_R_RANGE);
+ }
+ tm.tm_year--;
+ secs = year_secs(tm.tm_year + 1900);
+ t += secs;
+ }
+ while ((secs = year_secs(tm.tm_year + 1900)) <= t) {
+ t -= secs;
+ tm.tm_year++;
+ if (tm.tm_year + 1900 > 9999) {
+ return (ISC_R_RANGE);
+ }
+ }
+ tm.tm_mon = 0;
+ while ((secs = month_secs(tm.tm_mon, tm.tm_year + 1900)) <= t) {
+ t -= secs;
+ tm.tm_mon++;
+ }
+ tm.tm_mday = 1;
+ while (86400 <= t) {
+ t -= 86400;
+ tm.tm_mday++;
+ }
+ tm.tm_hour = 0;
+ while (3600 <= t) {
+ t -= 3600;
+ tm.tm_hour++;
+ }
+ tm.tm_min = 0;
+ while (60 <= t) {
+ t -= 60;
+ tm.tm_min++;
+ }
+ tm.tm_sec = (int)t;
+ /* yyyy mm dd HH MM SS */
+ snprintf(buf, sizeof(buf), "%04d%02d%02d%02d%02d%02d",
+ tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday, tm.tm_hour,
+ tm.tm_min, tm.tm_sec);
+
+ isc_buffer_availableregion(target, &region);
+ l = strlen(buf);
+
+ if (l > region.length) {
+ return (ISC_R_NOSPACE);
+ }
+
+ memmove(region.base, buf, l);
+ isc_buffer_add(target, l);
+ return (ISC_R_SUCCESS);
+}
+
+int64_t
+dns_time64_from32(uint32_t value) {
+ isc_stdtime_t now;
+ int64_t start;
+ int64_t t;
+
+ /*
+ * Adjust the time to the closest epoch. This should be changed
+ * to use a 64-bit counterpart to isc_stdtime_get() if one ever
+ * is defined, but even the current code is good until the year
+ * 2106.
+ */
+ isc_stdtime_get(&now);
+ start = (int64_t)now;
+ if (isc_serial_gt(value, now)) {
+ t = start + (value - now);
+ } else {
+ t = start - (now - value);
+ }
+
+ return (t);
+}
+
+isc_result_t
+dns_time32_totext(uint32_t value, isc_buffer_t *target) {
+ return (dns_time64_totext(dns_time64_from32(value), target));
+}
+
+isc_result_t
+dns_time64_fromtext(const char *source, int64_t *target) {
+ int year, month, day, hour, minute, second;
+ int64_t value;
+ int secs;
+ int i;
+
+#define RANGE(min, max, value) \
+ do { \
+ if (value < (min) || value > (max)) \
+ return ((ISC_R_RANGE)); \
+ } while (0)
+
+ if (strlen(source) != 14U) {
+ return (DNS_R_SYNTAX);
+ }
+ /*
+ * Confirm the source only consists digits. sscanf() allows some
+ * minor exceptions.
+ */
+ for (i = 0; i < 14; i++) {
+ if (!isdigit((unsigned char)source[i])) {
+ return (DNS_R_SYNTAX);
+ }
+ }
+ if (sscanf(source, "%4d%2d%2d%2d%2d%2d", &year, &month, &day, &hour,
+ &minute, &second) != 6)
+ {
+ return (DNS_R_SYNTAX);
+ }
+
+ RANGE(0, 9999, year);
+ RANGE(1, 12, month);
+ RANGE(1, days[month - 1] + ((month == 2 && is_leap(year)) ? 1 : 0),
+ day);
+#ifdef __COVERITY__
+ /*
+ * Use a simplified range to silence Coverity warning (in
+ * arithmetic with day below).
+ */
+ RANGE(1, 31, day);
+#endif /* __COVERITY__ */
+
+ RANGE(0, 23, hour);
+ RANGE(0, 59, minute);
+ RANGE(0, 60, second); /* 60 == leap second. */
+
+ /*
+ * Calculate seconds from epoch.
+ * Note: this uses a idealized calendar.
+ */
+ value = second + (60 * minute) + (3600 * hour) + ((day - 1) * 86400);
+ for (i = 0; i < (month - 1); i++) {
+ value += days[i] * 86400;
+ }
+ if (is_leap(year) && month > 2) {
+ value += 86400;
+ }
+ if (year < 1970) {
+ for (i = 1969; i >= year; i--) {
+ secs = (is_leap(i) ? 366 : 365) * 86400;
+ value -= secs;
+ }
+ } else {
+ for (i = 1970; i < year; i++) {
+ secs = (is_leap(i) ? 366 : 365) * 86400;
+ value += secs;
+ }
+ }
+
+ *target = value;
+ return (ISC_R_SUCCESS);
+}
+
+isc_result_t
+dns_time32_fromtext(const char *source, uint32_t *target) {
+ int64_t value64;
+ isc_result_t result;
+ result = dns_time64_fromtext(source, &value64);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+ *target = (uint32_t)value64;
+
+ return (ISC_R_SUCCESS);
+}
diff --git a/lib/dns/tkey.c b/lib/dns/tkey.c
new file mode 100644
index 0000000..d60f65d
--- /dev/null
+++ b/lib/dns/tkey.c
@@ -0,0 +1,1605 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/*! \file */
+
+#include <inttypes.h>
+#include <stdbool.h>
+
+#if HAVE_GSSAPI_GSSAPI_H
+#include <gssapi/gssapi.h>
+#elif HAVE_GSSAPI_H
+#include <gssapi.h>
+#endif
+
+#include <isc/buffer.h>
+#include <isc/md.h>
+#include <isc/mem.h>
+#include <isc/nonce.h>
+#include <isc/print.h>
+#include <isc/random.h>
+#include <isc/result.h>
+#include <isc/string.h>
+#include <isc/util.h>
+
+#include <dns/dnssec.h>
+#include <dns/fixedname.h>
+#include <dns/keyvalues.h>
+#include <dns/log.h>
+#include <dns/message.h>
+#include <dns/name.h>
+#include <dns/rdata.h>
+#include <dns/rdatalist.h>
+#include <dns/rdataset.h>
+#include <dns/rdatastruct.h>
+#include <dns/result.h>
+#include <dns/tkey.h>
+#include <dns/tsig.h>
+
+#include <dst/dst.h>
+#include <dst/gssapi.h>
+
+#include "dst_internal.h"
+
+#define TEMP_BUFFER_SZ 8192
+#define TKEY_RANDOM_AMOUNT 16
+
+#define RETERR(x) \
+ do { \
+ result = (x); \
+ if (result != ISC_R_SUCCESS) \
+ goto failure; \
+ } while (0)
+
+static void
+tkey_log(const char *fmt, ...) ISC_FORMAT_PRINTF(1, 2);
+
+static void
+tkey_log(const char *fmt, ...) {
+ va_list ap;
+
+ va_start(ap, fmt);
+ isc_log_vwrite(dns_lctx, DNS_LOGCATEGORY_GENERAL, DNS_LOGMODULE_REQUEST,
+ ISC_LOG_DEBUG(4), fmt, ap);
+ va_end(ap);
+}
+
+static void
+dumpmessage(dns_message_t *msg) {
+ isc_buffer_t outbuf;
+ unsigned char *output;
+ int len = TEMP_BUFFER_SZ;
+ isc_result_t result;
+
+ for (;;) {
+ output = isc_mem_get(msg->mctx, len);
+
+ isc_buffer_init(&outbuf, output, len);
+ result = dns_message_totext(msg, &dns_master_style_debug, 0,
+ &outbuf);
+ if (result == ISC_R_NOSPACE) {
+ isc_mem_put(msg->mctx, output, len);
+ len *= 2;
+ continue;
+ }
+
+ if (result == ISC_R_SUCCESS) {
+ tkey_log("%.*s", (int)isc_buffer_usedlength(&outbuf),
+ (char *)isc_buffer_base(&outbuf));
+ } else {
+ tkey_log("Warning: dns_message_totext: %s",
+ isc_result_totext(result));
+ }
+ break;
+ }
+
+ if (output != NULL) {
+ isc_mem_put(msg->mctx, output, len);
+ }
+}
+
+isc_result_t
+dns_tkeyctx_create(isc_mem_t *mctx, dns_tkeyctx_t **tctxp) {
+ dns_tkeyctx_t *tctx;
+
+ REQUIRE(mctx != NULL);
+ REQUIRE(tctxp != NULL && *tctxp == NULL);
+
+ tctx = isc_mem_get(mctx, sizeof(dns_tkeyctx_t));
+ tctx->mctx = NULL;
+ isc_mem_attach(mctx, &tctx->mctx);
+ tctx->dhkey = NULL;
+ tctx->domain = NULL;
+ tctx->gsscred = NULL;
+ tctx->gssapi_keytab = NULL;
+
+ *tctxp = tctx;
+ return (ISC_R_SUCCESS);
+}
+
+void
+dns_tkeyctx_destroy(dns_tkeyctx_t **tctxp) {
+ isc_mem_t *mctx;
+ dns_tkeyctx_t *tctx;
+
+ REQUIRE(tctxp != NULL && *tctxp != NULL);
+
+ tctx = *tctxp;
+ *tctxp = NULL;
+ mctx = tctx->mctx;
+
+ if (tctx->dhkey != NULL) {
+ dst_key_free(&tctx->dhkey);
+ }
+ if (tctx->domain != NULL) {
+ if (dns_name_dynamic(tctx->domain)) {
+ dns_name_free(tctx->domain, mctx);
+ }
+ isc_mem_put(mctx, tctx->domain, sizeof(dns_name_t));
+ }
+ if (tctx->gssapi_keytab != NULL) {
+ isc_mem_free(mctx, tctx->gssapi_keytab);
+ }
+ if (tctx->gsscred != NULL) {
+ dst_gssapi_releasecred(&tctx->gsscred);
+ }
+ isc_mem_putanddetach(&mctx, tctx, sizeof(dns_tkeyctx_t));
+}
+
+static isc_result_t
+add_rdata_to_list(dns_message_t *msg, dns_name_t *name, dns_rdata_t *rdata,
+ uint32_t ttl, dns_namelist_t *namelist) {
+ isc_result_t result;
+ isc_region_t r, newr;
+ dns_rdata_t *newrdata = NULL;
+ dns_name_t *newname = NULL;
+ dns_rdatalist_t *newlist = NULL;
+ dns_rdataset_t *newset = NULL;
+ isc_buffer_t *tmprdatabuf = NULL;
+
+ RETERR(dns_message_gettemprdata(msg, &newrdata));
+
+ dns_rdata_toregion(rdata, &r);
+ isc_buffer_allocate(msg->mctx, &tmprdatabuf, r.length);
+ isc_buffer_availableregion(tmprdatabuf, &newr);
+ memmove(newr.base, r.base, r.length);
+ dns_rdata_fromregion(newrdata, rdata->rdclass, rdata->type, &newr);
+ dns_message_takebuffer(msg, &tmprdatabuf);
+
+ RETERR(dns_message_gettempname(msg, &newname));
+ dns_name_copy(name, newname);
+
+ RETERR(dns_message_gettemprdatalist(msg, &newlist));
+ newlist->rdclass = newrdata->rdclass;
+ newlist->type = newrdata->type;
+ newlist->ttl = ttl;
+ ISC_LIST_APPEND(newlist->rdata, newrdata, link);
+
+ RETERR(dns_message_gettemprdataset(msg, &newset));
+ RETERR(dns_rdatalist_tordataset(newlist, newset));
+
+ ISC_LIST_INIT(newname->list);
+ ISC_LIST_APPEND(newname->list, newset, link);
+
+ ISC_LIST_APPEND(*namelist, newname, link);
+
+ return (ISC_R_SUCCESS);
+
+failure:
+ if (newrdata != NULL) {
+ if (ISC_LINK_LINKED(newrdata, link)) {
+ INSIST(newlist != NULL);
+ ISC_LIST_UNLINK(newlist->rdata, newrdata, link);
+ }
+ dns_message_puttemprdata(msg, &newrdata);
+ }
+ if (newname != NULL) {
+ dns_message_puttempname(msg, &newname);
+ }
+ if (newset != NULL) {
+ dns_rdataset_disassociate(newset);
+ dns_message_puttemprdataset(msg, &newset);
+ }
+ if (newlist != NULL) {
+ dns_message_puttemprdatalist(msg, &newlist);
+ }
+ return (result);
+}
+
+static void
+free_namelist(dns_message_t *msg, dns_namelist_t *namelist) {
+ dns_name_t *name;
+ dns_rdataset_t *set;
+
+ while (!ISC_LIST_EMPTY(*namelist)) {
+ name = ISC_LIST_HEAD(*namelist);
+ ISC_LIST_UNLINK(*namelist, name, link);
+ while (!ISC_LIST_EMPTY(name->list)) {
+ set = ISC_LIST_HEAD(name->list);
+ ISC_LIST_UNLINK(name->list, set, link);
+ if (dns_rdataset_isassociated(set)) {
+ dns_rdataset_disassociate(set);
+ }
+ dns_message_puttemprdataset(msg, &set);
+ }
+ dns_message_puttempname(msg, &name);
+ }
+}
+
+static isc_result_t
+compute_secret(isc_buffer_t *shared, isc_region_t *queryrandomness,
+ isc_region_t *serverrandomness, isc_buffer_t *secret) {
+ isc_md_t *md;
+ isc_region_t r, r2;
+ unsigned char digests[ISC_MAX_MD_SIZE * 2];
+ unsigned char *digest1, *digest2;
+ unsigned int digestslen, digestlen1 = 0, digestlen2 = 0;
+ unsigned int i;
+ isc_result_t result;
+
+ isc_buffer_usedregion(shared, &r);
+
+ md = isc_md_new();
+ if (md == NULL) {
+ return (ISC_R_NOSPACE);
+ }
+
+ /*
+ * MD5 ( query data | DH value ).
+ */
+ digest1 = digests;
+
+ result = isc_md_init(md, ISC_MD_MD5);
+ if (result != ISC_R_SUCCESS) {
+ goto end;
+ }
+
+ result = isc_md_update(md, queryrandomness->base,
+ queryrandomness->length);
+ if (result != ISC_R_SUCCESS) {
+ goto end;
+ }
+
+ result = isc_md_update(md, r.base, r.length);
+ if (result != ISC_R_SUCCESS) {
+ goto end;
+ }
+
+ result = isc_md_final(md, digest1, &digestlen1);
+ if (result != ISC_R_SUCCESS) {
+ goto end;
+ }
+
+ result = isc_md_reset(md);
+ if (result != ISC_R_SUCCESS) {
+ goto end;
+ }
+
+ /*
+ * MD5 ( server data | DH value ).
+ */
+ digest2 = digests + digestlen1;
+
+ result = isc_md_init(md, ISC_MD_MD5);
+ if (result != ISC_R_SUCCESS) {
+ goto end;
+ }
+
+ result = isc_md_update(md, serverrandomness->base,
+ serverrandomness->length);
+ if (result != ISC_R_SUCCESS) {
+ goto end;
+ }
+
+ result = isc_md_update(md, r.base, r.length);
+ if (result != ISC_R_SUCCESS) {
+ goto end;
+ }
+
+ result = isc_md_final(md, digest2, &digestlen2);
+ if (result != ISC_R_SUCCESS) {
+ goto end;
+ }
+
+ isc_md_free(md);
+ md = NULL;
+
+ digestslen = digestlen1 + digestlen2;
+
+ /*
+ * XOR ( DH value, MD5-1 | MD5-2).
+ */
+ isc_buffer_availableregion(secret, &r);
+ isc_buffer_usedregion(shared, &r2);
+ if (r.length < digestslen || r.length < r2.length) {
+ return (ISC_R_NOSPACE);
+ }
+ if (r2.length > digestslen) {
+ memmove(r.base, r2.base, r2.length);
+ for (i = 0; i < digestslen; i++) {
+ r.base[i] ^= digests[i];
+ }
+ isc_buffer_add(secret, r2.length);
+ } else {
+ memmove(r.base, digests, digestslen);
+ for (i = 0; i < r2.length; i++) {
+ r.base[i] ^= r2.base[i];
+ }
+ isc_buffer_add(secret, digestslen);
+ }
+ result = ISC_R_SUCCESS;
+end:
+ if (md != NULL) {
+ isc_md_free(md);
+ }
+ return (result);
+}
+
+static isc_result_t
+process_dhtkey(dns_message_t *msg, dns_name_t *signer, dns_name_t *name,
+ dns_rdata_tkey_t *tkeyin, dns_tkeyctx_t *tctx,
+ dns_rdata_tkey_t *tkeyout, dns_tsig_keyring_t *ring,
+ dns_namelist_t *namelist) {
+ isc_result_t result = ISC_R_SUCCESS;
+ dns_name_t *keyname, ourname;
+ dns_rdataset_t *keyset = NULL;
+ dns_rdata_t keyrdata = DNS_RDATA_INIT, ourkeyrdata = DNS_RDATA_INIT;
+ bool found_key = false, found_incompatible = false;
+ dst_key_t *pubkey = NULL;
+ isc_buffer_t ourkeybuf, *shared = NULL;
+ isc_region_t r, r2, ourkeyr;
+ unsigned char keydata[DST_KEY_MAXSIZE];
+ unsigned int sharedsize;
+ isc_buffer_t secret;
+ unsigned char *randomdata = NULL, secretdata[256];
+ dns_ttl_t ttl = 0;
+
+ if (tctx->dhkey == NULL) {
+ tkey_log("process_dhtkey: tkey-dhkey not defined");
+ tkeyout->error = dns_tsigerror_badalg;
+ return (DNS_R_REFUSED);
+ }
+
+ if (!dns_name_equal(&tkeyin->algorithm, DNS_TSIG_HMACMD5_NAME)) {
+ tkey_log("process_dhtkey: algorithms other than "
+ "hmac-md5 are not supported");
+ tkeyout->error = dns_tsigerror_badalg;
+ return (ISC_R_SUCCESS);
+ }
+
+ /*
+ * Look for a DH KEY record that will work with ours.
+ */
+ for (result = dns_message_firstname(msg, DNS_SECTION_ADDITIONAL);
+ result == ISC_R_SUCCESS && !found_key;
+ result = dns_message_nextname(msg, DNS_SECTION_ADDITIONAL))
+ {
+ keyname = NULL;
+ dns_message_currentname(msg, DNS_SECTION_ADDITIONAL, &keyname);
+ keyset = NULL;
+ result = dns_message_findtype(keyname, dns_rdatatype_key, 0,
+ &keyset);
+ if (result != ISC_R_SUCCESS) {
+ continue;
+ }
+
+ for (result = dns_rdataset_first(keyset);
+ result == ISC_R_SUCCESS && !found_key;
+ result = dns_rdataset_next(keyset))
+ {
+ dns_rdataset_current(keyset, &keyrdata);
+ pubkey = NULL;
+ result = dns_dnssec_keyfromrdata(keyname, &keyrdata,
+ msg->mctx, &pubkey);
+ if (result != ISC_R_SUCCESS) {
+ dns_rdata_reset(&keyrdata);
+ continue;
+ }
+ if (dst_key_alg(pubkey) == DNS_KEYALG_DH) {
+ if (dst_key_paramcompare(pubkey, tctx->dhkey)) {
+ found_key = true;
+ ttl = keyset->ttl;
+ break;
+ } else {
+ found_incompatible = true;
+ }
+ }
+ dst_key_free(&pubkey);
+ dns_rdata_reset(&keyrdata);
+ }
+ }
+
+ if (!found_key) {
+ if (found_incompatible) {
+ tkey_log("process_dhtkey: found an incompatible key");
+ tkeyout->error = dns_tsigerror_badkey;
+ return (ISC_R_SUCCESS);
+ } else {
+ tkey_log("process_dhtkey: failed to find a key");
+ return (DNS_R_FORMERR);
+ }
+ }
+
+ RETERR(add_rdata_to_list(msg, keyname, &keyrdata, ttl, namelist));
+
+ isc_buffer_init(&ourkeybuf, keydata, sizeof(keydata));
+ RETERR(dst_key_todns(tctx->dhkey, &ourkeybuf));
+ isc_buffer_usedregion(&ourkeybuf, &ourkeyr);
+ dns_rdata_fromregion(&ourkeyrdata, dns_rdataclass_any,
+ dns_rdatatype_key, &ourkeyr);
+
+ dns_name_init(&ourname, NULL);
+ dns_name_clone(dst_key_name(tctx->dhkey), &ourname);
+
+ /*
+ * XXXBEW The TTL should be obtained from the database, if it exists.
+ */
+ RETERR(add_rdata_to_list(msg, &ourname, &ourkeyrdata, 0, namelist));
+
+ RETERR(dst_key_secretsize(tctx->dhkey, &sharedsize));
+ isc_buffer_allocate(msg->mctx, &shared, sharedsize);
+
+ result = dst_key_computesecret(pubkey, tctx->dhkey, shared);
+ if (result != ISC_R_SUCCESS) {
+ tkey_log("process_dhtkey: failed to compute shared secret: %s",
+ isc_result_totext(result));
+ goto failure;
+ }
+ dst_key_free(&pubkey);
+
+ isc_buffer_init(&secret, secretdata, sizeof(secretdata));
+
+ randomdata = isc_mem_get(tkeyout->mctx, TKEY_RANDOM_AMOUNT);
+
+ isc_nonce_buf(randomdata, TKEY_RANDOM_AMOUNT);
+
+ r.base = randomdata;
+ r.length = TKEY_RANDOM_AMOUNT;
+ r2.base = tkeyin->key;
+ r2.length = tkeyin->keylen;
+ RETERR(compute_secret(shared, &r2, &r, &secret));
+ isc_buffer_free(&shared);
+
+ RETERR(dns_tsigkey_create(
+ name, &tkeyin->algorithm, isc_buffer_base(&secret),
+ isc_buffer_usedlength(&secret), true, signer, tkeyin->inception,
+ tkeyin->expire, ring->mctx, ring, NULL));
+
+ /* This key is good for a long time */
+ tkeyout->inception = tkeyin->inception;
+ tkeyout->expire = tkeyin->expire;
+
+ tkeyout->key = randomdata;
+ tkeyout->keylen = TKEY_RANDOM_AMOUNT;
+
+ return (ISC_R_SUCCESS);
+
+failure:
+ if (!ISC_LIST_EMPTY(*namelist)) {
+ free_namelist(msg, namelist);
+ }
+ if (shared != NULL) {
+ isc_buffer_free(&shared);
+ }
+ if (pubkey != NULL) {
+ dst_key_free(&pubkey);
+ }
+ if (randomdata != NULL) {
+ isc_mem_put(tkeyout->mctx, randomdata, TKEY_RANDOM_AMOUNT);
+ }
+ return (result);
+}
+
+static isc_result_t
+process_gsstkey(dns_message_t *msg, dns_name_t *name, dns_rdata_tkey_t *tkeyin,
+ dns_tkeyctx_t *tctx, dns_rdata_tkey_t *tkeyout,
+ dns_tsig_keyring_t *ring) {
+ isc_result_t result = ISC_R_SUCCESS;
+ dst_key_t *dstkey = NULL;
+ dns_tsigkey_t *tsigkey = NULL;
+ dns_fixedname_t fixed;
+ dns_name_t *principal;
+ isc_stdtime_t now;
+ isc_region_t intoken;
+ isc_buffer_t *outtoken = NULL;
+ dns_gss_ctx_id_t gss_ctx = NULL;
+
+ /*
+ * You have to define either a gss credential (principal) to
+ * accept with tkey-gssapi-credential, or you have to
+ * configure a specific keytab (with tkey-gssapi-keytab) in
+ * order to use gsstkey.
+ */
+ if (tctx->gsscred == NULL && tctx->gssapi_keytab == NULL) {
+ tkey_log("process_gsstkey(): no tkey-gssapi-credential "
+ "or tkey-gssapi-keytab configured");
+ return (DNS_R_REFUSED);
+ }
+
+ if (!dns_name_equal(&tkeyin->algorithm, DNS_TSIG_GSSAPI_NAME) &&
+ !dns_name_equal(&tkeyin->algorithm, DNS_TSIG_GSSAPIMS_NAME))
+ {
+ tkeyout->error = dns_tsigerror_badalg;
+ tkey_log("process_gsstkey(): dns_tsigerror_badalg"); /* XXXSRA
+ */
+ return (ISC_R_SUCCESS);
+ }
+
+ /*
+ * XXXDCL need to check for key expiry per 4.1.1
+ * XXXDCL need a way to check fully established, perhaps w/key_flags
+ */
+
+ intoken.base = tkeyin->key;
+ intoken.length = tkeyin->keylen;
+
+ result = dns_tsigkey_find(&tsigkey, name, &tkeyin->algorithm, ring);
+ if (result == ISC_R_SUCCESS) {
+ gss_ctx = dst_key_getgssctx(tsigkey->key);
+ }
+
+ principal = dns_fixedname_initname(&fixed);
+
+ /*
+ * Note that tctx->gsscred may be NULL if tctx->gssapi_keytab is set
+ */
+ result = dst_gssapi_acceptctx(tctx->gsscred, tctx->gssapi_keytab,
+ &intoken, &outtoken, &gss_ctx, principal,
+ tctx->mctx);
+ if (result == DNS_R_INVALIDTKEY) {
+ if (tsigkey != NULL) {
+ dns_tsigkey_detach(&tsigkey);
+ }
+ tkeyout->error = dns_tsigerror_badkey;
+ tkey_log("process_gsstkey(): dns_tsigerror_badkey"); /* XXXSRA
+ */
+ return (ISC_R_SUCCESS);
+ }
+ if (result != DNS_R_CONTINUE && result != ISC_R_SUCCESS) {
+ goto failure;
+ }
+ /*
+ * XXXDCL Section 4.1.3: Limit GSS_S_CONTINUE_NEEDED to 10 times.
+ */
+
+ isc_stdtime_get(&now);
+
+ if (dns_name_countlabels(principal) == 0U) {
+ if (tsigkey != NULL) {
+ dns_tsigkey_detach(&tsigkey);
+ }
+ } else if (tsigkey == NULL) {
+#if HAVE_GSSAPI
+ OM_uint32 gret, minor, lifetime;
+#endif /* HAVE_GSSAPI */
+ uint32_t expire;
+
+ RETERR(dst_key_fromgssapi(name, gss_ctx, ring->mctx, &dstkey,
+ &intoken));
+ /*
+ * Limit keys to 1 hour or the context's lifetime whichever
+ * is smaller.
+ */
+ expire = now + 3600;
+#if HAVE_GSSAPI
+ gret = gss_context_time(&minor, gss_ctx, &lifetime);
+ if (gret == GSS_S_COMPLETE && now + lifetime < expire) {
+ expire = now + lifetime;
+ }
+#endif /* HAVE_GSSAPI */
+ RETERR(dns_tsigkey_createfromkey(
+ name, &tkeyin->algorithm, dstkey, true, principal, now,
+ expire, ring->mctx, ring, &tsigkey));
+ dst_key_free(&dstkey);
+ tkeyout->inception = now;
+ tkeyout->expire = expire;
+ } else {
+ tkeyout->inception = tsigkey->inception;
+ tkeyout->expire = tsigkey->expire;
+ }
+
+ if (outtoken) {
+ tkeyout->key = isc_mem_get(tkeyout->mctx,
+ isc_buffer_usedlength(outtoken));
+ tkeyout->keylen = isc_buffer_usedlength(outtoken);
+ memmove(tkeyout->key, isc_buffer_base(outtoken),
+ isc_buffer_usedlength(outtoken));
+ isc_buffer_free(&outtoken);
+ } else {
+ tkeyout->key = isc_mem_get(tkeyout->mctx, tkeyin->keylen);
+ tkeyout->keylen = tkeyin->keylen;
+ memmove(tkeyout->key, tkeyin->key, tkeyin->keylen);
+ }
+
+ tkeyout->error = dns_rcode_noerror;
+
+ tkey_log("process_gsstkey(): dns_tsigerror_noerror"); /* XXXSRA */
+
+ /*
+ * We found a TKEY to respond with. If the request is not TSIG signed,
+ * we need to make sure the response is signed (see RFC 3645, Section
+ * 2.2).
+ */
+ if (tsigkey != NULL) {
+ if (msg->tsigkey == NULL && msg->sig0key == NULL) {
+ dns_message_settsigkey(msg, tsigkey);
+ }
+ dns_tsigkey_detach(&tsigkey);
+ }
+
+ return (ISC_R_SUCCESS);
+
+failure:
+ if (tsigkey != NULL) {
+ dns_tsigkey_detach(&tsigkey);
+ }
+
+ if (dstkey != NULL) {
+ dst_key_free(&dstkey);
+ }
+
+ if (outtoken != NULL) {
+ isc_buffer_free(&outtoken);
+ }
+
+ tkey_log("process_gsstkey(): %s", isc_result_totext(result)); /* XXXSRA
+ */
+
+ return (result);
+}
+
+static isc_result_t
+process_deletetkey(dns_name_t *signer, dns_name_t *name,
+ dns_rdata_tkey_t *tkeyin, dns_rdata_tkey_t *tkeyout,
+ dns_tsig_keyring_t *ring) {
+ isc_result_t result;
+ dns_tsigkey_t *tsigkey = NULL;
+ const dns_name_t *identity;
+
+ result = dns_tsigkey_find(&tsigkey, name, &tkeyin->algorithm, ring);
+ if (result != ISC_R_SUCCESS) {
+ tkeyout->error = dns_tsigerror_badname;
+ return (ISC_R_SUCCESS);
+ }
+
+ /*
+ * Only allow a delete if the identity that created the key is the
+ * same as the identity that signed the message.
+ */
+ identity = dns_tsigkey_identity(tsigkey);
+ if (identity == NULL || !dns_name_equal(identity, signer)) {
+ dns_tsigkey_detach(&tsigkey);
+ return (DNS_R_REFUSED);
+ }
+
+ /*
+ * Set the key to be deleted when no references are left. If the key
+ * was not generated with TKEY and is in the config file, it may be
+ * reloaded later.
+ */
+ dns_tsigkey_setdeleted(tsigkey);
+
+ /* Release the reference */
+ dns_tsigkey_detach(&tsigkey);
+
+ return (ISC_R_SUCCESS);
+}
+
+isc_result_t
+dns_tkey_processquery(dns_message_t *msg, dns_tkeyctx_t *tctx,
+ dns_tsig_keyring_t *ring) {
+ isc_result_t result = ISC_R_SUCCESS;
+ dns_rdata_tkey_t tkeyin, tkeyout;
+ bool freetkeyin = false;
+ dns_name_t *qname, *name, *keyname, *signer, tsigner;
+ dns_fixedname_t fkeyname;
+ dns_rdataset_t *tkeyset;
+ dns_rdata_t rdata;
+ dns_namelist_t namelist;
+ char tkeyoutdata[512];
+ isc_buffer_t tkeyoutbuf;
+
+ REQUIRE(msg != NULL);
+ REQUIRE(tctx != NULL);
+ REQUIRE(ring != NULL);
+
+ ISC_LIST_INIT(namelist);
+
+ /*
+ * Interpret the question section.
+ */
+ result = dns_message_firstname(msg, DNS_SECTION_QUESTION);
+ if (result != ISC_R_SUCCESS) {
+ return (DNS_R_FORMERR);
+ }
+
+ qname = NULL;
+ dns_message_currentname(msg, DNS_SECTION_QUESTION, &qname);
+
+ /*
+ * Look for a TKEY record that matches the question.
+ */
+ tkeyset = NULL;
+ name = NULL;
+ result = dns_message_findname(msg, DNS_SECTION_ADDITIONAL, qname,
+ dns_rdatatype_tkey, 0, &name, &tkeyset);
+ if (result != ISC_R_SUCCESS) {
+ /*
+ * Try the answer section, since that's where Win2000
+ * puts it.
+ */
+ name = NULL;
+ if (dns_message_findname(msg, DNS_SECTION_ANSWER, qname,
+ dns_rdatatype_tkey, 0, &name,
+ &tkeyset) != ISC_R_SUCCESS)
+ {
+ result = DNS_R_FORMERR;
+ tkey_log("dns_tkey_processquery: couldn't find a TKEY "
+ "matching the question");
+ goto failure;
+ }
+ }
+ result = dns_rdataset_first(tkeyset);
+ if (result != ISC_R_SUCCESS) {
+ result = DNS_R_FORMERR;
+ goto failure;
+ }
+ dns_rdata_init(&rdata);
+ dns_rdataset_current(tkeyset, &rdata);
+
+ RETERR(dns_rdata_tostruct(&rdata, &tkeyin, NULL));
+ freetkeyin = true;
+
+ if (tkeyin.error != dns_rcode_noerror) {
+ result = DNS_R_FORMERR;
+ goto failure;
+ }
+
+ /*
+ * Before we go any farther, verify that the message was signed.
+ * GSSAPI TKEY doesn't require a signature, the rest do.
+ */
+ dns_name_init(&tsigner, NULL);
+ result = dns_message_signer(msg, &tsigner);
+ if (result != ISC_R_SUCCESS) {
+ if (tkeyin.mode == DNS_TKEYMODE_GSSAPI &&
+ result == ISC_R_NOTFOUND)
+ {
+ signer = NULL;
+ } else {
+ tkey_log("dns_tkey_processquery: query was not "
+ "properly signed - rejecting");
+ result = DNS_R_FORMERR;
+ goto failure;
+ }
+ } else {
+ signer = &tsigner;
+ }
+
+ tkeyout.common.rdclass = tkeyin.common.rdclass;
+ tkeyout.common.rdtype = tkeyin.common.rdtype;
+ ISC_LINK_INIT(&tkeyout.common, link);
+ tkeyout.mctx = msg->mctx;
+
+ dns_name_init(&tkeyout.algorithm, NULL);
+ dns_name_clone(&tkeyin.algorithm, &tkeyout.algorithm);
+
+ tkeyout.inception = tkeyout.expire = 0;
+ tkeyout.mode = tkeyin.mode;
+ tkeyout.error = 0;
+ tkeyout.keylen = tkeyout.otherlen = 0;
+ tkeyout.key = tkeyout.other = NULL;
+
+ /*
+ * A delete operation must have a fully specified key name. If this
+ * is not a delete, we do the following:
+ * if (qname != ".")
+ * keyname = qname + defaultdomain
+ * else
+ * keyname = <random hex> + defaultdomain
+ */
+ if (tkeyin.mode != DNS_TKEYMODE_DELETE) {
+ dns_tsigkey_t *tsigkey = NULL;
+
+ if (tctx->domain == NULL && tkeyin.mode != DNS_TKEYMODE_GSSAPI)
+ {
+ tkey_log("dns_tkey_processquery: tkey-domain not set");
+ result = DNS_R_REFUSED;
+ goto failure;
+ }
+
+ keyname = dns_fixedname_initname(&fkeyname);
+
+ if (!dns_name_equal(qname, dns_rootname)) {
+ unsigned int n = dns_name_countlabels(qname);
+ dns_name_copy(qname, keyname);
+ dns_name_getlabelsequence(keyname, 0, n - 1, keyname);
+ } else {
+ static char hexdigits[16] = { '0', '1', '2', '3',
+ '4', '5', '6', '7',
+ '8', '9', 'A', 'B',
+ 'C', 'D', 'E', 'F' };
+ unsigned char randomdata[16];
+ char randomtext[32];
+ isc_buffer_t b;
+ unsigned int i, j;
+
+ isc_nonce_buf(randomdata, sizeof(randomdata));
+
+ for (i = 0, j = 0; i < sizeof(randomdata); i++) {
+ unsigned char val = randomdata[i];
+ randomtext[j++] = hexdigits[val >> 4];
+ randomtext[j++] = hexdigits[val & 0xF];
+ }
+ isc_buffer_init(&b, randomtext, sizeof(randomtext));
+ isc_buffer_add(&b, sizeof(randomtext));
+ result = dns_name_fromtext(keyname, &b, NULL, 0, NULL);
+ if (result != ISC_R_SUCCESS) {
+ goto failure;
+ }
+ }
+
+ if (tkeyin.mode == DNS_TKEYMODE_GSSAPI) {
+ /* Yup. This is a hack */
+ result = dns_name_concatenate(keyname, dns_rootname,
+ keyname, NULL);
+ if (result != ISC_R_SUCCESS) {
+ goto failure;
+ }
+ } else {
+ result = dns_name_concatenate(keyname, tctx->domain,
+ keyname, NULL);
+ if (result != ISC_R_SUCCESS) {
+ goto failure;
+ }
+ }
+
+ result = dns_tsigkey_find(&tsigkey, keyname, NULL, ring);
+
+ if (result == ISC_R_SUCCESS) {
+ tkeyout.error = dns_tsigerror_badname;
+ dns_tsigkey_detach(&tsigkey);
+ goto failure_with_tkey;
+ } else if (result != ISC_R_NOTFOUND) {
+ goto failure;
+ }
+ } else {
+ keyname = qname;
+ }
+
+ switch (tkeyin.mode) {
+ case DNS_TKEYMODE_DIFFIEHELLMAN:
+ tkeyout.error = dns_rcode_noerror;
+ RETERR(process_dhtkey(msg, signer, keyname, &tkeyin, tctx,
+ &tkeyout, ring, &namelist));
+ break;
+ case DNS_TKEYMODE_GSSAPI:
+ tkeyout.error = dns_rcode_noerror;
+ RETERR(process_gsstkey(msg, keyname, &tkeyin, tctx, &tkeyout,
+ ring));
+ break;
+ case DNS_TKEYMODE_DELETE:
+ tkeyout.error = dns_rcode_noerror;
+ RETERR(process_deletetkey(signer, keyname, &tkeyin, &tkeyout,
+ ring));
+ break;
+ case DNS_TKEYMODE_SERVERASSIGNED:
+ case DNS_TKEYMODE_RESOLVERASSIGNED:
+ result = DNS_R_NOTIMP;
+ goto failure;
+ default:
+ tkeyout.error = dns_tsigerror_badmode;
+ }
+
+failure_with_tkey:
+
+ dns_rdata_init(&rdata);
+ isc_buffer_init(&tkeyoutbuf, tkeyoutdata, sizeof(tkeyoutdata));
+ result = dns_rdata_fromstruct(&rdata, tkeyout.common.rdclass,
+ tkeyout.common.rdtype, &tkeyout,
+ &tkeyoutbuf);
+
+ if (freetkeyin) {
+ dns_rdata_freestruct(&tkeyin);
+ freetkeyin = false;
+ }
+
+ if (tkeyout.key != NULL) {
+ isc_mem_put(tkeyout.mctx, tkeyout.key, tkeyout.keylen);
+ }
+ if (tkeyout.other != NULL) {
+ isc_mem_put(tkeyout.mctx, tkeyout.other, tkeyout.otherlen);
+ }
+ if (result != ISC_R_SUCCESS) {
+ goto failure;
+ }
+
+ RETERR(add_rdata_to_list(msg, keyname, &rdata, 0, &namelist));
+
+ RETERR(dns_message_reply(msg, true));
+
+ name = ISC_LIST_HEAD(namelist);
+ while (name != NULL) {
+ dns_name_t *next = ISC_LIST_NEXT(name, link);
+ ISC_LIST_UNLINK(namelist, name, link);
+ dns_message_addname(msg, name, DNS_SECTION_ANSWER);
+ name = next;
+ }
+
+ return (ISC_R_SUCCESS);
+
+failure:
+
+ if (freetkeyin) {
+ dns_rdata_freestruct(&tkeyin);
+ }
+ if (!ISC_LIST_EMPTY(namelist)) {
+ free_namelist(msg, &namelist);
+ }
+ return (result);
+}
+
+static isc_result_t
+buildquery(dns_message_t *msg, const dns_name_t *name, dns_rdata_tkey_t *tkey,
+ bool win2k) {
+ dns_name_t *qname = NULL, *aname = NULL;
+ dns_rdataset_t *question = NULL, *tkeyset = NULL;
+ dns_rdatalist_t *tkeylist = NULL;
+ dns_rdata_t *rdata = NULL;
+ isc_buffer_t *dynbuf = NULL;
+ isc_result_t result;
+ unsigned int len;
+
+ REQUIRE(msg != NULL);
+ REQUIRE(name != NULL);
+ REQUIRE(tkey != NULL);
+
+ RETERR(dns_message_gettempname(msg, &qname));
+ RETERR(dns_message_gettempname(msg, &aname));
+
+ RETERR(dns_message_gettemprdataset(msg, &question));
+ dns_rdataset_makequestion(question, dns_rdataclass_any,
+ dns_rdatatype_tkey);
+
+ len = 16 + tkey->algorithm.length + tkey->keylen + tkey->otherlen;
+ isc_buffer_allocate(msg->mctx, &dynbuf, len);
+ RETERR(dns_message_gettemprdata(msg, &rdata));
+
+ RETERR(dns_rdata_fromstruct(rdata, dns_rdataclass_any,
+ dns_rdatatype_tkey, tkey, dynbuf));
+ dns_message_takebuffer(msg, &dynbuf);
+
+ RETERR(dns_message_gettemprdatalist(msg, &tkeylist));
+ tkeylist->rdclass = dns_rdataclass_any;
+ tkeylist->type = dns_rdatatype_tkey;
+ ISC_LIST_APPEND(tkeylist->rdata, rdata, link);
+
+ RETERR(dns_message_gettemprdataset(msg, &tkeyset));
+ RETERR(dns_rdatalist_tordataset(tkeylist, tkeyset));
+
+ dns_name_copy(name, qname);
+ dns_name_copy(name, aname);
+
+ ISC_LIST_APPEND(qname->list, question, link);
+ ISC_LIST_APPEND(aname->list, tkeyset, link);
+
+ dns_message_addname(msg, qname, DNS_SECTION_QUESTION);
+
+ /*
+ * Windows 2000 needs this in the answer section, not the additional
+ * section where the RFC specifies.
+ */
+ if (win2k) {
+ dns_message_addname(msg, aname, DNS_SECTION_ANSWER);
+ } else {
+ dns_message_addname(msg, aname, DNS_SECTION_ADDITIONAL);
+ }
+
+ return (ISC_R_SUCCESS);
+
+failure:
+ if (qname != NULL) {
+ dns_message_puttempname(msg, &qname);
+ }
+ if (aname != NULL) {
+ dns_message_puttempname(msg, &aname);
+ }
+ if (question != NULL) {
+ dns_rdataset_disassociate(question);
+ dns_message_puttemprdataset(msg, &question);
+ }
+ if (dynbuf != NULL) {
+ isc_buffer_free(&dynbuf);
+ }
+ if (rdata != NULL) {
+ dns_message_puttemprdata(msg, &rdata);
+ }
+ if (tkeylist != NULL) {
+ dns_message_puttemprdatalist(msg, &tkeylist);
+ }
+ if (tkeyset != NULL) {
+ if (dns_rdataset_isassociated(tkeyset)) {
+ dns_rdataset_disassociate(tkeyset);
+ }
+ dns_message_puttemprdataset(msg, &tkeyset);
+ }
+ return (result);
+}
+
+isc_result_t
+dns_tkey_builddhquery(dns_message_t *msg, dst_key_t *key,
+ const dns_name_t *name, const dns_name_t *algorithm,
+ isc_buffer_t *nonce, uint32_t lifetime) {
+ dns_rdata_tkey_t tkey;
+ dns_rdata_t *rdata = NULL;
+ isc_buffer_t *dynbuf = NULL;
+ isc_region_t r;
+ dns_name_t keyname;
+ dns_namelist_t namelist;
+ isc_result_t result;
+ isc_stdtime_t now;
+ dns_name_t *item;
+
+ REQUIRE(msg != NULL);
+ REQUIRE(key != NULL);
+ REQUIRE(dst_key_alg(key) == DNS_KEYALG_DH);
+ REQUIRE(dst_key_isprivate(key));
+ REQUIRE(name != NULL);
+ REQUIRE(algorithm != NULL);
+
+ tkey.common.rdclass = dns_rdataclass_any;
+ tkey.common.rdtype = dns_rdatatype_tkey;
+ ISC_LINK_INIT(&tkey.common, link);
+ tkey.mctx = msg->mctx;
+ dns_name_init(&tkey.algorithm, NULL);
+ dns_name_clone(algorithm, &tkey.algorithm);
+ isc_stdtime_get(&now);
+ tkey.inception = now;
+ tkey.expire = now + lifetime;
+ tkey.mode = DNS_TKEYMODE_DIFFIEHELLMAN;
+ if (nonce != NULL) {
+ isc_buffer_usedregion(nonce, &r);
+ } else {
+ r.base = NULL;
+ r.length = 0;
+ }
+ tkey.error = 0;
+ tkey.key = r.base;
+ tkey.keylen = r.length;
+ tkey.other = NULL;
+ tkey.otherlen = 0;
+
+ RETERR(buildquery(msg, name, &tkey, false));
+
+ RETERR(dns_message_gettemprdata(msg, &rdata));
+ isc_buffer_allocate(msg->mctx, &dynbuf, 1024);
+ RETERR(dst_key_todns(key, dynbuf));
+ isc_buffer_usedregion(dynbuf, &r);
+ dns_rdata_fromregion(rdata, dns_rdataclass_any, dns_rdatatype_key, &r);
+ dns_message_takebuffer(msg, &dynbuf);
+
+ dns_name_init(&keyname, NULL);
+ dns_name_clone(dst_key_name(key), &keyname);
+
+ ISC_LIST_INIT(namelist);
+ RETERR(add_rdata_to_list(msg, &keyname, rdata, 0, &namelist));
+ item = ISC_LIST_HEAD(namelist);
+ while (item != NULL) {
+ dns_name_t *next = ISC_LIST_NEXT(item, link);
+ ISC_LIST_UNLINK(namelist, item, link);
+ dns_message_addname(msg, item, DNS_SECTION_ADDITIONAL);
+ item = next;
+ }
+
+ return (ISC_R_SUCCESS);
+
+failure:
+
+ if (dynbuf != NULL) {
+ isc_buffer_free(&dynbuf);
+ }
+ return (result);
+}
+
+isc_result_t
+dns_tkey_buildgssquery(dns_message_t *msg, const dns_name_t *name,
+ const dns_name_t *gname, isc_buffer_t *intoken,
+ uint32_t lifetime, dns_gss_ctx_id_t *context, bool win2k,
+ isc_mem_t *mctx, char **err_message) {
+ dns_rdata_tkey_t tkey;
+ isc_result_t result;
+ isc_stdtime_t now;
+ isc_buffer_t token;
+ unsigned char array[TEMP_BUFFER_SZ];
+
+ UNUSED(intoken);
+
+ REQUIRE(msg != NULL);
+ REQUIRE(name != NULL);
+ REQUIRE(gname != NULL);
+ REQUIRE(context != NULL);
+ REQUIRE(mctx != NULL);
+
+ isc_buffer_init(&token, array, sizeof(array));
+ result = dst_gssapi_initctx(gname, NULL, &token, context, mctx,
+ err_message);
+ if (result != DNS_R_CONTINUE && result != ISC_R_SUCCESS) {
+ return (result);
+ }
+
+ tkey.common.rdclass = dns_rdataclass_any;
+ tkey.common.rdtype = dns_rdatatype_tkey;
+ ISC_LINK_INIT(&tkey.common, link);
+ tkey.mctx = NULL;
+ dns_name_init(&tkey.algorithm, NULL);
+
+ if (win2k) {
+ dns_name_clone(DNS_TSIG_GSSAPIMS_NAME, &tkey.algorithm);
+ } else {
+ dns_name_clone(DNS_TSIG_GSSAPI_NAME, &tkey.algorithm);
+ }
+
+ isc_stdtime_get(&now);
+ tkey.inception = now;
+ tkey.expire = now + lifetime;
+ tkey.mode = DNS_TKEYMODE_GSSAPI;
+ tkey.error = 0;
+ tkey.key = isc_buffer_base(&token);
+ tkey.keylen = isc_buffer_usedlength(&token);
+ tkey.other = NULL;
+ tkey.otherlen = 0;
+
+ return (buildquery(msg, name, &tkey, win2k));
+}
+
+isc_result_t
+dns_tkey_builddeletequery(dns_message_t *msg, dns_tsigkey_t *key) {
+ dns_rdata_tkey_t tkey;
+
+ REQUIRE(msg != NULL);
+ REQUIRE(key != NULL);
+
+ tkey.common.rdclass = dns_rdataclass_any;
+ tkey.common.rdtype = dns_rdatatype_tkey;
+ ISC_LINK_INIT(&tkey.common, link);
+ tkey.mctx = msg->mctx;
+ dns_name_init(&tkey.algorithm, NULL);
+ dns_name_clone(key->algorithm, &tkey.algorithm);
+ tkey.inception = tkey.expire = 0;
+ tkey.mode = DNS_TKEYMODE_DELETE;
+ tkey.error = 0;
+ tkey.keylen = tkey.otherlen = 0;
+ tkey.key = tkey.other = NULL;
+
+ return (buildquery(msg, &key->name, &tkey, false));
+}
+
+static isc_result_t
+find_tkey(dns_message_t *msg, dns_name_t **name, dns_rdata_t *rdata,
+ int section) {
+ dns_rdataset_t *tkeyset;
+ isc_result_t result;
+
+ result = dns_message_firstname(msg, section);
+ while (result == ISC_R_SUCCESS) {
+ *name = NULL;
+ dns_message_currentname(msg, section, name);
+ tkeyset = NULL;
+ result = dns_message_findtype(*name, dns_rdatatype_tkey, 0,
+ &tkeyset);
+ if (result == ISC_R_SUCCESS) {
+ result = dns_rdataset_first(tkeyset);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+ dns_rdataset_current(tkeyset, rdata);
+ return (ISC_R_SUCCESS);
+ }
+ result = dns_message_nextname(msg, section);
+ }
+ if (result == ISC_R_NOMORE) {
+ return (ISC_R_NOTFOUND);
+ }
+ return (result);
+}
+
+isc_result_t
+dns_tkey_processdhresponse(dns_message_t *qmsg, dns_message_t *rmsg,
+ dst_key_t *key, isc_buffer_t *nonce,
+ dns_tsigkey_t **outkey, dns_tsig_keyring_t *ring) {
+ dns_rdata_t qtkeyrdata = DNS_RDATA_INIT, rtkeyrdata = DNS_RDATA_INIT;
+ dns_name_t keyname, *tkeyname, *theirkeyname, *ourkeyname, *tempname;
+ dns_rdataset_t *theirkeyset = NULL, *ourkeyset = NULL;
+ dns_rdata_t theirkeyrdata = DNS_RDATA_INIT;
+ dst_key_t *theirkey = NULL;
+ dns_rdata_tkey_t qtkey, rtkey;
+ unsigned char secretdata[256];
+ unsigned int sharedsize;
+ isc_buffer_t *shared = NULL, secret;
+ isc_region_t r, r2;
+ isc_result_t result;
+ bool freertkey = false;
+
+ REQUIRE(qmsg != NULL);
+ REQUIRE(rmsg != NULL);
+ REQUIRE(key != NULL);
+ REQUIRE(dst_key_alg(key) == DNS_KEYALG_DH);
+ REQUIRE(dst_key_isprivate(key));
+ if (outkey != NULL) {
+ REQUIRE(*outkey == NULL);
+ }
+
+ if (rmsg->rcode != dns_rcode_noerror) {
+ return (dns_result_fromrcode(rmsg->rcode));
+ }
+ RETERR(find_tkey(rmsg, &tkeyname, &rtkeyrdata, DNS_SECTION_ANSWER));
+ RETERR(dns_rdata_tostruct(&rtkeyrdata, &rtkey, NULL));
+ freertkey = true;
+
+ RETERR(find_tkey(qmsg, &tempname, &qtkeyrdata, DNS_SECTION_ADDITIONAL));
+ RETERR(dns_rdata_tostruct(&qtkeyrdata, &qtkey, NULL));
+
+ if (rtkey.error != dns_rcode_noerror ||
+ rtkey.mode != DNS_TKEYMODE_DIFFIEHELLMAN ||
+ rtkey.mode != qtkey.mode ||
+ !dns_name_equal(&rtkey.algorithm, &qtkey.algorithm) ||
+ rmsg->rcode != dns_rcode_noerror)
+ {
+ tkey_log("dns_tkey_processdhresponse: tkey mode invalid "
+ "or error set(1)");
+ result = DNS_R_INVALIDTKEY;
+ dns_rdata_freestruct(&qtkey);
+ goto failure;
+ }
+
+ dns_rdata_freestruct(&qtkey);
+
+ dns_name_init(&keyname, NULL);
+ dns_name_clone(dst_key_name(key), &keyname);
+
+ ourkeyname = NULL;
+ ourkeyset = NULL;
+ RETERR(dns_message_findname(rmsg, DNS_SECTION_ANSWER, &keyname,
+ dns_rdatatype_key, 0, &ourkeyname,
+ &ourkeyset));
+
+ result = dns_message_firstname(rmsg, DNS_SECTION_ANSWER);
+ while (result == ISC_R_SUCCESS) {
+ theirkeyname = NULL;
+ dns_message_currentname(rmsg, DNS_SECTION_ANSWER,
+ &theirkeyname);
+ if (dns_name_equal(theirkeyname, ourkeyname)) {
+ goto next;
+ }
+ theirkeyset = NULL;
+ result = dns_message_findtype(theirkeyname, dns_rdatatype_key,
+ 0, &theirkeyset);
+ if (result == ISC_R_SUCCESS) {
+ RETERR(dns_rdataset_first(theirkeyset));
+ break;
+ }
+ next:
+ result = dns_message_nextname(rmsg, DNS_SECTION_ANSWER);
+ }
+
+ if (theirkeyset == NULL) {
+ tkey_log("dns_tkey_processdhresponse: failed to find server "
+ "key");
+ result = ISC_R_NOTFOUND;
+ goto failure;
+ }
+
+ dns_rdataset_current(theirkeyset, &theirkeyrdata);
+ RETERR(dns_dnssec_keyfromrdata(theirkeyname, &theirkeyrdata, rmsg->mctx,
+ &theirkey));
+
+ RETERR(dst_key_secretsize(key, &sharedsize));
+ isc_buffer_allocate(rmsg->mctx, &shared, sharedsize);
+
+ RETERR(dst_key_computesecret(theirkey, key, shared));
+
+ isc_buffer_init(&secret, secretdata, sizeof(secretdata));
+
+ r.base = rtkey.key;
+ r.length = rtkey.keylen;
+ if (nonce != NULL) {
+ isc_buffer_usedregion(nonce, &r2);
+ } else {
+ r2.base = NULL;
+ r2.length = 0;
+ }
+ RETERR(compute_secret(shared, &r2, &r, &secret));
+
+ isc_buffer_usedregion(&secret, &r);
+ result = dns_tsigkey_create(tkeyname, &rtkey.algorithm, r.base,
+ r.length, true, NULL, rtkey.inception,
+ rtkey.expire, rmsg->mctx, ring, outkey);
+ isc_buffer_free(&shared);
+ dns_rdata_freestruct(&rtkey);
+ dst_key_free(&theirkey);
+ return (result);
+
+failure:
+ if (shared != NULL) {
+ isc_buffer_free(&shared);
+ }
+
+ if (theirkey != NULL) {
+ dst_key_free(&theirkey);
+ }
+
+ if (freertkey) {
+ dns_rdata_freestruct(&rtkey);
+ }
+
+ return (result);
+}
+
+isc_result_t
+dns_tkey_processgssresponse(dns_message_t *qmsg, dns_message_t *rmsg,
+ const dns_name_t *gname, dns_gss_ctx_id_t *context,
+ isc_buffer_t *outtoken, dns_tsigkey_t **outkey,
+ dns_tsig_keyring_t *ring, char **err_message) {
+ dns_rdata_t rtkeyrdata = DNS_RDATA_INIT, qtkeyrdata = DNS_RDATA_INIT;
+ dns_name_t *tkeyname;
+ dns_rdata_tkey_t rtkey, qtkey;
+ dst_key_t *dstkey = NULL;
+ isc_buffer_t intoken;
+ isc_result_t result;
+ unsigned char array[TEMP_BUFFER_SZ];
+
+ REQUIRE(outtoken != NULL);
+ REQUIRE(qmsg != NULL);
+ REQUIRE(rmsg != NULL);
+ REQUIRE(gname != NULL);
+ REQUIRE(ring != NULL);
+ if (outkey != NULL) {
+ REQUIRE(*outkey == NULL);
+ }
+
+ if (rmsg->rcode != dns_rcode_noerror) {
+ return (dns_result_fromrcode(rmsg->rcode));
+ }
+ RETERR(find_tkey(rmsg, &tkeyname, &rtkeyrdata, DNS_SECTION_ANSWER));
+ RETERR(dns_rdata_tostruct(&rtkeyrdata, &rtkey, NULL));
+
+ /*
+ * Win2k puts the item in the ANSWER section, while the RFC
+ * specifies it should be in the ADDITIONAL section. Check first
+ * where it should be, and then where it may be.
+ */
+ result = find_tkey(qmsg, &tkeyname, &qtkeyrdata,
+ DNS_SECTION_ADDITIONAL);
+ if (result == ISC_R_NOTFOUND) {
+ result = find_tkey(qmsg, &tkeyname, &qtkeyrdata,
+ DNS_SECTION_ANSWER);
+ }
+ if (result != ISC_R_SUCCESS) {
+ goto failure;
+ }
+
+ RETERR(dns_rdata_tostruct(&qtkeyrdata, &qtkey, NULL));
+
+ if (rtkey.error != dns_rcode_noerror ||
+ rtkey.mode != DNS_TKEYMODE_GSSAPI ||
+ !dns_name_equal(&rtkey.algorithm, &qtkey.algorithm))
+ {
+ tkey_log("dns_tkey_processgssresponse: tkey mode invalid "
+ "or error set(2) %d",
+ rtkey.error);
+ dumpmessage(qmsg);
+ dumpmessage(rmsg);
+ result = DNS_R_INVALIDTKEY;
+ goto failure;
+ }
+
+ isc_buffer_init(outtoken, array, sizeof(array));
+ isc_buffer_init(&intoken, rtkey.key, rtkey.keylen);
+ RETERR(dst_gssapi_initctx(gname, &intoken, outtoken, context,
+ ring->mctx, err_message));
+
+ RETERR(dst_key_fromgssapi(dns_rootname, *context, rmsg->mctx, &dstkey,
+ NULL));
+
+ RETERR(dns_tsigkey_createfromkey(
+ tkeyname, DNS_TSIG_GSSAPI_NAME, dstkey, false, NULL,
+ rtkey.inception, rtkey.expire, ring->mctx, ring, outkey));
+ dst_key_free(&dstkey);
+ dns_rdata_freestruct(&rtkey);
+ return (result);
+
+failure:
+ /*
+ * XXXSRA This probably leaks memory from rtkey and qtkey.
+ */
+ if (dstkey != NULL) {
+ dst_key_free(&dstkey);
+ }
+ return (result);
+}
+
+isc_result_t
+dns_tkey_processdeleteresponse(dns_message_t *qmsg, dns_message_t *rmsg,
+ dns_tsig_keyring_t *ring) {
+ dns_rdata_t qtkeyrdata = DNS_RDATA_INIT, rtkeyrdata = DNS_RDATA_INIT;
+ dns_name_t *tkeyname, *tempname;
+ dns_rdata_tkey_t qtkey, rtkey;
+ dns_tsigkey_t *tsigkey = NULL;
+ isc_result_t result;
+
+ REQUIRE(qmsg != NULL);
+ REQUIRE(rmsg != NULL);
+
+ if (rmsg->rcode != dns_rcode_noerror) {
+ return (dns_result_fromrcode(rmsg->rcode));
+ }
+
+ RETERR(find_tkey(rmsg, &tkeyname, &rtkeyrdata, DNS_SECTION_ANSWER));
+ RETERR(dns_rdata_tostruct(&rtkeyrdata, &rtkey, NULL));
+
+ RETERR(find_tkey(qmsg, &tempname, &qtkeyrdata, DNS_SECTION_ADDITIONAL));
+ RETERR(dns_rdata_tostruct(&qtkeyrdata, &qtkey, NULL));
+
+ if (rtkey.error != dns_rcode_noerror ||
+ rtkey.mode != DNS_TKEYMODE_DELETE || rtkey.mode != qtkey.mode ||
+ !dns_name_equal(&rtkey.algorithm, &qtkey.algorithm) ||
+ rmsg->rcode != dns_rcode_noerror)
+ {
+ tkey_log("dns_tkey_processdeleteresponse: tkey mode invalid "
+ "or error set(3)");
+ result = DNS_R_INVALIDTKEY;
+ dns_rdata_freestruct(&qtkey);
+ dns_rdata_freestruct(&rtkey);
+ goto failure;
+ }
+
+ dns_rdata_freestruct(&qtkey);
+
+ RETERR(dns_tsigkey_find(&tsigkey, tkeyname, &rtkey.algorithm, ring));
+
+ dns_rdata_freestruct(&rtkey);
+
+ /*
+ * Mark the key as deleted.
+ */
+ dns_tsigkey_setdeleted(tsigkey);
+ /*
+ * Release the reference.
+ */
+ dns_tsigkey_detach(&tsigkey);
+
+failure:
+ return (result);
+}
+
+isc_result_t
+dns_tkey_gssnegotiate(dns_message_t *qmsg, dns_message_t *rmsg,
+ const dns_name_t *server, dns_gss_ctx_id_t *context,
+ dns_tsigkey_t **outkey, dns_tsig_keyring_t *ring,
+ bool win2k, char **err_message) {
+ dns_rdata_t rtkeyrdata = DNS_RDATA_INIT, qtkeyrdata = DNS_RDATA_INIT;
+ dns_name_t *tkeyname;
+ dns_rdata_tkey_t rtkey, qtkey, tkey;
+ isc_buffer_t intoken, outtoken;
+ dst_key_t *dstkey = NULL;
+ isc_result_t result;
+ unsigned char array[TEMP_BUFFER_SZ];
+ bool freertkey = false;
+
+ REQUIRE(qmsg != NULL);
+ REQUIRE(rmsg != NULL);
+ REQUIRE(server != NULL);
+ if (outkey != NULL) {
+ REQUIRE(*outkey == NULL);
+ }
+
+ if (rmsg->rcode != dns_rcode_noerror) {
+ return (dns_result_fromrcode(rmsg->rcode));
+ }
+
+ RETERR(find_tkey(rmsg, &tkeyname, &rtkeyrdata, DNS_SECTION_ANSWER));
+ RETERR(dns_rdata_tostruct(&rtkeyrdata, &rtkey, NULL));
+ freertkey = true;
+
+ if (win2k) {
+ RETERR(find_tkey(qmsg, &tkeyname, &qtkeyrdata,
+ DNS_SECTION_ANSWER));
+ } else {
+ RETERR(find_tkey(qmsg, &tkeyname, &qtkeyrdata,
+ DNS_SECTION_ADDITIONAL));
+ }
+
+ RETERR(dns_rdata_tostruct(&qtkeyrdata, &qtkey, NULL));
+
+ if (rtkey.error != dns_rcode_noerror ||
+ rtkey.mode != DNS_TKEYMODE_GSSAPI ||
+ !dns_name_equal(&rtkey.algorithm, &qtkey.algorithm))
+ {
+ tkey_log("dns_tkey_processdhresponse: tkey mode invalid "
+ "or error set(4)");
+ result = DNS_R_INVALIDTKEY;
+ goto failure;
+ }
+
+ isc_buffer_init(&intoken, rtkey.key, rtkey.keylen);
+ isc_buffer_init(&outtoken, array, sizeof(array));
+
+ result = dst_gssapi_initctx(server, &intoken, &outtoken, context,
+ ring->mctx, err_message);
+ if (result != DNS_R_CONTINUE && result != ISC_R_SUCCESS) {
+ return (result);
+ }
+
+ if (result == DNS_R_CONTINUE) {
+ dns_fixedname_t fixed;
+
+ dns_fixedname_init(&fixed);
+ dns_name_copy(tkeyname, dns_fixedname_name(&fixed));
+ tkeyname = dns_fixedname_name(&fixed);
+
+ tkey.common.rdclass = dns_rdataclass_any;
+ tkey.common.rdtype = dns_rdatatype_tkey;
+ ISC_LINK_INIT(&tkey.common, link);
+ tkey.mctx = NULL;
+ dns_name_init(&tkey.algorithm, NULL);
+
+ if (win2k) {
+ dns_name_clone(DNS_TSIG_GSSAPIMS_NAME, &tkey.algorithm);
+ } else {
+ dns_name_clone(DNS_TSIG_GSSAPI_NAME, &tkey.algorithm);
+ }
+
+ tkey.inception = qtkey.inception;
+ tkey.expire = qtkey.expire;
+ tkey.mode = DNS_TKEYMODE_GSSAPI;
+ tkey.error = 0;
+ tkey.key = isc_buffer_base(&outtoken);
+ tkey.keylen = isc_buffer_usedlength(&outtoken);
+ tkey.other = NULL;
+ tkey.otherlen = 0;
+
+ dns_message_reset(qmsg, DNS_MESSAGE_INTENTRENDER);
+ RETERR(buildquery(qmsg, tkeyname, &tkey, win2k));
+ return (DNS_R_CONTINUE);
+ }
+
+ RETERR(dst_key_fromgssapi(dns_rootname, *context, rmsg->mctx, &dstkey,
+ NULL));
+
+ /*
+ * XXXSRA This seems confused. If we got CONTINUE from initctx,
+ * the GSS negotiation hasn't completed yet, so we can't sign
+ * anything yet.
+ */
+
+ RETERR(dns_tsigkey_createfromkey(
+ tkeyname,
+ (win2k ? DNS_TSIG_GSSAPIMS_NAME : DNS_TSIG_GSSAPI_NAME), dstkey,
+ true, NULL, rtkey.inception, rtkey.expire, ring->mctx, ring,
+ outkey));
+ dst_key_free(&dstkey);
+ dns_rdata_freestruct(&rtkey);
+ return (result);
+
+failure:
+ /*
+ * XXXSRA This probably leaks memory from qtkey.
+ */
+ if (freertkey) {
+ dns_rdata_freestruct(&rtkey);
+ }
+ if (dstkey != NULL) {
+ dst_key_free(&dstkey);
+ }
+ return (result);
+}
diff --git a/lib/dns/transport.c b/lib/dns/transport.c
new file mode 100644
index 0000000..ae1ab74
--- /dev/null
+++ b/lib/dns/transport.c
@@ -0,0 +1,474 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#include <inttypes.h>
+
+#include <isc/list.h>
+#include <isc/mem.h>
+#include <isc/refcount.h>
+#include <isc/result.h>
+#include <isc/rwlock.h>
+#include <isc/util.h>
+
+#include <dns/name.h>
+#include <dns/rbt.h>
+#include <dns/transport.h>
+
+#define TRANSPORT_MAGIC ISC_MAGIC('T', 'r', 'n', 's')
+#define VALID_TRANSPORT(ptr) ISC_MAGIC_VALID(ptr, TRANSPORT_MAGIC)
+
+#define TRANSPORT_LIST_MAGIC ISC_MAGIC('T', 'r', 'L', 's')
+#define VALID_TRANSPORT_LIST(ptr) ISC_MAGIC_VALID(ptr, TRANSPORT_LIST_MAGIC)
+
+struct dns_transport_list {
+ unsigned int magic;
+ isc_refcount_t references;
+ isc_mem_t *mctx;
+ isc_rwlock_t lock;
+ dns_rbt_t *transports[DNS_TRANSPORT_COUNT];
+};
+
+typedef enum ternary { ter_none = 0, ter_true = 1, ter_false = 2 } ternary_t;
+
+struct dns_transport {
+ unsigned int magic;
+ isc_refcount_t references;
+ isc_mem_t *mctx;
+ dns_transport_type_t type;
+ struct {
+ char *tlsname;
+ char *certfile;
+ char *keyfile;
+ char *cafile;
+ char *remote_hostname;
+ char *ciphers;
+ uint32_t protocol_versions;
+ ternary_t prefer_server_ciphers;
+ } tls;
+ struct {
+ char *endpoint;
+ dns_http_mode_t mode;
+ } doh;
+};
+
+static void
+free_dns_transport(void *node, void *arg) {
+ dns_transport_t *transport = node;
+
+ REQUIRE(node != NULL);
+
+ UNUSED(arg);
+
+ dns_transport_detach(&transport);
+}
+
+static isc_result_t
+list_add(dns_transport_list_t *list, const dns_name_t *name,
+ const dns_transport_type_t type, dns_transport_t *transport) {
+ isc_result_t result;
+ dns_rbt_t *rbt = NULL;
+
+ RWLOCK(&list->lock, isc_rwlocktype_write);
+ rbt = list->transports[type];
+ INSIST(rbt != NULL);
+
+ result = dns_rbt_addname(rbt, name, transport);
+
+ RWUNLOCK(&list->lock, isc_rwlocktype_write);
+
+ return (result);
+}
+
+dns_transport_type_t
+dns_transport_get_type(dns_transport_t *transport) {
+ REQUIRE(VALID_TRANSPORT(transport));
+
+ return (transport->type);
+}
+
+char *
+dns_transport_get_certfile(dns_transport_t *transport) {
+ REQUIRE(VALID_TRANSPORT(transport));
+
+ return (transport->tls.certfile);
+}
+
+char *
+dns_transport_get_keyfile(dns_transport_t *transport) {
+ REQUIRE(VALID_TRANSPORT(transport));
+
+ return (transport->tls.keyfile);
+}
+
+char *
+dns_transport_get_cafile(dns_transport_t *transport) {
+ REQUIRE(VALID_TRANSPORT(transport));
+
+ return (transport->tls.cafile);
+}
+
+char *
+dns_transport_get_remote_hostname(dns_transport_t *transport) {
+ REQUIRE(VALID_TRANSPORT(transport));
+
+ return (transport->tls.remote_hostname);
+}
+
+char *
+dns_transport_get_endpoint(dns_transport_t *transport) {
+ REQUIRE(VALID_TRANSPORT(transport));
+
+ return (transport->doh.endpoint);
+}
+
+dns_http_mode_t
+dns_transport_get_mode(dns_transport_t *transport) {
+ REQUIRE(VALID_TRANSPORT(transport));
+
+ return (transport->doh.mode);
+}
+
+dns_transport_t *
+dns_transport_new(const dns_name_t *name, dns_transport_type_t type,
+ dns_transport_list_t *list) {
+ dns_transport_t *transport = isc_mem_get(list->mctx,
+ sizeof(*transport));
+ *transport = (dns_transport_t){ .type = type };
+ isc_refcount_init(&transport->references, 1);
+ isc_mem_attach(list->mctx, &transport->mctx);
+ transport->magic = TRANSPORT_MAGIC;
+
+ list_add(list, name, type, transport);
+
+ return (transport);
+}
+
+void
+dns_transport_set_certfile(dns_transport_t *transport, const char *certfile) {
+ REQUIRE(VALID_TRANSPORT(transport));
+ REQUIRE(transport->type == DNS_TRANSPORT_TLS ||
+ transport->type == DNS_TRANSPORT_HTTP);
+
+ if (transport->tls.certfile != NULL) {
+ isc_mem_free(transport->mctx, transport->tls.certfile);
+ }
+
+ if (certfile != NULL) {
+ transport->tls.certfile = isc_mem_strdup(transport->mctx,
+ certfile);
+ }
+}
+
+void
+dns_transport_set_keyfile(dns_transport_t *transport, const char *keyfile) {
+ REQUIRE(VALID_TRANSPORT(transport));
+ REQUIRE(transport->type == DNS_TRANSPORT_TLS ||
+ transport->type == DNS_TRANSPORT_HTTP);
+
+ if (transport->tls.keyfile != NULL) {
+ isc_mem_free(transport->mctx, transport->tls.keyfile);
+ }
+
+ if (keyfile != NULL) {
+ transport->tls.keyfile = isc_mem_strdup(transport->mctx,
+ keyfile);
+ }
+}
+
+void
+dns_transport_set_cafile(dns_transport_t *transport, const char *cafile) {
+ REQUIRE(VALID_TRANSPORT(transport));
+ REQUIRE(transport->type == DNS_TRANSPORT_TLS ||
+ transport->type == DNS_TRANSPORT_HTTP);
+
+ if (transport->tls.cafile != NULL) {
+ isc_mem_free(transport->mctx, transport->tls.cafile);
+ }
+
+ if (cafile != NULL) {
+ transport->tls.cafile = isc_mem_strdup(transport->mctx, cafile);
+ }
+}
+
+void
+dns_transport_set_remote_hostname(dns_transport_t *transport,
+ const char *hostname) {
+ REQUIRE(VALID_TRANSPORT(transport));
+ REQUIRE(transport->type == DNS_TRANSPORT_TLS ||
+ transport->type == DNS_TRANSPORT_HTTP);
+
+ if (transport->tls.remote_hostname != NULL) {
+ isc_mem_free(transport->mctx, transport->tls.remote_hostname);
+ }
+
+ if (hostname != NULL) {
+ transport->tls.remote_hostname = isc_mem_strdup(transport->mctx,
+ hostname);
+ }
+}
+
+void
+dns_transport_set_endpoint(dns_transport_t *transport, const char *endpoint) {
+ REQUIRE(VALID_TRANSPORT(transport));
+ REQUIRE(transport->type == DNS_TRANSPORT_HTTP);
+
+ if (transport->doh.endpoint != NULL) {
+ isc_mem_free(transport->mctx, transport->doh.endpoint);
+ }
+
+ if (endpoint != NULL) {
+ transport->doh.endpoint = isc_mem_strdup(transport->mctx,
+ endpoint);
+ }
+}
+
+void
+dns_transport_set_mode(dns_transport_t *transport, dns_http_mode_t mode) {
+ REQUIRE(VALID_TRANSPORT(transport));
+ REQUIRE(transport->type == DNS_TRANSPORT_HTTP);
+
+ transport->doh.mode = mode;
+}
+
+void
+dns_transport_set_tls_versions(dns_transport_t *transport,
+ const uint32_t tls_versions) {
+ REQUIRE(VALID_TRANSPORT(transport));
+ REQUIRE(transport->type == DNS_TRANSPORT_HTTP ||
+ transport->type == DNS_TRANSPORT_TLS);
+
+ transport->tls.protocol_versions = tls_versions;
+}
+
+uint32_t
+dns_transport_get_tls_versions(const dns_transport_t *transport) {
+ REQUIRE(VALID_TRANSPORT(transport));
+
+ return (transport->tls.protocol_versions);
+}
+
+void
+dns_transport_set_ciphers(dns_transport_t *transport, const char *ciphers) {
+ REQUIRE(VALID_TRANSPORT(transport));
+ REQUIRE(transport->type == DNS_TRANSPORT_TLS ||
+ transport->type == DNS_TRANSPORT_HTTP);
+
+ if (transport->tls.ciphers != NULL) {
+ isc_mem_free(transport->mctx, transport->tls.ciphers);
+ }
+
+ if (ciphers != NULL) {
+ transport->tls.ciphers = isc_mem_strdup(transport->mctx,
+ ciphers);
+ }
+}
+
+void
+dns_transport_set_tlsname(dns_transport_t *transport, const char *tlsname) {
+ REQUIRE(VALID_TRANSPORT(transport));
+ REQUIRE(transport->type == DNS_TRANSPORT_TLS ||
+ transport->type == DNS_TRANSPORT_HTTP);
+
+ if (transport->tls.tlsname != NULL) {
+ isc_mem_free(transport->mctx, transport->tls.tlsname);
+ }
+
+ if (tlsname != NULL) {
+ transport->tls.tlsname = isc_mem_strdup(transport->mctx,
+ tlsname);
+ }
+}
+
+char *
+dns_transport_get_ciphers(dns_transport_t *transport) {
+ REQUIRE(VALID_TRANSPORT(transport));
+
+ return (transport->tls.ciphers);
+}
+
+char *
+dns_transport_get_tlsname(dns_transport_t *transport) {
+ REQUIRE(VALID_TRANSPORT(transport));
+
+ return (transport->tls.tlsname);
+}
+
+void
+dns_transport_set_prefer_server_ciphers(dns_transport_t *transport,
+ const bool prefer) {
+ REQUIRE(VALID_TRANSPORT(transport));
+ REQUIRE(transport->type == DNS_TRANSPORT_TLS ||
+ transport->type == DNS_TRANSPORT_HTTP);
+
+ transport->tls.prefer_server_ciphers = prefer ? ter_true : ter_false;
+}
+
+bool
+dns_transport_get_prefer_server_ciphers(const dns_transport_t *transport,
+ bool *preferp) {
+ REQUIRE(VALID_TRANSPORT(transport));
+ REQUIRE(preferp != NULL);
+ if (transport->tls.prefer_server_ciphers == ter_none) {
+ return (false);
+ } else if (transport->tls.prefer_server_ciphers == ter_true) {
+ *preferp = true;
+ return (true);
+ } else if (transport->tls.prefer_server_ciphers == ter_false) {
+ *preferp = false;
+ return (true);
+ }
+
+ UNREACHABLE();
+ return false;
+}
+
+static void
+transport_destroy(dns_transport_t *transport) {
+ isc_refcount_destroy(&transport->references);
+ transport->magic = 0;
+
+ if (transport->doh.endpoint != NULL) {
+ isc_mem_free(transport->mctx, transport->doh.endpoint);
+ }
+ if (transport->tls.remote_hostname != NULL) {
+ isc_mem_free(transport->mctx, transport->tls.remote_hostname);
+ }
+ if (transport->tls.cafile != NULL) {
+ isc_mem_free(transport->mctx, transport->tls.cafile);
+ }
+ if (transport->tls.keyfile != NULL) {
+ isc_mem_free(transport->mctx, transport->tls.keyfile);
+ }
+ if (transport->tls.certfile != NULL) {
+ isc_mem_free(transport->mctx, transport->tls.certfile);
+ }
+ if (transport->tls.ciphers != NULL) {
+ isc_mem_free(transport->mctx, transport->tls.ciphers);
+ }
+
+ if (transport->tls.tlsname != NULL) {
+ isc_mem_free(transport->mctx, transport->tls.tlsname);
+ }
+
+ isc_mem_putanddetach(&transport->mctx, transport, sizeof(*transport));
+}
+
+void
+dns_transport_attach(dns_transport_t *source, dns_transport_t **targetp) {
+ REQUIRE(source != NULL);
+ REQUIRE(targetp != NULL && *targetp == NULL);
+
+ isc_refcount_increment(&source->references);
+
+ *targetp = source;
+}
+
+void
+dns_transport_detach(dns_transport_t **transportp) {
+ dns_transport_t *transport = NULL;
+
+ REQUIRE(transportp != NULL);
+ REQUIRE(VALID_TRANSPORT(*transportp));
+
+ transport = *transportp;
+ *transportp = NULL;
+
+ if (isc_refcount_decrement(&transport->references) == 1) {
+ transport_destroy(transport);
+ }
+}
+
+dns_transport_t *
+dns_transport_find(const dns_transport_type_t type, const dns_name_t *name,
+ dns_transport_list_t *list) {
+ isc_result_t result;
+ dns_transport_t *transport = NULL;
+ dns_rbt_t *rbt = NULL;
+
+ REQUIRE(VALID_TRANSPORT_LIST(list));
+ REQUIRE(list->transports[type] != NULL);
+
+ rbt = list->transports[type];
+
+ RWLOCK(&list->lock, isc_rwlocktype_read);
+ result = dns_rbt_findname(rbt, name, 0, NULL, (void *)&transport);
+ if (result == ISC_R_SUCCESS) {
+ isc_refcount_increment(&transport->references);
+ }
+ RWUNLOCK(&list->lock, isc_rwlocktype_read);
+
+ return (transport);
+}
+
+dns_transport_list_t *
+dns_transport_list_new(isc_mem_t *mctx) {
+ dns_transport_list_t *list = isc_mem_get(mctx, sizeof(*list));
+
+ *list = (dns_transport_list_t){ 0 };
+
+ isc_rwlock_init(&list->lock, 0, 0);
+
+ isc_mem_attach(mctx, &list->mctx);
+ isc_refcount_init(&list->references, 1);
+
+ list->magic = TRANSPORT_LIST_MAGIC;
+
+ for (size_t type = 0; type < DNS_TRANSPORT_COUNT; type++) {
+ isc_result_t result;
+ result = dns_rbt_create(list->mctx, free_dns_transport, NULL,
+ &list->transports[type]);
+ RUNTIME_CHECK(result == ISC_R_SUCCESS);
+ }
+
+ return (list);
+}
+
+void
+dns_transport_list_attach(dns_transport_list_t *source,
+ dns_transport_list_t **targetp) {
+ REQUIRE(VALID_TRANSPORT_LIST(source));
+ REQUIRE(targetp != NULL && *targetp == NULL);
+
+ isc_refcount_increment(&source->references);
+
+ *targetp = source;
+}
+
+static void
+transport_list_destroy(dns_transport_list_t *list) {
+ isc_refcount_destroy(&list->references);
+ list->magic = 0;
+
+ for (size_t type = 0; type < DNS_TRANSPORT_COUNT; type++) {
+ if (list->transports[type] != NULL) {
+ dns_rbt_destroy(&list->transports[type]);
+ }
+ }
+ isc_rwlock_destroy(&list->lock);
+ isc_mem_putanddetach(&list->mctx, list, sizeof(*list));
+}
+
+void
+dns_transport_list_detach(dns_transport_list_t **listp) {
+ dns_transport_list_t *list = NULL;
+
+ REQUIRE(listp != NULL);
+ REQUIRE(VALID_TRANSPORT_LIST(*listp));
+
+ list = *listp;
+ *listp = NULL;
+
+ if (isc_refcount_decrement(&list->references) == 1) {
+ transport_list_destroy(list);
+ }
+}
diff --git a/lib/dns/tsec.c b/lib/dns/tsec.c
new file mode 100644
index 0000000..fcb7613
--- /dev/null
+++ b/lib/dns/tsec.c
@@ -0,0 +1,149 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#include <isc/mem.h>
+#include <isc/result.h>
+#include <isc/util.h>
+
+#include <dns/tsec.h>
+#include <dns/tsig.h>
+
+#include <dst/dst.h>
+
+#define DNS_TSEC_MAGIC ISC_MAGIC('T', 's', 'e', 'c')
+#define DNS_TSEC_VALID(t) ISC_MAGIC_VALID(t, DNS_TSEC_MAGIC)
+
+/*%
+ * DNS Transaction Security object. We assume this is not shared by
+ * multiple threads, and so the structure does not contain a lock.
+ */
+struct dns_tsec {
+ unsigned int magic;
+ dns_tsectype_t type;
+ isc_mem_t *mctx;
+ union {
+ dns_tsigkey_t *tsigkey;
+ dst_key_t *key;
+ } ukey;
+};
+
+isc_result_t
+dns_tsec_create(isc_mem_t *mctx, dns_tsectype_t type, dst_key_t *key,
+ dns_tsec_t **tsecp) {
+ isc_result_t result;
+ dns_tsec_t *tsec;
+ dns_tsigkey_t *tsigkey = NULL;
+ const dns_name_t *algname;
+
+ REQUIRE(mctx != NULL);
+ REQUIRE(tsecp != NULL && *tsecp == NULL);
+
+ tsec = isc_mem_get(mctx, sizeof(*tsec));
+
+ tsec->type = type;
+ tsec->mctx = mctx;
+
+ switch (type) {
+ case dns_tsectype_tsig:
+ switch (dst_key_alg(key)) {
+ case DST_ALG_HMACMD5:
+ algname = dns_tsig_hmacmd5_name;
+ break;
+ case DST_ALG_HMACSHA1:
+ algname = dns_tsig_hmacsha1_name;
+ break;
+ case DST_ALG_HMACSHA224:
+ algname = dns_tsig_hmacsha224_name;
+ break;
+ case DST_ALG_HMACSHA256:
+ algname = dns_tsig_hmacsha256_name;
+ break;
+ case DST_ALG_HMACSHA384:
+ algname = dns_tsig_hmacsha384_name;
+ break;
+ case DST_ALG_HMACSHA512:
+ algname = dns_tsig_hmacsha512_name;
+ break;
+ default:
+ isc_mem_put(mctx, tsec, sizeof(*tsec));
+ return (DNS_R_BADALG);
+ }
+ result = dns_tsigkey_createfromkey(dst_key_name(key), algname,
+ key, false, NULL, 0, 0, mctx,
+ NULL, &tsigkey);
+ if (result != ISC_R_SUCCESS) {
+ isc_mem_put(mctx, tsec, sizeof(*tsec));
+ return (result);
+ }
+ tsec->ukey.tsigkey = tsigkey;
+ break;
+ case dns_tsectype_sig0:
+ tsec->ukey.key = key;
+ break;
+ default:
+ UNREACHABLE();
+ }
+
+ tsec->magic = DNS_TSEC_MAGIC;
+
+ *tsecp = tsec;
+ return (ISC_R_SUCCESS);
+}
+
+void
+dns_tsec_destroy(dns_tsec_t **tsecp) {
+ dns_tsec_t *tsec;
+
+ REQUIRE(tsecp != NULL && *tsecp != NULL);
+ tsec = *tsecp;
+ *tsecp = NULL;
+ REQUIRE(DNS_TSEC_VALID(tsec));
+
+ switch (tsec->type) {
+ case dns_tsectype_tsig:
+ dns_tsigkey_detach(&tsec->ukey.tsigkey);
+ break;
+ case dns_tsectype_sig0:
+ dst_key_free(&tsec->ukey.key);
+ break;
+ default:
+ UNREACHABLE();
+ }
+
+ tsec->magic = 0;
+ isc_mem_put(tsec->mctx, tsec, sizeof(*tsec));
+}
+
+dns_tsectype_t
+dns_tsec_gettype(dns_tsec_t *tsec) {
+ REQUIRE(DNS_TSEC_VALID(tsec));
+
+ return (tsec->type);
+}
+
+void
+dns_tsec_getkey(dns_tsec_t *tsec, void *keyp) {
+ REQUIRE(DNS_TSEC_VALID(tsec));
+ REQUIRE(keyp != NULL);
+
+ switch (tsec->type) {
+ case dns_tsectype_tsig:
+ dns_tsigkey_attach(tsec->ukey.tsigkey, (dns_tsigkey_t **)keyp);
+ break;
+ case dns_tsectype_sig0:
+ *(dst_key_t **)keyp = tsec->ukey.key;
+ break;
+ default:
+ UNREACHABLE();
+ }
+}
diff --git a/lib/dns/tsig.c b/lib/dns/tsig.c
new file mode 100644
index 0000000..857ec4c
--- /dev/null
+++ b/lib/dns/tsig.c
@@ -0,0 +1,1919 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/*! \file */
+
+#include <inttypes.h>
+#include <stdbool.h>
+#include <stdlib.h>
+
+#include <isc/buffer.h>
+#include <isc/mem.h>
+#include <isc/print.h>
+#include <isc/refcount.h>
+#include <isc/result.h>
+#include <isc/serial.h>
+#include <isc/string.h> /* Required for HP/UX (and others?) */
+#include <isc/time.h>
+#include <isc/util.h>
+
+#include <dns/fixedname.h>
+#include <dns/keyvalues.h>
+#include <dns/log.h>
+#include <dns/message.h>
+#include <dns/rbt.h>
+#include <dns/rdata.h>
+#include <dns/rdatalist.h>
+#include <dns/rdataset.h>
+#include <dns/rdatastruct.h>
+#include <dns/tsig.h>
+
+#include "tsig_p.h"
+
+#define TSIG_MAGIC ISC_MAGIC('T', 'S', 'I', 'G')
+#define VALID_TSIG_KEY(x) ISC_MAGIC_VALID(x, TSIG_MAGIC)
+
+#ifndef DNS_TSIG_MAXGENERATEDKEYS
+#define DNS_TSIG_MAXGENERATEDKEYS 4096
+#endif /* ifndef DNS_TSIG_MAXGENERATEDKEYS */
+
+#define is_response(msg) ((msg->flags & DNS_MESSAGEFLAG_QR) != 0)
+
+#define BADTIMELEN 6
+
+static unsigned char hmacmd5_ndata[] = "\010hmac-md5\007sig-alg\003reg\003int";
+static unsigned char hmacmd5_offsets[] = { 0, 9, 17, 21, 25 };
+
+static dns_name_t const hmacmd5 = DNS_NAME_INITABSOLUTE(hmacmd5_ndata,
+ hmacmd5_offsets);
+const dns_name_t *dns_tsig_hmacmd5_name = &hmacmd5;
+
+static unsigned char gsstsig_ndata[] = "\010gss-tsig";
+static unsigned char gsstsig_offsets[] = { 0, 9 };
+static dns_name_t const gsstsig = DNS_NAME_INITABSOLUTE(gsstsig_ndata,
+ gsstsig_offsets);
+const dns_name_t *dns_tsig_gssapi_name = &gsstsig;
+
+/*
+ * Since Microsoft doesn't follow its own standard, we will use this
+ * alternate name as a second guess.
+ */
+static unsigned char gsstsigms_ndata[] = "\003gss\011microsoft\003com";
+static unsigned char gsstsigms_offsets[] = { 0, 4, 14, 18 };
+static dns_name_t const gsstsigms = DNS_NAME_INITABSOLUTE(gsstsigms_ndata,
+ gsstsigms_offsets);
+const dns_name_t *dns_tsig_gssapims_name = &gsstsigms;
+
+static unsigned char hmacsha1_ndata[] = "\011hmac-sha1";
+static unsigned char hmacsha1_offsets[] = { 0, 10 };
+static dns_name_t const hmacsha1 = DNS_NAME_INITABSOLUTE(hmacsha1_ndata,
+ hmacsha1_offsets);
+const dns_name_t *dns_tsig_hmacsha1_name = &hmacsha1;
+
+static unsigned char hmacsha224_ndata[] = "\013hmac-sha224";
+static unsigned char hmacsha224_offsets[] = { 0, 12 };
+static dns_name_t const hmacsha224 = DNS_NAME_INITABSOLUTE(hmacsha224_ndata,
+ hmacsha224_offsets);
+const dns_name_t *dns_tsig_hmacsha224_name = &hmacsha224;
+
+static unsigned char hmacsha256_ndata[] = "\013hmac-sha256";
+static unsigned char hmacsha256_offsets[] = { 0, 12 };
+static dns_name_t const hmacsha256 = DNS_NAME_INITABSOLUTE(hmacsha256_ndata,
+ hmacsha256_offsets);
+const dns_name_t *dns_tsig_hmacsha256_name = &hmacsha256;
+
+static unsigned char hmacsha384_ndata[] = "\013hmac-sha384";
+static unsigned char hmacsha384_offsets[] = { 0, 12 };
+static dns_name_t const hmacsha384 = DNS_NAME_INITABSOLUTE(hmacsha384_ndata,
+ hmacsha384_offsets);
+const dns_name_t *dns_tsig_hmacsha384_name = &hmacsha384;
+
+static unsigned char hmacsha512_ndata[] = "\013hmac-sha512";
+static unsigned char hmacsha512_offsets[] = { 0, 12 };
+static dns_name_t const hmacsha512 = DNS_NAME_INITABSOLUTE(hmacsha512_ndata,
+ hmacsha512_offsets);
+const dns_name_t *dns_tsig_hmacsha512_name = &hmacsha512;
+
+static const struct {
+ const dns_name_t *name;
+ unsigned int dstalg;
+} known_algs[] = { { &hmacmd5, DST_ALG_HMACMD5 },
+ { &gsstsig, DST_ALG_GSSAPI },
+ { &gsstsigms, DST_ALG_GSSAPI },
+ { &hmacsha1, DST_ALG_HMACSHA1 },
+ { &hmacsha224, DST_ALG_HMACSHA224 },
+ { &hmacsha256, DST_ALG_HMACSHA256 },
+ { &hmacsha384, DST_ALG_HMACSHA384 },
+ { &hmacsha512, DST_ALG_HMACSHA512 } };
+
+static isc_result_t
+tsig_verify_tcp(isc_buffer_t *source, dns_message_t *msg);
+
+static void
+tsig_log(dns_tsigkey_t *key, int level, const char *fmt, ...)
+ ISC_FORMAT_PRINTF(3, 4);
+
+static void
+cleanup_ring(dns_tsig_keyring_t *ring);
+static void
+tsigkey_free(dns_tsigkey_t *key);
+
+bool
+dns__tsig_algvalid(unsigned int alg) {
+ return (alg == DST_ALG_HMACMD5 || alg == DST_ALG_HMACSHA1 ||
+ alg == DST_ALG_HMACSHA224 || alg == DST_ALG_HMACSHA256 ||
+ alg == DST_ALG_HMACSHA384 || alg == DST_ALG_HMACSHA512);
+}
+
+static void
+tsig_log(dns_tsigkey_t *key, int level, const char *fmt, ...) {
+ va_list ap;
+ char message[4096];
+ char namestr[DNS_NAME_FORMATSIZE];
+ char creatorstr[DNS_NAME_FORMATSIZE];
+
+ if (!isc_log_wouldlog(dns_lctx, level)) {
+ return;
+ }
+ if (key != NULL) {
+ dns_name_format(&key->name, namestr, sizeof(namestr));
+ } else {
+ strlcpy(namestr, "<null>", sizeof(namestr));
+ }
+
+ if (key != NULL && key->generated && key->creator) {
+ dns_name_format(key->creator, creatorstr, sizeof(creatorstr));
+ } else {
+ strlcpy(creatorstr, "<null>", sizeof(creatorstr));
+ }
+
+ va_start(ap, fmt);
+ vsnprintf(message, sizeof(message), fmt, ap);
+ va_end(ap);
+ if (key != NULL && key->generated) {
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_DNSSEC,
+ DNS_LOGMODULE_TSIG, level,
+ "tsig key '%s' (%s): %s", namestr, creatorstr,
+ message);
+ } else {
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_DNSSEC,
+ DNS_LOGMODULE_TSIG, level, "tsig key '%s': %s",
+ namestr, message);
+ }
+}
+
+static void
+remove_fromring(dns_tsigkey_t *tkey) {
+ if (tkey->generated) {
+ ISC_LIST_UNLINK(tkey->ring->lru, tkey, link);
+ tkey->ring->generated--;
+ }
+ (void)dns_rbt_deletename(tkey->ring->keys, &tkey->name, false);
+}
+
+static void
+adjust_lru(dns_tsigkey_t *tkey) {
+ if (tkey->generated) {
+ RWLOCK(&tkey->ring->lock, isc_rwlocktype_write);
+ /*
+ * We may have been removed from the LRU list between
+ * removing the read lock and acquiring the write lock.
+ */
+ if (ISC_LINK_LINKED(tkey, link) && tkey->ring->lru.tail != tkey)
+ {
+ ISC_LIST_UNLINK(tkey->ring->lru, tkey, link);
+ ISC_LIST_APPEND(tkey->ring->lru, tkey, link);
+ }
+ RWUNLOCK(&tkey->ring->lock, isc_rwlocktype_write);
+ }
+}
+
+/*
+ * A supplemental routine just to add a key to ring. Note that reference
+ * counter should be counted separately because we may be adding the key
+ * as part of creation of the key, in which case the reference counter was
+ * already initialized. Also note we don't need RWLOCK for the reference
+ * counter: it's protected by a separate lock.
+ */
+static isc_result_t
+keyring_add(dns_tsig_keyring_t *ring, const dns_name_t *name,
+ dns_tsigkey_t *tkey) {
+ isc_result_t result;
+
+ RWLOCK(&ring->lock, isc_rwlocktype_write);
+ ring->writecount++;
+
+ /*
+ * Do on the fly cleaning. Find some nodes we might not
+ * want around any more.
+ */
+ if (ring->writecount > 10) {
+ cleanup_ring(ring);
+ ring->writecount = 0;
+ }
+
+ result = dns_rbt_addname(ring->keys, name, tkey);
+ if (result == ISC_R_SUCCESS) {
+ if (tkey->generated) {
+ /*
+ * Add the new key to the LRU list and remove the
+ * least recently used key if there are too many
+ * keys on the list.
+ */
+ ISC_LIST_APPEND(ring->lru, tkey, link);
+ if (ring->generated++ > ring->maxgenerated) {
+ remove_fromring(ISC_LIST_HEAD(ring->lru));
+ }
+ }
+
+ tkey->ring = ring;
+ }
+
+ RWUNLOCK(&ring->lock, isc_rwlocktype_write);
+
+ return (result);
+}
+
+isc_result_t
+dns_tsigkey_createfromkey(const dns_name_t *name, const dns_name_t *algorithm,
+ dst_key_t *dstkey, bool generated,
+ const dns_name_t *creator, isc_stdtime_t inception,
+ isc_stdtime_t expire, isc_mem_t *mctx,
+ dns_tsig_keyring_t *ring, dns_tsigkey_t **key) {
+ dns_tsigkey_t *tkey;
+ isc_result_t ret;
+ unsigned int refs = 0;
+ unsigned int dstalg = 0;
+
+ REQUIRE(key == NULL || *key == NULL);
+ REQUIRE(name != NULL);
+ REQUIRE(algorithm != NULL);
+ REQUIRE(mctx != NULL);
+ REQUIRE(key != NULL || ring != NULL);
+
+ tkey = isc_mem_get(mctx, sizeof(dns_tsigkey_t));
+
+ dns_name_init(&tkey->name, NULL);
+ dns_name_dup(name, mctx, &tkey->name);
+ (void)dns_name_downcase(&tkey->name, &tkey->name, NULL);
+
+ /* Check against known algorithm names */
+ dstalg = dns__tsig_algfromname(algorithm);
+ if (dstalg != 0) {
+ /*
+ * 'algorithm' must be set to a static pointer
+ * so that dns__tsig_algallocated() can compare them.
+ */
+ tkey->algorithm = dns__tsig_algnamefromname(algorithm);
+ if (dstkey != NULL && dst_key_alg(dstkey) != dstalg) {
+ ret = DNS_R_BADALG;
+ goto cleanup_name;
+ }
+ } else {
+ dns_name_t *tmpname;
+ if (dstkey != NULL) {
+ ret = DNS_R_BADALG;
+ goto cleanup_name;
+ }
+ tmpname = isc_mem_get(mctx, sizeof(dns_name_t));
+ dns_name_init(tmpname, NULL);
+ dns_name_dup(algorithm, mctx, tmpname);
+ (void)dns_name_downcase(tmpname, tmpname, NULL);
+ tkey->algorithm = tmpname;
+ }
+
+ if (creator != NULL) {
+ tkey->creator = isc_mem_get(mctx, sizeof(dns_name_t));
+ dns_name_init(tkey->creator, NULL);
+ dns_name_dup(creator, mctx, tkey->creator);
+ } else {
+ tkey->creator = NULL;
+ }
+
+ tkey->key = NULL;
+ if (dstkey != NULL) {
+ dst_key_attach(dstkey, &tkey->key);
+ }
+ tkey->ring = ring;
+
+ if (key != NULL) {
+ refs = 1;
+ }
+ if (ring != NULL) {
+ refs++;
+ }
+
+ isc_refcount_init(&tkey->refs, refs);
+
+ tkey->generated = generated;
+ tkey->inception = inception;
+ tkey->expire = expire;
+ tkey->mctx = NULL;
+ isc_mem_attach(mctx, &tkey->mctx);
+ ISC_LINK_INIT(tkey, link);
+
+ tkey->magic = TSIG_MAGIC;
+
+ if (ring != NULL) {
+ ret = keyring_add(ring, name, tkey);
+ if (ret != ISC_R_SUCCESS) {
+ goto cleanup_refs;
+ }
+ }
+
+ /*
+ * Ignore this if it's a GSS key, since the key size is meaningless.
+ */
+ if (dstkey != NULL && dst_key_size(dstkey) < 64 &&
+ dstalg != DST_ALG_GSSAPI)
+ {
+ char namestr[DNS_NAME_FORMATSIZE];
+ dns_name_format(name, namestr, sizeof(namestr));
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_DNSSEC,
+ DNS_LOGMODULE_TSIG, ISC_LOG_INFO,
+ "the key '%s' is too short to be secure",
+ namestr);
+ }
+
+ if (key != NULL) {
+ *key = tkey;
+ }
+
+ return (ISC_R_SUCCESS);
+
+cleanup_refs:
+ tkey->magic = 0;
+ while (refs-- > 0) {
+ isc_refcount_decrement0(&tkey->refs);
+ }
+ isc_refcount_destroy(&tkey->refs);
+
+ if (tkey->key != NULL) {
+ dst_key_free(&tkey->key);
+ }
+ if (tkey->creator != NULL) {
+ dns_name_free(tkey->creator, mctx);
+ isc_mem_put(mctx, tkey->creator, sizeof(dns_name_t));
+ }
+ if (dns__tsig_algallocated(tkey->algorithm)) {
+ dns_name_t *tmpname;
+ DE_CONST(tkey->algorithm, tmpname);
+ if (dns_name_dynamic(tmpname)) {
+ dns_name_free(tmpname, mctx);
+ }
+ isc_mem_put(mctx, tmpname, sizeof(dns_name_t));
+ }
+cleanup_name:
+ dns_name_free(&tkey->name, mctx);
+ isc_mem_put(mctx, tkey, sizeof(dns_tsigkey_t));
+
+ return (ret);
+}
+
+/*
+ * Find a few nodes to destroy if possible.
+ */
+static void
+cleanup_ring(dns_tsig_keyring_t *ring) {
+ isc_result_t result;
+ dns_rbtnodechain_t chain;
+ dns_name_t foundname;
+ dns_fixedname_t fixedorigin;
+ dns_name_t *origin;
+ isc_stdtime_t now;
+ dns_rbtnode_t *node;
+ dns_tsigkey_t *tkey;
+
+ /*
+ * Start up a new iterator each time.
+ */
+ isc_stdtime_get(&now);
+ dns_name_init(&foundname, NULL);
+ origin = dns_fixedname_initname(&fixedorigin);
+
+again:
+ dns_rbtnodechain_init(&chain);
+ result = dns_rbtnodechain_first(&chain, ring->keys, &foundname, origin);
+ if (result != ISC_R_SUCCESS && result != DNS_R_NEWORIGIN) {
+ dns_rbtnodechain_invalidate(&chain);
+ return;
+ }
+
+ for (;;) {
+ node = NULL;
+ dns_rbtnodechain_current(&chain, &foundname, origin, &node);
+ tkey = node->data;
+ if (tkey != NULL) {
+ if (tkey->generated &&
+ isc_refcount_current(&tkey->refs) == 1 &&
+ tkey->inception != tkey->expire &&
+ tkey->expire < now)
+ {
+ tsig_log(tkey, 2, "tsig expire: deleting");
+ /* delete the key */
+ dns_rbtnodechain_invalidate(&chain);
+ remove_fromring(tkey);
+ goto again;
+ }
+ }
+ result = dns_rbtnodechain_next(&chain, &foundname, origin);
+ if (result != ISC_R_SUCCESS && result != DNS_R_NEWORIGIN) {
+ dns_rbtnodechain_invalidate(&chain);
+ return;
+ }
+ }
+}
+
+static void
+destroyring(dns_tsig_keyring_t *ring) {
+ isc_refcount_destroy(&ring->references);
+ dns_rbt_destroy(&ring->keys);
+ isc_rwlock_destroy(&ring->lock);
+ isc_mem_putanddetach(&ring->mctx, ring, sizeof(dns_tsig_keyring_t));
+}
+
+/*
+ * Look up the DST_ALG_ constant for a given name.
+ */
+unsigned int
+dns__tsig_algfromname(const dns_name_t *algorithm) {
+ int i;
+ int n = sizeof(known_algs) / sizeof(*known_algs);
+ for (i = 0; i < n; ++i) {
+ const dns_name_t *name = known_algs[i].name;
+ if (algorithm == name || dns_name_equal(algorithm, name)) {
+ return (known_algs[i].dstalg);
+ }
+ }
+ return (0);
+}
+
+/*
+ * Convert an algorithm name into a pointer to the
+ * corresponding pre-defined dns_name_t structure.
+ */
+const dns_name_t *
+dns__tsig_algnamefromname(const dns_name_t *algorithm) {
+ int i;
+ int n = sizeof(known_algs) / sizeof(*known_algs);
+ for (i = 0; i < n; ++i) {
+ const dns_name_t *name = known_algs[i].name;
+ if (algorithm == name || dns_name_equal(algorithm, name)) {
+ return (name);
+ }
+ }
+ return (NULL);
+}
+
+/*
+ * Test whether the passed algorithm is NOT a pointer to one of the
+ * pre-defined known algorithms (and therefore one that has been
+ * dynamically allocated).
+ *
+ * This will return an incorrect result if passed a dynamically allocated
+ * dns_name_t that happens to match one of the pre-defined names.
+ */
+bool
+dns__tsig_algallocated(const dns_name_t *algorithm) {
+ int i;
+ int n = sizeof(known_algs) / sizeof(*known_algs);
+ for (i = 0; i < n; ++i) {
+ const dns_name_t *name = known_algs[i].name;
+ if (algorithm == name) {
+ return (false);
+ }
+ }
+ return (true);
+}
+
+static isc_result_t
+restore_key(dns_tsig_keyring_t *ring, isc_stdtime_t now, FILE *fp) {
+ dst_key_t *dstkey = NULL;
+ char namestr[1024];
+ char creatorstr[1024];
+ char algorithmstr[1024];
+ char keystr[4096];
+ unsigned int inception, expire;
+ int n;
+ isc_buffer_t b;
+ dns_name_t *name, *creator, *algorithm;
+ dns_fixedname_t fname, fcreator, falgorithm;
+ isc_result_t result;
+ unsigned int dstalg;
+
+ n = fscanf(fp, "%1023s %1023s %u %u %1023s %4095s\n", namestr,
+ creatorstr, &inception, &expire, algorithmstr, keystr);
+ if (n == EOF) {
+ return (ISC_R_NOMORE);
+ }
+ if (n != 6) {
+ return (ISC_R_FAILURE);
+ }
+
+ if (isc_serial_lt(expire, now)) {
+ return (DNS_R_EXPIRED);
+ }
+
+ name = dns_fixedname_initname(&fname);
+ isc_buffer_init(&b, namestr, strlen(namestr));
+ isc_buffer_add(&b, strlen(namestr));
+ result = dns_name_fromtext(name, &b, dns_rootname, 0, NULL);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+
+ creator = dns_fixedname_initname(&fcreator);
+ isc_buffer_init(&b, creatorstr, strlen(creatorstr));
+ isc_buffer_add(&b, strlen(creatorstr));
+ result = dns_name_fromtext(creator, &b, dns_rootname, 0, NULL);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+
+ algorithm = dns_fixedname_initname(&falgorithm);
+ isc_buffer_init(&b, algorithmstr, strlen(algorithmstr));
+ isc_buffer_add(&b, strlen(algorithmstr));
+ result = dns_name_fromtext(algorithm, &b, dns_rootname, 0, NULL);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+
+ dstalg = dns__tsig_algfromname(algorithm);
+ if (dstalg == 0) {
+ return (DNS_R_BADALG);
+ }
+
+ result = dst_key_restore(name, dstalg, DNS_KEYOWNER_ENTITY,
+ DNS_KEYPROTO_DNSSEC, dns_rdataclass_in,
+ ring->mctx, keystr, &dstkey);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+
+ result = dns_tsigkey_createfromkey(name, algorithm, dstkey, true,
+ creator, inception, expire,
+ ring->mctx, ring, NULL);
+ if (dstkey != NULL) {
+ dst_key_free(&dstkey);
+ }
+ return (result);
+}
+
+static void
+dump_key(dns_tsigkey_t *tkey, FILE *fp) {
+ char *buffer = NULL;
+ int length = 0;
+ char namestr[DNS_NAME_FORMATSIZE];
+ char creatorstr[DNS_NAME_FORMATSIZE];
+ char algorithmstr[DNS_NAME_FORMATSIZE];
+ isc_result_t result;
+
+ REQUIRE(tkey != NULL);
+ REQUIRE(fp != NULL);
+
+ dns_name_format(&tkey->name, namestr, sizeof(namestr));
+ dns_name_format(tkey->creator, creatorstr, sizeof(creatorstr));
+ dns_name_format(tkey->algorithm, algorithmstr, sizeof(algorithmstr));
+ result = dst_key_dump(tkey->key, tkey->mctx, &buffer, &length);
+ if (result == ISC_R_SUCCESS) {
+ fprintf(fp, "%s %s %u %u %s %.*s\n", namestr, creatorstr,
+ tkey->inception, tkey->expire, algorithmstr, length,
+ buffer);
+ }
+ if (buffer != NULL) {
+ isc_mem_put(tkey->mctx, buffer, length);
+ }
+}
+
+isc_result_t
+dns_tsigkeyring_dumpanddetach(dns_tsig_keyring_t **ringp, FILE *fp) {
+ isc_result_t result;
+ dns_rbtnodechain_t chain;
+ dns_name_t foundname;
+ dns_fixedname_t fixedorigin;
+ dns_name_t *origin;
+ isc_stdtime_t now;
+ dns_rbtnode_t *node;
+ dns_tsigkey_t *tkey;
+ dns_tsig_keyring_t *ring;
+
+ REQUIRE(ringp != NULL && *ringp != NULL);
+
+ ring = *ringp;
+ *ringp = NULL;
+
+ if (isc_refcount_decrement(&ring->references) > 1) {
+ return (DNS_R_CONTINUE);
+ }
+
+ isc_stdtime_get(&now);
+ dns_name_init(&foundname, NULL);
+ origin = dns_fixedname_initname(&fixedorigin);
+ dns_rbtnodechain_init(&chain);
+ result = dns_rbtnodechain_first(&chain, ring->keys, &foundname, origin);
+ if (result != ISC_R_SUCCESS && result != DNS_R_NEWORIGIN) {
+ dns_rbtnodechain_invalidate(&chain);
+ goto destroy;
+ }
+
+ for (;;) {
+ node = NULL;
+ dns_rbtnodechain_current(&chain, &foundname, origin, &node);
+ tkey = node->data;
+ if (tkey != NULL && tkey->generated && tkey->expire >= now) {
+ dump_key(tkey, fp);
+ }
+ result = dns_rbtnodechain_next(&chain, &foundname, origin);
+ if (result != ISC_R_SUCCESS && result != DNS_R_NEWORIGIN) {
+ dns_rbtnodechain_invalidate(&chain);
+ if (result == ISC_R_NOMORE) {
+ result = ISC_R_SUCCESS;
+ }
+ goto destroy;
+ }
+ }
+
+destroy:
+ destroyring(ring);
+ return (result);
+}
+
+const dns_name_t *
+dns_tsigkey_identity(const dns_tsigkey_t *tsigkey) {
+ REQUIRE(tsigkey == NULL || VALID_TSIG_KEY(tsigkey));
+
+ if (tsigkey == NULL) {
+ return (NULL);
+ }
+ if (tsigkey->generated) {
+ return (tsigkey->creator);
+ } else {
+ return (&tsigkey->name);
+ }
+}
+
+isc_result_t
+dns_tsigkey_create(const dns_name_t *name, const dns_name_t *algorithm,
+ unsigned char *secret, int length, bool generated,
+ const dns_name_t *creator, isc_stdtime_t inception,
+ isc_stdtime_t expire, isc_mem_t *mctx,
+ dns_tsig_keyring_t *ring, dns_tsigkey_t **key) {
+ dst_key_t *dstkey = NULL;
+ isc_result_t result;
+ unsigned int dstalg = 0;
+
+ REQUIRE(length >= 0);
+ if (length > 0) {
+ REQUIRE(secret != NULL);
+ }
+
+ dstalg = dns__tsig_algfromname(algorithm);
+ if (dns__tsig_algvalid(dstalg)) {
+ if (secret != NULL) {
+ isc_buffer_t b;
+
+ isc_buffer_init(&b, secret, length);
+ isc_buffer_add(&b, length);
+ result = dst_key_frombuffer(
+ name, dstalg, DNS_KEYOWNER_ENTITY,
+ DNS_KEYPROTO_DNSSEC, dns_rdataclass_in, &b,
+ mctx, &dstkey);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+ }
+ } else if (length > 0) {
+ return (DNS_R_BADALG);
+ }
+
+ result = dns_tsigkey_createfromkey(name, algorithm, dstkey, generated,
+ creator, inception, expire, mctx,
+ ring, key);
+ if (dstkey != NULL) {
+ dst_key_free(&dstkey);
+ }
+ return (result);
+}
+
+void
+dns_tsigkey_attach(dns_tsigkey_t *source, dns_tsigkey_t **targetp) {
+ REQUIRE(VALID_TSIG_KEY(source));
+ REQUIRE(targetp != NULL && *targetp == NULL);
+
+ isc_refcount_increment(&source->refs);
+ *targetp = source;
+}
+
+static void
+tsigkey_free(dns_tsigkey_t *key) {
+ REQUIRE(VALID_TSIG_KEY(key));
+
+ key->magic = 0;
+ dns_name_free(&key->name, key->mctx);
+ if (dns__tsig_algallocated(key->algorithm)) {
+ dns_name_t *name;
+ DE_CONST(key->algorithm, name);
+ dns_name_free(name, key->mctx);
+ isc_mem_put(key->mctx, name, sizeof(dns_name_t));
+ }
+ if (key->key != NULL) {
+ dst_key_free(&key->key);
+ }
+ if (key->creator != NULL) {
+ dns_name_free(key->creator, key->mctx);
+ isc_mem_put(key->mctx, key->creator, sizeof(dns_name_t));
+ }
+ isc_mem_putanddetach(&key->mctx, key, sizeof(dns_tsigkey_t));
+}
+
+void
+dns_tsigkey_detach(dns_tsigkey_t **keyp) {
+ REQUIRE(keyp != NULL && VALID_TSIG_KEY(*keyp));
+ dns_tsigkey_t *key = *keyp;
+ *keyp = NULL;
+
+ if (isc_refcount_decrement(&key->refs) == 1) {
+ isc_refcount_destroy(&key->refs);
+ tsigkey_free(key);
+ }
+}
+
+void
+dns_tsigkey_setdeleted(dns_tsigkey_t *key) {
+ REQUIRE(VALID_TSIG_KEY(key));
+ REQUIRE(key->ring != NULL);
+
+ RWLOCK(&key->ring->lock, isc_rwlocktype_write);
+ remove_fromring(key);
+ RWUNLOCK(&key->ring->lock, isc_rwlocktype_write);
+}
+
+isc_result_t
+dns_tsig_sign(dns_message_t *msg) {
+ dns_tsigkey_t *key = NULL;
+ dns_rdata_any_tsig_t tsig, querytsig;
+ unsigned char data[128];
+ isc_buffer_t databuf, sigbuf;
+ isc_buffer_t *dynbuf = NULL;
+ dns_name_t *owner = NULL;
+ dns_rdata_t *rdata = NULL;
+ dns_rdatalist_t *datalist = NULL;
+ dns_rdataset_t *dataset = NULL;
+ isc_region_t r;
+ isc_stdtime_t now;
+ isc_mem_t *mctx;
+ dst_context_t *ctx = NULL;
+ isc_result_t ret;
+ unsigned char badtimedata[BADTIMELEN];
+ unsigned int sigsize = 0;
+ bool response;
+
+ REQUIRE(msg != NULL);
+ key = dns_message_gettsigkey(msg);
+ REQUIRE(VALID_TSIG_KEY(key));
+
+ /*
+ * If this is a response, there should be a TSIG in the query with the
+ * the exception if this is a TKEY request (see RFC 3645, Section 2.2).
+ */
+ response = is_response(msg);
+ if (response && msg->querytsig == NULL) {
+ if (msg->tkey != 1) {
+ return (DNS_R_EXPECTEDTSIG);
+ }
+ }
+
+ mctx = msg->mctx;
+
+ tsig.mctx = mctx;
+ tsig.common.rdclass = dns_rdataclass_any;
+ tsig.common.rdtype = dns_rdatatype_tsig;
+ ISC_LINK_INIT(&tsig.common, link);
+ dns_name_init(&tsig.algorithm, NULL);
+ dns_name_clone(key->algorithm, &tsig.algorithm);
+
+ if (msg->fuzzing) {
+ now = msg->fuzztime;
+ } else {
+ isc_stdtime_get(&now);
+ }
+
+ tsig.timesigned = now + msg->timeadjust;
+ tsig.fudge = DNS_TSIG_FUDGE;
+
+ tsig.originalid = msg->id;
+
+ isc_buffer_init(&databuf, data, sizeof(data));
+
+ if (response) {
+ tsig.error = msg->querytsigstatus;
+ } else {
+ tsig.error = dns_rcode_noerror;
+ }
+
+ if (tsig.error != dns_tsigerror_badtime) {
+ tsig.otherlen = 0;
+ tsig.other = NULL;
+ } else {
+ isc_buffer_t otherbuf;
+
+ tsig.otherlen = BADTIMELEN;
+ tsig.other = badtimedata;
+ isc_buffer_init(&otherbuf, tsig.other, tsig.otherlen);
+ isc_buffer_putuint48(&otherbuf, tsig.timesigned);
+ }
+
+ if ((key->key != NULL) && (tsig.error != dns_tsigerror_badsig) &&
+ (tsig.error != dns_tsigerror_badkey))
+ {
+ unsigned char header[DNS_MESSAGE_HEADERLEN];
+ isc_buffer_t headerbuf;
+ uint16_t digestbits;
+ bool querytsig_ok = false;
+
+ /*
+ * If it is a response, we assume that the request MAC
+ * has validated at this point. This is why we include a
+ * MAC length > 0 in the reply.
+ */
+ ret = dst_context_create(key->key, mctx, DNS_LOGCATEGORY_DNSSEC,
+ true, 0, &ctx);
+ if (ret != ISC_R_SUCCESS) {
+ return (ret);
+ }
+
+ /*
+ * If this is a response, and if there was a TSIG in
+ * the query, digest the request's MAC.
+ *
+ * (Note: querytsig should be non-NULL for all
+ * responses except TKEY responses. Those may be signed
+ * with the newly-negotiated TSIG key even if the query
+ * wasn't signed.)
+ */
+ if (response && msg->querytsig != NULL) {
+ dns_rdata_t querytsigrdata = DNS_RDATA_INIT;
+
+ INSIST(msg->verified_sig);
+
+ ret = dns_rdataset_first(msg->querytsig);
+ if (ret != ISC_R_SUCCESS) {
+ goto cleanup_context;
+ }
+ dns_rdataset_current(msg->querytsig, &querytsigrdata);
+ ret = dns_rdata_tostruct(&querytsigrdata, &querytsig,
+ NULL);
+ if (ret != ISC_R_SUCCESS) {
+ goto cleanup_context;
+ }
+ isc_buffer_putuint16(&databuf, querytsig.siglen);
+ if (isc_buffer_availablelength(&databuf) <
+ querytsig.siglen)
+ {
+ ret = ISC_R_NOSPACE;
+ goto cleanup_context;
+ }
+ isc_buffer_putmem(&databuf, querytsig.signature,
+ querytsig.siglen);
+ isc_buffer_usedregion(&databuf, &r);
+ ret = dst_context_adddata(ctx, &r);
+ if (ret != ISC_R_SUCCESS) {
+ goto cleanup_context;
+ }
+ querytsig_ok = true;
+ }
+
+ /*
+ * Digest the header.
+ */
+ isc_buffer_init(&headerbuf, header, sizeof(header));
+ dns_message_renderheader(msg, &headerbuf);
+ isc_buffer_usedregion(&headerbuf, &r);
+ ret = dst_context_adddata(ctx, &r);
+ if (ret != ISC_R_SUCCESS) {
+ goto cleanup_context;
+ }
+
+ /*
+ * Digest the remainder of the message.
+ */
+ isc_buffer_usedregion(msg->buffer, &r);
+ isc_region_consume(&r, DNS_MESSAGE_HEADERLEN);
+ ret = dst_context_adddata(ctx, &r);
+ if (ret != ISC_R_SUCCESS) {
+ goto cleanup_context;
+ }
+
+ if (msg->tcp_continuation == 0) {
+ /*
+ * Digest the name, class, ttl, alg.
+ */
+ dns_name_toregion(&key->name, &r);
+ ret = dst_context_adddata(ctx, &r);
+ if (ret != ISC_R_SUCCESS) {
+ goto cleanup_context;
+ }
+
+ isc_buffer_clear(&databuf);
+ isc_buffer_putuint16(&databuf, dns_rdataclass_any);
+ isc_buffer_putuint32(&databuf, 0); /* ttl */
+ isc_buffer_usedregion(&databuf, &r);
+ ret = dst_context_adddata(ctx, &r);
+ if (ret != ISC_R_SUCCESS) {
+ goto cleanup_context;
+ }
+
+ dns_name_toregion(&tsig.algorithm, &r);
+ ret = dst_context_adddata(ctx, &r);
+ if (ret != ISC_R_SUCCESS) {
+ goto cleanup_context;
+ }
+ }
+ /* Digest the timesigned and fudge */
+ isc_buffer_clear(&databuf);
+ if (tsig.error == dns_tsigerror_badtime && querytsig_ok) {
+ tsig.timesigned = querytsig.timesigned;
+ }
+ isc_buffer_putuint48(&databuf, tsig.timesigned);
+ isc_buffer_putuint16(&databuf, tsig.fudge);
+ isc_buffer_usedregion(&databuf, &r);
+ ret = dst_context_adddata(ctx, &r);
+ if (ret != ISC_R_SUCCESS) {
+ goto cleanup_context;
+ }
+
+ if (msg->tcp_continuation == 0) {
+ /*
+ * Digest the error and other data length.
+ */
+ isc_buffer_clear(&databuf);
+ isc_buffer_putuint16(&databuf, tsig.error);
+ isc_buffer_putuint16(&databuf, tsig.otherlen);
+
+ isc_buffer_usedregion(&databuf, &r);
+ ret = dst_context_adddata(ctx, &r);
+ if (ret != ISC_R_SUCCESS) {
+ goto cleanup_context;
+ }
+
+ /*
+ * Digest other data.
+ */
+ if (tsig.otherlen > 0) {
+ r.length = tsig.otherlen;
+ r.base = tsig.other;
+ ret = dst_context_adddata(ctx, &r);
+ if (ret != ISC_R_SUCCESS) {
+ goto cleanup_context;
+ }
+ }
+ }
+
+ ret = dst_key_sigsize(key->key, &sigsize);
+ if (ret != ISC_R_SUCCESS) {
+ goto cleanup_context;
+ }
+ tsig.signature = isc_mem_get(mctx, sigsize);
+
+ isc_buffer_init(&sigbuf, tsig.signature, sigsize);
+ ret = dst_context_sign(ctx, &sigbuf);
+ if (ret != ISC_R_SUCCESS) {
+ goto cleanup_signature;
+ }
+ dst_context_destroy(&ctx);
+ digestbits = dst_key_getbits(key->key);
+ if (digestbits != 0) {
+ unsigned int bytes = (digestbits + 7) / 8;
+ if (querytsig_ok && bytes < querytsig.siglen) {
+ bytes = querytsig.siglen;
+ }
+ if (bytes > isc_buffer_usedlength(&sigbuf)) {
+ bytes = isc_buffer_usedlength(&sigbuf);
+ }
+ tsig.siglen = bytes;
+ } else {
+ tsig.siglen = isc_buffer_usedlength(&sigbuf);
+ }
+ } else {
+ tsig.siglen = 0;
+ tsig.signature = NULL;
+ }
+
+ ret = dns_message_gettemprdata(msg, &rdata);
+ if (ret != ISC_R_SUCCESS) {
+ goto cleanup_signature;
+ }
+ isc_buffer_allocate(msg->mctx, &dynbuf, 512);
+ ret = dns_rdata_fromstruct(rdata, dns_rdataclass_any,
+ dns_rdatatype_tsig, &tsig, dynbuf);
+ if (ret != ISC_R_SUCCESS) {
+ goto cleanup_dynbuf;
+ }
+
+ dns_message_takebuffer(msg, &dynbuf);
+
+ if (tsig.signature != NULL) {
+ isc_mem_put(mctx, tsig.signature, sigsize);
+ tsig.signature = NULL;
+ }
+
+ ret = dns_message_gettempname(msg, &owner);
+ if (ret != ISC_R_SUCCESS) {
+ goto cleanup_rdata;
+ }
+ dns_name_copy(&key->name, owner);
+
+ ret = dns_message_gettemprdatalist(msg, &datalist);
+ if (ret != ISC_R_SUCCESS) {
+ goto cleanup_owner;
+ }
+
+ ret = dns_message_gettemprdataset(msg, &dataset);
+ if (ret != ISC_R_SUCCESS) {
+ goto cleanup_rdatalist;
+ }
+ datalist->rdclass = dns_rdataclass_any;
+ datalist->type = dns_rdatatype_tsig;
+ ISC_LIST_APPEND(datalist->rdata, rdata, link);
+ RUNTIME_CHECK(dns_rdatalist_tordataset(datalist, dataset) ==
+ ISC_R_SUCCESS);
+ msg->tsig = dataset;
+ msg->tsigname = owner;
+
+ /* Windows does not like the tsig name being compressed. */
+ msg->tsigname->attributes |= DNS_NAMEATTR_NOCOMPRESS;
+
+ return (ISC_R_SUCCESS);
+
+cleanup_rdatalist:
+ dns_message_puttemprdatalist(msg, &datalist);
+cleanup_owner:
+ dns_message_puttempname(msg, &owner);
+ goto cleanup_rdata;
+cleanup_dynbuf:
+ isc_buffer_free(&dynbuf);
+cleanup_rdata:
+ dns_message_puttemprdata(msg, &rdata);
+cleanup_signature:
+ if (tsig.signature != NULL) {
+ isc_mem_put(mctx, tsig.signature, sigsize);
+ }
+cleanup_context:
+ if (ctx != NULL) {
+ dst_context_destroy(&ctx);
+ }
+ return (ret);
+}
+
+isc_result_t
+dns_tsig_verify(isc_buffer_t *source, dns_message_t *msg,
+ dns_tsig_keyring_t *ring1, dns_tsig_keyring_t *ring2) {
+ dns_rdata_any_tsig_t tsig, querytsig;
+ isc_region_t r, source_r, header_r, sig_r;
+ isc_buffer_t databuf;
+ unsigned char data[32];
+ dns_name_t *keyname;
+ dns_rdata_t rdata = DNS_RDATA_INIT;
+ isc_stdtime_t now;
+ isc_result_t ret;
+ dns_tsigkey_t *tsigkey;
+ dst_key_t *key = NULL;
+ unsigned char header[DNS_MESSAGE_HEADERLEN];
+ dst_context_t *ctx = NULL;
+ isc_mem_t *mctx;
+ uint16_t addcount, id;
+ unsigned int siglen;
+ unsigned int alg;
+ bool response;
+
+ REQUIRE(source != NULL);
+ REQUIRE(DNS_MESSAGE_VALID(msg));
+ tsigkey = dns_message_gettsigkey(msg);
+ response = is_response(msg);
+
+ REQUIRE(tsigkey == NULL || VALID_TSIG_KEY(tsigkey));
+
+ msg->verify_attempted = 1;
+ msg->verified_sig = 0;
+ msg->tsigstatus = dns_tsigerror_badsig;
+
+ if (msg->tcp_continuation) {
+ if (tsigkey == NULL || msg->querytsig == NULL) {
+ return (DNS_R_UNEXPECTEDTSIG);
+ }
+ return (tsig_verify_tcp(source, msg));
+ }
+
+ /*
+ * There should be a TSIG record...
+ */
+ if (msg->tsig == NULL) {
+ return (DNS_R_EXPECTEDTSIG);
+ }
+
+ /*
+ * If this is a response and there's no key or query TSIG, there
+ * shouldn't be one on the response.
+ */
+ if (response && (tsigkey == NULL || msg->querytsig == NULL)) {
+ return (DNS_R_UNEXPECTEDTSIG);
+ }
+
+ mctx = msg->mctx;
+
+ /*
+ * If we're here, we know the message is well formed and contains a
+ * TSIG record.
+ */
+
+ keyname = msg->tsigname;
+ ret = dns_rdataset_first(msg->tsig);
+ if (ret != ISC_R_SUCCESS) {
+ return (ret);
+ }
+ dns_rdataset_current(msg->tsig, &rdata);
+ ret = dns_rdata_tostruct(&rdata, &tsig, NULL);
+ if (ret != ISC_R_SUCCESS) {
+ return (ret);
+ }
+ dns_rdata_reset(&rdata);
+ if (response) {
+ ret = dns_rdataset_first(msg->querytsig);
+ if (ret != ISC_R_SUCCESS) {
+ return (ret);
+ }
+ dns_rdataset_current(msg->querytsig, &rdata);
+ ret = dns_rdata_tostruct(&rdata, &querytsig, NULL);
+ if (ret != ISC_R_SUCCESS) {
+ return (ret);
+ }
+ }
+
+ /*
+ * Do the key name and algorithm match that of the query?
+ */
+ if (response &&
+ (!dns_name_equal(keyname, &tsigkey->name) ||
+ !dns_name_equal(&tsig.algorithm, &querytsig.algorithm)))
+ {
+ msg->tsigstatus = dns_tsigerror_badkey;
+ tsig_log(msg->tsigkey, 2,
+ "key name and algorithm do not match");
+ return (DNS_R_TSIGVERIFYFAILURE);
+ }
+
+ /*
+ * Get the current time.
+ */
+ if (msg->fuzzing) {
+ now = msg->fuzztime;
+ } else {
+ isc_stdtime_get(&now);
+ }
+
+ /*
+ * Find dns_tsigkey_t based on keyname.
+ */
+ if (tsigkey == NULL) {
+ ret = ISC_R_NOTFOUND;
+ if (ring1 != NULL) {
+ ret = dns_tsigkey_find(&tsigkey, keyname,
+ &tsig.algorithm, ring1);
+ }
+ if (ret == ISC_R_NOTFOUND && ring2 != NULL) {
+ ret = dns_tsigkey_find(&tsigkey, keyname,
+ &tsig.algorithm, ring2);
+ }
+ if (ret != ISC_R_SUCCESS) {
+ msg->tsigstatus = dns_tsigerror_badkey;
+ ret = dns_tsigkey_create(keyname, &tsig.algorithm, NULL,
+ 0, false, NULL, now, now, mctx,
+ NULL, &msg->tsigkey);
+ if (ret != ISC_R_SUCCESS) {
+ return (ret);
+ }
+ tsig_log(msg->tsigkey, 2, "unknown key");
+ return (DNS_R_TSIGVERIFYFAILURE);
+ }
+ msg->tsigkey = tsigkey;
+ }
+
+ key = tsigkey->key;
+
+ /*
+ * Check digest length.
+ */
+ alg = dst_key_alg(key);
+ ret = dst_key_sigsize(key, &siglen);
+ if (ret != ISC_R_SUCCESS) {
+ return (ret);
+ }
+ if (dns__tsig_algvalid(alg)) {
+ if (tsig.siglen > siglen) {
+ tsig_log(msg->tsigkey, 2, "signature length too big");
+ return (DNS_R_FORMERR);
+ }
+ if (tsig.siglen > 0 &&
+ (tsig.siglen < 10 || tsig.siglen < ((siglen + 1) / 2)))
+ {
+ tsig_log(msg->tsigkey, 2,
+ "signature length below minimum");
+ return (DNS_R_FORMERR);
+ }
+ }
+
+ if (tsig.siglen > 0) {
+ uint16_t addcount_n;
+
+ sig_r.base = tsig.signature;
+ sig_r.length = tsig.siglen;
+
+ ret = dst_context_create(key, mctx, DNS_LOGCATEGORY_DNSSEC,
+ false, 0, &ctx);
+ if (ret != ISC_R_SUCCESS) {
+ return (ret);
+ }
+
+ if (response) {
+ isc_buffer_init(&databuf, data, sizeof(data));
+ isc_buffer_putuint16(&databuf, querytsig.siglen);
+ isc_buffer_usedregion(&databuf, &r);
+ ret = dst_context_adddata(ctx, &r);
+ if (ret != ISC_R_SUCCESS) {
+ goto cleanup_context;
+ }
+ if (querytsig.siglen > 0) {
+ r.length = querytsig.siglen;
+ r.base = querytsig.signature;
+ ret = dst_context_adddata(ctx, &r);
+ if (ret != ISC_R_SUCCESS) {
+ goto cleanup_context;
+ }
+ }
+ }
+
+ /*
+ * Extract the header.
+ */
+ isc_buffer_usedregion(source, &r);
+ memmove(header, r.base, DNS_MESSAGE_HEADERLEN);
+ isc_region_consume(&r, DNS_MESSAGE_HEADERLEN);
+
+ /*
+ * Decrement the additional field counter.
+ */
+ memmove(&addcount, &header[DNS_MESSAGE_HEADERLEN - 2], 2);
+ addcount_n = ntohs(addcount);
+ addcount = htons((uint16_t)(addcount_n - 1));
+ memmove(&header[DNS_MESSAGE_HEADERLEN - 2], &addcount, 2);
+
+ /*
+ * Put in the original id.
+ */
+ id = htons(tsig.originalid);
+ memmove(&header[0], &id, 2);
+
+ /*
+ * Digest the modified header.
+ */
+ header_r.base = (unsigned char *)header;
+ header_r.length = DNS_MESSAGE_HEADERLEN;
+ ret = dst_context_adddata(ctx, &header_r);
+ if (ret != ISC_R_SUCCESS) {
+ goto cleanup_context;
+ }
+
+ /*
+ * Digest all non-TSIG records.
+ */
+ isc_buffer_usedregion(source, &source_r);
+ r.base = source_r.base + DNS_MESSAGE_HEADERLEN;
+ r.length = msg->sigstart - DNS_MESSAGE_HEADERLEN;
+ ret = dst_context_adddata(ctx, &r);
+ if (ret != ISC_R_SUCCESS) {
+ goto cleanup_context;
+ }
+
+ /*
+ * Digest the key name.
+ */
+ dns_name_toregion(&tsigkey->name, &r);
+ ret = dst_context_adddata(ctx, &r);
+ if (ret != ISC_R_SUCCESS) {
+ goto cleanup_context;
+ }
+
+ isc_buffer_init(&databuf, data, sizeof(data));
+ isc_buffer_putuint16(&databuf, tsig.common.rdclass);
+ isc_buffer_putuint32(&databuf, msg->tsig->ttl);
+ isc_buffer_usedregion(&databuf, &r);
+ ret = dst_context_adddata(ctx, &r);
+ if (ret != ISC_R_SUCCESS) {
+ goto cleanup_context;
+ }
+
+ /*
+ * Digest the key algorithm.
+ */
+ dns_name_toregion(tsigkey->algorithm, &r);
+ ret = dst_context_adddata(ctx, &r);
+ if (ret != ISC_R_SUCCESS) {
+ goto cleanup_context;
+ }
+
+ isc_buffer_clear(&databuf);
+ isc_buffer_putuint48(&databuf, tsig.timesigned);
+ isc_buffer_putuint16(&databuf, tsig.fudge);
+ isc_buffer_putuint16(&databuf, tsig.error);
+ isc_buffer_putuint16(&databuf, tsig.otherlen);
+ isc_buffer_usedregion(&databuf, &r);
+ ret = dst_context_adddata(ctx, &r);
+ if (ret != ISC_R_SUCCESS) {
+ goto cleanup_context;
+ }
+
+ if (tsig.otherlen > 0) {
+ r.base = tsig.other;
+ r.length = tsig.otherlen;
+ ret = dst_context_adddata(ctx, &r);
+ if (ret != ISC_R_SUCCESS) {
+ goto cleanup_context;
+ }
+ }
+
+ ret = dst_context_verify(ctx, &sig_r);
+ if (ret == DST_R_VERIFYFAILURE) {
+ ret = DNS_R_TSIGVERIFYFAILURE;
+ tsig_log(msg->tsigkey, 2,
+ "signature failed to verify(1)");
+ goto cleanup_context;
+ } else if (ret != ISC_R_SUCCESS) {
+ goto cleanup_context;
+ }
+ msg->verified_sig = 1;
+ } else if (!response || (tsig.error != dns_tsigerror_badsig &&
+ tsig.error != dns_tsigerror_badkey))
+ {
+ tsig_log(msg->tsigkey, 2, "signature was empty");
+ return (DNS_R_TSIGVERIFYFAILURE);
+ }
+
+ /*
+ * Here at this point, the MAC has been verified. Even if any of
+ * the following code returns a TSIG error, the reply will be
+ * signed and WILL always include the request MAC in the digest
+ * computation.
+ */
+
+ /*
+ * Is the time ok?
+ */
+ if (now + msg->timeadjust > tsig.timesigned + tsig.fudge) {
+ msg->tsigstatus = dns_tsigerror_badtime;
+ tsig_log(msg->tsigkey, 2, "signature has expired");
+ ret = DNS_R_CLOCKSKEW;
+ goto cleanup_context;
+ } else if (now + msg->timeadjust < tsig.timesigned - tsig.fudge) {
+ msg->tsigstatus = dns_tsigerror_badtime;
+ tsig_log(msg->tsigkey, 2, "signature is in the future");
+ ret = DNS_R_CLOCKSKEW;
+ goto cleanup_context;
+ }
+
+ if (dns__tsig_algvalid(alg)) {
+ uint16_t digestbits = dst_key_getbits(key);
+
+ if (tsig.siglen > 0 && digestbits != 0 &&
+ tsig.siglen < ((digestbits + 7) / 8))
+ {
+ msg->tsigstatus = dns_tsigerror_badtrunc;
+ tsig_log(msg->tsigkey, 2,
+ "truncated signature length too small");
+ ret = DNS_R_TSIGVERIFYFAILURE;
+ goto cleanup_context;
+ }
+ if (tsig.siglen > 0 && digestbits == 0 && tsig.siglen < siglen)
+ {
+ msg->tsigstatus = dns_tsigerror_badtrunc;
+ tsig_log(msg->tsigkey, 2, "signature length too small");
+ ret = DNS_R_TSIGVERIFYFAILURE;
+ goto cleanup_context;
+ }
+ }
+
+ if (response && tsig.error != dns_rcode_noerror) {
+ msg->tsigstatus = tsig.error;
+ if (tsig.error == dns_tsigerror_badtime) {
+ ret = DNS_R_CLOCKSKEW;
+ } else {
+ ret = DNS_R_TSIGERRORSET;
+ }
+ goto cleanup_context;
+ }
+
+ msg->tsigstatus = dns_rcode_noerror;
+ ret = ISC_R_SUCCESS;
+
+cleanup_context:
+ if (ctx != NULL) {
+ dst_context_destroy(&ctx);
+ }
+
+ return (ret);
+}
+
+static isc_result_t
+tsig_verify_tcp(isc_buffer_t *source, dns_message_t *msg) {
+ dns_rdata_any_tsig_t tsig, querytsig;
+ isc_region_t r, source_r, header_r, sig_r;
+ isc_buffer_t databuf;
+ unsigned char data[32];
+ dns_name_t *keyname;
+ dns_rdata_t rdata = DNS_RDATA_INIT;
+ isc_stdtime_t now;
+ isc_result_t ret;
+ dns_tsigkey_t *tsigkey;
+ dst_key_t *key = NULL;
+ unsigned char header[DNS_MESSAGE_HEADERLEN];
+ uint16_t addcount, id;
+ bool has_tsig = false;
+ isc_mem_t *mctx;
+ unsigned int siglen;
+ unsigned int alg;
+
+ REQUIRE(source != NULL);
+ REQUIRE(msg != NULL);
+ REQUIRE(dns_message_gettsigkey(msg) != NULL);
+ REQUIRE(msg->tcp_continuation == 1);
+ REQUIRE(msg->querytsig != NULL);
+
+ msg->verified_sig = 0;
+ msg->tsigstatus = dns_tsigerror_badsig;
+
+ if (!is_response(msg)) {
+ return (DNS_R_EXPECTEDRESPONSE);
+ }
+
+ mctx = msg->mctx;
+
+ tsigkey = dns_message_gettsigkey(msg);
+ key = tsigkey->key;
+
+ /*
+ * Extract and parse the previous TSIG
+ */
+ ret = dns_rdataset_first(msg->querytsig);
+ if (ret != ISC_R_SUCCESS) {
+ return (ret);
+ }
+ dns_rdataset_current(msg->querytsig, &rdata);
+ ret = dns_rdata_tostruct(&rdata, &querytsig, NULL);
+ if (ret != ISC_R_SUCCESS) {
+ return (ret);
+ }
+ dns_rdata_reset(&rdata);
+
+ /*
+ * If there is a TSIG in this message, do some checks.
+ */
+ if (msg->tsig != NULL) {
+ has_tsig = true;
+
+ keyname = msg->tsigname;
+ ret = dns_rdataset_first(msg->tsig);
+ if (ret != ISC_R_SUCCESS) {
+ goto cleanup_querystruct;
+ }
+ dns_rdataset_current(msg->tsig, &rdata);
+ ret = dns_rdata_tostruct(&rdata, &tsig, NULL);
+ if (ret != ISC_R_SUCCESS) {
+ goto cleanup_querystruct;
+ }
+
+ /*
+ * Do the key name and algorithm match that of the query?
+ */
+ if (!dns_name_equal(keyname, &tsigkey->name) ||
+ !dns_name_equal(&tsig.algorithm, &querytsig.algorithm))
+ {
+ msg->tsigstatus = dns_tsigerror_badkey;
+ ret = DNS_R_TSIGVERIFYFAILURE;
+ tsig_log(msg->tsigkey, 2,
+ "key name and algorithm do not match");
+ goto cleanup_querystruct;
+ }
+
+ /*
+ * Check digest length.
+ */
+ alg = dst_key_alg(key);
+ ret = dst_key_sigsize(key, &siglen);
+ if (ret != ISC_R_SUCCESS) {
+ goto cleanup_querystruct;
+ }
+ if (dns__tsig_algvalid(alg)) {
+ if (tsig.siglen > siglen) {
+ tsig_log(tsigkey, 2,
+ "signature length too big");
+ ret = DNS_R_FORMERR;
+ goto cleanup_querystruct;
+ }
+ if (tsig.siglen > 0 &&
+ (tsig.siglen < 10 ||
+ tsig.siglen < ((siglen + 1) / 2)))
+ {
+ tsig_log(tsigkey, 2,
+ "signature length below minimum");
+ ret = DNS_R_FORMERR;
+ goto cleanup_querystruct;
+ }
+ }
+ }
+
+ if (msg->tsigctx == NULL) {
+ ret = dst_context_create(key, mctx, DNS_LOGCATEGORY_DNSSEC,
+ false, 0, &msg->tsigctx);
+ if (ret != ISC_R_SUCCESS) {
+ goto cleanup_querystruct;
+ }
+
+ /*
+ * Digest the length of the query signature
+ */
+ isc_buffer_init(&databuf, data, sizeof(data));
+ isc_buffer_putuint16(&databuf, querytsig.siglen);
+ isc_buffer_usedregion(&databuf, &r);
+ ret = dst_context_adddata(msg->tsigctx, &r);
+ if (ret != ISC_R_SUCCESS) {
+ goto cleanup_context;
+ }
+
+ /*
+ * Digest the data of the query signature
+ */
+ if (querytsig.siglen > 0) {
+ r.length = querytsig.siglen;
+ r.base = querytsig.signature;
+ ret = dst_context_adddata(msg->tsigctx, &r);
+ if (ret != ISC_R_SUCCESS) {
+ goto cleanup_context;
+ }
+ }
+ }
+
+ /*
+ * Extract the header.
+ */
+ isc_buffer_usedregion(source, &r);
+ memmove(header, r.base, DNS_MESSAGE_HEADERLEN);
+ isc_region_consume(&r, DNS_MESSAGE_HEADERLEN);
+
+ /*
+ * Decrement the additional field counter if necessary.
+ */
+ if (has_tsig) {
+ uint16_t addcount_n;
+
+ memmove(&addcount, &header[DNS_MESSAGE_HEADERLEN - 2], 2);
+ addcount_n = ntohs(addcount);
+ addcount = htons((uint16_t)(addcount_n - 1));
+ memmove(&header[DNS_MESSAGE_HEADERLEN - 2], &addcount, 2);
+
+ /*
+ * Put in the original id.
+ *
+ * XXX Can TCP transfers be forwarded? How would that
+ * work?
+ */
+ id = htons(tsig.originalid);
+ memmove(&header[0], &id, 2);
+ }
+
+ /*
+ * Digest the modified header.
+ */
+ header_r.base = (unsigned char *)header;
+ header_r.length = DNS_MESSAGE_HEADERLEN;
+ ret = dst_context_adddata(msg->tsigctx, &header_r);
+ if (ret != ISC_R_SUCCESS) {
+ goto cleanup_context;
+ }
+
+ /*
+ * Digest all non-TSIG records.
+ */
+ isc_buffer_usedregion(source, &source_r);
+ r.base = source_r.base + DNS_MESSAGE_HEADERLEN;
+ if (has_tsig) {
+ r.length = msg->sigstart - DNS_MESSAGE_HEADERLEN;
+ } else {
+ r.length = source_r.length - DNS_MESSAGE_HEADERLEN;
+ }
+ ret = dst_context_adddata(msg->tsigctx, &r);
+ if (ret != ISC_R_SUCCESS) {
+ goto cleanup_context;
+ }
+
+ /*
+ * Digest the time signed and fudge.
+ */
+ if (has_tsig) {
+ isc_buffer_init(&databuf, data, sizeof(data));
+ isc_buffer_putuint48(&databuf, tsig.timesigned);
+ isc_buffer_putuint16(&databuf, tsig.fudge);
+ isc_buffer_usedregion(&databuf, &r);
+ ret = dst_context_adddata(msg->tsigctx, &r);
+ if (ret != ISC_R_SUCCESS) {
+ goto cleanup_context;
+ }
+
+ sig_r.base = tsig.signature;
+ sig_r.length = tsig.siglen;
+ if (tsig.siglen == 0) {
+ if (tsig.error != dns_rcode_noerror) {
+ msg->tsigstatus = tsig.error;
+ if (tsig.error == dns_tsigerror_badtime) {
+ ret = DNS_R_CLOCKSKEW;
+ } else {
+ ret = DNS_R_TSIGERRORSET;
+ }
+ } else {
+ tsig_log(msg->tsigkey, 2, "signature is empty");
+ ret = DNS_R_TSIGVERIFYFAILURE;
+ }
+ goto cleanup_context;
+ }
+
+ ret = dst_context_verify(msg->tsigctx, &sig_r);
+ if (ret == DST_R_VERIFYFAILURE) {
+ tsig_log(msg->tsigkey, 2,
+ "signature failed to verify(2)");
+ ret = DNS_R_TSIGVERIFYFAILURE;
+ goto cleanup_context;
+ } else if (ret != ISC_R_SUCCESS) {
+ goto cleanup_context;
+ }
+ msg->verified_sig = 1;
+
+ /*
+ * Here at this point, the MAC has been verified. Even
+ * if any of the following code returns a TSIG error,
+ * the reply will be signed and WILL always include the
+ * request MAC in the digest computation.
+ */
+
+ /*
+ * Is the time ok?
+ */
+ if (msg->fuzzing) {
+ now = msg->fuzztime;
+ } else {
+ isc_stdtime_get(&now);
+ }
+
+ if (now + msg->timeadjust > tsig.timesigned + tsig.fudge) {
+ msg->tsigstatus = dns_tsigerror_badtime;
+ tsig_log(msg->tsigkey, 2, "signature has expired");
+ ret = DNS_R_CLOCKSKEW;
+ goto cleanup_context;
+ } else if (now + msg->timeadjust < tsig.timesigned - tsig.fudge)
+ {
+ msg->tsigstatus = dns_tsigerror_badtime;
+ tsig_log(msg->tsigkey, 2, "signature is in the future");
+ ret = DNS_R_CLOCKSKEW;
+ goto cleanup_context;
+ }
+
+ alg = dst_key_alg(key);
+ ret = dst_key_sigsize(key, &siglen);
+ if (ret != ISC_R_SUCCESS) {
+ goto cleanup_context;
+ }
+ if (dns__tsig_algvalid(alg)) {
+ uint16_t digestbits = dst_key_getbits(key);
+
+ if (tsig.siglen > 0 && digestbits != 0 &&
+ tsig.siglen < ((digestbits + 7) / 8))
+ {
+ msg->tsigstatus = dns_tsigerror_badtrunc;
+ tsig_log(msg->tsigkey, 2,
+ "truncated signature length "
+ "too small");
+ ret = DNS_R_TSIGVERIFYFAILURE;
+ goto cleanup_context;
+ }
+ if (tsig.siglen > 0 && digestbits == 0 &&
+ tsig.siglen < siglen)
+ {
+ msg->tsigstatus = dns_tsigerror_badtrunc;
+ tsig_log(msg->tsigkey, 2,
+ "signature length too small");
+ ret = DNS_R_TSIGVERIFYFAILURE;
+ goto cleanup_context;
+ }
+ }
+
+ if (tsig.error != dns_rcode_noerror) {
+ msg->tsigstatus = tsig.error;
+ if (tsig.error == dns_tsigerror_badtime) {
+ ret = DNS_R_CLOCKSKEW;
+ } else {
+ ret = DNS_R_TSIGERRORSET;
+ }
+ goto cleanup_context;
+ }
+ }
+
+ msg->tsigstatus = dns_rcode_noerror;
+ ret = ISC_R_SUCCESS;
+
+cleanup_context:
+ /*
+ * Except in error conditions, don't destroy the DST context
+ * for unsigned messages; it is a running sum till the next
+ * TSIG signed message.
+ */
+ if ((ret != ISC_R_SUCCESS || has_tsig) && msg->tsigctx != NULL) {
+ dst_context_destroy(&msg->tsigctx);
+ }
+
+cleanup_querystruct:
+ dns_rdata_freestruct(&querytsig);
+
+ return (ret);
+}
+
+isc_result_t
+dns_tsigkey_find(dns_tsigkey_t **tsigkey, const dns_name_t *name,
+ const dns_name_t *algorithm, dns_tsig_keyring_t *ring) {
+ dns_tsigkey_t *key;
+ isc_stdtime_t now;
+ isc_result_t result;
+
+ REQUIRE(tsigkey != NULL);
+ REQUIRE(*tsigkey == NULL);
+ REQUIRE(name != NULL);
+ REQUIRE(ring != NULL);
+
+ RWLOCK(&ring->lock, isc_rwlocktype_write);
+ cleanup_ring(ring);
+ RWUNLOCK(&ring->lock, isc_rwlocktype_write);
+
+ isc_stdtime_get(&now);
+ RWLOCK(&ring->lock, isc_rwlocktype_read);
+ key = NULL;
+ result = dns_rbt_findname(ring->keys, name, 0, NULL, (void *)&key);
+ if (result == DNS_R_PARTIALMATCH || result == ISC_R_NOTFOUND) {
+ RWUNLOCK(&ring->lock, isc_rwlocktype_read);
+ return (ISC_R_NOTFOUND);
+ }
+ if (algorithm != NULL && !dns_name_equal(key->algorithm, algorithm)) {
+ RWUNLOCK(&ring->lock, isc_rwlocktype_read);
+ return (ISC_R_NOTFOUND);
+ }
+ if (key->inception != key->expire && isc_serial_lt(key->expire, now)) {
+ /*
+ * The key has expired.
+ */
+ RWUNLOCK(&ring->lock, isc_rwlocktype_read);
+ RWLOCK(&ring->lock, isc_rwlocktype_write);
+ remove_fromring(key);
+ RWUNLOCK(&ring->lock, isc_rwlocktype_write);
+ return (ISC_R_NOTFOUND);
+ }
+#if 0
+ /*
+ * MPAXXX We really should look at the inception time.
+ */
+ if (key->inception != key->expire &&
+ isc_serial_lt(key->inception, now)) {
+ RWUNLOCK(&ring->lock, isc_rwlocktype_read);
+ adjust_lru(key);
+ return (ISC_R_NOTFOUND);
+ }
+#endif /* if 0 */
+ isc_refcount_increment(&key->refs);
+ RWUNLOCK(&ring->lock, isc_rwlocktype_read);
+ adjust_lru(key);
+ *tsigkey = key;
+ return (ISC_R_SUCCESS);
+}
+
+static void
+free_tsignode(void *node, void *_unused) {
+ dns_tsigkey_t *key;
+
+ REQUIRE(node != NULL);
+
+ UNUSED(_unused);
+
+ key = node;
+ if (key->generated) {
+ if (ISC_LINK_LINKED(key, link)) {
+ ISC_LIST_UNLINK(key->ring->lru, key, link);
+ }
+ }
+ dns_tsigkey_detach(&key);
+}
+
+isc_result_t
+dns_tsigkeyring_create(isc_mem_t *mctx, dns_tsig_keyring_t **ringp) {
+ isc_result_t result;
+ dns_tsig_keyring_t *ring;
+
+ REQUIRE(mctx != NULL);
+ REQUIRE(ringp != NULL);
+ REQUIRE(*ringp == NULL);
+
+ ring = isc_mem_get(mctx, sizeof(dns_tsig_keyring_t));
+
+ isc_rwlock_init(&ring->lock, 0, 0);
+ ring->keys = NULL;
+ result = dns_rbt_create(mctx, free_tsignode, NULL, &ring->keys);
+ if (result != ISC_R_SUCCESS) {
+ isc_rwlock_destroy(&ring->lock);
+ isc_mem_put(mctx, ring, sizeof(dns_tsig_keyring_t));
+ return (result);
+ }
+
+ ring->writecount = 0;
+ ring->mctx = NULL;
+ ring->generated = 0;
+ ring->maxgenerated = DNS_TSIG_MAXGENERATEDKEYS;
+ ISC_LIST_INIT(ring->lru);
+ isc_mem_attach(mctx, &ring->mctx);
+ isc_refcount_init(&ring->references, 1);
+
+ *ringp = ring;
+ return (ISC_R_SUCCESS);
+}
+
+isc_result_t
+dns_tsigkeyring_add(dns_tsig_keyring_t *ring, const dns_name_t *name,
+ dns_tsigkey_t *tkey) {
+ isc_result_t result;
+
+ REQUIRE(VALID_TSIG_KEY(tkey));
+ REQUIRE(tkey->ring == NULL);
+ REQUIRE(name != NULL);
+
+ result = keyring_add(ring, name, tkey);
+ if (result == ISC_R_SUCCESS) {
+ isc_refcount_increment(&tkey->refs);
+ }
+
+ return (result);
+}
+
+void
+dns_tsigkeyring_attach(dns_tsig_keyring_t *source,
+ dns_tsig_keyring_t **target) {
+ REQUIRE(source != NULL);
+ REQUIRE(target != NULL && *target == NULL);
+
+ isc_refcount_increment(&source->references);
+
+ *target = source;
+}
+
+void
+dns_tsigkeyring_detach(dns_tsig_keyring_t **ringp) {
+ dns_tsig_keyring_t *ring;
+
+ REQUIRE(ringp != NULL);
+ REQUIRE(*ringp != NULL);
+
+ ring = *ringp;
+ *ringp = NULL;
+
+ if (isc_refcount_decrement(&ring->references) == 1) {
+ destroyring(ring);
+ }
+}
+
+void
+dns_keyring_restore(dns_tsig_keyring_t *ring, FILE *fp) {
+ isc_stdtime_t now;
+ isc_result_t result;
+
+ isc_stdtime_get(&now);
+ do {
+ result = restore_key(ring, now, fp);
+ if (result == ISC_R_NOMORE) {
+ return;
+ }
+ if (result == DNS_R_BADALG || result == DNS_R_EXPIRED) {
+ result = ISC_R_SUCCESS;
+ }
+ } while (result == ISC_R_SUCCESS);
+}
diff --git a/lib/dns/tsig_p.h b/lib/dns/tsig_p.h
new file mode 100644
index 0000000..a19688c
--- /dev/null
+++ b/lib/dns/tsig_p.h
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#pragma once
+
+/*! \file */
+
+#include <stdbool.h>
+
+#include <isc/result.h>
+
+#include <dns/types.h>
+
+/*%
+ * These functions must not be used outside this module and
+ * its associated unit tests.
+ */
+
+ISC_LANG_BEGINDECLS
+
+bool
+dns__tsig_algvalid(unsigned int alg);
+unsigned int
+dns__tsig_algfromname(const dns_name_t *algorithm);
+const dns_name_t *
+dns__tsig_algnamefromname(const dns_name_t *algorithm);
+bool
+dns__tsig_algallocated(const dns_name_t *algorithm);
+
+ISC_LANG_ENDDECLS
diff --git a/lib/dns/ttl.c b/lib/dns/ttl.c
new file mode 100644
index 0000000..9c6a8ae
--- /dev/null
+++ b/lib/dns/ttl.c
@@ -0,0 +1,225 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/*! \file */
+
+#include <ctype.h>
+#include <errno.h>
+#include <inttypes.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#include <isc/buffer.h>
+#include <isc/parseint.h>
+#include <isc/print.h>
+#include <isc/region.h>
+#include <isc/result.h>
+#include <isc/string.h>
+#include <isc/util.h>
+
+#include <dns/ttl.h>
+
+#define RETERR(x) \
+ do { \
+ isc_result_t _r = (x); \
+ if (_r != ISC_R_SUCCESS) \
+ return ((_r)); \
+ } while (0)
+
+static isc_result_t
+bind_ttl(isc_textregion_t *source, uint32_t *ttl);
+
+/*
+ * Helper for dns_ttl_totext().
+ */
+static isc_result_t
+ttlfmt(unsigned int t, const char *s, bool verbose, bool space,
+ isc_buffer_t *target) {
+ char tmp[60];
+ unsigned int len;
+ isc_region_t region;
+
+ if (verbose) {
+ len = snprintf(tmp, sizeof(tmp), "%s%u %s%s", space ? " " : "",
+ t, s, t == 1 ? "" : "s");
+ } else {
+ len = snprintf(tmp, sizeof(tmp), "%u%c", t, s[0]);
+ }
+
+ INSIST(len + 1 <= sizeof(tmp));
+ isc_buffer_availableregion(target, &region);
+ if (len > region.length) {
+ return (ISC_R_NOSPACE);
+ }
+ memmove(region.base, tmp, len);
+ isc_buffer_add(target, len);
+
+ return (ISC_R_SUCCESS);
+}
+
+/*
+ * Derived from bind8 ns_format_ttl().
+ */
+isc_result_t
+dns_ttl_totext(uint32_t src, bool verbose, bool upcase, isc_buffer_t *target) {
+ unsigned secs, mins, hours, days, weeks, x;
+
+ secs = src % 60;
+ src /= 60;
+ mins = src % 60;
+ src /= 60;
+ hours = src % 24;
+ src /= 24;
+ days = src % 7;
+ src /= 7;
+ weeks = src;
+ src = 0;
+ POST(src);
+
+ x = 0;
+ if (weeks != 0) {
+ RETERR(ttlfmt(weeks, "week", verbose, (x > 0), target));
+ x++;
+ }
+ if (days != 0) {
+ RETERR(ttlfmt(days, "day", verbose, (x > 0), target));
+ x++;
+ }
+ if (hours != 0) {
+ RETERR(ttlfmt(hours, "hour", verbose, (x > 0), target));
+ x++;
+ }
+ if (mins != 0) {
+ RETERR(ttlfmt(mins, "minute", verbose, (x > 0), target));
+ x++;
+ }
+ if (secs != 0 || (weeks == 0 && days == 0 && hours == 0 && mins == 0)) {
+ RETERR(ttlfmt(secs, "second", verbose, (x > 0), target));
+ x++;
+ }
+ INSIST(x > 0);
+ /*
+ * If only a single unit letter is printed, print it
+ * in upper case. (Why? Because BIND 8 does that.
+ * Presumably it has a reason.)
+ */
+ if (x == 1 && upcase && !verbose) {
+ isc_region_t region;
+ /*
+ * The unit letter is the last character in the
+ * used region of the buffer.
+ *
+ * toupper() does not need its argument to be masked of cast
+ * here because region.base is type unsigned char *.
+ */
+ isc_buffer_usedregion(target, &region);
+ region.base[region.length - 1] =
+ toupper(region.base[region.length - 1]);
+ }
+ return (ISC_R_SUCCESS);
+}
+
+isc_result_t
+dns_counter_fromtext(isc_textregion_t *source, uint32_t *ttl) {
+ return (bind_ttl(source, ttl));
+}
+
+isc_result_t
+dns_ttl_fromtext(isc_textregion_t *source, uint32_t *ttl) {
+ isc_result_t result;
+
+ result = bind_ttl(source, ttl);
+ if (result != ISC_R_SUCCESS && result != ISC_R_RANGE) {
+ result = DNS_R_BADTTL;
+ }
+ return (result);
+}
+
+static isc_result_t
+bind_ttl(isc_textregion_t *source, uint32_t *ttl) {
+ uint64_t tmp = 0ULL;
+ uint32_t n;
+ char *s;
+ char buf[64];
+ char nbuf[64]; /* Number buffer */
+
+ /*
+ * Copy the buffer as it may not be NULL terminated.
+ * No legal counter / ttl is longer that 63 characters.
+ */
+ if (source->length > sizeof(buf) - 1) {
+ return (DNS_R_SYNTAX);
+ }
+ /* Copy source->length bytes and NUL terminate. */
+ snprintf(buf, sizeof(buf), "%.*s", (int)source->length, source->base);
+ s = buf;
+
+ do {
+ isc_result_t result;
+
+ char *np = nbuf;
+ while (*s != '\0' && isdigit((unsigned char)*s)) {
+ *np++ = *s++;
+ }
+ *np++ = '\0';
+ INSIST(np - nbuf <= (int)sizeof(nbuf));
+ result = isc_parse_uint32(&n, nbuf, 10);
+ if (result != ISC_R_SUCCESS) {
+ return (DNS_R_SYNTAX);
+ }
+ switch (*s) {
+ case 'w':
+ case 'W':
+ tmp += (uint64_t)n * 7 * 24 * 3600;
+ s++;
+ break;
+ case 'd':
+ case 'D':
+ tmp += (uint64_t)n * 24 * 3600;
+ s++;
+ break;
+ case 'h':
+ case 'H':
+ tmp += (uint64_t)n * 3600;
+ s++;
+ break;
+ case 'm':
+ case 'M':
+ tmp += (uint64_t)n * 60;
+ s++;
+ break;
+ case 's':
+ case 'S':
+ tmp += (uint64_t)n;
+ s++;
+ break;
+ case '\0':
+ /* Plain number? */
+ if (tmp != 0ULL) {
+ return (DNS_R_SYNTAX);
+ }
+ tmp = n;
+ break;
+ default:
+ return (DNS_R_SYNTAX);
+ }
+ } while (*s != '\0');
+
+ if (tmp > 0xffffffffULL) {
+ return (ISC_R_RANGE);
+ }
+
+ *ttl = (uint32_t)(tmp & 0xffffffffUL);
+ return (ISC_R_SUCCESS);
+}
diff --git a/lib/dns/update.c b/lib/dns/update.c
new file mode 100644
index 0000000..13fd05b
--- /dev/null
+++ b/lib/dns/update.c
@@ -0,0 +1,2279 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#include <inttypes.h>
+#include <stdbool.h>
+#include <time.h>
+
+#include <isc/log.h>
+#include <isc/magic.h>
+#include <isc/mem.h>
+#include <isc/netaddr.h>
+#include <isc/print.h>
+#include <isc/random.h>
+#include <isc/result.h>
+#include <isc/serial.h>
+#include <isc/stats.h>
+#include <isc/stdtime.h>
+#include <isc/string.h>
+#include <isc/taskpool.h>
+#include <isc/time.h>
+#include <isc/util.h>
+
+#include <dns/db.h>
+#include <dns/dbiterator.h>
+#include <dns/diff.h>
+#include <dns/dnssec.h>
+#include <dns/events.h>
+#include <dns/fixedname.h>
+#include <dns/journal.h>
+#include <dns/kasp.h>
+#include <dns/keyvalues.h>
+#include <dns/log.h>
+#include <dns/message.h>
+#include <dns/nsec.h>
+#include <dns/nsec3.h>
+#include <dns/private.h>
+#include <dns/rdataclass.h>
+#include <dns/rdataset.h>
+#include <dns/rdatasetiter.h>
+#include <dns/rdatastruct.h>
+#include <dns/rdatatype.h>
+#include <dns/soa.h>
+#include <dns/ssu.h>
+#include <dns/stats.h>
+#include <dns/tsig.h>
+#include <dns/update.h>
+#include <dns/view.h>
+#include <dns/zone.h>
+#include <dns/zt.h>
+
+/**************************************************************************/
+
+#define STATE_MAGIC ISC_MAGIC('S', 'T', 'T', 'E')
+#define DNS_STATE_VALID(state) ISC_MAGIC_VALID(state, STATE_MAGIC)
+
+/*%
+ * Log level for tracing dynamic update protocol requests.
+ */
+#define LOGLEVEL_PROTOCOL ISC_LOG_INFO
+
+/*%
+ * Log level for low-level debug tracing.
+ */
+#define LOGLEVEL_DEBUG ISC_LOG_DEBUG(8)
+
+/*%
+ * Check an operation for failure. These macros all assume that
+ * the function using them has a 'result' variable and a 'failure'
+ * label.
+ */
+#define CHECK(op) \
+ do { \
+ result = (op); \
+ if (result != ISC_R_SUCCESS) \
+ goto failure; \
+ } while (0)
+
+/*%
+ * Fail unconditionally with result 'code', which must not
+ * be ISC_R_SUCCESS. The reason for failure presumably has
+ * been logged already.
+ *
+ * The test against ISC_R_SUCCESS is there to keep the Solaris compiler
+ * from complaining about "end-of-loop code not reached".
+ */
+
+#define FAIL(code) \
+ do { \
+ result = (code); \
+ if (result != ISC_R_SUCCESS) \
+ goto failure; \
+ } while (0)
+
+/*%
+ * Fail unconditionally and log as a client error.
+ * The test against ISC_R_SUCCESS is there to keep the Solaris compiler
+ * from complaining about "end-of-loop code not reached".
+ */
+#define FAILC(code, msg) \
+ do { \
+ const char *_what = "failed"; \
+ result = (code); \
+ switch (result) { \
+ case DNS_R_NXDOMAIN: \
+ case DNS_R_YXDOMAIN: \
+ case DNS_R_YXRRSET: \
+ case DNS_R_NXRRSET: \
+ _what = "unsuccessful"; \
+ } \
+ update_log(log, zone, LOGLEVEL_PROTOCOL, "update %s: %s (%s)", \
+ _what, msg, isc_result_totext(result)); \
+ if (result != ISC_R_SUCCESS) \
+ goto failure; \
+ } while (0)
+
+#define FAILN(code, name, msg) \
+ do { \
+ const char *_what = "failed"; \
+ result = (code); \
+ switch (result) { \
+ case DNS_R_NXDOMAIN: \
+ case DNS_R_YXDOMAIN: \
+ case DNS_R_YXRRSET: \
+ case DNS_R_NXRRSET: \
+ _what = "unsuccessful"; \
+ } \
+ if (isc_log_wouldlog(dns_lctx, LOGLEVEL_PROTOCOL)) { \
+ char _nbuf[DNS_NAME_FORMATSIZE]; \
+ dns_name_format(name, _nbuf, sizeof(_nbuf)); \
+ update_log(log, zone, LOGLEVEL_PROTOCOL, \
+ "update %s: %s: %s (%s)", _what, _nbuf, \
+ msg, isc_result_totext(result)); \
+ } \
+ if (result != ISC_R_SUCCESS) \
+ goto failure; \
+ } while (0)
+
+#define FAILNT(code, name, type, msg) \
+ do { \
+ const char *_what = "failed"; \
+ result = (code); \
+ switch (result) { \
+ case DNS_R_NXDOMAIN: \
+ case DNS_R_YXDOMAIN: \
+ case DNS_R_YXRRSET: \
+ case DNS_R_NXRRSET: \
+ _what = "unsuccessful"; \
+ } \
+ if (isc_log_wouldlog(dns_lctx, LOGLEVEL_PROTOCOL)) { \
+ char _nbuf[DNS_NAME_FORMATSIZE]; \
+ char _tbuf[DNS_RDATATYPE_FORMATSIZE]; \
+ dns_name_format(name, _nbuf, sizeof(_nbuf)); \
+ dns_rdatatype_format(type, _tbuf, sizeof(_tbuf)); \
+ update_log(log, zone, LOGLEVEL_PROTOCOL, \
+ "update %s: %s/%s: %s (%s)", _what, _nbuf, \
+ _tbuf, msg, isc_result_totext(result)); \
+ } \
+ if (result != ISC_R_SUCCESS) \
+ goto failure; \
+ } while (0)
+
+/*%
+ * Fail unconditionally and log as a server error.
+ * The test against ISC_R_SUCCESS is there to keep the Solaris compiler
+ * from complaining about "end-of-loop code not reached".
+ */
+#define FAILS(code, msg) \
+ do { \
+ result = (code); \
+ update_log(log, zone, LOGLEVEL_PROTOCOL, "error: %s: %s", msg, \
+ isc_result_totext(result)); \
+ if (result != ISC_R_SUCCESS) \
+ goto failure; \
+ } while (0)
+
+/**************************************************************************/
+
+typedef struct rr rr_t;
+
+struct rr {
+ /* dns_name_t name; */
+ uint32_t ttl;
+ dns_rdata_t rdata;
+};
+
+typedef struct update_event update_event_t;
+
+/**************************************************************************/
+
+static void
+update_log(dns_update_log_t *callback, dns_zone_t *zone, int level,
+ const char *fmt, ...) ISC_FORMAT_PRINTF(4, 5);
+
+static void
+update_log(dns_update_log_t *callback, dns_zone_t *zone, int level,
+ const char *fmt, ...) {
+ va_list ap;
+ char message[4096];
+
+ if (callback == NULL) {
+ return;
+ }
+
+ if (!isc_log_wouldlog(dns_lctx, level)) {
+ return;
+ }
+
+ va_start(ap, fmt);
+ vsnprintf(message, sizeof(message), fmt, ap);
+ va_end(ap);
+
+ (callback->func)(callback->arg, zone, level, message);
+}
+
+/*%
+ * Update a single RR in version 'ver' of 'db' and log the
+ * update in 'diff'.
+ *
+ * Ensures:
+ * \li '*tuple' == NULL. Either the tuple is freed, or its
+ * ownership has been transferred to the diff.
+ */
+static isc_result_t
+do_one_tuple(dns_difftuple_t **tuple, dns_db_t *db, dns_dbversion_t *ver,
+ dns_diff_t *diff) {
+ dns_diff_t temp_diff;
+ isc_result_t result;
+
+ /*
+ * Create a singleton diff.
+ */
+ dns_diff_init(diff->mctx, &temp_diff);
+ ISC_LIST_APPEND(temp_diff.tuples, *tuple, link);
+
+ /*
+ * Apply it to the database.
+ */
+ result = dns_diff_apply(&temp_diff, db, ver);
+ ISC_LIST_UNLINK(temp_diff.tuples, *tuple, link);
+ if (result != ISC_R_SUCCESS) {
+ dns_difftuple_free(tuple);
+ return (result);
+ }
+
+ /*
+ * Merge it into the current pending journal entry.
+ */
+ dns_diff_appendminimal(diff, tuple);
+
+ /*
+ * Do not clear temp_diff.
+ */
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+update_one_rr(dns_db_t *db, dns_dbversion_t *ver, dns_diff_t *diff,
+ dns_diffop_t op, dns_name_t *name, dns_ttl_t ttl,
+ dns_rdata_t *rdata) {
+ dns_difftuple_t *tuple = NULL;
+ isc_result_t result;
+ result = dns_difftuple_create(diff->mctx, op, name, ttl, rdata, &tuple);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+ return (do_one_tuple(&tuple, db, ver, diff));
+}
+
+/**************************************************************************/
+/*
+ * Callback-style iteration over rdatasets and rdatas.
+ *
+ * foreach_rrset() can be used to iterate over the RRsets
+ * of a name and call a callback function with each
+ * one. Similarly, foreach_rr() can be used to iterate
+ * over the individual RRs at name, optionally restricted
+ * to RRs of a given type.
+ *
+ * The callback functions are called "actions" and take
+ * two arguments: a void pointer for passing arbitrary
+ * context information, and a pointer to the current RRset
+ * or RR. By convention, their names end in "_action".
+ */
+
+/*
+ * XXXRTH We might want to make this public somewhere in libdns.
+ */
+
+/*%
+ * Function type for foreach_rrset() iterator actions.
+ */
+typedef isc_result_t
+rrset_func(void *data, dns_rdataset_t *rrset);
+
+/*%
+ * Function type for foreach_rr() iterator actions.
+ */
+typedef isc_result_t
+rr_func(void *data, rr_t *rr);
+
+/*%
+ * Internal context struct for foreach_node_rr().
+ */
+typedef struct {
+ rr_func *rr_action;
+ void *rr_action_data;
+} foreach_node_rr_ctx_t;
+
+/*%
+ * Internal helper function for foreach_node_rr().
+ */
+static isc_result_t
+foreach_node_rr_action(void *data, dns_rdataset_t *rdataset) {
+ isc_result_t result;
+ foreach_node_rr_ctx_t *ctx = data;
+ for (result = dns_rdataset_first(rdataset); result == ISC_R_SUCCESS;
+ result = dns_rdataset_next(rdataset))
+ {
+ rr_t rr = { 0, DNS_RDATA_INIT };
+
+ dns_rdataset_current(rdataset, &rr.rdata);
+ rr.ttl = rdataset->ttl;
+ result = (*ctx->rr_action)(ctx->rr_action_data, &rr);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+ }
+ if (result != ISC_R_NOMORE) {
+ return (result);
+ }
+ return (ISC_R_SUCCESS);
+}
+
+/*%
+ * For each rdataset of 'name' in 'ver' of 'db', call 'action'
+ * with the rdataset and 'action_data' as arguments. If the name
+ * does not exist, do nothing.
+ *
+ * If 'action' returns an error, abort iteration and return the error.
+ */
+static isc_result_t
+foreach_rrset(dns_db_t *db, dns_dbversion_t *ver, dns_name_t *name,
+ rrset_func *action, void *action_data) {
+ isc_result_t result;
+ dns_dbnode_t *node;
+ dns_rdatasetiter_t *iter;
+
+ node = NULL;
+ result = dns_db_findnode(db, name, false, &node);
+ if (result == ISC_R_NOTFOUND) {
+ return (ISC_R_SUCCESS);
+ }
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+
+ iter = NULL;
+ result = dns_db_allrdatasets(db, node, ver, 0, (isc_stdtime_t)0, &iter);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup_node;
+ }
+
+ for (result = dns_rdatasetiter_first(iter); result == ISC_R_SUCCESS;
+ result = dns_rdatasetiter_next(iter))
+ {
+ dns_rdataset_t rdataset;
+
+ dns_rdataset_init(&rdataset);
+ dns_rdatasetiter_current(iter, &rdataset);
+
+ result = (*action)(action_data, &rdataset);
+
+ dns_rdataset_disassociate(&rdataset);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup_iterator;
+ }
+ }
+ if (result == ISC_R_NOMORE) {
+ result = ISC_R_SUCCESS;
+ }
+
+cleanup_iterator:
+ dns_rdatasetiter_destroy(&iter);
+
+cleanup_node:
+ dns_db_detachnode(db, &node);
+
+ return (result);
+}
+
+/*%
+ * For each RR of 'name' in 'ver' of 'db', call 'action'
+ * with the RR and 'action_data' as arguments. If the name
+ * does not exist, do nothing.
+ *
+ * If 'action' returns an error, abort iteration
+ * and return the error.
+ */
+static isc_result_t
+foreach_node_rr(dns_db_t *db, dns_dbversion_t *ver, dns_name_t *name,
+ rr_func *rr_action, void *rr_action_data) {
+ foreach_node_rr_ctx_t ctx;
+ ctx.rr_action = rr_action;
+ ctx.rr_action_data = rr_action_data;
+ return (foreach_rrset(db, ver, name, foreach_node_rr_action, &ctx));
+}
+
+/*%
+ * For each of the RRs specified by 'db', 'ver', 'name', 'type',
+ * (which can be dns_rdatatype_any to match any type), and 'covers', call
+ * 'action' with the RR and 'action_data' as arguments. If the name
+ * does not exist, or if no RRset of the given type exists at the name,
+ * do nothing.
+ *
+ * If 'action' returns an error, abort iteration and return the error.
+ */
+static isc_result_t
+foreach_rr(dns_db_t *db, dns_dbversion_t *ver, dns_name_t *name,
+ dns_rdatatype_t type, dns_rdatatype_t covers, rr_func *rr_action,
+ void *rr_action_data) {
+ isc_result_t result;
+ dns_dbnode_t *node;
+ dns_rdataset_t rdataset;
+
+ if (type == dns_rdatatype_any) {
+ return (foreach_node_rr(db, ver, name, rr_action,
+ rr_action_data));
+ }
+
+ node = NULL;
+ if (type == dns_rdatatype_nsec3 ||
+ (type == dns_rdatatype_rrsig && covers == dns_rdatatype_nsec3))
+ {
+ result = dns_db_findnsec3node(db, name, false, &node);
+ } else {
+ result = dns_db_findnode(db, name, false, &node);
+ }
+ if (result == ISC_R_NOTFOUND) {
+ return (ISC_R_SUCCESS);
+ }
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+
+ dns_rdataset_init(&rdataset);
+ result = dns_db_findrdataset(db, node, ver, type, covers,
+ (isc_stdtime_t)0, &rdataset, NULL);
+ if (result == ISC_R_NOTFOUND) {
+ result = ISC_R_SUCCESS;
+ goto cleanup_node;
+ }
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup_node;
+ }
+
+ for (result = dns_rdataset_first(&rdataset); result == ISC_R_SUCCESS;
+ result = dns_rdataset_next(&rdataset))
+ {
+ rr_t rr = { 0, DNS_RDATA_INIT };
+ dns_rdataset_current(&rdataset, &rr.rdata);
+ rr.ttl = rdataset.ttl;
+ result = (*rr_action)(rr_action_data, &rr);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup_rdataset;
+ }
+ }
+ if (result != ISC_R_NOMORE) {
+ goto cleanup_rdataset;
+ }
+ result = ISC_R_SUCCESS;
+
+cleanup_rdataset:
+ dns_rdataset_disassociate(&rdataset);
+cleanup_node:
+ dns_db_detachnode(db, &node);
+
+ return (result);
+}
+
+/**************************************************************************/
+/*
+ * Various tests on the database contents (for prerequisites, etc).
+ */
+
+/*%
+ * Function type for predicate functions that compare a database RR 'db_rr'
+ * against an update RR 'update_rr'.
+ */
+typedef bool
+rr_predicate(dns_rdata_t *update_rr, dns_rdata_t *db_rr);
+
+/*%
+ * Helper function for rrset_exists().
+ */
+static isc_result_t
+rrset_exists_action(void *data, rr_t *rr) {
+ UNUSED(data);
+ UNUSED(rr);
+ return (ISC_R_EXISTS);
+}
+
+/*%
+ * Utility macro for RR existence checking functions.
+ *
+ * If the variable 'result' has the value ISC_R_EXISTS or
+ * ISC_R_SUCCESS, set *exists to true or false,
+ * respectively, and return success.
+ *
+ * If 'result' has any other value, there was a failure.
+ * Return the failure result code and do not set *exists.
+ *
+ * This would be more readable as "do { if ... } while(0)",
+ * but that form generates tons of warnings on Solaris 2.6.
+ */
+#define RETURN_EXISTENCE_FLAG \
+ return ((result == ISC_R_EXISTS) \
+ ? (*exists = true, ISC_R_SUCCESS) \
+ : ((result == ISC_R_SUCCESS) \
+ ? (*exists = false, ISC_R_SUCCESS) \
+ : result))
+
+/*%
+ * Set '*exists' to true iff an rrset of the given type exists,
+ * to false otherwise.
+ */
+static isc_result_t
+rrset_exists(dns_db_t *db, dns_dbversion_t *ver, dns_name_t *name,
+ dns_rdatatype_t type, dns_rdatatype_t covers, bool *exists) {
+ isc_result_t result;
+ result = foreach_rr(db, ver, name, type, covers, rrset_exists_action,
+ NULL);
+ RETURN_EXISTENCE_FLAG;
+}
+
+/*%
+ * Set '*visible' to true if the RRset exists and is part of the
+ * visible zone. Otherwise '*visible' is set to false unless a
+ * error occurs.
+ */
+static isc_result_t
+rrset_visible(dns_db_t *db, dns_dbversion_t *ver, dns_name_t *name,
+ dns_rdatatype_t type, bool *visible) {
+ isc_result_t result;
+ dns_fixedname_t fixed;
+
+ dns_fixedname_init(&fixed);
+ result = dns_db_find(db, name, ver, type, DNS_DBFIND_NOWILD,
+ (isc_stdtime_t)0, NULL, dns_fixedname_name(&fixed),
+ NULL, NULL);
+ switch (result) {
+ case ISC_R_SUCCESS:
+ *visible = true;
+ break;
+ /*
+ * Glue, obscured, deleted or replaced records.
+ */
+ case DNS_R_DELEGATION:
+ case DNS_R_DNAME:
+ case DNS_R_CNAME:
+ case DNS_R_NXDOMAIN:
+ case DNS_R_NXRRSET:
+ case DNS_R_EMPTYNAME:
+ case DNS_R_COVERINGNSEC:
+ *visible = false;
+ result = ISC_R_SUCCESS;
+ break;
+ default:
+ *visible = false; /* silence false compiler warning */
+ break;
+ }
+ return (result);
+}
+
+/*%
+ * Context struct and helper function for name_exists().
+ */
+
+static isc_result_t
+name_exists_action(void *data, dns_rdataset_t *rrset) {
+ UNUSED(data);
+ UNUSED(rrset);
+ return (ISC_R_EXISTS);
+}
+
+/*%
+ * Set '*exists' to true iff the given name exists, to false otherwise.
+ */
+static isc_result_t
+name_exists(dns_db_t *db, dns_dbversion_t *ver, dns_name_t *name,
+ bool *exists) {
+ isc_result_t result;
+ result = foreach_rrset(db, ver, name, name_exists_action, NULL);
+ RETURN_EXISTENCE_FLAG;
+}
+
+/**************************************************************************/
+/*
+ * Checking of "RRset exists (value dependent)" prerequisites.
+ *
+ * In the RFC2136 section 3.2.5, this is the pseudocode involving
+ * a variable called "temp", a mapping of <name, type> tuples to rrsets.
+ *
+ * Here, we represent the "temp" data structure as (non-minimal) "dns_diff_t"
+ * where each tuple has op==DNS_DIFFOP_EXISTS.
+ */
+
+/*%
+ * A comparison function defining the sorting order for the entries
+ * in the "temp" data structure. The major sort key is the owner name,
+ * followed by the type and rdata.
+ */
+static int
+temp_order(const void *av, const void *bv) {
+ dns_difftuple_t const *const *ap = av;
+ dns_difftuple_t const *const *bp = bv;
+ dns_difftuple_t const *a = *ap;
+ dns_difftuple_t const *b = *bp;
+ int r;
+ r = dns_name_compare(&a->name, &b->name);
+ if (r != 0) {
+ return (r);
+ }
+ r = (b->rdata.type - a->rdata.type);
+ if (r != 0) {
+ return (r);
+ }
+ r = dns_rdata_casecompare(&a->rdata, &b->rdata);
+ return (r);
+}
+
+/**************************************************************************/
+/*
+ * Conditional deletion of RRs.
+ */
+
+/*%
+ * Context structure for delete_if().
+ */
+
+typedef struct {
+ rr_predicate *predicate;
+ dns_db_t *db;
+ dns_dbversion_t *ver;
+ dns_diff_t *diff;
+ dns_name_t *name;
+ dns_rdata_t *update_rr;
+} conditional_delete_ctx_t;
+
+/*%
+ * Predicate functions for delete_if().
+ */
+
+/*%
+ * Return true always.
+ */
+static bool
+true_p(dns_rdata_t *update_rr, dns_rdata_t *db_rr) {
+ UNUSED(update_rr);
+ UNUSED(db_rr);
+ return (true);
+}
+
+/*%
+ * Return true if the record is a RRSIG.
+ */
+static bool
+rrsig_p(dns_rdata_t *update_rr, dns_rdata_t *db_rr) {
+ UNUSED(update_rr);
+ return ((db_rr->type == dns_rdatatype_rrsig) ? true : false);
+}
+
+/*%
+ * Internal helper function for delete_if().
+ */
+static isc_result_t
+delete_if_action(void *data, rr_t *rr) {
+ conditional_delete_ctx_t *ctx = data;
+ if ((*ctx->predicate)(ctx->update_rr, &rr->rdata)) {
+ isc_result_t result;
+ result = update_one_rr(ctx->db, ctx->ver, ctx->diff,
+ DNS_DIFFOP_DEL, ctx->name, rr->ttl,
+ &rr->rdata);
+ return (result);
+ } else {
+ return (ISC_R_SUCCESS);
+ }
+}
+
+/*%
+ * Conditionally delete RRs. Apply 'predicate' to the RRs
+ * specified by 'db', 'ver', 'name', and 'type' (which can
+ * be dns_rdatatype_any to match any type). Delete those
+ * RRs for which the predicate returns true, and log the
+ * deletions in 'diff'.
+ */
+static isc_result_t
+delete_if(rr_predicate *predicate, dns_db_t *db, dns_dbversion_t *ver,
+ dns_name_t *name, dns_rdatatype_t type, dns_rdatatype_t covers,
+ dns_rdata_t *update_rr, dns_diff_t *diff) {
+ conditional_delete_ctx_t ctx;
+ ctx.predicate = predicate;
+ ctx.db = db;
+ ctx.ver = ver;
+ ctx.diff = diff;
+ ctx.name = name;
+ ctx.update_rr = update_rr;
+ return (foreach_rr(db, ver, name, type, covers, delete_if_action,
+ &ctx));
+}
+
+/**************************************************************************/
+/*
+ * Incremental updating of NSECs and RRSIGs.
+ */
+
+/*%
+ * We abuse the dns_diff_t type to represent a set of domain names
+ * affected by the update.
+ */
+static isc_result_t
+namelist_append_name(dns_diff_t *list, dns_name_t *name) {
+ isc_result_t result;
+ dns_difftuple_t *tuple = NULL;
+ static dns_rdata_t dummy_rdata = DNS_RDATA_INIT;
+
+ CHECK(dns_difftuple_create(list->mctx, DNS_DIFFOP_EXISTS, name, 0,
+ &dummy_rdata, &tuple));
+ dns_diff_append(list, &tuple);
+failure:
+ return (result);
+}
+
+static isc_result_t
+namelist_append_subdomain(dns_db_t *db, dns_name_t *name,
+ dns_diff_t *affected) {
+ isc_result_t result;
+ dns_fixedname_t fixedname;
+ dns_name_t *child;
+ dns_dbiterator_t *dbit = NULL;
+
+ child = dns_fixedname_initname(&fixedname);
+
+ CHECK(dns_db_createiterator(db, DNS_DB_NONSEC3, &dbit));
+
+ for (result = dns_dbiterator_seek(dbit, name); result == ISC_R_SUCCESS;
+ result = dns_dbiterator_next(dbit))
+ {
+ dns_dbnode_t *node = NULL;
+ CHECK(dns_dbiterator_current(dbit, &node, child));
+ dns_db_detachnode(db, &node);
+ if (!dns_name_issubdomain(child, name)) {
+ break;
+ }
+ CHECK(namelist_append_name(affected, child));
+ }
+ if (result == ISC_R_NOMORE) {
+ result = ISC_R_SUCCESS;
+ }
+failure:
+ if (dbit != NULL) {
+ dns_dbiterator_destroy(&dbit);
+ }
+ return (result);
+}
+
+/*%
+ * Helper function for non_nsec_rrset_exists().
+ */
+static isc_result_t
+is_non_nsec_action(void *data, dns_rdataset_t *rrset) {
+ UNUSED(data);
+ if (!(rrset->type == dns_rdatatype_nsec ||
+ rrset->type == dns_rdatatype_nsec3 ||
+ (rrset->type == dns_rdatatype_rrsig &&
+ (rrset->covers == dns_rdatatype_nsec ||
+ rrset->covers == dns_rdatatype_nsec3))))
+ {
+ return (ISC_R_EXISTS);
+ }
+ return (ISC_R_SUCCESS);
+}
+
+/*%
+ * Check whether there is an rrset other than a NSEC or RRSIG NSEC,
+ * i.e., anything that justifies the continued existence of a name
+ * after a secure update.
+ *
+ * If such an rrset exists, set '*exists' to true.
+ * Otherwise, set it to false.
+ */
+static isc_result_t
+non_nsec_rrset_exists(dns_db_t *db, dns_dbversion_t *ver, dns_name_t *name,
+ bool *exists) {
+ isc_result_t result;
+ result = foreach_rrset(db, ver, name, is_non_nsec_action, NULL);
+ RETURN_EXISTENCE_FLAG;
+}
+
+/*%
+ * A comparison function for sorting dns_diff_t:s by name.
+ */
+static int
+name_order(const void *av, const void *bv) {
+ dns_difftuple_t const *const *ap = av;
+ dns_difftuple_t const *const *bp = bv;
+ dns_difftuple_t const *a = *ap;
+ dns_difftuple_t const *b = *bp;
+ return (dns_name_compare(&a->name, &b->name));
+}
+
+static isc_result_t
+uniqify_name_list(dns_diff_t *list) {
+ isc_result_t result;
+ dns_difftuple_t *p, *q;
+
+ CHECK(dns_diff_sort(list, name_order));
+
+ p = ISC_LIST_HEAD(list->tuples);
+ while (p != NULL) {
+ do {
+ q = ISC_LIST_NEXT(p, link);
+ if (q == NULL || !dns_name_equal(&p->name, &q->name)) {
+ break;
+ }
+ ISC_LIST_UNLINK(list->tuples, q, link);
+ dns_difftuple_free(&q);
+ } while (1);
+ p = ISC_LIST_NEXT(p, link);
+ }
+failure:
+ return (result);
+}
+
+static isc_result_t
+is_active(dns_db_t *db, dns_dbversion_t *ver, dns_name_t *name, bool *flag,
+ bool *cut, bool *unsecure) {
+ isc_result_t result;
+ dns_fixedname_t foundname;
+ dns_fixedname_init(&foundname);
+ result = dns_db_find(db, name, ver, dns_rdatatype_any,
+ DNS_DBFIND_GLUEOK | DNS_DBFIND_NOWILD,
+ (isc_stdtime_t)0, NULL,
+ dns_fixedname_name(&foundname), NULL, NULL);
+ if (result == ISC_R_SUCCESS || result == DNS_R_EMPTYNAME) {
+ *flag = true;
+ *cut = false;
+ if (unsecure != NULL) {
+ *unsecure = false;
+ }
+ return (ISC_R_SUCCESS);
+ } else if (result == DNS_R_ZONECUT) {
+ *flag = true;
+ *cut = true;
+ if (unsecure != NULL) {
+ /*
+ * We are at the zonecut. Check to see if there
+ * is a DS RRset.
+ */
+ if (dns_db_find(db, name, ver, dns_rdatatype_ds, 0,
+ (isc_stdtime_t)0, NULL,
+ dns_fixedname_name(&foundname), NULL,
+ NULL) == DNS_R_NXRRSET)
+ {
+ *unsecure = true;
+ } else {
+ *unsecure = false;
+ }
+ }
+ return (ISC_R_SUCCESS);
+ } else if (result == DNS_R_GLUE || result == DNS_R_DNAME ||
+ result == DNS_R_DELEGATION || result == DNS_R_NXDOMAIN)
+ {
+ *flag = false;
+ *cut = false;
+ if (unsecure != NULL) {
+ *unsecure = false;
+ }
+ return (ISC_R_SUCCESS);
+ } else {
+ /*
+ * Silence compiler.
+ */
+ *flag = false;
+ *cut = false;
+ if (unsecure != NULL) {
+ *unsecure = false;
+ }
+ return (result);
+ }
+}
+
+/*%
+ * Find the next/previous name that has a NSEC record.
+ * In other words, skip empty database nodes and names that
+ * have had their NSECs removed because they are obscured by
+ * a zone cut.
+ */
+static isc_result_t
+next_active(dns_update_log_t *log, dns_zone_t *zone, dns_db_t *db,
+ dns_dbversion_t *ver, dns_name_t *oldname, dns_name_t *newname,
+ bool forward) {
+ isc_result_t result;
+ dns_dbiterator_t *dbit = NULL;
+ bool has_nsec = false;
+ unsigned int wraps = 0;
+ bool secure = dns_db_issecure(db);
+
+ CHECK(dns_db_createiterator(db, 0, &dbit));
+
+ CHECK(dns_dbiterator_seek(dbit, oldname));
+ do {
+ dns_dbnode_t *node = NULL;
+
+ if (forward) {
+ result = dns_dbiterator_next(dbit);
+ } else {
+ result = dns_dbiterator_prev(dbit);
+ }
+ if (result == ISC_R_NOMORE) {
+ /*
+ * Wrap around.
+ */
+ if (forward) {
+ CHECK(dns_dbiterator_first(dbit));
+ } else {
+ CHECK(dns_dbiterator_last(dbit));
+ }
+ wraps++;
+ if (wraps == 2) {
+ update_log(log, zone, ISC_LOG_ERROR,
+ "secure zone with no NSECs");
+ result = DNS_R_BADZONE;
+ goto failure;
+ }
+ }
+ CHECK(dns_dbiterator_current(dbit, &node, newname));
+ dns_db_detachnode(db, &node);
+
+ /*
+ * The iterator may hold the tree lock, and
+ * rrset_exists() calls dns_db_findnode() which
+ * may try to reacquire it. To avoid deadlock
+ * we must pause the iterator first.
+ */
+ CHECK(dns_dbiterator_pause(dbit));
+ if (secure) {
+ CHECK(rrset_exists(db, ver, newname, dns_rdatatype_nsec,
+ 0, &has_nsec));
+ } else {
+ dns_fixedname_t ffound;
+ dns_name_t *found;
+ found = dns_fixedname_initname(&ffound);
+ result = dns_db_find(
+ db, newname, ver, dns_rdatatype_soa,
+ DNS_DBFIND_NOWILD, 0, NULL, found, NULL, NULL);
+ if (result == ISC_R_SUCCESS ||
+ result == DNS_R_EMPTYNAME ||
+ result == DNS_R_NXRRSET || result == DNS_R_CNAME ||
+ (result == DNS_R_DELEGATION &&
+ dns_name_equal(newname, found)))
+ {
+ has_nsec = true;
+ result = ISC_R_SUCCESS;
+ } else if (result != DNS_R_NXDOMAIN) {
+ break;
+ }
+ }
+ } while (!has_nsec);
+failure:
+ if (dbit != NULL) {
+ dns_dbiterator_destroy(&dbit);
+ }
+
+ return (result);
+}
+
+/*%
+ * Add a NSEC record for "name", recording the change in "diff".
+ * The existing NSEC is removed.
+ */
+static isc_result_t
+add_nsec(dns_update_log_t *log, dns_zone_t *zone, dns_db_t *db,
+ dns_dbversion_t *ver, dns_name_t *name, dns_ttl_t nsecttl,
+ dns_diff_t *diff) {
+ isc_result_t result;
+ dns_dbnode_t *node = NULL;
+ unsigned char buffer[DNS_NSEC_BUFFERSIZE];
+ dns_rdata_t rdata = DNS_RDATA_INIT;
+ dns_difftuple_t *tuple = NULL;
+ dns_fixedname_t fixedname;
+ dns_name_t *target;
+
+ target = dns_fixedname_initname(&fixedname);
+
+ /*
+ * Find the successor name, aka NSEC target.
+ */
+ CHECK(next_active(log, zone, db, ver, name, target, true));
+
+ /*
+ * Create the NSEC RDATA.
+ */
+ CHECK(dns_db_findnode(db, name, false, &node));
+ dns_rdata_init(&rdata);
+ CHECK(dns_nsec_buildrdata(db, ver, node, target, buffer, &rdata));
+ dns_db_detachnode(db, &node);
+
+ /*
+ * Delete the old NSEC and record the change.
+ */
+ CHECK(delete_if(true_p, db, ver, name, dns_rdatatype_nsec, 0, NULL,
+ diff));
+ /*
+ * Add the new NSEC and record the change.
+ */
+ CHECK(dns_difftuple_create(diff->mctx, DNS_DIFFOP_ADD, name, nsecttl,
+ &rdata, &tuple));
+ CHECK(do_one_tuple(&tuple, db, ver, diff));
+ INSIST(tuple == NULL);
+
+failure:
+ if (node != NULL) {
+ dns_db_detachnode(db, &node);
+ }
+ return (result);
+}
+
+/*%
+ * Add a placeholder NSEC record for "name", recording the change in "diff".
+ */
+static isc_result_t
+add_placeholder_nsec(dns_db_t *db, dns_dbversion_t *ver, dns_name_t *name,
+ dns_diff_t *diff) {
+ isc_result_t result;
+ dns_difftuple_t *tuple = NULL;
+ isc_region_t r;
+ unsigned char data[1] = { 0 }; /* The root domain, no bits. */
+ dns_rdata_t rdata = DNS_RDATA_INIT;
+
+ r.base = data;
+ r.length = sizeof(data);
+ dns_rdata_fromregion(&rdata, dns_db_class(db), dns_rdatatype_nsec, &r);
+ CHECK(dns_difftuple_create(diff->mctx, DNS_DIFFOP_ADD, name, 0, &rdata,
+ &tuple));
+ CHECK(do_one_tuple(&tuple, db, ver, diff));
+failure:
+ return (result);
+}
+
+static isc_result_t
+find_zone_keys(dns_zone_t *zone, dns_db_t *db, dns_dbversion_t *ver,
+ isc_mem_t *mctx, unsigned int maxkeys, dst_key_t **keys,
+ unsigned int *nkeys) {
+ isc_result_t result;
+ isc_stdtime_t now;
+ dns_dbnode_t *node = NULL;
+ const char *directory = dns_zone_getkeydirectory(zone);
+
+ CHECK(dns_db_findnode(db, dns_db_origin(db), false, &node));
+ isc_stdtime_get(&now);
+
+ dns_zone_lock_keyfiles(zone);
+ result = dns_dnssec_findzonekeys(db, ver, node, dns_db_origin(db),
+ directory, now, mctx, maxkeys, keys,
+ nkeys);
+ dns_zone_unlock_keyfiles(zone);
+
+failure:
+ if (node != NULL) {
+ dns_db_detachnode(db, &node);
+ }
+ return (result);
+}
+
+/*%
+ * Add RRSIG records for an RRset, recording the change in "diff".
+ */
+static isc_result_t
+add_sigs(dns_update_log_t *log, dns_zone_t *zone, dns_db_t *db,
+ dns_dbversion_t *ver, dns_name_t *name, dns_rdatatype_t type,
+ dns_diff_t *diff, dst_key_t **keys, unsigned int nkeys,
+ isc_stdtime_t inception, isc_stdtime_t expire, bool check_ksk,
+ bool keyset_kskonly) {
+ isc_result_t result;
+ dns_dbnode_t *node = NULL;
+ dns_kasp_t *kasp = dns_zone_getkasp(zone);
+ dns_rdataset_t rdataset;
+ dns_rdata_t sig_rdata = DNS_RDATA_INIT;
+ dns_stats_t *dnssecsignstats = dns_zone_getdnssecsignstats(zone);
+ isc_buffer_t buffer;
+ unsigned char data[1024]; /* XXX */
+ unsigned int i, j;
+ bool added_sig = false;
+ bool use_kasp = false;
+ isc_mem_t *mctx = diff->mctx;
+
+ if (kasp != NULL) {
+ check_ksk = false;
+ keyset_kskonly = true;
+ use_kasp = true;
+ }
+
+ dns_rdataset_init(&rdataset);
+ isc_buffer_init(&buffer, data, sizeof(data));
+
+ /* Get the rdataset to sign. */
+ if (type == dns_rdatatype_nsec3) {
+ CHECK(dns_db_findnsec3node(db, name, false, &node));
+ } else {
+ CHECK(dns_db_findnode(db, name, false, &node));
+ }
+ CHECK(dns_db_findrdataset(db, node, ver, type, 0, (isc_stdtime_t)0,
+ &rdataset, NULL));
+ dns_db_detachnode(db, &node);
+
+#define REVOKE(x) ((dst_key_flags(x) & DNS_KEYFLAG_REVOKE) != 0)
+#define KSK(x) ((dst_key_flags(x) & DNS_KEYFLAG_KSK) != 0)
+#define ID(x) dst_key_id(x)
+#define ALG(x) dst_key_alg(x)
+
+ /*
+ * If we are honoring KSK flags then we need to check that we
+ * have both KSK and non-KSK keys that are not revoked per
+ * algorithm.
+ */
+ for (i = 0; i < nkeys; i++) {
+ bool both = false;
+
+ /* Don't add signatures for offline or inactive keys */
+ if (!dst_key_isprivate(keys[i])) {
+ continue;
+ }
+ if (dst_key_inactive(keys[i])) {
+ continue;
+ }
+
+ if (check_ksk && !REVOKE(keys[i])) {
+ bool have_ksk, have_nonksk;
+ if (KSK(keys[i])) {
+ have_ksk = true;
+ have_nonksk = false;
+ } else {
+ have_ksk = false;
+ have_nonksk = true;
+ }
+ for (j = 0; j < nkeys; j++) {
+ if (j == i || ALG(keys[i]) != ALG(keys[j])) {
+ continue;
+ }
+
+ /* Don't consider inactive keys, however
+ * the KSK may be temporary offline, so do
+ * consider KSKs which private key files are
+ * unavailable.
+ */
+ if (dst_key_inactive(keys[j])) {
+ continue;
+ }
+
+ if (REVOKE(keys[j])) {
+ continue;
+ }
+ if (KSK(keys[j])) {
+ have_ksk = true;
+ } else if (dst_key_isprivate(keys[j])) {
+ have_nonksk = true;
+ }
+ both = have_ksk && have_nonksk;
+ if (both) {
+ break;
+ }
+ }
+ }
+
+ if (use_kasp) {
+ /*
+ * A dnssec-policy is found. Check what RRsets this
+ * key should sign.
+ */
+ isc_stdtime_t when;
+ isc_result_t kresult;
+ bool ksk = false;
+ bool zsk = false;
+
+ kresult = dst_key_getbool(keys[i], DST_BOOL_KSK, &ksk);
+ if (kresult != ISC_R_SUCCESS) {
+ if (KSK(keys[i])) {
+ ksk = true;
+ }
+ }
+ kresult = dst_key_getbool(keys[i], DST_BOOL_ZSK, &zsk);
+ if (kresult != ISC_R_SUCCESS) {
+ if (!KSK(keys[i])) {
+ zsk = true;
+ }
+ }
+
+ if (type == dns_rdatatype_dnskey ||
+ type == dns_rdatatype_cdnskey ||
+ type == dns_rdatatype_cds)
+ {
+ /*
+ * DNSKEY RRset is signed with KSK.
+ * CDS and CDNSKEY RRsets too (RFC 7344, 4.1).
+ */
+ if (!ksk) {
+ continue;
+ }
+ } else if (!zsk) {
+ /*
+ * Other RRsets are signed with ZSK.
+ */
+ continue;
+ } else if (zsk &&
+ !dst_key_is_signing(keys[i], DST_BOOL_ZSK,
+ inception, &when))
+ {
+ /*
+ * This key is not active for zone-signing.
+ */
+ continue;
+ }
+
+ /*
+ * If this key is revoked, it may only sign the
+ * DNSKEY RRset.
+ */
+ if (REVOKE(keys[i]) && type != dns_rdatatype_dnskey) {
+ continue;
+ }
+ } else if (both) {
+ /*
+ * CDS and CDNSKEY are signed with KSK (RFC 7344, 4.1).
+ */
+ if (type == dns_rdatatype_dnskey ||
+ type == dns_rdatatype_cdnskey ||
+ type == dns_rdatatype_cds)
+ {
+ if (!KSK(keys[i]) && keyset_kskonly) {
+ continue;
+ }
+ } else if (KSK(keys[i])) {
+ continue;
+ }
+ } else if (REVOKE(keys[i]) && type != dns_rdatatype_dnskey) {
+ continue;
+ }
+
+ /* Calculate the signature, creating a RRSIG RDATA. */
+ CHECK(dns_dnssec_sign(name, &rdataset, keys[i], &inception,
+ &expire, mctx, &buffer, &sig_rdata));
+
+ /* Update the database and journal with the RRSIG. */
+ /* XXX inefficient - will cause dataset merging */
+ CHECK(update_one_rr(db, ver, diff, DNS_DIFFOP_ADDRESIGN, name,
+ rdataset.ttl, &sig_rdata));
+ dns_rdata_reset(&sig_rdata);
+ isc_buffer_init(&buffer, data, sizeof(data));
+ added_sig = true;
+ /* Update DNSSEC sign statistics. */
+ if (dnssecsignstats != NULL) {
+ dns_dnssecsignstats_increment(dnssecsignstats,
+ ID(keys[i]),
+ (uint8_t)ALG(keys[i]),
+ dns_dnssecsignstats_sign);
+ }
+ }
+ if (!added_sig) {
+ update_log(log, zone, ISC_LOG_ERROR,
+ "found no active private keys, "
+ "unable to generate any signatures");
+ result = ISC_R_NOTFOUND;
+ }
+
+failure:
+ if (dns_rdataset_isassociated(&rdataset)) {
+ dns_rdataset_disassociate(&rdataset);
+ }
+ if (node != NULL) {
+ dns_db_detachnode(db, &node);
+ }
+ return (result);
+}
+
+/*
+ * Delete expired RRsigs and any RRsigs we are about to re-sign.
+ * See also zone.c:del_sigs().
+ */
+static isc_result_t
+del_keysigs(dns_db_t *db, dns_dbversion_t *ver, dns_name_t *name,
+ dns_diff_t *diff, dst_key_t **keys, unsigned int nkeys) {
+ isc_result_t result;
+ dns_dbnode_t *node = NULL;
+ dns_rdataset_t rdataset;
+ dns_rdata_t rdata = DNS_RDATA_INIT;
+ unsigned int i;
+ dns_rdata_rrsig_t rrsig;
+ bool found;
+
+ dns_rdataset_init(&rdataset);
+
+ result = dns_db_findnode(db, name, false, &node);
+ if (result == ISC_R_NOTFOUND) {
+ return (ISC_R_SUCCESS);
+ }
+ if (result != ISC_R_SUCCESS) {
+ goto failure;
+ }
+ result = dns_db_findrdataset(db, node, ver, dns_rdatatype_rrsig,
+ dns_rdatatype_dnskey, (isc_stdtime_t)0,
+ &rdataset, NULL);
+ dns_db_detachnode(db, &node);
+
+ if (result == ISC_R_NOTFOUND) {
+ return (ISC_R_SUCCESS);
+ }
+ if (result != ISC_R_SUCCESS) {
+ goto failure;
+ }
+
+ for (result = dns_rdataset_first(&rdataset); result == ISC_R_SUCCESS;
+ result = dns_rdataset_next(&rdataset))
+ {
+ dns_rdataset_current(&rdataset, &rdata);
+ result = dns_rdata_tostruct(&rdata, &rrsig, NULL);
+ RUNTIME_CHECK(result == ISC_R_SUCCESS);
+ found = false;
+ for (i = 0; i < nkeys; i++) {
+ if (rrsig.keyid == dst_key_id(keys[i])) {
+ found = true;
+ if (!dst_key_isprivate(keys[i]) &&
+ !dst_key_inactive(keys[i]))
+ {
+ /*
+ * The re-signing code in zone.c
+ * will mark this as offline.
+ * Just skip the record for now.
+ */
+ break;
+ }
+ result = update_one_rr(db, ver, diff,
+ DNS_DIFFOP_DEL, name,
+ rdataset.ttl, &rdata);
+ break;
+ }
+ }
+ /*
+ * If there is not a matching DNSKEY then delete the RRSIG.
+ */
+ if (!found) {
+ result = update_one_rr(db, ver, diff, DNS_DIFFOP_DEL,
+ name, rdataset.ttl, &rdata);
+ }
+ dns_rdata_reset(&rdata);
+ if (result != ISC_R_SUCCESS) {
+ break;
+ }
+ }
+ dns_rdataset_disassociate(&rdataset);
+ if (result == ISC_R_NOMORE) {
+ result = ISC_R_SUCCESS;
+ }
+failure:
+ if (node != NULL) {
+ dns_db_detachnode(db, &node);
+ }
+ return (result);
+}
+
+static isc_result_t
+add_exposed_sigs(dns_update_log_t *log, dns_zone_t *zone, dns_db_t *db,
+ dns_dbversion_t *ver, dns_name_t *name, bool cut,
+ dns_diff_t *diff, dst_key_t **keys, unsigned int nkeys,
+ isc_stdtime_t inception, isc_stdtime_t expire, bool check_ksk,
+ bool keyset_kskonly, unsigned int *sigs) {
+ isc_result_t result;
+ dns_dbnode_t *node;
+ dns_rdatasetiter_t *iter;
+
+ node = NULL;
+ result = dns_db_findnode(db, name, false, &node);
+ if (result == ISC_R_NOTFOUND) {
+ return (ISC_R_SUCCESS);
+ }
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+
+ iter = NULL;
+ result = dns_db_allrdatasets(db, node, ver, 0, (isc_stdtime_t)0, &iter);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup_node;
+ }
+
+ for (result = dns_rdatasetiter_first(iter); result == ISC_R_SUCCESS;
+ result = dns_rdatasetiter_next(iter))
+ {
+ dns_rdataset_t rdataset;
+ dns_rdatatype_t type;
+ bool flag;
+
+ dns_rdataset_init(&rdataset);
+ dns_rdatasetiter_current(iter, &rdataset);
+ type = rdataset.type;
+ dns_rdataset_disassociate(&rdataset);
+
+ /*
+ * We don't need to sign unsigned NSEC records at the cut
+ * as they are handled elsewhere.
+ */
+ if ((type == dns_rdatatype_rrsig) ||
+ (cut && type != dns_rdatatype_ds))
+ {
+ continue;
+ }
+ result = rrset_exists(db, ver, name, dns_rdatatype_rrsig, type,
+ &flag);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup_iterator;
+ }
+ if (flag) {
+ continue;
+ }
+ result = add_sigs(log, zone, db, ver, name, type, diff, keys,
+ nkeys, inception, expire, check_ksk,
+ keyset_kskonly);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup_iterator;
+ }
+ (*sigs)++;
+ }
+ if (result == ISC_R_NOMORE) {
+ result = ISC_R_SUCCESS;
+ }
+
+cleanup_iterator:
+ dns_rdatasetiter_destroy(&iter);
+
+cleanup_node:
+ dns_db_detachnode(db, &node);
+
+ return (result);
+}
+
+/*%
+ * Update RRSIG, NSEC and NSEC3 records affected by an update. The original
+ * update, including the SOA serial update but excluding the RRSIG & NSEC
+ * changes, is in "diff" and has already been applied to "newver" of "db".
+ * The database version prior to the update is "oldver".
+ *
+ * The necessary RRSIG, NSEC and NSEC3 changes will be applied to "newver"
+ * and added (as a minimal diff) to "diff".
+ *
+ * The RRSIGs generated will be valid for 'sigvalidityinterval' seconds.
+ */
+isc_result_t
+dns_update_signatures(dns_update_log_t *log, dns_zone_t *zone, dns_db_t *db,
+ dns_dbversion_t *oldver, dns_dbversion_t *newver,
+ dns_diff_t *diff, uint32_t sigvalidityinterval) {
+ return (dns_update_signaturesinc(log, zone, db, oldver, newver, diff,
+ sigvalidityinterval, NULL));
+}
+
+struct dns_update_state {
+ unsigned int magic;
+ dns_diff_t diffnames;
+ dns_diff_t affected;
+ dns_diff_t sig_diff;
+ dns_diff_t nsec_diff;
+ dns_diff_t nsec_mindiff;
+ dns_diff_t work;
+ dst_key_t *zone_keys[DNS_MAXZONEKEYS];
+ unsigned int nkeys;
+ isc_stdtime_t inception, expire, soaexpire, keyexpire;
+ dns_ttl_t nsecttl;
+ bool check_ksk, keyset_kskonly, build_nsec3;
+ enum {
+ sign_updates,
+ remove_orphaned,
+ build_chain,
+ process_nsec,
+ sign_nsec,
+ update_nsec3,
+ process_nsec3,
+ sign_nsec3
+ } state;
+};
+
+static uint32_t
+dns__jitter_expire(dns_zone_t *zone, uint32_t sigvalidityinterval) {
+ /* Spread out signatures over time */
+ if (sigvalidityinterval >= 3600U) {
+ uint32_t expiryinterval =
+ dns_zone_getsigresigninginterval(zone);
+
+ if (sigvalidityinterval < 7200U) {
+ expiryinterval = 1200;
+ } else if (expiryinterval > sigvalidityinterval) {
+ expiryinterval = sigvalidityinterval;
+ } else {
+ expiryinterval = sigvalidityinterval - expiryinterval;
+ }
+ uint32_t jitter = isc_random_uniform(expiryinterval);
+ sigvalidityinterval -= jitter;
+ }
+ return (sigvalidityinterval);
+}
+
+isc_result_t
+dns_update_signaturesinc(dns_update_log_t *log, dns_zone_t *zone, dns_db_t *db,
+ dns_dbversion_t *oldver, dns_dbversion_t *newver,
+ dns_diff_t *diff, uint32_t sigvalidityinterval,
+ dns_update_state_t **statep) {
+ isc_result_t result = ISC_R_SUCCESS;
+ dns_update_state_t mystate, *state;
+
+ dns_difftuple_t *t, *next;
+ bool flag, build_nsec;
+ unsigned int i;
+ isc_stdtime_t now;
+ dns_rdata_soa_t soa;
+ dns_rdata_t rdata = DNS_RDATA_INIT;
+ dns_rdataset_t rdataset;
+ dns_dbnode_t *node = NULL;
+ bool unsecure;
+ bool cut;
+ dns_rdatatype_t privatetype = dns_zone_getprivatetype(zone);
+ unsigned int sigs = 0;
+ unsigned int maxsigs = dns_zone_getsignatures(zone);
+
+ if (statep == NULL || *statep == NULL) {
+ if (statep == NULL) {
+ state = &mystate;
+ } else {
+ state = isc_mem_get(diff->mctx, sizeof(*state));
+ }
+
+ dns_diff_init(diff->mctx, &state->diffnames);
+ dns_diff_init(diff->mctx, &state->affected);
+ dns_diff_init(diff->mctx, &state->sig_diff);
+ dns_diff_init(diff->mctx, &state->nsec_diff);
+ dns_diff_init(diff->mctx, &state->nsec_mindiff);
+ dns_diff_init(diff->mctx, &state->work);
+ state->nkeys = 0;
+ state->build_nsec3 = false;
+
+ result = find_zone_keys(zone, db, newver, diff->mctx,
+ DNS_MAXZONEKEYS, state->zone_keys,
+ &state->nkeys);
+ if (result != ISC_R_SUCCESS) {
+ update_log(log, zone, ISC_LOG_ERROR,
+ "could not get zone keys for secure "
+ "dynamic update");
+ goto failure;
+ }
+
+ isc_stdtime_get(&now);
+ state->inception = now - 3600; /* Allow for some clock skew. */
+ state->expire = now +
+ dns__jitter_expire(zone, sigvalidityinterval);
+ state->soaexpire = now + sigvalidityinterval;
+ state->keyexpire = dns_zone_getkeyvalidityinterval(zone);
+ if (state->keyexpire == 0) {
+ state->keyexpire = state->expire;
+ } else {
+ state->keyexpire += now;
+ }
+
+ /*
+ * Do we look at the KSK flag on the DNSKEY to determining which
+ * keys sign which RRsets? First check the zone option then
+ * check the keys flags to make sure at least one has a ksk set
+ * and one doesn't.
+ */
+ state->check_ksk = ((dns_zone_getoptions(zone) &
+ DNS_ZONEOPT_UPDATECHECKKSK) != 0);
+ state->keyset_kskonly = ((dns_zone_getoptions(zone) &
+ DNS_ZONEOPT_DNSKEYKSKONLY) != 0);
+
+ /*
+ * Calculate the NSEC/NSEC3 TTL as a minimum of the SOA TTL and
+ * MINIMUM field.
+ */
+ CHECK(dns_db_findnode(db, dns_db_origin(db), false, &node));
+ dns_rdataset_init(&rdataset);
+ CHECK(dns_db_findrdataset(db, node, newver, dns_rdatatype_soa,
+ 0, (isc_stdtime_t)0, &rdataset,
+ NULL));
+ CHECK(dns_rdataset_first(&rdataset));
+ dns_rdataset_current(&rdataset, &rdata);
+ CHECK(dns_rdata_tostruct(&rdata, &soa, NULL));
+ state->nsecttl = ISC_MIN(rdataset.ttl, soa.minimum);
+ dns_rdataset_disassociate(&rdataset);
+ dns_db_detachnode(db, &node);
+
+ /*
+ * Find all RRsets directly affected by the update, and
+ * update their RRSIGs. Also build a list of names affected
+ * by the update in "diffnames".
+ */
+ CHECK(dns_diff_sort(diff, temp_order));
+ state->state = sign_updates;
+ state->magic = STATE_MAGIC;
+ if (statep != NULL) {
+ *statep = state;
+ }
+ } else {
+ REQUIRE(DNS_STATE_VALID(*statep));
+ state = *statep;
+ }
+
+next_state:
+ switch (state->state) {
+ case sign_updates:
+ t = ISC_LIST_HEAD(diff->tuples);
+ while (t != NULL) {
+ dns_name_t *name = &t->name;
+ /*
+ * Now "name" is a new, unique name affected by the
+ * update.
+ */
+
+ CHECK(namelist_append_name(&state->diffnames, name));
+
+ while (t != NULL && dns_name_equal(&t->name, name)) {
+ dns_rdatatype_t type;
+ type = t->rdata.type;
+
+ /*
+ * Now "name" and "type" denote a new unique
+ * RRset affected by the update.
+ */
+
+ /* Don't sign RRSIGs. */
+ if (type == dns_rdatatype_rrsig) {
+ goto skip;
+ }
+
+ /*
+ * Delete all old RRSIGs covering this type,
+ * since they are all invalid when the signed
+ * RRset has changed. We may not be able to
+ * recreate all of them - tough.
+ * Special case changes to the zone's DNSKEY
+ * records to support offline KSKs.
+ */
+ if (type == dns_rdatatype_dnskey) {
+ del_keysigs(db, newver, name,
+ &state->sig_diff,
+ state->zone_keys,
+ state->nkeys);
+ } else {
+ CHECK(delete_if(
+ true_p, db, newver, name,
+ dns_rdatatype_rrsig, type, NULL,
+ &state->sig_diff));
+ }
+
+ /*
+ * If this RRset is still visible after the
+ * update, add a new signature for it.
+ */
+ CHECK(rrset_visible(db, newver, name, type,
+ &flag));
+ if (flag) {
+ isc_stdtime_t exp;
+ if (type == dns_rdatatype_dnskey ||
+ type == dns_rdatatype_cdnskey ||
+ type == dns_rdatatype_cds)
+ {
+ exp = state->keyexpire;
+ } else if (type == dns_rdatatype_soa) {
+ exp = state->soaexpire;
+ } else {
+ exp = state->expire;
+ }
+
+ CHECK(add_sigs(
+ log, zone, db, newver, name,
+ type, &state->sig_diff,
+ state->zone_keys, state->nkeys,
+ state->inception, exp,
+ state->check_ksk,
+ state->keyset_kskonly));
+ sigs++;
+ }
+ skip:
+ /* Skip any other updates to the same RRset. */
+ while (t != NULL &&
+ dns_name_equal(&t->name, name) &&
+ t->rdata.type == type)
+ {
+ next = ISC_LIST_NEXT(t, link);
+ ISC_LIST_UNLINK(diff->tuples, t, link);
+ ISC_LIST_APPEND(state->work.tuples, t,
+ link);
+ t = next;
+ }
+ }
+ if (state != &mystate && sigs > maxsigs) {
+ return (DNS_R_CONTINUE);
+ }
+ }
+ ISC_LIST_APPENDLIST(diff->tuples, state->work.tuples, link);
+
+ update_log(log, zone, ISC_LOG_DEBUG(3),
+ "updated data signatures");
+ FALLTHROUGH;
+ case remove_orphaned:
+ state->state = remove_orphaned;
+
+ /* Remove orphaned NSECs and RRSIG NSECs. */
+ for (t = ISC_LIST_HEAD(state->diffnames.tuples); t != NULL;
+ t = ISC_LIST_NEXT(t, link))
+ {
+ CHECK(non_nsec_rrset_exists(db, newver, &t->name,
+ &flag));
+ if (!flag) {
+ CHECK(delete_if(true_p, db, newver, &t->name,
+ dns_rdatatype_any, 0, NULL,
+ &state->sig_diff));
+ }
+ }
+ update_log(log, zone, ISC_LOG_DEBUG(3),
+ "removed any orphaned NSEC records");
+
+ /*
+ * See if we need to build NSEC or NSEC3 chains.
+ */
+ CHECK(dns_private_chains(db, newver, privatetype, &build_nsec,
+ &state->build_nsec3));
+ if (!build_nsec) {
+ state->state = update_nsec3;
+ goto next_state;
+ }
+
+ update_log(log, zone, ISC_LOG_DEBUG(3),
+ "rebuilding NSEC chain");
+
+ FALLTHROUGH;
+ case build_chain:
+ state->state = build_chain;
+ /*
+ * When a name is created or deleted, its predecessor needs to
+ * have its NSEC updated.
+ */
+ for (t = ISC_LIST_HEAD(state->diffnames.tuples); t != NULL;
+ t = ISC_LIST_NEXT(t, link))
+ {
+ bool existed, exists;
+ dns_fixedname_t fixedname;
+ dns_name_t *prevname;
+
+ prevname = dns_fixedname_initname(&fixedname);
+
+ if (oldver != NULL) {
+ CHECK(name_exists(db, oldver, &t->name,
+ &existed));
+ } else {
+ existed = false;
+ }
+ CHECK(name_exists(db, newver, &t->name, &exists));
+ if (exists == existed) {
+ continue;
+ }
+
+ /*
+ * Find the predecessor.
+ * When names become obscured or unobscured in this
+ * update transaction, we may find the wrong
+ * predecessor because the NSECs have not yet been
+ * updated to reflect the delegation change. This
+ * should not matter because in this case, the correct
+ * predecessor is either the delegation node or a
+ * newly unobscured node, and those nodes are on the
+ * "affected" list in any case.
+ */
+ CHECK(next_active(log, zone, db, newver, &t->name,
+ prevname, false));
+ CHECK(namelist_append_name(&state->affected, prevname));
+ }
+
+ /*
+ * Find names potentially affected by delegation changes
+ * (obscured by adding an NS or DNAME, or unobscured by
+ * removing one).
+ */
+ for (t = ISC_LIST_HEAD(state->diffnames.tuples); t != NULL;
+ t = ISC_LIST_NEXT(t, link))
+ {
+ bool ns_existed, dname_existed;
+ bool ns_exists, dname_exists;
+
+ if (oldver != NULL) {
+ CHECK(rrset_exists(db, oldver, &t->name,
+ dns_rdatatype_ns, 0,
+ &ns_existed));
+ } else {
+ ns_existed = false;
+ }
+ if (oldver != NULL) {
+ CHECK(rrset_exists(db, oldver, &t->name,
+ dns_rdatatype_dname, 0,
+ &dname_existed));
+ } else {
+ dname_existed = false;
+ }
+ CHECK(rrset_exists(db, newver, &t->name,
+ dns_rdatatype_ns, 0, &ns_exists));
+ CHECK(rrset_exists(db, newver, &t->name,
+ dns_rdatatype_dname, 0,
+ &dname_exists));
+ if ((ns_exists || dname_exists) ==
+ (ns_existed || dname_existed))
+ {
+ continue;
+ }
+ /*
+ * There was a delegation change. Mark all subdomains
+ * of t->name as potentially needing a NSEC update.
+ */
+ CHECK(namelist_append_subdomain(db, &t->name,
+ &state->affected));
+ }
+ ISC_LIST_APPENDLIST(state->affected.tuples,
+ state->diffnames.tuples, link);
+ INSIST(ISC_LIST_EMPTY(state->diffnames.tuples));
+
+ CHECK(uniqify_name_list(&state->affected));
+
+ FALLTHROUGH;
+ case process_nsec:
+ state->state = process_nsec;
+
+ /*
+ * Determine which names should have NSECs, and delete/create
+ * NSECs to make it so. We don't know the final NSEC targets
+ * yet, so we just create placeholder NSECs with arbitrary
+ * contents to indicate that their respective owner names
+ * should be part of the NSEC chain.
+ */
+ while ((t = ISC_LIST_HEAD(state->affected.tuples)) != NULL) {
+ bool exists;
+ dns_name_t *name = &t->name;
+
+ CHECK(name_exists(db, newver, name, &exists));
+ if (!exists) {
+ goto unlink;
+ }
+ CHECK(is_active(db, newver, name, &flag, &cut, NULL));
+ if (!flag) {
+ /*
+ * This name is obscured. Delete any
+ * existing NSEC record.
+ */
+ CHECK(delete_if(true_p, db, newver, name,
+ dns_rdatatype_nsec, 0, NULL,
+ &state->nsec_diff));
+ CHECK(delete_if(rrsig_p, db, newver, name,
+ dns_rdatatype_any, 0, NULL,
+ diff));
+ } else {
+ /*
+ * This name is not obscured. It needs to have
+ * a NSEC unless it is the at the origin, in
+ * which case it should already exist if there
+ * is a complete NSEC chain and if there isn't
+ * a complete NSEC chain we don't want to add
+ * one as that would signal that there is a
+ * complete NSEC chain.
+ */
+ if (!dns_name_equal(name, dns_db_origin(db))) {
+ CHECK(rrset_exists(db, newver, name,
+ dns_rdatatype_nsec,
+ 0, &flag));
+ if (!flag) {
+ CHECK(add_placeholder_nsec(
+ db, newver, name,
+ diff));
+ }
+ }
+ CHECK(add_exposed_sigs(
+ log, zone, db, newver, name, cut,
+ &state->sig_diff, state->zone_keys,
+ state->nkeys, state->inception,
+ state->expire, state->check_ksk,
+ state->keyset_kskonly, &sigs));
+ }
+ unlink:
+ ISC_LIST_UNLINK(state->affected.tuples, t, link);
+ ISC_LIST_APPEND(state->work.tuples, t, link);
+ if (state != &mystate && sigs > maxsigs) {
+ return (DNS_R_CONTINUE);
+ }
+ }
+ ISC_LIST_APPENDLIST(state->affected.tuples, state->work.tuples,
+ link);
+
+ /*
+ * Now we know which names are part of the NSEC chain.
+ * Make them all point at their correct targets.
+ */
+ for (t = ISC_LIST_HEAD(state->affected.tuples); t != NULL;
+ t = ISC_LIST_NEXT(t, link))
+ {
+ CHECK(rrset_exists(db, newver, &t->name,
+ dns_rdatatype_nsec, 0, &flag));
+ if (flag) {
+ /*
+ * There is a NSEC, but we don't know if it
+ * is correct. Delete it and create a correct
+ * one to be sure. If the update was
+ * unnecessary, the diff minimization
+ * will take care of eliminating it from the
+ * journal, IXFRs, etc.
+ *
+ * The RRSIG bit should always be set in the
+ * NSECs we generate, because they will all
+ * get RRSIG NSECs.
+ * (XXX what if the zone keys are missing?).
+ * Because the RRSIG NSECs have not necessarily
+ * been created yet, the correctness of the
+ * bit mask relies on the assumption that NSECs
+ * are only created if there is other data, and
+ * if there is other data, there are other
+ * RRSIGs.
+ */
+ CHECK(add_nsec(log, zone, db, newver, &t->name,
+ state->nsecttl,
+ &state->nsec_diff));
+ }
+ }
+
+ /*
+ * Minimize the set of NSEC updates so that we don't
+ * have to regenerate the RRSIG NSECs for NSECs that were
+ * replaced with identical ones.
+ */
+ while ((t = ISC_LIST_HEAD(state->nsec_diff.tuples)) != NULL) {
+ ISC_LIST_UNLINK(state->nsec_diff.tuples, t, link);
+ dns_diff_appendminimal(&state->nsec_mindiff, &t);
+ }
+
+ update_log(log, zone, ISC_LOG_DEBUG(3),
+ "signing rebuilt NSEC chain");
+
+ FALLTHROUGH;
+ case sign_nsec:
+ state->state = sign_nsec;
+ /* Update RRSIG NSECs. */
+ while ((t = ISC_LIST_HEAD(state->nsec_mindiff.tuples)) != NULL)
+ {
+ if (t->op == DNS_DIFFOP_DEL) {
+ CHECK(delete_if(true_p, db, newver, &t->name,
+ dns_rdatatype_rrsig,
+ dns_rdatatype_nsec, NULL,
+ &state->sig_diff));
+ } else if (t->op == DNS_DIFFOP_ADD) {
+ CHECK(add_sigs(log, zone, db, newver, &t->name,
+ dns_rdatatype_nsec,
+ &state->sig_diff,
+ state->zone_keys, state->nkeys,
+ state->inception, state->expire,
+ state->check_ksk,
+ state->keyset_kskonly));
+ sigs++;
+ } else {
+ UNREACHABLE();
+ }
+ ISC_LIST_UNLINK(state->nsec_mindiff.tuples, t, link);
+ ISC_LIST_APPEND(state->work.tuples, t, link);
+ if (state != &mystate && sigs > maxsigs) {
+ return (DNS_R_CONTINUE);
+ }
+ }
+ ISC_LIST_APPENDLIST(state->nsec_mindiff.tuples,
+ state->work.tuples, link);
+ FALLTHROUGH;
+ case update_nsec3:
+ state->state = update_nsec3;
+
+ /* Record our changes for the journal. */
+ while ((t = ISC_LIST_HEAD(state->sig_diff.tuples)) != NULL) {
+ ISC_LIST_UNLINK(state->sig_diff.tuples, t, link);
+ dns_diff_appendminimal(diff, &t);
+ }
+ while ((t = ISC_LIST_HEAD(state->nsec_mindiff.tuples)) != NULL)
+ {
+ ISC_LIST_UNLINK(state->nsec_mindiff.tuples, t, link);
+ dns_diff_appendminimal(diff, &t);
+ }
+
+ INSIST(ISC_LIST_EMPTY(state->sig_diff.tuples));
+ INSIST(ISC_LIST_EMPTY(state->nsec_diff.tuples));
+ INSIST(ISC_LIST_EMPTY(state->nsec_mindiff.tuples));
+
+ if (!state->build_nsec3) {
+ update_log(log, zone, ISC_LOG_DEBUG(3),
+ "no NSEC3 chains to rebuild");
+ goto failure;
+ }
+
+ update_log(log, zone, ISC_LOG_DEBUG(3),
+ "rebuilding NSEC3 chains");
+
+ dns_diff_clear(&state->diffnames);
+ dns_diff_clear(&state->affected);
+
+ CHECK(dns_diff_sort(diff, temp_order));
+
+ /*
+ * Find names potentially affected by delegation changes
+ * (obscured by adding an NS or DNAME, or unobscured by
+ * removing one).
+ */
+ t = ISC_LIST_HEAD(diff->tuples);
+ while (t != NULL) {
+ dns_name_t *name = &t->name;
+
+ bool ns_existed, dname_existed;
+ bool ns_exists, dname_exists;
+ bool exists, existed;
+
+ if (t->rdata.type == dns_rdatatype_nsec ||
+ t->rdata.type == dns_rdatatype_rrsig)
+ {
+ t = ISC_LIST_NEXT(t, link);
+ continue;
+ }
+
+ CHECK(namelist_append_name(&state->affected, name));
+
+ if (oldver != NULL) {
+ CHECK(rrset_exists(db, oldver, name,
+ dns_rdatatype_ns, 0,
+ &ns_existed));
+ } else {
+ ns_existed = false;
+ }
+ if (oldver != NULL) {
+ CHECK(rrset_exists(db, oldver, name,
+ dns_rdatatype_dname, 0,
+ &dname_existed));
+ } else {
+ dname_existed = false;
+ }
+ CHECK(rrset_exists(db, newver, name, dns_rdatatype_ns,
+ 0, &ns_exists));
+ CHECK(rrset_exists(db, newver, name,
+ dns_rdatatype_dname, 0,
+ &dname_exists));
+
+ exists = ns_exists || dname_exists;
+ existed = ns_existed || dname_existed;
+ if (exists == existed) {
+ goto nextname;
+ }
+ /*
+ * There was a delegation change. Mark all subdomains
+ * of t->name as potentially needing a NSEC3 update.
+ */
+ CHECK(namelist_append_subdomain(db, name,
+ &state->affected));
+
+ nextname:
+ while (t != NULL && dns_name_equal(&t->name, name)) {
+ t = ISC_LIST_NEXT(t, link);
+ }
+ }
+
+ FALLTHROUGH;
+ case process_nsec3:
+ state->state = process_nsec3;
+ while ((t = ISC_LIST_HEAD(state->affected.tuples)) != NULL) {
+ dns_name_t *name = &t->name;
+
+ unsecure = false; /* Silence compiler warning. */
+ CHECK(is_active(db, newver, name, &flag, &cut,
+ &unsecure));
+
+ if (!flag) {
+ CHECK(delete_if(rrsig_p, db, newver, name,
+ dns_rdatatype_any, 0, NULL,
+ diff));
+ CHECK(dns_nsec3_delnsec3sx(db, newver, name,
+ privatetype,
+ &state->nsec_diff));
+ } else {
+ CHECK(add_exposed_sigs(
+ log, zone, db, newver, name, cut,
+ &state->sig_diff, state->zone_keys,
+ state->nkeys, state->inception,
+ state->expire, state->check_ksk,
+ state->keyset_kskonly, &sigs));
+ CHECK(dns_nsec3_addnsec3sx(
+ db, newver, name, state->nsecttl,
+ unsecure, privatetype,
+ &state->nsec_diff));
+ }
+ ISC_LIST_UNLINK(state->affected.tuples, t, link);
+ ISC_LIST_APPEND(state->work.tuples, t, link);
+ if (state != &mystate && sigs > maxsigs) {
+ return (DNS_R_CONTINUE);
+ }
+ }
+ ISC_LIST_APPENDLIST(state->affected.tuples, state->work.tuples,
+ link);
+
+ /*
+ * Minimize the set of NSEC3 updates so that we don't
+ * have to regenerate the RRSIG NSEC3s for NSEC3s that were
+ * replaced with identical ones.
+ */
+ while ((t = ISC_LIST_HEAD(state->nsec_diff.tuples)) != NULL) {
+ ISC_LIST_UNLINK(state->nsec_diff.tuples, t, link);
+ dns_diff_appendminimal(&state->nsec_mindiff, &t);
+ }
+
+ update_log(log, zone, ISC_LOG_DEBUG(3),
+ "signing rebuilt NSEC3 chain");
+
+ FALLTHROUGH;
+ case sign_nsec3:
+ state->state = sign_nsec3;
+ /* Update RRSIG NSEC3s. */
+ while ((t = ISC_LIST_HEAD(state->nsec_mindiff.tuples)) != NULL)
+ {
+ if (t->op == DNS_DIFFOP_DEL) {
+ CHECK(delete_if(true_p, db, newver, &t->name,
+ dns_rdatatype_rrsig,
+ dns_rdatatype_nsec3, NULL,
+ &state->sig_diff));
+ } else if (t->op == DNS_DIFFOP_ADD) {
+ CHECK(add_sigs(log, zone, db, newver, &t->name,
+ dns_rdatatype_nsec3,
+ &state->sig_diff,
+ state->zone_keys, state->nkeys,
+ state->inception, state->expire,
+ state->check_ksk,
+ state->keyset_kskonly));
+ sigs++;
+ } else {
+ UNREACHABLE();
+ }
+ ISC_LIST_UNLINK(state->nsec_mindiff.tuples, t, link);
+ ISC_LIST_APPEND(state->work.tuples, t, link);
+ if (state != &mystate && sigs > maxsigs) {
+ return (DNS_R_CONTINUE);
+ }
+ }
+ ISC_LIST_APPENDLIST(state->nsec_mindiff.tuples,
+ state->work.tuples, link);
+
+ /* Record our changes for the journal. */
+ while ((t = ISC_LIST_HEAD(state->sig_diff.tuples)) != NULL) {
+ ISC_LIST_UNLINK(state->sig_diff.tuples, t, link);
+ dns_diff_appendminimal(diff, &t);
+ }
+ while ((t = ISC_LIST_HEAD(state->nsec_mindiff.tuples)) != NULL)
+ {
+ ISC_LIST_UNLINK(state->nsec_mindiff.tuples, t, link);
+ dns_diff_appendminimal(diff, &t);
+ }
+
+ INSIST(ISC_LIST_EMPTY(state->sig_diff.tuples));
+ INSIST(ISC_LIST_EMPTY(state->nsec_diff.tuples));
+ INSIST(ISC_LIST_EMPTY(state->nsec_mindiff.tuples));
+ break;
+ default:
+ UNREACHABLE();
+ }
+
+failure:
+ if (node != NULL) {
+ dns_db_detachnode(db, &node);
+ }
+
+ dns_diff_clear(&state->sig_diff);
+ dns_diff_clear(&state->nsec_diff);
+ dns_diff_clear(&state->nsec_mindiff);
+
+ dns_diff_clear(&state->affected);
+ dns_diff_clear(&state->diffnames);
+ dns_diff_clear(&state->work);
+
+ for (i = 0; i < state->nkeys; i++) {
+ dst_key_free(&state->zone_keys[i]);
+ }
+
+ if (state != &mystate) {
+ *statep = NULL;
+ state->magic = 0;
+ isc_mem_put(diff->mctx, state, sizeof(*state));
+ }
+
+ return (result);
+}
+
+static isc_stdtime_t
+epoch_to_yyyymmdd(time_t when) {
+ struct tm t, *tm = localtime_r(&when, &t);
+ if (tm == NULL) {
+ return (0);
+ }
+ return (((tm->tm_year + 1900) * 10000) + ((tm->tm_mon + 1) * 100) +
+ tm->tm_mday);
+}
+
+static uint32_t
+dns__update_soaserial(uint32_t serial, dns_updatemethod_t method) {
+ isc_stdtime_t now;
+
+ switch (method) {
+ case dns_updatemethod_none:
+ return (serial);
+ case dns_updatemethod_unixtime:
+ isc_stdtime_get(&now);
+ return (now);
+ case dns_updatemethod_date:
+ isc_stdtime_get(&now);
+ return (epoch_to_yyyymmdd((time_t)now) * 100);
+ case dns_updatemethod_increment:
+ /* RFC1982 */
+ serial = (serial + 1) & 0xFFFFFFFF;
+ if (serial == 0) {
+ return (1);
+ }
+ return (serial);
+ default:
+ UNREACHABLE();
+ }
+}
+
+uint32_t
+dns_update_soaserial(uint32_t serial, dns_updatemethod_t method,
+ dns_updatemethod_t *used) {
+ uint32_t new_serial = dns__update_soaserial(serial, method);
+ switch (method) {
+ case dns_updatemethod_none:
+ case dns_updatemethod_increment:
+ break;
+ case dns_updatemethod_unixtime:
+ case dns_updatemethod_date:
+ if (!(new_serial != 0 && isc_serial_gt(new_serial, serial))) {
+ /*
+ * If the new date serial following YYYYMMDD00 is equal
+ * to or smaller than the current serial, but YYYYMMDD99
+ * would be larger, pretend we have used the
+ * "dns_updatemethod_date" method.
+ */
+ if (method == dns_updatemethod_unixtime ||
+ !isc_serial_gt(new_serial + 99, serial))
+ {
+ method = dns_updatemethod_increment;
+ }
+ new_serial = dns__update_soaserial(
+ serial, dns_updatemethod_increment);
+ }
+ break;
+ default:
+ UNREACHABLE();
+ }
+
+ if (used != NULL) {
+ *used = method;
+ }
+
+ return (new_serial);
+}
diff --git a/lib/dns/validator.c b/lib/dns/validator.c
new file mode 100644
index 0000000..56a0ced
--- /dev/null
+++ b/lib/dns/validator.c
@@ -0,0 +1,3394 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#include <inttypes.h>
+#include <stdbool.h>
+
+#include <isc/base32.h>
+#include <isc/md.h>
+#include <isc/mem.h>
+#include <isc/print.h>
+#include <isc/result.h>
+#include <isc/string.h>
+#include <isc/task.h>
+#include <isc/util.h>
+
+#include <dns/client.h>
+#include <dns/db.h>
+#include <dns/dnssec.h>
+#include <dns/ds.h>
+#include <dns/events.h>
+#include <dns/keytable.h>
+#include <dns/keyvalues.h>
+#include <dns/log.h>
+#include <dns/message.h>
+#include <dns/ncache.h>
+#include <dns/nsec.h>
+#include <dns/nsec3.h>
+#include <dns/rdata.h>
+#include <dns/rdataset.h>
+#include <dns/rdatatype.h>
+#include <dns/resolver.h>
+#include <dns/validator.h>
+#include <dns/view.h>
+
+/*! \file
+ * \brief
+ * Basic processing sequences:
+ *
+ * \li When called with rdataset and sigrdataset:
+ * validator_start -> validate_answer -> proveunsecure
+ * validator_start -> validate_answer -> validate_nx (if secure wildcard)
+ *
+ * \li When called with rdataset but no sigrdataset:
+ * validator_start -> proveunsecure
+ *
+ * \li When called with no rdataset or sigrdataset:
+ * validator_start -> validate_nx-> proveunsecure
+ *
+ * validator_start: determine what type of validation to do.
+ * validate_answer: attempt to perform a positive validation.
+ * proveunsecure: attempt to prove the answer comes from an unsecure zone.
+ * validate_nx: attempt to prove a negative response.
+ */
+
+#define VALIDATOR_MAGIC ISC_MAGIC('V', 'a', 'l', '?')
+#define VALID_VALIDATOR(v) ISC_MAGIC_VALID(v, VALIDATOR_MAGIC)
+
+#define VALATTR_SHUTDOWN 0x0001 /*%< Shutting down. */
+#define VALATTR_CANCELED 0x0002 /*%< Canceled. */
+#define VALATTR_TRIEDVERIFY \
+ 0x0004 /*%< We have found a key and \
+ * have attempted a verify. */
+#define VALATTR_INSECURITY 0x0010 /*%< Attempting proveunsecure. */
+
+/*!
+ * NSEC proofs to be looked for.
+ */
+#define VALATTR_NEEDNOQNAME 0x00000100
+#define VALATTR_NEEDNOWILDCARD 0x00000200
+#define VALATTR_NEEDNODATA 0x00000400
+
+/*!
+ * NSEC proofs that have been found.
+ */
+#define VALATTR_FOUNDNOQNAME 0x00001000
+#define VALATTR_FOUNDNOWILDCARD 0x00002000
+#define VALATTR_FOUNDNODATA 0x00004000
+#define VALATTR_FOUNDCLOSEST 0x00008000
+#define VALATTR_FOUNDOPTOUT 0x00010000
+#define VALATTR_FOUNDUNKNOWN 0x00020000
+
+#define NEEDNODATA(val) ((val->attributes & VALATTR_NEEDNODATA) != 0)
+#define NEEDNOQNAME(val) ((val->attributes & VALATTR_NEEDNOQNAME) != 0)
+#define NEEDNOWILDCARD(val) ((val->attributes & VALATTR_NEEDNOWILDCARD) != 0)
+#define FOUNDNODATA(val) ((val->attributes & VALATTR_FOUNDNODATA) != 0)
+#define FOUNDNOQNAME(val) ((val->attributes & VALATTR_FOUNDNOQNAME) != 0)
+#define FOUNDNOWILDCARD(val) ((val->attributes & VALATTR_FOUNDNOWILDCARD) != 0)
+#define FOUNDCLOSEST(val) ((val->attributes & VALATTR_FOUNDCLOSEST) != 0)
+#define FOUNDOPTOUT(val) ((val->attributes & VALATTR_FOUNDOPTOUT) != 0)
+
+#define SHUTDOWN(v) (((v)->attributes & VALATTR_SHUTDOWN) != 0)
+#define CANCELED(v) (((v)->attributes & VALATTR_CANCELED) != 0)
+
+#define NEGATIVE(r) (((r)->attributes & DNS_RDATASETATTR_NEGATIVE) != 0)
+#define NXDOMAIN(r) (((r)->attributes & DNS_RDATASETATTR_NXDOMAIN) != 0)
+
+static void
+destroy(dns_validator_t *val);
+
+static isc_result_t
+select_signing_key(dns_validator_t *val, dns_rdataset_t *rdataset);
+
+static isc_result_t
+validate_answer(dns_validator_t *val, bool resume);
+
+static isc_result_t
+validate_dnskey(dns_validator_t *val);
+
+static isc_result_t
+validate_nx(dns_validator_t *val, bool resume);
+
+static isc_result_t
+proveunsecure(dns_validator_t *val, bool have_ds, bool resume);
+
+static void
+validator_logv(dns_validator_t *val, isc_logcategory_t *category,
+ isc_logmodule_t *module, int level, const char *fmt, va_list ap)
+ ISC_FORMAT_PRINTF(5, 0);
+
+static void
+validator_log(void *val, int level, const char *fmt, ...)
+ ISC_FORMAT_PRINTF(3, 4);
+
+static void
+validator_logcreate(dns_validator_t *val, dns_name_t *name,
+ dns_rdatatype_t type, const char *caller,
+ const char *operation);
+
+/*%
+ * Ensure the validator's rdatasets are marked as expired.
+ */
+static void
+expire_rdatasets(dns_validator_t *val) {
+ if (dns_rdataset_isassociated(&val->frdataset)) {
+ dns_rdataset_expire(&val->frdataset);
+ }
+ if (dns_rdataset_isassociated(&val->fsigrdataset)) {
+ dns_rdataset_expire(&val->fsigrdataset);
+ }
+}
+
+/*%
+ * Ensure the validator's rdatasets are disassociated.
+ */
+static void
+disassociate_rdatasets(dns_validator_t *val) {
+ if (dns_rdataset_isassociated(&val->fdsset)) {
+ dns_rdataset_disassociate(&val->fdsset);
+ }
+ if (dns_rdataset_isassociated(&val->frdataset)) {
+ dns_rdataset_disassociate(&val->frdataset);
+ }
+ if (dns_rdataset_isassociated(&val->fsigrdataset)) {
+ dns_rdataset_disassociate(&val->fsigrdataset);
+ }
+}
+
+/*%
+ * Mark the rdatasets in val->event with trust level "answer",
+ * indicating that they did not validate, but could be cached as insecure.
+ *
+ * If we are validating a name that is marked as "must be secure", log a
+ * warning and return DNS_R_MUSTBESECURE instead.
+ */
+static isc_result_t
+markanswer(dns_validator_t *val, const char *where, const char *mbstext) {
+ if (val->mustbesecure && mbstext != NULL) {
+ validator_log(val, ISC_LOG_WARNING,
+ "must be secure failure, %s", mbstext);
+ return (DNS_R_MUSTBESECURE);
+ }
+
+ validator_log(val, ISC_LOG_DEBUG(3), "marking as answer (%s)", where);
+ if (val->event->rdataset != NULL) {
+ dns_rdataset_settrust(val->event->rdataset, dns_trust_answer);
+ }
+ if (val->event->sigrdataset != NULL) {
+ dns_rdataset_settrust(val->event->sigrdataset,
+ dns_trust_answer);
+ }
+
+ return (ISC_R_SUCCESS);
+}
+
+/*%
+ * Mark the RRsets in val->event with trust level secure.
+ */
+static void
+marksecure(dns_validatorevent_t *event) {
+ dns_rdataset_settrust(event->rdataset, dns_trust_secure);
+ if (event->sigrdataset != NULL) {
+ dns_rdataset_settrust(event->sigrdataset, dns_trust_secure);
+ }
+ event->secure = true;
+}
+
+/*
+ * Validator 'val' is finished; send the completion event to the task
+ * that called dns_validator_create(), with result `result`.
+ */
+static void
+validator_done(dns_validator_t *val, isc_result_t result) {
+ isc_task_t *task;
+
+ if (val->event == NULL) {
+ return;
+ }
+
+ /*
+ * Caller must be holding the lock.
+ */
+
+ val->event->result = result;
+ task = val->event->ev_sender;
+ val->event->ev_sender = val;
+ val->event->ev_type = DNS_EVENT_VALIDATORDONE;
+ val->event->ev_action = val->action;
+ val->event->ev_arg = val->arg;
+ isc_task_sendanddetach(&task, (isc_event_t **)&val->event);
+}
+
+/*
+ * Called when deciding whether to destroy validator 'val'.
+ */
+static bool
+exit_check(dns_validator_t *val) {
+ /*
+ * Caller must be holding the lock.
+ */
+ if (!SHUTDOWN(val)) {
+ return (false);
+ }
+
+ INSIST(val->event == NULL);
+
+ if (val->fetch != NULL || val->subvalidator != NULL) {
+ return (false);
+ }
+
+ return (true);
+}
+
+/*%
+ * Look in the NSEC record returned from a DS query to see if there is
+ * a NS RRset at this name. If it is found we are at a delegation point.
+ */
+static bool
+isdelegation(dns_name_t *name, dns_rdataset_t *rdataset,
+ isc_result_t dbresult) {
+ dns_fixedname_t fixed;
+ dns_label_t hashlabel;
+ dns_name_t nsec3name;
+ dns_rdata_nsec3_t nsec3;
+ dns_rdata_t rdata = DNS_RDATA_INIT;
+ dns_rdataset_t set;
+ int order;
+ int scope;
+ bool found;
+ isc_buffer_t buffer;
+ isc_result_t result;
+ unsigned char hash[NSEC3_MAX_HASH_LENGTH];
+ unsigned char owner[NSEC3_MAX_HASH_LENGTH];
+ unsigned int length;
+
+ REQUIRE(dbresult == DNS_R_NXRRSET || dbresult == DNS_R_NCACHENXRRSET);
+
+ dns_rdataset_init(&set);
+ if (dbresult == DNS_R_NXRRSET) {
+ dns_rdataset_clone(rdataset, &set);
+ } else {
+ result = dns_ncache_getrdataset(rdataset, name,
+ dns_rdatatype_nsec, &set);
+ if (result == ISC_R_NOTFOUND) {
+ goto trynsec3;
+ }
+ if (result != ISC_R_SUCCESS) {
+ return (false);
+ }
+ }
+
+ INSIST(set.type == dns_rdatatype_nsec);
+
+ found = false;
+ result = dns_rdataset_first(&set);
+ if (result == ISC_R_SUCCESS) {
+ dns_rdataset_current(&set, &rdata);
+ found = dns_nsec_typepresent(&rdata, dns_rdatatype_ns);
+ dns_rdata_reset(&rdata);
+ }
+ dns_rdataset_disassociate(&set);
+ return (found);
+
+trynsec3:
+ /*
+ * Iterate over the ncache entry.
+ */
+ found = false;
+ dns_name_init(&nsec3name, NULL);
+ dns_fixedname_init(&fixed);
+ dns_name_downcase(name, dns_fixedname_name(&fixed), NULL);
+ name = dns_fixedname_name(&fixed);
+ for (result = dns_rdataset_first(rdataset); result == ISC_R_SUCCESS;
+ result = dns_rdataset_next(rdataset))
+ {
+ dns_ncache_current(rdataset, &nsec3name, &set);
+ if (set.type != dns_rdatatype_nsec3) {
+ dns_rdataset_disassociate(&set);
+ continue;
+ }
+ dns_name_getlabel(&nsec3name, 0, &hashlabel);
+ isc_region_consume(&hashlabel, 1);
+ isc_buffer_init(&buffer, owner, sizeof(owner));
+ result = isc_base32hexnp_decoderegion(&hashlabel, &buffer);
+ if (result != ISC_R_SUCCESS) {
+ dns_rdataset_disassociate(&set);
+ continue;
+ }
+ for (result = dns_rdataset_first(&set); result == ISC_R_SUCCESS;
+ result = dns_rdataset_next(&set))
+ {
+ dns_rdata_reset(&rdata);
+ dns_rdataset_current(&set, &rdata);
+ (void)dns_rdata_tostruct(&rdata, &nsec3, NULL);
+ if (nsec3.hash != 1) {
+ continue;
+ }
+ length = isc_iterated_hash(
+ hash, nsec3.hash, nsec3.iterations, nsec3.salt,
+ nsec3.salt_length, name->ndata, name->length);
+ if (length != isc_buffer_usedlength(&buffer)) {
+ continue;
+ }
+ order = memcmp(hash, owner, length);
+ if (order == 0) {
+ found = dns_nsec3_typepresent(&rdata,
+ dns_rdatatype_ns);
+ dns_rdataset_disassociate(&set);
+ return (found);
+ }
+ if ((nsec3.flags & DNS_NSEC3FLAG_OPTOUT) == 0) {
+ continue;
+ }
+ /*
+ * Does this optout span cover the name?
+ */
+ scope = memcmp(owner, nsec3.next, nsec3.next_length);
+ if ((scope < 0 && order > 0 &&
+ memcmp(hash, nsec3.next, length) < 0) ||
+ (scope >= 0 &&
+ (order > 0 ||
+ memcmp(hash, nsec3.next, length) < 0)))
+ {
+ dns_rdataset_disassociate(&set);
+ return (true);
+ }
+ }
+ dns_rdataset_disassociate(&set);
+ }
+ return (found);
+}
+
+/*%
+ * We have been asked to look for a key.
+ * If found, resume the validation process.
+ * If not found, fail the validation process.
+ */
+static void
+fetch_callback_dnskey(isc_task_t *task, isc_event_t *event) {
+ dns_fetchevent_t *devent;
+ dns_validator_t *val;
+ dns_rdataset_t *rdataset;
+ bool want_destroy;
+ isc_result_t result;
+ isc_result_t eresult;
+ isc_result_t saved_result;
+ dns_fetch_t *fetch;
+
+ UNUSED(task);
+ INSIST(event->ev_type == DNS_EVENT_FETCHDONE);
+ devent = (dns_fetchevent_t *)event;
+ val = devent->ev_arg;
+ rdataset = &val->frdataset;
+ eresult = devent->result;
+
+ /* Free resources which are not of interest. */
+ if (devent->node != NULL) {
+ dns_db_detachnode(devent->db, &devent->node);
+ }
+ if (devent->db != NULL) {
+ dns_db_detach(&devent->db);
+ }
+ if (dns_rdataset_isassociated(&val->fsigrdataset)) {
+ dns_rdataset_disassociate(&val->fsigrdataset);
+ }
+ isc_event_free(&event);
+
+ INSIST(val->event != NULL);
+
+ validator_log(val, ISC_LOG_DEBUG(3), "in fetch_callback_dnskey");
+ LOCK(&val->lock);
+ fetch = val->fetch;
+ val->fetch = NULL;
+ if (CANCELED(val)) {
+ validator_done(val, ISC_R_CANCELED);
+ } else if (eresult == ISC_R_SUCCESS || eresult == DNS_R_NCACHENXRRSET) {
+ /*
+ * We have an answer to our DNSKEY query. Either the DNSKEY
+ * RRset or a NODATA response.
+ */
+ validator_log(val, ISC_LOG_DEBUG(3), "%s with trust %s",
+ eresult == ISC_R_SUCCESS ? "keyset"
+ : "NCACHENXRRSET",
+ dns_trust_totext(rdataset->trust));
+ /*
+ * Only extract the dst key if the keyset exists and is secure.
+ */
+ if (eresult == ISC_R_SUCCESS &&
+ rdataset->trust >= dns_trust_secure)
+ {
+ result = select_signing_key(val, rdataset);
+ if (result == ISC_R_SUCCESS) {
+ val->keyset = &val->frdataset;
+ }
+ }
+ result = validate_answer(val, true);
+ if (result == DNS_R_NOVALIDSIG &&
+ (val->attributes & VALATTR_TRIEDVERIFY) == 0)
+ {
+ saved_result = result;
+ validator_log(val, ISC_LOG_DEBUG(3),
+ "falling back to insecurity proof");
+ result = proveunsecure(val, false, false);
+ if (result == DNS_R_NOTINSECURE) {
+ result = saved_result;
+ }
+ }
+ if (result != DNS_R_WAIT) {
+ validator_done(val, result);
+ }
+ } else {
+ validator_log(val, ISC_LOG_DEBUG(3),
+ "fetch_callback_dnskey: got %s",
+ isc_result_totext(eresult));
+ if (eresult == ISC_R_CANCELED) {
+ validator_done(val, eresult);
+ } else {
+ validator_done(val, DNS_R_BROKENCHAIN);
+ }
+ }
+
+ want_destroy = exit_check(val);
+ UNLOCK(&val->lock);
+
+ if (fetch != NULL) {
+ dns_resolver_destroyfetch(&fetch);
+ }
+
+ if (want_destroy) {
+ destroy(val);
+ }
+}
+
+/*%
+ * We have been asked to look for a DS. This may be part of
+ * walking a trust chain, or an insecurity proof.
+ */
+static void
+fetch_callback_ds(isc_task_t *task, isc_event_t *event) {
+ dns_fetchevent_t *devent;
+ dns_validator_t *val;
+ dns_rdataset_t *rdataset;
+ bool want_destroy, trustchain;
+ isc_result_t result;
+ isc_result_t eresult;
+ dns_fetch_t *fetch;
+
+ UNUSED(task);
+ INSIST(event->ev_type == DNS_EVENT_FETCHDONE);
+ devent = (dns_fetchevent_t *)event;
+ val = devent->ev_arg;
+ rdataset = &val->frdataset;
+ eresult = devent->result;
+
+ /*
+ * Set 'trustchain' to true if we're walking a chain of
+ * trust; false if we're attempting to prove insecurity.
+ */
+ trustchain = ((val->attributes & VALATTR_INSECURITY) == 0);
+
+ /* Free resources which are not of interest. */
+ if (devent->node != NULL) {
+ dns_db_detachnode(devent->db, &devent->node);
+ }
+ if (devent->db != NULL) {
+ dns_db_detach(&devent->db);
+ }
+ if (dns_rdataset_isassociated(&val->fsigrdataset)) {
+ dns_rdataset_disassociate(&val->fsigrdataset);
+ }
+
+ INSIST(val->event != NULL);
+
+ validator_log(val, ISC_LOG_DEBUG(3), "in fetch_callback_ds");
+ LOCK(&val->lock);
+ fetch = val->fetch;
+ val->fetch = NULL;
+
+ if (CANCELED(val)) {
+ validator_done(val, ISC_R_CANCELED);
+ goto done;
+ }
+
+ switch (eresult) {
+ case DNS_R_NXDOMAIN:
+ case DNS_R_NCACHENXDOMAIN:
+ /*
+ * These results only make sense if we're attempting
+ * an insecurity proof, not when walking a chain of trust.
+ */
+ if (trustchain) {
+ goto unexpected;
+ }
+
+ FALLTHROUGH;
+ case ISC_R_SUCCESS:
+ if (trustchain) {
+ /*
+ * We looked for a DS record as part of
+ * following a key chain upwards; resume following
+ * the chain.
+ */
+ validator_log(val, ISC_LOG_DEBUG(3),
+ "dsset with trust %s",
+ dns_trust_totext(rdataset->trust));
+ val->dsset = &val->frdataset;
+ result = validate_dnskey(val);
+ if (result != DNS_R_WAIT) {
+ validator_done(val, result);
+ }
+ } else {
+ /*
+ * There is a DS which may or may not be a zone cut.
+ * In either case we are still in a secure zone,
+ * so keep looking for the break in the chain
+ * of trust.
+ */
+ result = proveunsecure(val, (eresult == ISC_R_SUCCESS),
+ true);
+ if (result != DNS_R_WAIT) {
+ validator_done(val, result);
+ }
+ }
+ break;
+ case DNS_R_CNAME:
+ case DNS_R_NXRRSET:
+ case DNS_R_NCACHENXRRSET:
+ case DNS_R_SERVFAIL: /* RFC 1034 parent? */
+ if (trustchain) {
+ /*
+ * Failed to find a DS while following the
+ * chain of trust; now we need to prove insecurity.
+ */
+ validator_log(val, ISC_LOG_DEBUG(3),
+ "falling back to insecurity proof (%s)",
+ isc_result_totext(eresult));
+ result = proveunsecure(val, false, false);
+ if (result != DNS_R_WAIT) {
+ validator_done(val, result);
+ }
+ } else if (eresult == DNS_R_SERVFAIL) {
+ goto unexpected;
+ } else if (eresult != DNS_R_CNAME &&
+ isdelegation(devent->foundname, &val->frdataset,
+ eresult))
+ {
+ /*
+ * Failed to find a DS while trying to prove
+ * insecurity. If this is a zone cut, that
+ * means we're insecure.
+ */
+ result = markanswer(val, "fetch_callback_ds",
+ "no DS and this is a delegation");
+ validator_done(val, result);
+ } else {
+ /*
+ * Not a zone cut, so we have to keep looking for
+ * the break point in the chain of trust.
+ */
+ result = proveunsecure(val, false, true);
+ if (result != DNS_R_WAIT) {
+ validator_done(val, result);
+ }
+ }
+ break;
+
+ default:
+ unexpected:
+ validator_log(val, ISC_LOG_DEBUG(3),
+ "fetch_callback_ds: got %s",
+ isc_result_totext(eresult));
+ if (eresult == ISC_R_CANCELED) {
+ validator_done(val, eresult);
+ } else {
+ validator_done(val, DNS_R_BROKENCHAIN);
+ }
+ }
+done:
+
+ isc_event_free(&event);
+ want_destroy = exit_check(val);
+ UNLOCK(&val->lock);
+
+ if (fetch != NULL) {
+ dns_resolver_destroyfetch(&fetch);
+ }
+
+ if (want_destroy) {
+ destroy(val);
+ }
+}
+
+/*%
+ * Callback from when a DNSKEY RRset has been validated.
+ *
+ * Resumes the stalled validation process.
+ */
+static void
+validator_callback_dnskey(isc_task_t *task, isc_event_t *event) {
+ dns_validatorevent_t *devent;
+ dns_validator_t *val;
+ bool want_destroy;
+ isc_result_t result;
+ isc_result_t eresult;
+ isc_result_t saved_result;
+
+ UNUSED(task);
+ INSIST(event->ev_type == DNS_EVENT_VALIDATORDONE);
+
+ devent = (dns_validatorevent_t *)event;
+ val = devent->ev_arg;
+ eresult = devent->result;
+
+ isc_event_free(&event);
+ dns_validator_destroy(&val->subvalidator);
+
+ INSIST(val->event != NULL);
+
+ validator_log(val, ISC_LOG_DEBUG(3), "in validator_callback_dnskey");
+ LOCK(&val->lock);
+ if (CANCELED(val)) {
+ validator_done(val, ISC_R_CANCELED);
+ } else if (eresult == ISC_R_SUCCESS) {
+ validator_log(val, ISC_LOG_DEBUG(3), "keyset with trust %s",
+ dns_trust_totext(val->frdataset.trust));
+ /*
+ * Only extract the dst key if the keyset is secure.
+ */
+ if (val->frdataset.trust >= dns_trust_secure) {
+ (void)select_signing_key(val, &val->frdataset);
+ }
+ result = validate_answer(val, true);
+ if (result == DNS_R_NOVALIDSIG &&
+ (val->attributes & VALATTR_TRIEDVERIFY) == 0)
+ {
+ saved_result = result;
+ validator_log(val, ISC_LOG_DEBUG(3),
+ "falling back to insecurity proof");
+ result = proveunsecure(val, false, false);
+ if (result == DNS_R_NOTINSECURE) {
+ result = saved_result;
+ }
+ }
+ if (result != DNS_R_WAIT) {
+ validator_done(val, result);
+ }
+ } else {
+ if (eresult != DNS_R_BROKENCHAIN) {
+ expire_rdatasets(val);
+ }
+ validator_log(val, ISC_LOG_DEBUG(3),
+ "validator_callback_dnskey: got %s",
+ isc_result_totext(eresult));
+ validator_done(val, DNS_R_BROKENCHAIN);
+ }
+
+ want_destroy = exit_check(val);
+ UNLOCK(&val->lock);
+ if (want_destroy) {
+ destroy(val);
+ }
+}
+
+/*%
+ * Callback when the DS record has been validated.
+ *
+ * Resumes validation of the zone key or the unsecure zone proof.
+ */
+static void
+validator_callback_ds(isc_task_t *task, isc_event_t *event) {
+ dns_validatorevent_t *devent;
+ dns_validator_t *val;
+ bool want_destroy;
+ isc_result_t result;
+ isc_result_t eresult;
+
+ UNUSED(task);
+ INSIST(event->ev_type == DNS_EVENT_VALIDATORDONE);
+
+ devent = (dns_validatorevent_t *)event;
+ val = devent->ev_arg;
+ eresult = devent->result;
+
+ isc_event_free(&event);
+ dns_validator_destroy(&val->subvalidator);
+
+ INSIST(val->event != NULL);
+
+ validator_log(val, ISC_LOG_DEBUG(3), "in validator_callback_ds");
+ LOCK(&val->lock);
+ if (CANCELED(val)) {
+ validator_done(val, ISC_R_CANCELED);
+ } else if (eresult == ISC_R_SUCCESS) {
+ bool have_dsset;
+ dns_name_t *name;
+ validator_log(val, ISC_LOG_DEBUG(3), "%s with trust %s",
+ val->frdataset.type == dns_rdatatype_ds ? "dsset"
+ : "ds "
+ "non-"
+ "existe"
+ "nce",
+ dns_trust_totext(val->frdataset.trust));
+ have_dsset = (val->frdataset.type == dns_rdatatype_ds);
+ name = dns_fixedname_name(&val->fname);
+ if ((val->attributes & VALATTR_INSECURITY) != 0 &&
+ val->frdataset.covers == dns_rdatatype_ds &&
+ NEGATIVE(&val->frdataset) &&
+ isdelegation(name, &val->frdataset, DNS_R_NCACHENXRRSET))
+ {
+ result = markanswer(val, "validator_callback_ds",
+ "no DS and this is a delegation");
+ } else if ((val->attributes & VALATTR_INSECURITY) != 0) {
+ result = proveunsecure(val, have_dsset, true);
+ } else {
+ result = validate_dnskey(val);
+ }
+ if (result != DNS_R_WAIT) {
+ validator_done(val, result);
+ }
+ } else {
+ if (eresult != DNS_R_BROKENCHAIN) {
+ expire_rdatasets(val);
+ }
+ validator_log(val, ISC_LOG_DEBUG(3),
+ "validator_callback_ds: got %s",
+ isc_result_totext(eresult));
+ validator_done(val, DNS_R_BROKENCHAIN);
+ }
+
+ want_destroy = exit_check(val);
+ UNLOCK(&val->lock);
+ if (want_destroy) {
+ destroy(val);
+ }
+}
+
+/*%
+ * Callback when the CNAME record has been validated.
+ *
+ * Resumes validation of the unsecure zone proof.
+ */
+static void
+validator_callback_cname(isc_task_t *task, isc_event_t *event) {
+ dns_validatorevent_t *devent;
+ dns_validator_t *val;
+ bool want_destroy;
+ isc_result_t result;
+ isc_result_t eresult;
+
+ UNUSED(task);
+ INSIST(event->ev_type == DNS_EVENT_VALIDATORDONE);
+
+ devent = (dns_validatorevent_t *)event;
+ val = devent->ev_arg;
+ eresult = devent->result;
+
+ isc_event_free(&event);
+ dns_validator_destroy(&val->subvalidator);
+
+ INSIST(val->event != NULL);
+ INSIST((val->attributes & VALATTR_INSECURITY) != 0);
+
+ validator_log(val, ISC_LOG_DEBUG(3), "in validator_callback_cname");
+ LOCK(&val->lock);
+ if (CANCELED(val)) {
+ validator_done(val, ISC_R_CANCELED);
+ } else if (eresult == ISC_R_SUCCESS) {
+ validator_log(val, ISC_LOG_DEBUG(3), "cname with trust %s",
+ dns_trust_totext(val->frdataset.trust));
+ result = proveunsecure(val, false, true);
+ if (result != DNS_R_WAIT) {
+ validator_done(val, result);
+ }
+ } else {
+ if (eresult != DNS_R_BROKENCHAIN) {
+ expire_rdatasets(val);
+ }
+ validator_log(val, ISC_LOG_DEBUG(3),
+ "validator_callback_cname: got %s",
+ isc_result_totext(eresult));
+ validator_done(val, DNS_R_BROKENCHAIN);
+ }
+
+ want_destroy = exit_check(val);
+ UNLOCK(&val->lock);
+ if (want_destroy) {
+ destroy(val);
+ }
+}
+
+/*%
+ * Callback for when NSEC records have been validated.
+ *
+ * Looks for NOQNAME, NODATA and OPTOUT proofs.
+ *
+ * Resumes the negative response validation by calling validate_nx().
+ */
+static void
+validator_callback_nsec(isc_task_t *task, isc_event_t *event) {
+ dns_validatorevent_t *devent;
+ dns_validator_t *val;
+ dns_rdataset_t *rdataset;
+ bool want_destroy;
+ isc_result_t result;
+ bool exists, data;
+
+ UNUSED(task);
+ INSIST(event->ev_type == DNS_EVENT_VALIDATORDONE);
+
+ devent = (dns_validatorevent_t *)event;
+ rdataset = devent->rdataset;
+ val = devent->ev_arg;
+ result = devent->result;
+ dns_validator_destroy(&val->subvalidator);
+
+ INSIST(val->event != NULL);
+
+ validator_log(val, ISC_LOG_DEBUG(3), "in validator_callback_nsec");
+ LOCK(&val->lock);
+ if (CANCELED(val)) {
+ validator_done(val, ISC_R_CANCELED);
+ } else if (result != ISC_R_SUCCESS) {
+ validator_log(val, ISC_LOG_DEBUG(3),
+ "validator_callback_nsec: got %s",
+ isc_result_totext(result));
+ if (result == DNS_R_BROKENCHAIN) {
+ val->authfail++;
+ }
+ if (result == ISC_R_CANCELED) {
+ validator_done(val, result);
+ } else {
+ result = validate_nx(val, true);
+ if (result != DNS_R_WAIT) {
+ validator_done(val, result);
+ }
+ }
+ } else {
+ dns_name_t **proofs = val->event->proofs;
+ dns_name_t *wild = dns_fixedname_name(&val->wild);
+
+ if (rdataset->type == dns_rdatatype_nsec &&
+ rdataset->trust == dns_trust_secure &&
+ (NEEDNODATA(val) || NEEDNOQNAME(val)) &&
+ !FOUNDNODATA(val) && !FOUNDNOQNAME(val) &&
+ dns_nsec_noexistnodata(val->event->type, val->event->name,
+ devent->name, rdataset, &exists,
+ &data, wild, validator_log,
+ val) == ISC_R_SUCCESS)
+ {
+ if (exists && !data) {
+ val->attributes |= VALATTR_FOUNDNODATA;
+ if (NEEDNODATA(val)) {
+ proofs[DNS_VALIDATOR_NODATAPROOF] =
+ devent->name;
+ }
+ }
+ if (!exists) {
+ dns_name_t *closest;
+ unsigned int clabels;
+
+ val->attributes |= VALATTR_FOUNDNOQNAME;
+
+ closest = dns_fixedname_name(&val->closest);
+ clabels = dns_name_countlabels(closest);
+ /*
+ * If we are validating a wildcard response
+ * clabels will not be zero. We then need
+ * to check if the generated wildcard from
+ * dns_nsec_noexistnodata is consistent with
+ * the wildcard used to generate the response.
+ */
+ if (clabels == 0 ||
+ dns_name_countlabels(wild) == clabels + 1)
+ {
+ val->attributes |= VALATTR_FOUNDCLOSEST;
+ }
+ /*
+ * The NSEC noqname proof also contains
+ * the closest encloser.
+ */
+ if (NEEDNOQNAME(val)) {
+ proofs[DNS_VALIDATOR_NOQNAMEPROOF] =
+ devent->name;
+ }
+ }
+ }
+
+ result = validate_nx(val, true);
+ if (result != DNS_R_WAIT) {
+ validator_done(val, result);
+ }
+ }
+
+ want_destroy = exit_check(val);
+ UNLOCK(&val->lock);
+ if (want_destroy) {
+ destroy(val);
+ }
+
+ /*
+ * Free stuff from the event.
+ */
+ isc_event_free(&event);
+}
+
+/*%
+ * Looks for the requested name and type in the view (zones and cache).
+ *
+ * Returns:
+ * \li ISC_R_SUCCESS
+ * \li ISC_R_NOTFOUND
+ * \li DNS_R_NCACHENXDOMAIN
+ * \li DNS_R_NCACHENXRRSET
+ * \li DNS_R_NXRRSET
+ * \li DNS_R_NXDOMAIN
+ * \li DNS_R_BROKENCHAIN
+ */
+static isc_result_t
+view_find(dns_validator_t *val, dns_name_t *name, dns_rdatatype_t type) {
+ dns_fixedname_t fixedname;
+ dns_name_t *foundname;
+ isc_result_t result;
+ unsigned int options;
+ isc_time_t now;
+ char namebuf[DNS_NAME_FORMATSIZE];
+ char typebuf[DNS_RDATATYPE_FORMATSIZE];
+
+ disassociate_rdatasets(val);
+
+ if (isc_time_now(&now) == ISC_R_SUCCESS &&
+ dns_resolver_getbadcache(val->view->resolver, name, type, &now))
+ {
+ dns_name_format(name, namebuf, sizeof(namebuf));
+ dns_rdatatype_format(type, typebuf, sizeof(typebuf));
+ validator_log(val, ISC_LOG_INFO, "bad cache hit (%s/%s)",
+ namebuf, typebuf);
+ return (DNS_R_BROKENCHAIN);
+ }
+
+ options = DNS_DBFIND_PENDINGOK;
+ foundname = dns_fixedname_initname(&fixedname);
+ result = dns_view_find(val->view, name, type, 0, options, false, false,
+ NULL, NULL, foundname, &val->frdataset,
+ &val->fsigrdataset);
+
+ if (result == DNS_R_NXDOMAIN) {
+ goto notfound;
+ } else if (result != ISC_R_SUCCESS && result != DNS_R_NCACHENXDOMAIN &&
+ result != DNS_R_NCACHENXRRSET && result != DNS_R_EMPTYNAME &&
+ result != DNS_R_NXRRSET && result != ISC_R_NOTFOUND)
+ {
+ result = ISC_R_NOTFOUND;
+ goto notfound;
+ }
+
+ return (result);
+
+notfound:
+ disassociate_rdatasets(val);
+
+ return (result);
+}
+
+/*%
+ * Checks to make sure we are not going to loop. As we use a SHARED fetch
+ * the validation process will stall if looping was to occur.
+ */
+static bool
+check_deadlock(dns_validator_t *val, dns_name_t *name, dns_rdatatype_t type,
+ dns_rdataset_t *rdataset, dns_rdataset_t *sigrdataset) {
+ dns_validator_t *parent;
+
+ for (parent = val; parent != NULL; parent = parent->parent) {
+ if (parent->event != NULL && parent->event->type == type &&
+ dns_name_equal(parent->event->name, name) &&
+ /*
+ * As NSEC3 records are meta data you sometimes
+ * need to prove a NSEC3 record which says that
+ * itself doesn't exist.
+ */
+ (parent->event->type != dns_rdatatype_nsec3 ||
+ rdataset == NULL || sigrdataset == NULL ||
+ parent->event->message == NULL ||
+ parent->event->rdataset != NULL ||
+ parent->event->sigrdataset != NULL))
+ {
+ validator_log(val, ISC_LOG_DEBUG(3),
+ "continuing validation would lead to "
+ "deadlock: aborting validation");
+ return (true);
+ }
+ }
+ return (false);
+}
+
+/*%
+ * Start a fetch for the requested name and type.
+ */
+static isc_result_t
+create_fetch(dns_validator_t *val, dns_name_t *name, dns_rdatatype_t type,
+ isc_taskaction_t callback, const char *caller) {
+ unsigned int fopts = 0;
+
+ disassociate_rdatasets(val);
+
+ if (check_deadlock(val, name, type, NULL, NULL)) {
+ validator_log(val, ISC_LOG_DEBUG(3),
+ "deadlock found (create_fetch)");
+ return (DNS_R_NOVALIDSIG);
+ }
+
+ if ((val->options & DNS_VALIDATOR_NOCDFLAG) != 0) {
+ fopts |= DNS_FETCHOPT_NOCDFLAG;
+ }
+
+ if ((val->options & DNS_VALIDATOR_NONTA) != 0) {
+ fopts |= DNS_FETCHOPT_NONTA;
+ }
+
+ validator_logcreate(val, name, type, caller, "fetch");
+ return (dns_resolver_createfetch(
+ val->view->resolver, name, type, NULL, NULL, NULL, NULL, 0,
+ fopts, 0, NULL, val->event->ev_sender, callback, val,
+ &val->frdataset, &val->fsigrdataset, &val->fetch));
+}
+
+/*%
+ * Start a subvalidation process.
+ */
+static isc_result_t
+create_validator(dns_validator_t *val, dns_name_t *name, dns_rdatatype_t type,
+ dns_rdataset_t *rdataset, dns_rdataset_t *sigrdataset,
+ isc_taskaction_t action, const char *caller) {
+ isc_result_t result;
+ unsigned int vopts = 0;
+ dns_rdataset_t *sig = NULL;
+
+ if (sigrdataset != NULL && dns_rdataset_isassociated(sigrdataset)) {
+ sig = sigrdataset;
+ }
+
+ if (check_deadlock(val, name, type, rdataset, sig)) {
+ validator_log(val, ISC_LOG_DEBUG(3),
+ "deadlock found (create_validator)");
+ return (DNS_R_NOVALIDSIG);
+ }
+
+ /* OK to clear other options, but preserve NOCDFLAG and NONTA. */
+ vopts |= (val->options &
+ (DNS_VALIDATOR_NOCDFLAG | DNS_VALIDATOR_NONTA));
+
+ validator_logcreate(val, name, type, caller, "validator");
+ result = dns_validator_create(val->view, name, type, rdataset, sig,
+ NULL, vopts, val->task, action, val,
+ &val->subvalidator);
+ if (result == ISC_R_SUCCESS) {
+ val->subvalidator->parent = val;
+ val->subvalidator->depth = val->depth + 1;
+ }
+ return (result);
+}
+
+/*%
+ * Try to find a key that could have signed val->siginfo among those in
+ * 'rdataset'. If found, build a dst_key_t for it and point val->key at
+ * it.
+ *
+ * If val->key is already non-NULL, locate it in the rdataset and then
+ * search past it for the *next* key that could have signed 'siginfo', then
+ * set val->key to that.
+ *
+ * Returns ISC_R_SUCCESS if a possible matching key has been found,
+ * ISC_R_NOTFOUND if not. Any other value indicates error.
+ */
+static isc_result_t
+select_signing_key(dns_validator_t *val, dns_rdataset_t *rdataset) {
+ isc_result_t result;
+ dns_rdata_rrsig_t *siginfo = val->siginfo;
+ isc_buffer_t b;
+ dns_rdata_t rdata = DNS_RDATA_INIT;
+ dst_key_t *oldkey = val->key;
+ bool foundold;
+
+ if (oldkey == NULL) {
+ foundold = true;
+ } else {
+ foundold = false;
+ val->key = NULL;
+ }
+
+ result = dns_rdataset_first(rdataset);
+ if (result != ISC_R_SUCCESS) {
+ goto failure;
+ }
+ do {
+ dns_rdataset_current(rdataset, &rdata);
+
+ isc_buffer_init(&b, rdata.data, rdata.length);
+ isc_buffer_add(&b, rdata.length);
+ INSIST(val->key == NULL);
+ result = dst_key_fromdns(&siginfo->signer, rdata.rdclass, &b,
+ val->view->mctx, &val->key);
+ if (result == ISC_R_SUCCESS) {
+ if (siginfo->algorithm ==
+ (dns_secalg_t)dst_key_alg(val->key) &&
+ siginfo->keyid ==
+ (dns_keytag_t)dst_key_id(val->key) &&
+ dst_key_iszonekey(val->key))
+ {
+ if (foundold) {
+ /*
+ * This is the key we're looking for.
+ */
+ return (ISC_R_SUCCESS);
+ } else if (dst_key_compare(oldkey, val->key)) {
+ foundold = true;
+ dst_key_free(&oldkey);
+ }
+ }
+ dst_key_free(&val->key);
+ }
+ dns_rdata_reset(&rdata);
+ result = dns_rdataset_next(rdataset);
+ } while (result == ISC_R_SUCCESS);
+
+ if (result == ISC_R_NOMORE) {
+ result = ISC_R_NOTFOUND;
+ }
+
+failure:
+ if (oldkey != NULL) {
+ dst_key_free(&oldkey);
+ }
+
+ return (result);
+}
+
+/*%
+ * Get the key that generated the signature in val->siginfo.
+ */
+static isc_result_t
+seek_dnskey(dns_validator_t *val) {
+ isc_result_t result;
+ dns_rdata_rrsig_t *siginfo = val->siginfo;
+ unsigned int nlabels;
+ int order;
+ dns_namereln_t namereln;
+
+ /*
+ * Is the signer name appropriate for this signature?
+ *
+ * The signer name must be at the same level as the owner name
+ * or closer to the DNS root.
+ */
+ namereln = dns_name_fullcompare(val->event->name, &siginfo->signer,
+ &order, &nlabels);
+ if (namereln != dns_namereln_subdomain &&
+ namereln != dns_namereln_equal)
+ {
+ return (DNS_R_CONTINUE);
+ }
+
+ if (namereln == dns_namereln_equal) {
+ /*
+ * If this is a self-signed keyset, it must not be a zone key
+ * (since seek_dnskey is not called from validate_dnskey).
+ */
+ if (val->event->rdataset->type == dns_rdatatype_dnskey) {
+ return (DNS_R_CONTINUE);
+ }
+
+ /*
+ * Records appearing in the parent zone at delegation
+ * points cannot be self-signed.
+ */
+ if (dns_rdatatype_atparent(val->event->rdataset->type)) {
+ return (DNS_R_CONTINUE);
+ }
+ } else {
+ /*
+ * SOA and NS RRsets can only be signed by a key with
+ * the same name.
+ */
+ if (val->event->rdataset->type == dns_rdatatype_soa ||
+ val->event->rdataset->type == dns_rdatatype_ns)
+ {
+ const char *type;
+
+ if (val->event->rdataset->type == dns_rdatatype_soa) {
+ type = "SOA";
+ } else {
+ type = "NS";
+ }
+ validator_log(val, ISC_LOG_DEBUG(3),
+ "%s signer mismatch", type);
+ return (DNS_R_CONTINUE);
+ }
+ }
+
+ /*
+ * Do we know about this key?
+ */
+ result = view_find(val, &siginfo->signer, dns_rdatatype_dnskey);
+ switch (result) {
+ case ISC_R_SUCCESS:
+ /*
+ * We have an rrset for the given keyname.
+ */
+ val->keyset = &val->frdataset;
+ if ((DNS_TRUST_PENDING(val->frdataset.trust) ||
+ DNS_TRUST_ANSWER(val->frdataset.trust)) &&
+ dns_rdataset_isassociated(&val->fsigrdataset))
+ {
+ /*
+ * We know the key but haven't validated it yet or
+ * we have a key of trust answer but a DS
+ * record for the zone may have been added.
+ */
+ result = create_validator(
+ val, &siginfo->signer, dns_rdatatype_dnskey,
+ &val->frdataset, &val->fsigrdataset,
+ validator_callback_dnskey, "seek_dnskey");
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+ return (DNS_R_WAIT);
+ } else if (DNS_TRUST_PENDING(val->frdataset.trust)) {
+ /*
+ * Having a pending key with no signature means that
+ * something is broken.
+ */
+ result = DNS_R_CONTINUE;
+ } else if (val->frdataset.trust < dns_trust_secure) {
+ /*
+ * The key is legitimately insecure. There's no
+ * point in even attempting verification.
+ */
+ val->key = NULL;
+ result = ISC_R_SUCCESS;
+ } else {
+ /*
+ * See if we've got the key used in the signature.
+ */
+ validator_log(val, ISC_LOG_DEBUG(3),
+ "keyset with trust %s",
+ dns_trust_totext(val->frdataset.trust));
+ result = select_signing_key(val, val->keyset);
+ if (result != ISC_R_SUCCESS) {
+ /*
+ * Either the key we're looking for is not
+ * in the rrset, or something bad happened.
+ * Give up.
+ */
+ result = DNS_R_CONTINUE;
+ }
+ }
+ break;
+
+ case ISC_R_NOTFOUND:
+ /*
+ * We don't know anything about this key.
+ */
+ result = create_fetch(val, &siginfo->signer,
+ dns_rdatatype_dnskey,
+ fetch_callback_dnskey, "seek_dnskey");
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+ return (DNS_R_WAIT);
+
+ case DNS_R_NCACHENXDOMAIN:
+ case DNS_R_NCACHENXRRSET:
+ case DNS_R_EMPTYNAME:
+ case DNS_R_NXDOMAIN:
+ case DNS_R_NXRRSET:
+ /*
+ * This key doesn't exist.
+ */
+ result = DNS_R_CONTINUE;
+ break;
+
+ case DNS_R_BROKENCHAIN:
+ return (result);
+
+ default:
+ break;
+ }
+
+ if (dns_rdataset_isassociated(&val->frdataset) &&
+ val->keyset != &val->frdataset)
+ {
+ dns_rdataset_disassociate(&val->frdataset);
+ }
+ if (dns_rdataset_isassociated(&val->fsigrdataset)) {
+ dns_rdataset_disassociate(&val->fsigrdataset);
+ }
+
+ return (result);
+}
+
+/*
+ * Compute the tag for a key represented in a DNSKEY rdata.
+ */
+static dns_keytag_t
+compute_keytag(dns_rdata_t *rdata) {
+ isc_region_t r;
+
+ dns_rdata_toregion(rdata, &r);
+ return (dst_region_computeid(&r));
+}
+
+/*%
+ * Is the DNSKEY rrset in val->event->rdataset self-signed?
+ */
+static bool
+selfsigned_dnskey(dns_validator_t *val) {
+ dns_rdataset_t *rdataset = val->event->rdataset;
+ dns_rdataset_t *sigrdataset = val->event->sigrdataset;
+ dns_name_t *name = val->event->name;
+ isc_result_t result;
+ isc_mem_t *mctx = val->view->mctx;
+ bool answer = false;
+
+ if (rdataset->type != dns_rdatatype_dnskey) {
+ return (false);
+ }
+
+ for (result = dns_rdataset_first(rdataset); result == ISC_R_SUCCESS;
+ result = dns_rdataset_next(rdataset))
+ {
+ dns_rdata_t keyrdata = DNS_RDATA_INIT;
+ dns_rdata_t sigrdata = DNS_RDATA_INIT;
+ dns_rdata_dnskey_t key;
+ dns_rdata_rrsig_t sig;
+ dns_keytag_t keytag;
+
+ dns_rdata_reset(&keyrdata);
+ dns_rdataset_current(rdataset, &keyrdata);
+ result = dns_rdata_tostruct(&keyrdata, &key, NULL);
+ RUNTIME_CHECK(result == ISC_R_SUCCESS);
+ keytag = compute_keytag(&keyrdata);
+
+ for (result = dns_rdataset_first(sigrdataset);
+ result == ISC_R_SUCCESS;
+ result = dns_rdataset_next(sigrdataset))
+ {
+ dst_key_t *dstkey = NULL;
+
+ dns_rdata_reset(&sigrdata);
+ dns_rdataset_current(sigrdataset, &sigrdata);
+ result = dns_rdata_tostruct(&sigrdata, &sig, NULL);
+ RUNTIME_CHECK(result == ISC_R_SUCCESS);
+
+ if (sig.algorithm != key.algorithm ||
+ sig.keyid != keytag ||
+ !dns_name_equal(name, &sig.signer))
+ {
+ continue;
+ }
+
+ /*
+ * If the REVOKE bit is not set we have a
+ * theoretically self signed DNSKEY RRset.
+ * This will be verified later.
+ */
+ if ((key.flags & DNS_KEYFLAG_REVOKE) == 0) {
+ answer = true;
+ continue;
+ }
+
+ result = dns_dnssec_keyfromrdata(name, &keyrdata, mctx,
+ &dstkey);
+ if (result != ISC_R_SUCCESS) {
+ continue;
+ }
+
+ /*
+ * If this RRset is pending and it is trusted,
+ * see if it was self signed by this DNSKEY.
+ */
+ if (DNS_TRUST_PENDING(rdataset->trust) &&
+ dns_view_istrusted(val->view, name, &key))
+ {
+ result = dns_dnssec_verify(
+ name, rdataset, dstkey, true,
+ val->view->maxbits, mctx, &sigrdata,
+ NULL);
+ if (result == ISC_R_SUCCESS) {
+ /*
+ * The key with the REVOKE flag has
+ * self signed the RRset so it is no
+ * good.
+ */
+ dns_view_untrust(val->view, name, &key);
+ }
+ } else if (rdataset->trust >= dns_trust_secure) {
+ /*
+ * We trust this RRset so if the key is
+ * marked revoked remove it.
+ */
+ dns_view_untrust(val->view, name, &key);
+ }
+
+ dst_key_free(&dstkey);
+ }
+ }
+
+ return (answer);
+}
+
+/*%
+ * Attempt to verify the rdataset using the given key and rdata (RRSIG).
+ * The signature was good and from a wildcard record and the QNAME does
+ * not match the wildcard we need to look for a NOQNAME proof.
+ *
+ * Returns:
+ * \li ISC_R_SUCCESS if the verification succeeds.
+ * \li Others if the verification fails.
+ */
+static isc_result_t
+verify(dns_validator_t *val, dst_key_t *key, dns_rdata_t *rdata,
+ uint16_t keyid) {
+ isc_result_t result;
+ dns_fixedname_t fixed;
+ bool ignore = false;
+ dns_name_t *wild;
+
+ val->attributes |= VALATTR_TRIEDVERIFY;
+ wild = dns_fixedname_initname(&fixed);
+again:
+ result = dns_dnssec_verify(val->event->name, val->event->rdataset, key,
+ ignore, val->view->maxbits, val->view->mctx,
+ rdata, wild);
+ if ((result == DNS_R_SIGEXPIRED || result == DNS_R_SIGFUTURE) &&
+ val->view->acceptexpired)
+ {
+ ignore = true;
+ goto again;
+ }
+
+ if (ignore && (result == ISC_R_SUCCESS || result == DNS_R_FROMWILDCARD))
+ {
+ validator_log(val, ISC_LOG_INFO,
+ "accepted expired %sRRSIG (keyid=%u)",
+ (result == DNS_R_FROMWILDCARD) ? "wildcard " : "",
+ keyid);
+ } else if (result == DNS_R_SIGEXPIRED || result == DNS_R_SIGFUTURE) {
+ validator_log(val, ISC_LOG_INFO,
+ "verify failed due to bad signature (keyid=%u): "
+ "%s",
+ keyid, isc_result_totext(result));
+ } else {
+ validator_log(val, ISC_LOG_DEBUG(3),
+ "verify rdataset (keyid=%u): %s", keyid,
+ isc_result_totext(result));
+ }
+ if (result == DNS_R_FROMWILDCARD) {
+ if (!dns_name_equal(val->event->name, wild)) {
+ dns_name_t *closest;
+ unsigned int labels;
+
+ /*
+ * Compute the closest encloser in case we need it
+ * for the NSEC3 NOQNAME proof.
+ */
+ closest = dns_fixedname_name(&val->closest);
+ dns_name_copy(wild, closest);
+ labels = dns_name_countlabels(closest) - 1;
+ dns_name_getlabelsequence(closest, 1, labels, closest);
+ val->attributes |= VALATTR_NEEDNOQNAME;
+ }
+ result = ISC_R_SUCCESS;
+ }
+ return (result);
+}
+
+/*%
+ * Attempts positive response validation of a normal RRset.
+ *
+ * Returns:
+ * \li ISC_R_SUCCESS Validation completed successfully
+ * \li DNS_R_WAIT Validation has started but is waiting
+ * for an event.
+ * \li Other return codes are possible and all indicate failure.
+ */
+static isc_result_t
+validate_answer(dns_validator_t *val, bool resume) {
+ isc_result_t result, vresult = DNS_R_NOVALIDSIG;
+ dns_validatorevent_t *event;
+ dns_rdata_t rdata = DNS_RDATA_INIT;
+
+ /*
+ * Caller must be holding the validator lock.
+ */
+
+ event = val->event;
+
+ if (resume) {
+ /*
+ * We already have a sigrdataset.
+ */
+ result = ISC_R_SUCCESS;
+ validator_log(val, ISC_LOG_DEBUG(3), "resuming validate");
+ } else {
+ result = dns_rdataset_first(event->sigrdataset);
+ }
+
+ for (; result == ISC_R_SUCCESS;
+ result = dns_rdataset_next(event->sigrdataset))
+ {
+ dns_rdata_reset(&rdata);
+ dns_rdataset_current(event->sigrdataset, &rdata);
+ if (val->siginfo == NULL) {
+ val->siginfo = isc_mem_get(val->view->mctx,
+ sizeof(*val->siginfo));
+ }
+ result = dns_rdata_tostruct(&rdata, val->siginfo, NULL);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+
+ /*
+ * At this point we could check that the signature algorithm
+ * was known and "sufficiently good".
+ */
+ if (!dns_resolver_algorithm_supported(val->view->resolver,
+ event->name,
+ val->siginfo->algorithm))
+ {
+ resume = false;
+ continue;
+ }
+
+ if (!resume) {
+ result = seek_dnskey(val);
+ if (result == DNS_R_CONTINUE) {
+ continue; /* Try the next SIG RR. */
+ }
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+ }
+
+ /*
+ * There isn't a secure DNSKEY for this signature so move
+ * onto the next RRSIG.
+ */
+ if (val->key == NULL) {
+ resume = false;
+ continue;
+ }
+
+ do {
+ isc_result_t tresult;
+ vresult = verify(val, val->key, &rdata,
+ val->siginfo->keyid);
+ if (vresult == ISC_R_SUCCESS) {
+ break;
+ }
+
+ tresult = select_signing_key(val, val->keyset);
+ if (tresult != ISC_R_SUCCESS) {
+ break;
+ }
+ } while (1);
+ if (vresult != ISC_R_SUCCESS) {
+ validator_log(val, ISC_LOG_DEBUG(3),
+ "failed to verify rdataset");
+ } else {
+ dns_rdataset_trimttl(event->rdataset,
+ event->sigrdataset, val->siginfo,
+ val->start,
+ val->view->acceptexpired);
+ }
+
+ if (val->key != NULL) {
+ dst_key_free(&val->key);
+ }
+ if (val->keyset != NULL) {
+ dns_rdataset_disassociate(val->keyset);
+ val->keyset = NULL;
+ }
+ val->key = NULL;
+ if (NEEDNOQNAME(val)) {
+ if (val->event->message == NULL) {
+ validator_log(val, ISC_LOG_DEBUG(3),
+ "no message available "
+ "for noqname proof");
+ return (DNS_R_NOVALIDSIG);
+ }
+ validator_log(val, ISC_LOG_DEBUG(3),
+ "looking for noqname proof");
+ return (validate_nx(val, false));
+ } else if (vresult == ISC_R_SUCCESS) {
+ marksecure(event);
+ validator_log(val, ISC_LOG_DEBUG(3),
+ "marking as secure, "
+ "noqname proof not needed");
+ return (ISC_R_SUCCESS);
+ } else {
+ validator_log(val, ISC_LOG_DEBUG(3),
+ "verify failure: %s",
+ isc_result_totext(result));
+ resume = false;
+ }
+ }
+ if (result != ISC_R_NOMORE) {
+ validator_log(val, ISC_LOG_DEBUG(3),
+ "failed to iterate signatures: %s",
+ isc_result_totext(result));
+ return (result);
+ }
+
+ validator_log(val, ISC_LOG_INFO, "no valid signature found");
+ return (vresult);
+}
+
+/*%
+ * Check whether this DNSKEY (keyrdata) signed the DNSKEY RRset
+ * (val->event->rdataset).
+ */
+static isc_result_t
+check_signer(dns_validator_t *val, dns_rdata_t *keyrdata, uint16_t keyid,
+ dns_secalg_t algorithm) {
+ dns_rdata_rrsig_t sig;
+ dst_key_t *dstkey = NULL;
+ isc_result_t result;
+
+ for (result = dns_rdataset_first(val->event->sigrdataset);
+ result == ISC_R_SUCCESS;
+ result = dns_rdataset_next(val->event->sigrdataset))
+ {
+ dns_rdata_t rdata = DNS_RDATA_INIT;
+
+ dns_rdataset_current(val->event->sigrdataset, &rdata);
+ result = dns_rdata_tostruct(&rdata, &sig, NULL);
+ RUNTIME_CHECK(result == ISC_R_SUCCESS);
+ if (keyid != sig.keyid || algorithm != sig.algorithm) {
+ continue;
+ }
+ if (dstkey == NULL) {
+ result = dns_dnssec_keyfromrdata(
+ val->event->name, keyrdata, val->view->mctx,
+ &dstkey);
+ if (result != ISC_R_SUCCESS) {
+ /*
+ * This really shouldn't happen, but...
+ */
+ continue;
+ }
+ }
+ result = verify(val, dstkey, &rdata, sig.keyid);
+ if (result == ISC_R_SUCCESS) {
+ break;
+ }
+ }
+
+ if (dstkey != NULL) {
+ dst_key_free(&dstkey);
+ }
+
+ return (result);
+}
+
+/*
+ * get_dsset() is called to look up a DS RRset corresponding to the name
+ * of a DNSKEY record, either in the cache or, if necessary, by starting a
+ * fetch. This is done in the context of validating a zone key to build a
+ * trust chain.
+ *
+ * Returns:
+ * \li ISC_R_COMPLETE a DS has not been found; the caller should
+ * stop trying to validate the zone key and
+ * return the result code in '*resp'.
+ * \li DNS_R_CONTINUE a DS has been found and the caller may
+ * continue the zone key validation.
+ */
+static isc_result_t
+get_dsset(dns_validator_t *val, dns_name_t *tname, isc_result_t *resp) {
+ isc_result_t result;
+
+ result = view_find(val, tname, dns_rdatatype_ds);
+ switch (result) {
+ case ISC_R_SUCCESS:
+ /*
+ * We have a DS RRset.
+ */
+ val->dsset = &val->frdataset;
+ if ((DNS_TRUST_PENDING(val->frdataset.trust) ||
+ DNS_TRUST_ANSWER(val->frdataset.trust)) &&
+ dns_rdataset_isassociated(&val->fsigrdataset))
+ {
+ /*
+ * ... which is signed but not yet validated.
+ */
+ result = create_validator(
+ val, tname, dns_rdatatype_ds, &val->frdataset,
+ &val->fsigrdataset, validator_callback_ds,
+ "validate_dnskey");
+ *resp = DNS_R_WAIT;
+ if (result != ISC_R_SUCCESS) {
+ *resp = result;
+ }
+ return (ISC_R_COMPLETE);
+ } else if (DNS_TRUST_PENDING(val->frdataset.trust)) {
+ /*
+ * There should never be an unsigned DS.
+ */
+ disassociate_rdatasets(val);
+ validator_log(val, ISC_LOG_DEBUG(2),
+ "unsigned DS record");
+ *resp = DNS_R_NOVALIDSIG;
+ return (ISC_R_COMPLETE);
+ }
+ break;
+
+ case ISC_R_NOTFOUND:
+ /*
+ * We don't have the DS. Find it.
+ */
+ result = create_fetch(val, tname, dns_rdatatype_ds,
+ fetch_callback_ds, "validate_dnskey");
+ *resp = DNS_R_WAIT;
+ if (result != ISC_R_SUCCESS) {
+ *resp = result;
+ }
+ return (ISC_R_COMPLETE);
+
+ case DNS_R_NCACHENXDOMAIN:
+ case DNS_R_NCACHENXRRSET:
+ case DNS_R_EMPTYNAME:
+ case DNS_R_NXDOMAIN:
+ case DNS_R_NXRRSET:
+ case DNS_R_CNAME:
+ /*
+ * The DS does not exist.
+ */
+ disassociate_rdatasets(val);
+ validator_log(val, ISC_LOG_DEBUG(2), "no DS record");
+ *resp = DNS_R_NOVALIDSIG;
+ return (ISC_R_COMPLETE);
+
+ case DNS_R_BROKENCHAIN:
+ *resp = result;
+ return (ISC_R_COMPLETE);
+
+ default:
+ break;
+ }
+
+ return (DNS_R_CONTINUE);
+}
+
+/*%
+ * Attempts positive response validation of an RRset containing zone keys
+ * (i.e. a DNSKEY rrset).
+ *
+ * Caller must be holding the validator lock.
+ *
+ * Returns:
+ * \li ISC_R_SUCCESS Validation completed successfully
+ * \li DNS_R_WAIT Validation has started but is waiting
+ * for an event.
+ * \li Other return codes are possible and all indicate failure.
+ */
+static isc_result_t
+validate_dnskey(dns_validator_t *val) {
+ isc_result_t result;
+ dns_rdata_t dsrdata = DNS_RDATA_INIT;
+ dns_rdata_t keyrdata = DNS_RDATA_INIT;
+ dns_keynode_t *keynode = NULL;
+ dns_rdata_ds_t ds;
+ bool supported_algorithm;
+ char digest_types[256];
+
+ /*
+ * If we don't already have a DS RRset, check to see if there's
+ * a DS style trust anchor configured for this key.
+ */
+ if (val->dsset == NULL) {
+ result = dns_keytable_find(val->keytable, val->event->name,
+ &keynode);
+ if (result == ISC_R_SUCCESS) {
+ if (dns_keynode_dsset(keynode, &val->fdsset)) {
+ val->dsset = &val->fdsset;
+ }
+ dns_keytable_detachkeynode(val->keytable, &keynode);
+ }
+ }
+
+ /*
+ * No trust anchor for this name, so we look up the DS at the parent.
+ */
+ if (val->dsset == NULL) {
+ isc_result_t tresult = ISC_R_SUCCESS;
+
+ /*
+ * If this is the root name and there was no trust anchor,
+ * we can give up now, since there's no DS at the root.
+ */
+ if (dns_name_equal(val->event->name, dns_rootname)) {
+ if ((val->attributes & VALATTR_TRIEDVERIFY) != 0) {
+ validator_log(val, ISC_LOG_DEBUG(3),
+ "root key failed to validate");
+ } else {
+ validator_log(val, ISC_LOG_DEBUG(3),
+ "no trusted root key");
+ }
+ result = DNS_R_NOVALIDSIG;
+ goto cleanup;
+ }
+
+ /*
+ * Look up the DS RRset for this name.
+ */
+ result = get_dsset(val, val->event->name, &tresult);
+ if (result == ISC_R_COMPLETE) {
+ result = tresult;
+ goto cleanup;
+ }
+ }
+
+ /*
+ * We have a DS set.
+ */
+ INSIST(val->dsset != NULL);
+
+ if (val->dsset->trust < dns_trust_secure) {
+ result = markanswer(val, "validate_dnskey (2)", "insecure DS");
+ goto cleanup;
+ }
+
+ /*
+ * Look through the DS record and find the keys that can sign the
+ * key set and the matching signature. For each such key, attempt
+ * verification.
+ */
+
+ supported_algorithm = false;
+
+ /*
+ * If DNS_DSDIGEST_SHA256 or DNS_DSDIGEST_SHA384 is present we
+ * are required to prefer it over DNS_DSDIGEST_SHA1. This in
+ * practice means that we need to ignore DNS_DSDIGEST_SHA1 if a
+ * DNS_DSDIGEST_SHA256 or DNS_DSDIGEST_SHA384 is present.
+ */
+ memset(digest_types, 1, sizeof(digest_types));
+ for (result = dns_rdataset_first(val->dsset); result == ISC_R_SUCCESS;
+ result = dns_rdataset_next(val->dsset))
+ {
+ dns_rdata_reset(&dsrdata);
+ dns_rdataset_current(val->dsset, &dsrdata);
+ result = dns_rdata_tostruct(&dsrdata, &ds, NULL);
+ RUNTIME_CHECK(result == ISC_R_SUCCESS);
+
+ if (!dns_resolver_ds_digest_supported(val->view->resolver,
+ val->event->name,
+ ds.digest_type))
+ {
+ continue;
+ }
+
+ if (!dns_resolver_algorithm_supported(val->view->resolver,
+ val->event->name,
+ ds.algorithm))
+ {
+ continue;
+ }
+
+ if ((ds.digest_type == DNS_DSDIGEST_SHA256 &&
+ ds.length == ISC_SHA256_DIGESTLENGTH) ||
+ (ds.digest_type == DNS_DSDIGEST_SHA384 &&
+ ds.length == ISC_SHA384_DIGESTLENGTH))
+ {
+ digest_types[DNS_DSDIGEST_SHA1] = 0;
+ break;
+ }
+ }
+
+ for (result = dns_rdataset_first(val->dsset); result == ISC_R_SUCCESS;
+ result = dns_rdataset_next(val->dsset))
+ {
+ dns_rdata_reset(&dsrdata);
+ dns_rdataset_current(val->dsset, &dsrdata);
+ result = dns_rdata_tostruct(&dsrdata, &ds, NULL);
+ RUNTIME_CHECK(result == ISC_R_SUCCESS);
+
+ if (digest_types[ds.digest_type] == 0) {
+ continue;
+ }
+
+ if (!dns_resolver_ds_digest_supported(val->view->resolver,
+ val->event->name,
+ ds.digest_type))
+ {
+ continue;
+ }
+
+ if (!dns_resolver_algorithm_supported(val->view->resolver,
+ val->event->name,
+ ds.algorithm))
+ {
+ continue;
+ }
+
+ supported_algorithm = true;
+
+ /*
+ * Find the DNSKEY matching the DS...
+ */
+ result = dns_dnssec_matchdskey(val->event->name, &dsrdata,
+ val->event->rdataset, &keyrdata);
+ if (result != ISC_R_SUCCESS) {
+ validator_log(val, ISC_LOG_DEBUG(3),
+ "no DNSKEY matching DS");
+ continue;
+ }
+
+ /*
+ * ... and check that it signed the DNSKEY RRset.
+ */
+ result = check_signer(val, &keyrdata, ds.key_tag, ds.algorithm);
+ if (result == ISC_R_SUCCESS) {
+ break;
+ }
+ validator_log(val, ISC_LOG_DEBUG(3),
+ "no RRSIG matching DS key");
+ }
+
+ if (result == ISC_R_SUCCESS) {
+ marksecure(val->event);
+ validator_log(val, ISC_LOG_DEBUG(3), "marking as secure (DS)");
+ } else if (result == ISC_R_NOMORE && !supported_algorithm) {
+ validator_log(val, ISC_LOG_DEBUG(3),
+ "no supported algorithm/digest (DS)");
+ result = markanswer(val, "validate_dnskey (3)",
+ "no supported algorithm/digest (DS)");
+ } else {
+ validator_log(val, ISC_LOG_INFO,
+ "no valid signature found (DS)");
+ result = DNS_R_NOVALIDSIG;
+ }
+
+cleanup:
+ if (val->dsset == &val->fdsset) {
+ val->dsset = NULL;
+ dns_rdataset_disassociate(&val->fdsset);
+ }
+
+ return (result);
+}
+
+/*%
+ * val_rdataset_first and val_rdataset_next provide iteration methods
+ * that hide whether we are iterating across the AUTHORITY section of
+ * a message, or a negative cache rdataset.
+ */
+static isc_result_t
+val_rdataset_first(dns_validator_t *val, dns_name_t **namep,
+ dns_rdataset_t **rdatasetp) {
+ dns_message_t *message = val->event->message;
+ isc_result_t result;
+
+ REQUIRE(rdatasetp != NULL);
+ REQUIRE(namep != NULL);
+ if (message == NULL) {
+ REQUIRE(*rdatasetp != NULL);
+ REQUIRE(*namep != NULL);
+ } else {
+ REQUIRE(*rdatasetp == NULL);
+ REQUIRE(*namep == NULL);
+ }
+
+ if (message != NULL) {
+ result = dns_message_firstname(message, DNS_SECTION_AUTHORITY);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+ dns_message_currentname(message, DNS_SECTION_AUTHORITY, namep);
+ *rdatasetp = ISC_LIST_HEAD((*namep)->list);
+ INSIST(*rdatasetp != NULL);
+ } else {
+ result = dns_rdataset_first(val->event->rdataset);
+ if (result == ISC_R_SUCCESS) {
+ dns_ncache_current(val->event->rdataset, *namep,
+ *rdatasetp);
+ }
+ }
+ return (result);
+}
+
+static isc_result_t
+val_rdataset_next(dns_validator_t *val, dns_name_t **namep,
+ dns_rdataset_t **rdatasetp) {
+ dns_message_t *message = val->event->message;
+ isc_result_t result = ISC_R_SUCCESS;
+
+ REQUIRE(rdatasetp != NULL && *rdatasetp != NULL);
+ REQUIRE(namep != NULL && *namep != NULL);
+
+ if (message != NULL) {
+ dns_rdataset_t *rdataset = *rdatasetp;
+ rdataset = ISC_LIST_NEXT(rdataset, link);
+ if (rdataset == NULL) {
+ *namep = NULL;
+ result = dns_message_nextname(message,
+ DNS_SECTION_AUTHORITY);
+ if (result == ISC_R_SUCCESS) {
+ dns_message_currentname(
+ message, DNS_SECTION_AUTHORITY, namep);
+ rdataset = ISC_LIST_HEAD((*namep)->list);
+ INSIST(rdataset != NULL);
+ }
+ }
+ *rdatasetp = rdataset;
+ } else {
+ dns_rdataset_disassociate(*rdatasetp);
+ result = dns_rdataset_next(val->event->rdataset);
+ if (result == ISC_R_SUCCESS) {
+ dns_ncache_current(val->event->rdataset, *namep,
+ *rdatasetp);
+ }
+ }
+ return (result);
+}
+
+/*%
+ * Look for NODATA at the wildcard and NOWILDCARD proofs in the
+ * previously validated NSEC records. As these proofs are mutually
+ * exclusive we stop when one is found.
+ *
+ * Returns
+ * \li ISC_R_SUCCESS
+ */
+static isc_result_t
+checkwildcard(dns_validator_t *val, dns_rdatatype_t type,
+ dns_name_t *zonename) {
+ dns_name_t *name, *wild, tname;
+ isc_result_t result;
+ bool exists, data;
+ char namebuf[DNS_NAME_FORMATSIZE];
+ dns_rdataset_t *rdataset, trdataset;
+
+ dns_name_init(&tname, NULL);
+ dns_rdataset_init(&trdataset);
+ wild = dns_fixedname_name(&val->wild);
+
+ if (dns_name_countlabels(wild) == 0) {
+ validator_log(val, ISC_LOG_DEBUG(3),
+ "in checkwildcard: no wildcard to check");
+ return (ISC_R_SUCCESS);
+ }
+
+ dns_name_format(wild, namebuf, sizeof(namebuf));
+ validator_log(val, ISC_LOG_DEBUG(3), "in checkwildcard: %s", namebuf);
+
+ if (val->event->message == NULL) {
+ name = &tname;
+ rdataset = &trdataset;
+ } else {
+ name = NULL;
+ rdataset = NULL;
+ }
+
+ for (result = val_rdataset_first(val, &name, &rdataset);
+ result == ISC_R_SUCCESS;
+ result = val_rdataset_next(val, &name, &rdataset))
+ {
+ if (rdataset->type != type ||
+ rdataset->trust != dns_trust_secure)
+ {
+ continue;
+ }
+
+ if (rdataset->type == dns_rdatatype_nsec &&
+ (NEEDNODATA(val) || NEEDNOWILDCARD(val)) &&
+ !FOUNDNODATA(val) && !FOUNDNOWILDCARD(val) &&
+ dns_nsec_noexistnodata(val->event->type, wild, name,
+ rdataset, &exists, &data, NULL,
+ validator_log, val) == ISC_R_SUCCESS)
+ {
+ dns_name_t **proofs = val->event->proofs;
+ if (exists && !data) {
+ val->attributes |= VALATTR_FOUNDNODATA;
+ }
+ if (exists && !data && NEEDNODATA(val)) {
+ proofs[DNS_VALIDATOR_NODATAPROOF] = name;
+ }
+ if (!exists) {
+ val->attributes |= VALATTR_FOUNDNOWILDCARD;
+ }
+ if (!exists && NEEDNOQNAME(val)) {
+ proofs[DNS_VALIDATOR_NOWILDCARDPROOF] = name;
+ }
+ if (dns_rdataset_isassociated(&trdataset)) {
+ dns_rdataset_disassociate(&trdataset);
+ }
+ return (ISC_R_SUCCESS);
+ }
+
+ if (rdataset->type == dns_rdatatype_nsec3 &&
+ (NEEDNODATA(val) || NEEDNOWILDCARD(val)) &&
+ !FOUNDNODATA(val) && !FOUNDNOWILDCARD(val) &&
+ dns_nsec3_noexistnodata(
+ val->event->type, wild, name, rdataset, zonename,
+ &exists, &data, NULL, NULL, NULL, NULL, NULL, NULL,
+ validator_log, val) == ISC_R_SUCCESS)
+ {
+ dns_name_t **proofs = val->event->proofs;
+ if (exists && !data) {
+ val->attributes |= VALATTR_FOUNDNODATA;
+ }
+ if (exists && !data && NEEDNODATA(val)) {
+ proofs[DNS_VALIDATOR_NODATAPROOF] = name;
+ }
+ if (!exists) {
+ val->attributes |= VALATTR_FOUNDNOWILDCARD;
+ }
+ if (!exists && NEEDNOQNAME(val)) {
+ proofs[DNS_VALIDATOR_NOWILDCARDPROOF] = name;
+ }
+ if (dns_rdataset_isassociated(&trdataset)) {
+ dns_rdataset_disassociate(&trdataset);
+ }
+ return (ISC_R_SUCCESS);
+ }
+ }
+ if (result == ISC_R_NOMORE) {
+ result = ISC_R_SUCCESS;
+ }
+ if (dns_rdataset_isassociated(&trdataset)) {
+ dns_rdataset_disassociate(&trdataset);
+ }
+ return (result);
+}
+
+/*
+ * Look for the needed proofs for a negative or wildcard response
+ * from a zone using NSEC3, and set flags in the validator as they
+ * are found.
+ */
+static isc_result_t
+findnsec3proofs(dns_validator_t *val) {
+ dns_name_t *name, tname;
+ isc_result_t result;
+ bool exists, data, optout, unknown;
+ bool setclosest, setnearest, *setclosestp;
+ dns_fixedname_t fclosest, fnearest, fzonename;
+ dns_name_t *closest, *nearest, *zonename, *closestp;
+ dns_name_t **proofs = val->event->proofs;
+ dns_rdataset_t *rdataset, trdataset;
+
+ dns_name_init(&tname, NULL);
+ dns_rdataset_init(&trdataset);
+ closest = dns_fixedname_initname(&fclosest);
+ nearest = dns_fixedname_initname(&fnearest);
+ zonename = dns_fixedname_initname(&fzonename);
+
+ if (val->event->message == NULL) {
+ name = &tname;
+ rdataset = &trdataset;
+ } else {
+ name = NULL;
+ rdataset = NULL;
+ }
+
+ for (result = val_rdataset_first(val, &name, &rdataset);
+ result == ISC_R_SUCCESS;
+ result = val_rdataset_next(val, &name, &rdataset))
+ {
+ if (rdataset->type != dns_rdatatype_nsec3 ||
+ rdataset->trust != dns_trust_secure)
+ {
+ continue;
+ }
+
+ result = dns_nsec3_noexistnodata(
+ val->event->type, val->event->name, name, rdataset,
+ zonename, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL, validator_log, val);
+ if (result != ISC_R_IGNORE && result != ISC_R_SUCCESS) {
+ if (dns_rdataset_isassociated(&trdataset)) {
+ dns_rdataset_disassociate(&trdataset);
+ }
+ return (result);
+ }
+ }
+ if (result != ISC_R_NOMORE) {
+ result = ISC_R_SUCCESS;
+ }
+ POST(result);
+
+ if (dns_name_countlabels(zonename) == 0) {
+ return (ISC_R_SUCCESS);
+ }
+
+ /*
+ * If the val->closest is set then we want to use it otherwise
+ * we need to discover it.
+ */
+ if (dns_name_countlabels(dns_fixedname_name(&val->closest)) != 0) {
+ char namebuf[DNS_NAME_FORMATSIZE];
+
+ dns_name_format(dns_fixedname_name(&val->closest), namebuf,
+ sizeof(namebuf));
+ validator_log(val, ISC_LOG_DEBUG(3),
+ "closest encloser from wildcard signature '%s'",
+ namebuf);
+ dns_name_copy(dns_fixedname_name(&val->closest), closest);
+ closestp = NULL;
+ setclosestp = NULL;
+ } else {
+ closestp = closest;
+ setclosestp = &setclosest;
+ }
+
+ for (result = val_rdataset_first(val, &name, &rdataset);
+ result == ISC_R_SUCCESS;
+ result = val_rdataset_next(val, &name, &rdataset))
+ {
+ if (rdataset->type != dns_rdatatype_nsec3 ||
+ rdataset->trust != dns_trust_secure)
+ {
+ continue;
+ }
+
+ /*
+ * We process all NSEC3 records to find the closest
+ * encloser and nearest name to the closest encloser.
+ */
+ setclosest = setnearest = false;
+ optout = false;
+ unknown = false;
+ result = dns_nsec3_noexistnodata(
+ val->event->type, val->event->name, name, rdataset,
+ zonename, &exists, &data, &optout, &unknown,
+ setclosestp, &setnearest, closestp, nearest,
+ validator_log, val);
+ if (unknown) {
+ val->attributes |= VALATTR_FOUNDUNKNOWN;
+ }
+ if (result == DNS_R_NSEC3ITERRANGE) {
+ /*
+ * We don't really know which NSEC3 record provides
+ * which proof. Just populate them.
+ */
+ if (NEEDNOQNAME(val) &&
+ proofs[DNS_VALIDATOR_NOQNAMEPROOF] == NULL)
+ {
+ proofs[DNS_VALIDATOR_NOQNAMEPROOF] = name;
+ } else if (setclosest) {
+ proofs[DNS_VALIDATOR_CLOSESTENCLOSER] = name;
+ } else if (NEEDNODATA(val) &&
+ proofs[DNS_VALIDATOR_NODATAPROOF] == NULL)
+ {
+ proofs[DNS_VALIDATOR_NODATAPROOF] = name;
+ } else if (NEEDNOWILDCARD(val) &&
+ proofs[DNS_VALIDATOR_NOWILDCARDPROOF] ==
+ NULL)
+ {
+ proofs[DNS_VALIDATOR_NOWILDCARDPROOF] = name;
+ }
+ return (result);
+ }
+ if (result != ISC_R_SUCCESS) {
+ continue;
+ }
+ if (setclosest) {
+ proofs[DNS_VALIDATOR_CLOSESTENCLOSER] = name;
+ }
+ if (exists && !data && NEEDNODATA(val)) {
+ val->attributes |= VALATTR_FOUNDNODATA;
+ proofs[DNS_VALIDATOR_NODATAPROOF] = name;
+ }
+ if (!exists && setnearest) {
+ val->attributes |= VALATTR_FOUNDNOQNAME;
+ proofs[DNS_VALIDATOR_NOQNAMEPROOF] = name;
+ if (optout) {
+ val->attributes |= VALATTR_FOUNDOPTOUT;
+ }
+ }
+ }
+ if (result == ISC_R_NOMORE) {
+ result = ISC_R_SUCCESS;
+ }
+
+ /*
+ * To know we have a valid noqname and optout proofs we need to also
+ * have a valid closest encloser. Otherwise we could still be looking
+ * at proofs from the parent zone.
+ */
+ if (dns_name_countlabels(closest) > 0 &&
+ dns_name_countlabels(nearest) ==
+ dns_name_countlabels(closest) + 1 &&
+ dns_name_issubdomain(nearest, closest))
+ {
+ val->attributes |= VALATTR_FOUNDCLOSEST;
+ result = dns_name_concatenate(dns_wildcardname, closest,
+ dns_fixedname_name(&val->wild),
+ NULL);
+ RUNTIME_CHECK(result == ISC_R_SUCCESS);
+ } else {
+ val->attributes &= ~VALATTR_FOUNDNOQNAME;
+ val->attributes &= ~VALATTR_FOUNDOPTOUT;
+ proofs[DNS_VALIDATOR_NOQNAMEPROOF] = NULL;
+ }
+
+ /*
+ * Do we need to check for the wildcard?
+ */
+ if (FOUNDNOQNAME(val) && FOUNDCLOSEST(val) &&
+ ((NEEDNODATA(val) && !FOUNDNODATA(val)) || NEEDNOWILDCARD(val)))
+ {
+ result = checkwildcard(val, dns_rdatatype_nsec3, zonename);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+ }
+ return (result);
+}
+
+/*
+ * Start a validator for negative response data.
+ *
+ * Returns:
+ * \li DNS_R_CONTINUE Validation skipped, continue
+ * \li DNS_R_WAIT Validation is in progress
+ *
+ * \li Other return codes indicate failure.
+ */
+static isc_result_t
+validate_neg_rrset(dns_validator_t *val, dns_name_t *name,
+ dns_rdataset_t *rdataset, dns_rdataset_t *sigrdataset) {
+ isc_result_t result;
+
+ /*
+ * If a signed zone is missing the zone key, bad
+ * things could happen. A query for data in the zone
+ * would lead to a query for the zone key, which
+ * would return a negative answer, which would contain
+ * an SOA and an NSEC signed by the missing key, which
+ * would trigger another query for the DNSKEY (since
+ * the first one is still in progress), and go into an
+ * infinite loop. Avoid that.
+ */
+ if (val->event->type == dns_rdatatype_dnskey &&
+ rdataset->type == dns_rdatatype_nsec &&
+ dns_name_equal(name, val->event->name))
+ {
+ dns_rdata_t nsec = DNS_RDATA_INIT;
+
+ result = dns_rdataset_first(rdataset);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+ dns_rdataset_current(rdataset, &nsec);
+ if (dns_nsec_typepresent(&nsec, dns_rdatatype_soa)) {
+ return (DNS_R_CONTINUE);
+ }
+ }
+
+ val->currentset = rdataset;
+ result = create_validator(val, name, rdataset->type, rdataset,
+ sigrdataset, validator_callback_nsec,
+ "validate_neg_rrset");
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+
+ val->authcount++;
+ return (DNS_R_WAIT);
+}
+
+/*%
+ * Validate the authority section records.
+ */
+static isc_result_t
+validate_authority(dns_validator_t *val, bool resume) {
+ dns_name_t *name;
+ dns_message_t *message = val->event->message;
+ isc_result_t result;
+
+ if (!resume) {
+ result = dns_message_firstname(message, DNS_SECTION_AUTHORITY);
+ } else {
+ result = ISC_R_SUCCESS;
+ }
+
+ for (; result == ISC_R_SUCCESS;
+ result = dns_message_nextname(message, DNS_SECTION_AUTHORITY))
+ {
+ dns_rdataset_t *rdataset = NULL, *sigrdataset = NULL;
+
+ name = NULL;
+ dns_message_currentname(message, DNS_SECTION_AUTHORITY, &name);
+ if (resume) {
+ rdataset = ISC_LIST_NEXT(val->currentset, link);
+ val->currentset = NULL;
+ resume = false;
+ } else {
+ rdataset = ISC_LIST_HEAD(name->list);
+ }
+
+ for (; rdataset != NULL;
+ rdataset = ISC_LIST_NEXT(rdataset, link))
+ {
+ if (rdataset->type == dns_rdatatype_rrsig) {
+ continue;
+ }
+
+ for (sigrdataset = ISC_LIST_HEAD(name->list);
+ sigrdataset != NULL;
+ sigrdataset = ISC_LIST_NEXT(sigrdataset, link))
+ {
+ if (sigrdataset->type == dns_rdatatype_rrsig &&
+ sigrdataset->covers == rdataset->type)
+ {
+ break;
+ }
+ }
+
+ result = validate_neg_rrset(val, name, rdataset,
+ sigrdataset);
+ if (result != DNS_R_CONTINUE) {
+ return (result);
+ }
+ }
+ }
+ if (result == ISC_R_NOMORE) {
+ result = ISC_R_SUCCESS;
+ }
+ return (result);
+}
+
+/*%
+ * Validate negative cache elements.
+ */
+static isc_result_t
+validate_ncache(dns_validator_t *val, bool resume) {
+ dns_name_t *name;
+ isc_result_t result;
+
+ if (!resume) {
+ result = dns_rdataset_first(val->event->rdataset);
+ } else {
+ result = dns_rdataset_next(val->event->rdataset);
+ }
+
+ for (; result == ISC_R_SUCCESS;
+ result = dns_rdataset_next(val->event->rdataset))
+ {
+ dns_rdataset_t *rdataset, *sigrdataset = NULL;
+
+ disassociate_rdatasets(val);
+
+ name = dns_fixedname_initname(&val->fname);
+ rdataset = &val->frdataset;
+ dns_ncache_current(val->event->rdataset, name, rdataset);
+
+ if (val->frdataset.type == dns_rdatatype_rrsig) {
+ continue;
+ }
+
+ result = dns_ncache_getsigrdataset(val->event->rdataset, name,
+ rdataset->type,
+ &val->fsigrdataset);
+ if (result == ISC_R_SUCCESS) {
+ sigrdataset = &val->fsigrdataset;
+ }
+
+ result = validate_neg_rrset(val, name, rdataset, sigrdataset);
+ if (result == DNS_R_CONTINUE) {
+ continue;
+ }
+
+ return (result);
+ }
+ if (result == ISC_R_NOMORE) {
+ result = ISC_R_SUCCESS;
+ }
+
+ return (result);
+}
+
+/*%
+ * Prove a negative answer is good or that there is a NOQNAME when the
+ * answer is from a wildcard.
+ *
+ * Loop through the authority section looking for NODATA, NOWILDCARD
+ * and NOQNAME proofs in the NSEC records by calling
+ * validator_callback_nsec().
+ *
+ * If the required proofs are found we are done.
+ *
+ * If the proofs are not found attempt to prove this is an unsecure
+ * response.
+ */
+static isc_result_t
+validate_nx(dns_validator_t *val, bool resume) {
+ isc_result_t result;
+
+ if (resume) {
+ validator_log(val, ISC_LOG_DEBUG(3), "resuming validate_nx");
+ }
+
+ if (val->event->message == NULL) {
+ result = validate_ncache(val, resume);
+ } else {
+ result = validate_authority(val, resume);
+ }
+
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+
+ /*
+ * Do we only need to check for NOQNAME? To get here we must have
+ * had a secure wildcard answer.
+ */
+ if (!NEEDNODATA(val) && !NEEDNOWILDCARD(val) && NEEDNOQNAME(val)) {
+ if (!FOUNDNOQNAME(val)) {
+ result = findnsec3proofs(val);
+ if (result == DNS_R_NSEC3ITERRANGE) {
+ validator_log(val, ISC_LOG_DEBUG(3),
+ "too many iterations");
+ markanswer(val, "validate_nx (3)", NULL);
+ return (ISC_R_SUCCESS);
+ }
+ }
+
+ if (FOUNDNOQNAME(val) && FOUNDCLOSEST(val) && !FOUNDOPTOUT(val))
+ {
+ validator_log(val, ISC_LOG_DEBUG(3),
+ "marking as secure, noqname proof found");
+ marksecure(val->event);
+ return (ISC_R_SUCCESS);
+ } else if (FOUNDOPTOUT(val) &&
+ dns_name_countlabels(
+ dns_fixedname_name(&val->wild)) != 0)
+ {
+ validator_log(val, ISC_LOG_DEBUG(3),
+ "optout proof found");
+ val->event->optout = true;
+ markanswer(val, "validate_nx (1)", NULL);
+ return (ISC_R_SUCCESS);
+ } else if ((val->attributes & VALATTR_FOUNDUNKNOWN) != 0) {
+ validator_log(val, ISC_LOG_DEBUG(3),
+ "unknown NSEC3 hash algorithm found");
+ markanswer(val, "validate_nx (2)", NULL);
+ return (ISC_R_SUCCESS);
+ }
+
+ validator_log(val, ISC_LOG_DEBUG(3), "noqname proof not found");
+ return (DNS_R_NOVALIDNSEC);
+ }
+
+ if (!FOUNDNOQNAME(val) && !FOUNDNODATA(val)) {
+ result = findnsec3proofs(val);
+ if (result == DNS_R_NSEC3ITERRANGE) {
+ validator_log(val, ISC_LOG_DEBUG(3),
+ "too many iterations");
+ markanswer(val, "validate_nx (4)", NULL);
+ return (ISC_R_SUCCESS);
+ }
+ }
+
+ /*
+ * Do we need to check for the wildcard?
+ */
+ if (FOUNDNOQNAME(val) && FOUNDCLOSEST(val) &&
+ ((NEEDNODATA(val) && !FOUNDNODATA(val)) || NEEDNOWILDCARD(val)))
+ {
+ result = checkwildcard(val, dns_rdatatype_nsec, NULL);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+ }
+
+ if ((NEEDNODATA(val) && (FOUNDNODATA(val) || FOUNDOPTOUT(val))) ||
+ (NEEDNOQNAME(val) && FOUNDNOQNAME(val) && NEEDNOWILDCARD(val) &&
+ FOUNDNOWILDCARD(val) && FOUNDCLOSEST(val)))
+ {
+ if ((val->attributes & VALATTR_FOUNDOPTOUT) != 0) {
+ val->event->optout = true;
+ }
+ validator_log(val, ISC_LOG_DEBUG(3),
+ "nonexistence proof(s) found");
+ if (val->event->message == NULL) {
+ marksecure(val->event);
+ } else {
+ val->event->secure = true;
+ }
+ return (ISC_R_SUCCESS);
+ }
+
+ if (val->authfail != 0 && val->authcount == val->authfail) {
+ return (DNS_R_BROKENCHAIN);
+ }
+
+ validator_log(val, ISC_LOG_DEBUG(3), "nonexistence proof(s) not found");
+ return (proveunsecure(val, false, false));
+}
+
+/*%
+ * Check that DS rdataset has at least one record with
+ * a supported algorithm and digest.
+ */
+static bool
+check_ds_algs(dns_validator_t *val, dns_name_t *name,
+ dns_rdataset_t *rdataset) {
+ dns_rdata_t dsrdata = DNS_RDATA_INIT;
+ dns_rdata_ds_t ds;
+ isc_result_t result;
+
+ for (result = dns_rdataset_first(rdataset); result == ISC_R_SUCCESS;
+ result = dns_rdataset_next(rdataset))
+ {
+ dns_rdataset_current(rdataset, &dsrdata);
+ result = dns_rdata_tostruct(&dsrdata, &ds, NULL);
+ RUNTIME_CHECK(result == ISC_R_SUCCESS);
+
+ if (dns_resolver_ds_digest_supported(val->view->resolver, name,
+ ds.digest_type) &&
+ dns_resolver_algorithm_supported(val->view->resolver, name,
+ ds.algorithm))
+ {
+ dns_rdata_reset(&dsrdata);
+ return (true);
+ }
+ dns_rdata_reset(&dsrdata);
+ }
+ return (false);
+}
+
+/*%
+ * seek_ds is called to look up DS rrsets at the label of val->event->name
+ * indicated by val->labels. This is done while building an insecurity
+ * proof, and so it will attempt validation of NXDOMAIN, NXRRSET or CNAME
+ * responses.
+ *
+ * Returns:
+ * \li ISC_R_COMPLETE a result has been determined and copied
+ * into `*resp`; ISC_R_SUCCESS indicates that
+ * the name has been proven insecure and any
+ * other result indicates failure.
+ * \li DNS_R_CONTINUE result is indeterminate; caller should
+ * continue walking down labels.
+ */
+static isc_result_t
+seek_ds(dns_validator_t *val, isc_result_t *resp) {
+ isc_result_t result;
+ char namebuf[DNS_NAME_FORMATSIZE];
+ dns_fixedname_t fixedfound;
+ dns_name_t *found = dns_fixedname_initname(&fixedfound);
+ dns_name_t *tname = dns_fixedname_initname(&val->fname);
+
+ if (val->labels == dns_name_countlabels(val->event->name)) {
+ dns_name_copy(val->event->name, tname);
+ } else {
+ dns_name_split(val->event->name, val->labels, NULL, tname);
+ }
+
+ dns_name_format(tname, namebuf, sizeof(namebuf));
+ validator_log(val, ISC_LOG_DEBUG(3), "checking existence of DS at '%s'",
+ namebuf);
+
+ result = view_find(val, tname, dns_rdatatype_ds);
+ switch (result) {
+ case ISC_R_SUCCESS:
+ /*
+ * There is a DS here. If it's already been
+ * validated, continue walking down labels.
+ */
+ if (val->frdataset.trust >= dns_trust_secure) {
+ if (!check_ds_algs(val, tname, &val->frdataset)) {
+ validator_log(val, ISC_LOG_DEBUG(3),
+ "no supported algorithm/"
+ "digest (%s/DS)",
+ namebuf);
+ *resp = markanswer(val, "proveunsecure (5)",
+ "no supported "
+ "algorithm/digest (DS)");
+ return (ISC_R_COMPLETE);
+ }
+
+ break;
+ }
+
+ /*
+ * Otherwise, try to validate it now.
+ */
+ if (dns_rdataset_isassociated(&val->fsigrdataset)) {
+ result = create_validator(
+ val, tname, dns_rdatatype_ds, &val->frdataset,
+ &val->fsigrdataset, validator_callback_ds,
+ "proveunsecure");
+ *resp = DNS_R_WAIT;
+ if (result != ISC_R_SUCCESS) {
+ *resp = result;
+ }
+ } else {
+ /*
+ * There should never be an unsigned DS.
+ */
+ validator_log(val, ISC_LOG_DEBUG(3),
+ "unsigned DS record");
+ *resp = DNS_R_NOVALIDSIG;
+ }
+
+ return (ISC_R_COMPLETE);
+
+ case ISC_R_NOTFOUND:
+ /*
+ * We don't know anything about the DS. Find it.
+ */
+ *resp = DNS_R_WAIT;
+ result = create_fetch(val, tname, dns_rdatatype_ds,
+ fetch_callback_ds, "proveunsecure");
+ if (result != ISC_R_SUCCESS) {
+ *resp = result;
+ }
+ return (ISC_R_COMPLETE);
+
+ case DNS_R_NXRRSET:
+ case DNS_R_NCACHENXRRSET:
+ /*
+ * There is no DS. If this is a delegation,
+ * we may be done.
+ *
+ * If we have "trust == answer" then this namespace
+ * has switched from insecure to should be secure.
+ */
+ if (DNS_TRUST_PENDING(val->frdataset.trust) ||
+ DNS_TRUST_ANSWER(val->frdataset.trust))
+ {
+ result = create_validator(
+ val, tname, dns_rdatatype_ds, &val->frdataset,
+ &val->fsigrdataset, validator_callback_ds,
+ "proveunsecure");
+ *resp = DNS_R_WAIT;
+ if (result != ISC_R_SUCCESS) {
+ *resp = result;
+ }
+ return (ISC_R_COMPLETE);
+ }
+
+ /*
+ * Zones using NSEC3 don't return a NSEC RRset so
+ * we need to use dns_view_findzonecut2 to find
+ * the zone cut.
+ */
+ if (result == DNS_R_NXRRSET &&
+ !dns_rdataset_isassociated(&val->frdataset) &&
+ dns_view_findzonecut(val->view, tname, found, NULL, 0, 0,
+ false, false, NULL,
+ NULL) == ISC_R_SUCCESS &&
+ dns_name_equal(tname, found))
+ {
+ *resp = markanswer(val, "proveunsecure (3)",
+ "no DS at zone cut");
+ return (ISC_R_COMPLETE);
+ }
+
+ if (val->frdataset.trust < dns_trust_secure) {
+ /*
+ * This shouldn't happen, since the negative
+ * response should have been validated. Since
+ * there's no way of validating existing
+ * negative response blobs, give up.
+ */
+ validator_log(val, ISC_LOG_WARNING,
+ "can't validate existing "
+ "negative responses (no DS)");
+ *resp = DNS_R_MUSTBESECURE;
+ return (ISC_R_COMPLETE);
+ }
+
+ if (isdelegation(tname, &val->frdataset, result)) {
+ *resp = markanswer(val, "proveunsecure (4)",
+ "this is a delegation");
+ return (ISC_R_COMPLETE);
+ }
+
+ break;
+
+ case DNS_R_NXDOMAIN:
+ case DNS_R_NCACHENXDOMAIN:
+ /*
+ * This is not a zone cut. Assuming things are
+ * as expected, continue.
+ */
+ if (!dns_rdataset_isassociated(&val->frdataset)) {
+ /*
+ * There should be an NSEC here, since we
+ * are still in a secure zone.
+ */
+ *resp = DNS_R_NOVALIDNSEC;
+ return (ISC_R_COMPLETE);
+ } else if (DNS_TRUST_PENDING(val->frdataset.trust) ||
+ DNS_TRUST_ANSWER(val->frdataset.trust))
+ {
+ /*
+ * If we have "trust == answer" then this
+ * namespace has switched from insecure to
+ * should be secure.
+ */
+ *resp = DNS_R_WAIT;
+ result = create_validator(
+ val, tname, dns_rdatatype_ds, &val->frdataset,
+ &val->fsigrdataset, validator_callback_ds,
+ "proveunsecure");
+ if (result != ISC_R_SUCCESS) {
+ *resp = result;
+ }
+ return (ISC_R_COMPLETE);
+ } else if (val->frdataset.trust < dns_trust_secure) {
+ /*
+ * This shouldn't happen, since the negative
+ * response should have been validated. Since
+ * there's no way of validating existing
+ * negative response blobs, give up.
+ */
+ validator_log(val, ISC_LOG_WARNING,
+ "can't validate existing "
+ "negative responses "
+ "(not a zone cut)");
+ *resp = DNS_R_NOVALIDSIG;
+ return (ISC_R_COMPLETE);
+ }
+
+ break;
+
+ case DNS_R_CNAME:
+ if (DNS_TRUST_PENDING(val->frdataset.trust) ||
+ DNS_TRUST_ANSWER(val->frdataset.trust))
+ {
+ result = create_validator(
+ val, tname, dns_rdatatype_cname,
+ &val->frdataset, &val->fsigrdataset,
+ validator_callback_cname,
+ "proveunsecure "
+ "(cname)");
+ *resp = DNS_R_WAIT;
+ if (result != ISC_R_SUCCESS) {
+ *resp = result;
+ }
+ return (ISC_R_COMPLETE);
+ }
+
+ break;
+
+ default:
+ *resp = result;
+ return (ISC_R_COMPLETE);
+ }
+
+ /*
+ * No definite answer yet; continue walking down labels.
+ */
+ return (DNS_R_CONTINUE);
+}
+
+/*%
+ * proveunsecure walks down, label by label, from the closest enclosing
+ * trust anchor to the name that is being validated, looking for an
+ * endpoint in the chain of trust. That occurs when we can prove that
+ * a DS record does not exist at a delegation point, or that a DS exists
+ * at a delegation point but we don't support its algorithm/digest. If
+ * no such endpoint is found, then the response should have been secure.
+ *
+ * Returns:
+ * \li ISC_R_SUCCESS val->event->name is in an unsecure zone
+ * \li DNS_R_WAIT validation is in progress.
+ * \li DNS_R_MUSTBESECURE val->event->name is supposed to be secure
+ * (policy) but we proved that it is unsecure.
+ * \li DNS_R_NOVALIDSIG
+ * \li DNS_R_NOVALIDNSEC
+ * \li DNS_R_NOTINSECURE
+ * \li DNS_R_BROKENCHAIN
+ */
+static isc_result_t
+proveunsecure(dns_validator_t *val, bool have_ds, bool resume) {
+ isc_result_t result;
+ char namebuf[DNS_NAME_FORMATSIZE];
+ dns_fixedname_t fixedsecroot;
+ dns_name_t *secroot = dns_fixedname_initname(&fixedsecroot);
+ unsigned int labels;
+
+ /*
+ * We're attempting to prove insecurity.
+ */
+ val->attributes |= VALATTR_INSECURITY;
+
+ dns_name_copy(val->event->name, secroot);
+
+ /*
+ * If this is a response to a DS query, we need to look in
+ * the parent zone for the trust anchor.
+ */
+ labels = dns_name_countlabels(secroot);
+ if (val->event->type == dns_rdatatype_ds && labels > 1U) {
+ dns_name_getlabelsequence(secroot, 1, labels - 1, secroot);
+ }
+
+ result = dns_keytable_finddeepestmatch(val->keytable, secroot, secroot);
+ if (result == ISC_R_NOTFOUND) {
+ validator_log(val, ISC_LOG_DEBUG(3), "not beneath secure root");
+ return (markanswer(val, "proveunsecure (1)",
+ "not beneath secure root"));
+ } else if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+
+ if (!resume) {
+ /*
+ * We are looking for interruptions in the chain of trust.
+ * That can only happen *below* the trust anchor, so we
+ * start looking at the next label down.
+ */
+ val->labels = dns_name_countlabels(secroot) + 1;
+ } else {
+ validator_log(val, ISC_LOG_DEBUG(3), "resuming proveunsecure");
+
+ /*
+ * If we have a DS rdataset and it is secure, check whether
+ * it has a supported algorithm combination. If not, this is
+ * an insecure delegation as far as this resolver is concerned.
+ */
+ if (have_ds && val->frdataset.trust >= dns_trust_secure &&
+ !check_ds_algs(val, dns_fixedname_name(&val->fname),
+ &val->frdataset))
+ {
+ dns_name_format(dns_fixedname_name(&val->fname),
+ namebuf, sizeof(namebuf));
+ validator_log(val, ISC_LOG_DEBUG(3),
+ "no supported algorithm/digest (%s/DS)",
+ namebuf);
+ result = markanswer(val, "proveunsecure (2)", namebuf);
+ goto out;
+ }
+ val->labels++;
+ }
+
+ /*
+ * Walk down through each of the remaining labels in the name,
+ * looking for DS records.
+ */
+ while (val->labels <= dns_name_countlabels(val->event->name)) {
+ isc_result_t tresult;
+
+ result = seek_ds(val, &tresult);
+ if (result == ISC_R_COMPLETE) {
+ result = tresult;
+ goto out;
+ }
+
+ INSIST(result == DNS_R_CONTINUE);
+ val->labels++;
+ }
+
+ /* Couldn't complete insecurity proof. */
+ validator_log(val, ISC_LOG_DEBUG(3), "insecurity proof failed: %s",
+ isc_result_totext(result));
+ return (DNS_R_NOTINSECURE);
+
+out:
+ if (result != DNS_R_WAIT) {
+ disassociate_rdatasets(val);
+ }
+ return (result);
+}
+
+/*%
+ * Start the validation process.
+ *
+ * Attempt to validate the answer based on the category it appears to
+ * fall in.
+ * \li 1. secure positive answer.
+ * \li 2. unsecure positive answer.
+ * \li 3. a negative answer (secure or unsecure).
+ *
+ * Note an answer that appears to be a secure positive answer may actually
+ * be an unsecure positive answer.
+ */
+static void
+validator_start(isc_task_t *task, isc_event_t *event) {
+ dns_validator_t *val;
+ dns_validatorevent_t *vevent;
+ bool want_destroy = false;
+ isc_result_t result = ISC_R_FAILURE;
+
+ UNUSED(task);
+ REQUIRE(event->ev_type == DNS_EVENT_VALIDATORSTART);
+ vevent = (dns_validatorevent_t *)event;
+ val = vevent->validator;
+
+ /* If the validator has been canceled, val->event == NULL */
+ if (val->event == NULL) {
+ return;
+ }
+
+ validator_log(val, ISC_LOG_DEBUG(3), "starting");
+
+ LOCK(&val->lock);
+
+ if (val->event->rdataset != NULL && val->event->sigrdataset != NULL) {
+ isc_result_t saved_result;
+
+ /*
+ * This looks like a simple validation. We say "looks like"
+ * because it might end up requiring an insecurity proof.
+ */
+ validator_log(val, ISC_LOG_DEBUG(3),
+ "attempting positive response validation");
+
+ INSIST(dns_rdataset_isassociated(val->event->rdataset));
+ INSIST(dns_rdataset_isassociated(val->event->sigrdataset));
+ if (selfsigned_dnskey(val)) {
+ result = validate_dnskey(val);
+ } else {
+ result = validate_answer(val, false);
+ }
+ if (result == DNS_R_NOVALIDSIG &&
+ (val->attributes & VALATTR_TRIEDVERIFY) == 0)
+ {
+ saved_result = result;
+ validator_log(val, ISC_LOG_DEBUG(3),
+ "falling back to insecurity proof");
+ result = proveunsecure(val, false, false);
+ if (result == DNS_R_NOTINSECURE) {
+ result = saved_result;
+ }
+ }
+ } else if (val->event->rdataset != NULL &&
+ val->event->rdataset->type != 0)
+ {
+ /*
+ * This is either an unsecure subdomain or a response
+ * from a broken server.
+ */
+ INSIST(dns_rdataset_isassociated(val->event->rdataset));
+ validator_log(val, ISC_LOG_DEBUG(3),
+ "attempting insecurity proof");
+
+ result = proveunsecure(val, false, false);
+ if (result == DNS_R_NOTINSECURE) {
+ validator_log(val, ISC_LOG_INFO,
+ "got insecure response; "
+ "parent indicates it should be secure");
+ }
+ } else if ((val->event->rdataset == NULL &&
+ val->event->sigrdataset == NULL))
+ {
+ /*
+ * This is a validation of a negative response.
+ */
+ validator_log(val, ISC_LOG_DEBUG(3),
+ "attempting negative response validation "
+ "from message");
+
+ if (val->event->message->rcode == dns_rcode_nxdomain) {
+ val->attributes |= VALATTR_NEEDNOQNAME;
+ val->attributes |= VALATTR_NEEDNOWILDCARD;
+ } else {
+ val->attributes |= VALATTR_NEEDNODATA;
+ }
+
+ result = validate_nx(val, false);
+ } else if ((val->event->rdataset != NULL &&
+ NEGATIVE(val->event->rdataset)))
+ {
+ /*
+ * This is a delayed validation of a negative cache entry.
+ */
+ validator_log(val, ISC_LOG_DEBUG(3),
+ "attempting negative response validation "
+ "from cache");
+
+ if (NXDOMAIN(val->event->rdataset)) {
+ val->attributes |= VALATTR_NEEDNOQNAME;
+ val->attributes |= VALATTR_NEEDNOWILDCARD;
+ } else {
+ val->attributes |= VALATTR_NEEDNODATA;
+ }
+
+ result = validate_nx(val, false);
+ } else {
+ UNREACHABLE();
+ }
+
+ if (result != DNS_R_WAIT) {
+ want_destroy = exit_check(val);
+ validator_done(val, result);
+ }
+
+ UNLOCK(&val->lock);
+ if (want_destroy) {
+ destroy(val);
+ }
+}
+
+isc_result_t
+dns_validator_create(dns_view_t *view, dns_name_t *name, dns_rdatatype_t type,
+ dns_rdataset_t *rdataset, dns_rdataset_t *sigrdataset,
+ dns_message_t *message, unsigned int options,
+ isc_task_t *task, isc_taskaction_t action, void *arg,
+ dns_validator_t **validatorp) {
+ isc_result_t result = ISC_R_FAILURE;
+ dns_validator_t *val;
+ isc_task_t *tclone = NULL;
+ dns_validatorevent_t *event;
+
+ REQUIRE(name != NULL);
+ REQUIRE(rdataset != NULL ||
+ (rdataset == NULL && sigrdataset == NULL && message != NULL));
+ REQUIRE(validatorp != NULL && *validatorp == NULL);
+
+ event = (dns_validatorevent_t *)isc_event_allocate(
+ view->mctx, task, DNS_EVENT_VALIDATORSTART, validator_start,
+ NULL, sizeof(dns_validatorevent_t));
+
+ isc_task_attach(task, &tclone);
+ event->result = ISC_R_FAILURE;
+ event->name = name;
+ event->type = type;
+ event->rdataset = rdataset;
+ event->sigrdataset = sigrdataset;
+ event->message = message;
+ memset(event->proofs, 0, sizeof(event->proofs));
+ event->optout = false;
+ event->secure = false;
+
+ val = isc_mem_get(view->mctx, sizeof(*val));
+ *val = (dns_validator_t){ .event = event,
+ .options = options,
+ .task = task,
+ .action = action,
+ .arg = arg };
+
+ dns_view_weakattach(view, &val->view);
+ isc_mutex_init(&val->lock);
+
+ result = dns_view_getsecroots(val->view, &val->keytable);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+
+ val->mustbesecure = dns_resolver_getmustbesecure(view->resolver, name);
+ dns_rdataset_init(&val->fdsset);
+ dns_rdataset_init(&val->frdataset);
+ dns_rdataset_init(&val->fsigrdataset);
+ dns_fixedname_init(&val->wild);
+ dns_fixedname_init(&val->closest);
+ isc_stdtime_get(&val->start);
+ ISC_LINK_INIT(val, link);
+ val->magic = VALIDATOR_MAGIC;
+
+ event->validator = val;
+
+ if ((options & DNS_VALIDATOR_DEFER) == 0) {
+ isc_task_send(task, ISC_EVENT_PTR(&event));
+ }
+
+ *validatorp = val;
+
+ return (ISC_R_SUCCESS);
+
+cleanup:
+ isc_mutex_destroy(&val->lock);
+
+ isc_task_detach(&tclone);
+ isc_event_free(ISC_EVENT_PTR(&event));
+
+ dns_view_weakdetach(&val->view);
+ isc_mem_put(view->mctx, val, sizeof(*val));
+
+ return (result);
+}
+
+void
+dns_validator_send(dns_validator_t *validator) {
+ isc_event_t *event;
+ REQUIRE(VALID_VALIDATOR(validator));
+
+ LOCK(&validator->lock);
+
+ INSIST((validator->options & DNS_VALIDATOR_DEFER) != 0);
+ event = (isc_event_t *)validator->event;
+ validator->options &= ~DNS_VALIDATOR_DEFER;
+ UNLOCK(&validator->lock);
+
+ isc_task_send(validator->task, ISC_EVENT_PTR(&event));
+}
+
+void
+dns_validator_cancel(dns_validator_t *validator) {
+ dns_fetch_t *fetch = NULL;
+
+ REQUIRE(VALID_VALIDATOR(validator));
+
+ LOCK(&validator->lock);
+
+ validator_log(validator, ISC_LOG_DEBUG(3), "dns_validator_cancel");
+
+ if ((validator->attributes & VALATTR_CANCELED) == 0) {
+ validator->attributes |= VALATTR_CANCELED;
+ if (validator->event != NULL) {
+ fetch = validator->fetch;
+ validator->fetch = NULL;
+
+ if (validator->subvalidator != NULL) {
+ dns_validator_cancel(validator->subvalidator);
+ }
+ if ((validator->options & DNS_VALIDATOR_DEFER) != 0) {
+ validator->options &= ~DNS_VALIDATOR_DEFER;
+ validator_done(validator, ISC_R_CANCELED);
+ }
+ }
+ }
+ UNLOCK(&validator->lock);
+
+ /* Need to cancel and destroy the fetch outside validator lock */
+ if (fetch != NULL) {
+ dns_resolver_cancelfetch(fetch);
+ dns_resolver_destroyfetch(&fetch);
+ }
+}
+
+static void
+destroy(dns_validator_t *val) {
+ isc_mem_t *mctx;
+
+ REQUIRE(SHUTDOWN(val));
+ REQUIRE(val->event == NULL);
+ REQUIRE(val->fetch == NULL);
+
+ val->magic = 0;
+ if (val->key != NULL) {
+ dst_key_free(&val->key);
+ }
+ if (val->keytable != NULL) {
+ dns_keytable_detach(&val->keytable);
+ }
+ if (val->subvalidator != NULL) {
+ dns_validator_destroy(&val->subvalidator);
+ }
+ disassociate_rdatasets(val);
+ mctx = val->view->mctx;
+ if (val->siginfo != NULL) {
+ isc_mem_put(mctx, val->siginfo, sizeof(*val->siginfo));
+ }
+ isc_mutex_destroy(&val->lock);
+ dns_view_weakdetach(&val->view);
+ isc_mem_put(mctx, val, sizeof(*val));
+}
+
+void
+dns_validator_destroy(dns_validator_t **validatorp) {
+ dns_validator_t *val;
+ bool want_destroy = false;
+
+ REQUIRE(validatorp != NULL);
+ val = *validatorp;
+ *validatorp = NULL;
+ REQUIRE(VALID_VALIDATOR(val));
+
+ LOCK(&val->lock);
+
+ val->attributes |= VALATTR_SHUTDOWN;
+ validator_log(val, ISC_LOG_DEBUG(4), "dns_validator_destroy");
+
+ want_destroy = exit_check(val);
+ UNLOCK(&val->lock);
+ if (want_destroy) {
+ destroy(val);
+ }
+}
+
+static void
+validator_logv(dns_validator_t *val, isc_logcategory_t *category,
+ isc_logmodule_t *module, int level, const char *fmt,
+ va_list ap) {
+ char msgbuf[2048];
+ static const char spaces[] = " *";
+ int depth = val->depth * 2;
+ const char *viewname, *sep1, *sep2;
+
+ vsnprintf(msgbuf, sizeof(msgbuf), fmt, ap);
+
+ if ((unsigned int)depth >= sizeof spaces) {
+ depth = sizeof spaces - 1;
+ }
+
+ /*
+ * Log the view name unless it's:
+ * * "_default/IN" (which means there's only one view
+ * configured in the server), or
+ * * "_dnsclient/IN" (which means this is being called
+ * from an application using dns/client.c).
+ */
+ if (val->view->rdclass == dns_rdataclass_in &&
+ (strcmp(val->view->name, "_default") == 0 ||
+ strcmp(val->view->name, DNS_CLIENTVIEW_NAME) == 0))
+ {
+ sep1 = viewname = sep2 = "";
+ } else {
+ sep1 = "view ";
+ viewname = val->view->name;
+ sep2 = ": ";
+ }
+
+ if (val->event != NULL && val->event->name != NULL) {
+ char namebuf[DNS_NAME_FORMATSIZE];
+ char typebuf[DNS_RDATATYPE_FORMATSIZE];
+
+ dns_name_format(val->event->name, namebuf, sizeof(namebuf));
+ dns_rdatatype_format(val->event->type, typebuf,
+ sizeof(typebuf));
+ isc_log_write(dns_lctx, category, module, level,
+ "%s%s%s%.*svalidating %s/%s: %s", sep1, viewname,
+ sep2, depth, spaces, namebuf, typebuf, msgbuf);
+ } else {
+ isc_log_write(dns_lctx, category, module, level,
+ "%s%s%s%.*svalidator @%p: %s", sep1, viewname,
+ sep2, depth, spaces, val, msgbuf);
+ }
+}
+
+static void
+validator_log(void *val, int level, const char *fmt, ...) {
+ va_list ap;
+
+ if (!isc_log_wouldlog(dns_lctx, level)) {
+ return;
+ }
+
+ va_start(ap, fmt);
+
+ validator_logv(val, DNS_LOGCATEGORY_DNSSEC, DNS_LOGMODULE_VALIDATOR,
+ level, fmt, ap);
+ va_end(ap);
+}
+
+static void
+validator_logcreate(dns_validator_t *val, dns_name_t *name,
+ dns_rdatatype_t type, const char *caller,
+ const char *operation) {
+ char namestr[DNS_NAME_FORMATSIZE];
+ char typestr[DNS_RDATATYPE_FORMATSIZE];
+
+ dns_name_format(name, namestr, sizeof(namestr));
+ dns_rdatatype_format(type, typestr, sizeof(typestr));
+ validator_log(val, ISC_LOG_DEBUG(9), "%s: creating %s for %s %s",
+ caller, operation, namestr, typestr);
+}
diff --git a/lib/dns/view.c b/lib/dns/view.c
new file mode 100644
index 0000000..49c9aee
--- /dev/null
+++ b/lib/dns/view.c
@@ -0,0 +1,2761 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/*! \file */
+
+#include <inttypes.h>
+#include <limits.h>
+#include <stdbool.h>
+
+#ifdef HAVE_LMDB
+#include <lmdb.h>
+#endif /* ifdef HAVE_LMDB */
+
+#include <isc/atomic.h>
+#include <isc/dir.h>
+#include <isc/file.h>
+#include <isc/hash.h>
+#include <isc/lex.h>
+#include <isc/print.h>
+#include <isc/result.h>
+#include <isc/stats.h>
+#include <isc/string.h> /* Required for HP/UX (and others?) */
+#include <isc/task.h>
+#include <isc/util.h>
+
+#include <dns/acl.h>
+#include <dns/adb.h>
+#include <dns/badcache.h>
+#include <dns/cache.h>
+#include <dns/db.h>
+#include <dns/dispatch.h>
+#include <dns/dlz.h>
+#include <dns/dns64.h>
+#include <dns/dnssec.h>
+#include <dns/events.h>
+#include <dns/forward.h>
+#include <dns/keytable.h>
+#include <dns/keyvalues.h>
+#include <dns/master.h>
+#include <dns/masterdump.h>
+#include <dns/nta.h>
+#include <dns/order.h>
+#include <dns/peer.h>
+#include <dns/rbt.h>
+#include <dns/rdataset.h>
+#include <dns/request.h>
+#include <dns/resolver.h>
+#include <dns/rpz.h>
+#include <dns/rrl.h>
+#include <dns/stats.h>
+#include <dns/time.h>
+#include <dns/transport.h>
+#include <dns/tsig.h>
+#include <dns/zone.h>
+#include <dns/zt.h>
+
+#define CHECK(op) \
+ do { \
+ result = (op); \
+ if (result != ISC_R_SUCCESS) \
+ goto cleanup; \
+ } while (0)
+
+#define RESSHUTDOWN(v) \
+ ((atomic_load(&(v)->attributes) & DNS_VIEWATTR_RESSHUTDOWN) != 0)
+#define ADBSHUTDOWN(v) \
+ ((atomic_load(&(v)->attributes) & DNS_VIEWATTR_ADBSHUTDOWN) != 0)
+#define REQSHUTDOWN(v) \
+ ((atomic_load(&(v)->attributes) & DNS_VIEWATTR_REQSHUTDOWN) != 0)
+
+#define DNS_VIEW_DELONLYHASH 111
+#define DNS_VIEW_FAILCACHESIZE 1021
+
+static void
+resolver_shutdown(isc_task_t *task, isc_event_t *event);
+static void
+adb_shutdown(isc_task_t *task, isc_event_t *event);
+static void
+req_shutdown(isc_task_t *task, isc_event_t *event);
+
+isc_result_t
+dns_view_create(isc_mem_t *mctx, dns_rdataclass_t rdclass, const char *name,
+ dns_view_t **viewp) {
+ dns_view_t *view;
+ isc_result_t result;
+ char buffer[1024];
+
+ /*
+ * Create a view.
+ */
+
+ REQUIRE(name != NULL);
+ REQUIRE(viewp != NULL && *viewp == NULL);
+
+ view = isc_mem_get(mctx, sizeof(*view));
+
+ view->nta_file = NULL;
+ view->mctx = NULL;
+ isc_mem_attach(mctx, &view->mctx);
+ view->name = isc_mem_strdup(mctx, name);
+
+ result = isc_file_sanitize(NULL, view->name, "nta", buffer,
+ sizeof(buffer));
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup_name;
+ }
+ view->nta_file = isc_mem_strdup(mctx, buffer);
+
+ isc_mutex_init(&view->lock);
+
+ isc_rwlock_init(&view->sfd_lock, 0, 0);
+
+ view->zonetable = NULL;
+ result = dns_zt_create(mctx, rdclass, &view->zonetable);
+ if (result != ISC_R_SUCCESS) {
+ UNEXPECTED_ERROR("dns_zt_create() failed: %s",
+ isc_result_totext(result));
+ result = ISC_R_UNEXPECTED;
+ goto cleanup_mutex;
+ }
+
+ view->secroots_priv = NULL;
+ view->ntatable_priv = NULL;
+ view->fwdtable = NULL;
+ result = dns_fwdtable_create(mctx, &view->fwdtable);
+ if (result != ISC_R_SUCCESS) {
+ UNEXPECTED_ERROR("dns_fwdtable_create() failed: %s",
+ isc_result_totext(result));
+ result = ISC_R_UNEXPECTED;
+ goto cleanup_zt;
+ }
+
+ view->cache = NULL;
+ view->cachedb = NULL;
+ ISC_LIST_INIT(view->dlz_searched);
+ ISC_LIST_INIT(view->dlz_unsearched);
+ view->hints = NULL;
+ view->resolver = NULL;
+ view->adb = NULL;
+ view->requestmgr = NULL;
+ view->rdclass = rdclass;
+ view->frozen = false;
+ view->task = NULL;
+ isc_refcount_init(&view->references, 1);
+ isc_refcount_init(&view->weakrefs, 1);
+ atomic_init(&view->attributes,
+ (DNS_VIEWATTR_RESSHUTDOWN | DNS_VIEWATTR_ADBSHUTDOWN |
+ DNS_VIEWATTR_REQSHUTDOWN));
+ view->transports = NULL;
+ view->statickeys = NULL;
+ view->dynamickeys = NULL;
+ view->matchclients = NULL;
+ view->matchdestinations = NULL;
+ view->matchrecursiveonly = false;
+ result = dns_tsigkeyring_create(view->mctx, &view->dynamickeys);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup_weakrefs;
+ }
+ view->peers = NULL;
+ view->order = NULL;
+ view->delonly = NULL;
+ view->rootdelonly = false;
+ view->rootexclude = NULL;
+ view->adbstats = NULL;
+ view->resstats = NULL;
+ view->resquerystats = NULL;
+ view->cacheshared = false;
+ ISC_LIST_INIT(view->dns64);
+ view->dns64cnt = 0;
+
+ /*
+ * Initialize configuration data with default values.
+ */
+ view->recursion = true;
+ view->qminimization = false;
+ view->qmin_strict = false;
+ view->auth_nxdomain = false; /* Was true in BIND 8 */
+ view->enablevalidation = true;
+ view->acceptexpired = false;
+ view->use_glue_cache = false;
+ view->minimal_any = false;
+ view->minimalresponses = dns_minimal_no;
+ view->transfer_format = dns_one_answer;
+ view->cacheacl = NULL;
+ view->cacheonacl = NULL;
+ view->checknames = false;
+ view->queryacl = NULL;
+ view->queryonacl = NULL;
+ view->recursionacl = NULL;
+ view->recursiononacl = NULL;
+ view->sortlist = NULL;
+ view->transferacl = NULL;
+ view->notifyacl = NULL;
+ view->updateacl = NULL;
+ view->upfwdacl = NULL;
+ view->denyansweracl = NULL;
+ view->nocasecompress = NULL;
+ view->msgcompression = true;
+ view->answeracl_exclude = NULL;
+ view->denyanswernames = NULL;
+ view->answernames_exclude = NULL;
+ view->rrl = NULL;
+ view->sfd = NULL;
+ view->provideixfr = true;
+ view->maxcachettl = 7 * 24 * 3600;
+ view->maxncachettl = 3 * 3600;
+ view->mincachettl = 0;
+ view->minncachettl = 0;
+ view->nta_lifetime = 0;
+ view->nta_recheck = 0;
+ view->prefetch_eligible = 0;
+ view->prefetch_trigger = 0;
+ view->dstport = 53;
+ view->preferred_glue = 0;
+ view->flush = false;
+ view->maxudp = 0;
+ view->staleanswerttl = 1;
+ view->staleanswersok = dns_stale_answer_conf;
+ view->staleanswersenable = false;
+ view->nocookieudp = 0;
+ view->padding = 0;
+ view->pad_acl = NULL;
+ view->maxbits = 0;
+ view->rpzs = NULL;
+ view->catzs = NULL;
+ view->managed_keys = NULL;
+ view->redirect = NULL;
+ view->redirectzone = NULL;
+ dns_fixedname_init(&view->redirectfixed);
+ view->requestnsid = false;
+ view->sendcookie = true;
+ view->requireservercookie = false;
+ view->synthfromdnssec = true;
+ view->trust_anchor_telemetry = true;
+ view->root_key_sentinel = true;
+ view->new_zone_dir = NULL;
+ view->new_zone_file = NULL;
+ view->new_zone_db = NULL;
+ view->new_zone_dbenv = NULL;
+ view->new_zone_mapsize = 0ULL;
+ view->new_zone_config = NULL;
+ view->cfg_destroy = NULL;
+ view->fail_ttl = 0;
+ view->failcache = NULL;
+ result = dns_badcache_init(view->mctx, DNS_VIEW_FAILCACHESIZE,
+ &view->failcache);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup_dynkeys;
+ }
+ view->v6bias = 0;
+ view->dtenv = NULL;
+ view->dttypes = 0;
+
+ view->plugins = NULL;
+ view->plugins_free = NULL;
+ view->hooktable = NULL;
+ view->hooktable_free = NULL;
+
+ isc_mutex_init(&view->new_zone_lock);
+
+ result = dns_order_create(view->mctx, &view->order);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup_new_zone_lock;
+ }
+
+ result = dns_peerlist_new(view->mctx, &view->peers);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup_order;
+ }
+
+ result = dns_aclenv_create(view->mctx, &view->aclenv);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup_peerlist;
+ }
+
+ ISC_LINK_INIT(view, link);
+ ISC_EVENT_INIT(&view->resevent, sizeof(view->resevent), 0, NULL,
+ DNS_EVENT_VIEWRESSHUTDOWN, resolver_shutdown, view, NULL,
+ NULL, NULL);
+ ISC_EVENT_INIT(&view->adbevent, sizeof(view->adbevent), 0, NULL,
+ DNS_EVENT_VIEWADBSHUTDOWN, adb_shutdown, view, NULL,
+ NULL, NULL);
+ ISC_EVENT_INIT(&view->reqevent, sizeof(view->reqevent), 0, NULL,
+ DNS_EVENT_VIEWREQSHUTDOWN, req_shutdown, view, NULL,
+ NULL, NULL);
+ view->viewlist = NULL;
+ view->magic = DNS_VIEW_MAGIC;
+
+ *viewp = view;
+
+ return (ISC_R_SUCCESS);
+
+cleanup_peerlist:
+ if (view->peers != NULL) {
+ dns_peerlist_detach(&view->peers);
+ }
+
+cleanup_order:
+ if (view->order != NULL) {
+ dns_order_detach(&view->order);
+ }
+
+cleanup_new_zone_lock:
+ isc_mutex_destroy(&view->new_zone_lock);
+
+ dns_badcache_destroy(&view->failcache);
+
+cleanup_dynkeys:
+ if (view->dynamickeys != NULL) {
+ dns_tsigkeyring_detach(&view->dynamickeys);
+ }
+
+cleanup_weakrefs:
+ isc_refcount_decrementz(&view->weakrefs);
+ isc_refcount_destroy(&view->weakrefs);
+
+ isc_refcount_decrementz(&view->references);
+ isc_refcount_destroy(&view->references);
+
+ if (view->fwdtable != NULL) {
+ dns_fwdtable_destroy(&view->fwdtable);
+ }
+
+cleanup_zt:
+ if (view->zonetable != NULL) {
+ dns_zt_detach(&view->zonetable);
+ }
+
+cleanup_mutex:
+ isc_rwlock_destroy(&view->sfd_lock);
+ isc_mutex_destroy(&view->lock);
+
+ if (view->nta_file != NULL) {
+ isc_mem_free(mctx, view->nta_file);
+ }
+
+cleanup_name:
+ isc_mem_free(mctx, view->name);
+ isc_mem_putanddetach(&view->mctx, view, sizeof(*view));
+
+ return (result);
+}
+
+static void
+destroy(dns_view_t *view) {
+ dns_dns64_t *dns64;
+ dns_dlzdb_t *dlzdb;
+
+ REQUIRE(!ISC_LINK_LINKED(view, link));
+ REQUIRE(RESSHUTDOWN(view));
+ REQUIRE(ADBSHUTDOWN(view));
+ REQUIRE(REQSHUTDOWN(view));
+
+ isc_refcount_destroy(&view->references);
+ isc_refcount_destroy(&view->weakrefs);
+
+ if (view->order != NULL) {
+ dns_order_detach(&view->order);
+ }
+ if (view->peers != NULL) {
+ dns_peerlist_detach(&view->peers);
+ }
+
+ if (view->dynamickeys != NULL) {
+ isc_result_t result;
+ char template[PATH_MAX];
+ char keyfile[PATH_MAX];
+ FILE *fp = NULL;
+
+ result = isc_file_mktemplate(NULL, template, sizeof(template));
+ if (result == ISC_R_SUCCESS) {
+ (void)isc_file_openuniqueprivate(template, &fp);
+ }
+ if (fp == NULL) {
+ dns_tsigkeyring_detach(&view->dynamickeys);
+ } else {
+ result = dns_tsigkeyring_dumpanddetach(
+ &view->dynamickeys, fp);
+ if (result == ISC_R_SUCCESS) {
+ if (fclose(fp) == 0) {
+ result = isc_file_sanitize(
+ NULL, view->name, "tsigkeys",
+ keyfile, sizeof(keyfile));
+ if (result == ISC_R_SUCCESS) {
+ result = isc_file_rename(
+ template, keyfile);
+ }
+ }
+ if (result != ISC_R_SUCCESS) {
+ (void)remove(template);
+ }
+ } else {
+ (void)fclose(fp);
+ (void)remove(template);
+ }
+ }
+ }
+ if (view->transports != NULL) {
+ dns_transport_list_detach(&view->transports);
+ }
+ if (view->statickeys != NULL) {
+ dns_tsigkeyring_detach(&view->statickeys);
+ }
+ if (view->adb != NULL) {
+ dns_adb_detach(&view->adb);
+ }
+ if (view->resolver != NULL) {
+ dns_resolver_detach(&view->resolver);
+ }
+ dns_rrl_view_destroy(view);
+ if (view->rpzs != NULL) {
+ dns_rpz_shutdown_rpzs(view->rpzs);
+ dns_rpz_detach_rpzs(&view->rpzs);
+ }
+ if (view->catzs != NULL) {
+ dns_catz_shutdown_catzs(view->catzs);
+ dns_catz_detach_catzs(&view->catzs);
+ }
+ for (dlzdb = ISC_LIST_HEAD(view->dlz_searched); dlzdb != NULL;
+ dlzdb = ISC_LIST_HEAD(view->dlz_searched))
+ {
+ ISC_LIST_UNLINK(view->dlz_searched, dlzdb, link);
+ dns_dlzdestroy(&dlzdb);
+ }
+ for (dlzdb = ISC_LIST_HEAD(view->dlz_unsearched); dlzdb != NULL;
+ dlzdb = ISC_LIST_HEAD(view->dlz_unsearched))
+ {
+ ISC_LIST_UNLINK(view->dlz_unsearched, dlzdb, link);
+ dns_dlzdestroy(&dlzdb);
+ }
+ if (view->requestmgr != NULL) {
+ dns_requestmgr_detach(&view->requestmgr);
+ }
+ if (view->task != NULL) {
+ isc_task_detach(&view->task);
+ }
+ if (view->hints != NULL) {
+ dns_db_detach(&view->hints);
+ }
+ if (view->cachedb != NULL) {
+ dns_db_detach(&view->cachedb);
+ }
+ if (view->cache != NULL) {
+ dns_cache_detach(&view->cache);
+ }
+ if (view->nocasecompress != NULL) {
+ dns_acl_detach(&view->nocasecompress);
+ }
+ if (view->matchclients != NULL) {
+ dns_acl_detach(&view->matchclients);
+ }
+ if (view->matchdestinations != NULL) {
+ dns_acl_detach(&view->matchdestinations);
+ }
+ if (view->cacheacl != NULL) {
+ dns_acl_detach(&view->cacheacl);
+ }
+ if (view->cacheonacl != NULL) {
+ dns_acl_detach(&view->cacheonacl);
+ }
+ if (view->queryacl != NULL) {
+ dns_acl_detach(&view->queryacl);
+ }
+ if (view->queryonacl != NULL) {
+ dns_acl_detach(&view->queryonacl);
+ }
+ if (view->recursionacl != NULL) {
+ dns_acl_detach(&view->recursionacl);
+ }
+ if (view->recursiononacl != NULL) {
+ dns_acl_detach(&view->recursiononacl);
+ }
+ if (view->sortlist != NULL) {
+ dns_acl_detach(&view->sortlist);
+ }
+ if (view->transferacl != NULL) {
+ dns_acl_detach(&view->transferacl);
+ }
+ if (view->notifyacl != NULL) {
+ dns_acl_detach(&view->notifyacl);
+ }
+ if (view->updateacl != NULL) {
+ dns_acl_detach(&view->updateacl);
+ }
+ if (view->upfwdacl != NULL) {
+ dns_acl_detach(&view->upfwdacl);
+ }
+ if (view->denyansweracl != NULL) {
+ dns_acl_detach(&view->denyansweracl);
+ }
+ if (view->pad_acl != NULL) {
+ dns_acl_detach(&view->pad_acl);
+ }
+ if (view->answeracl_exclude != NULL) {
+ dns_rbt_destroy(&view->answeracl_exclude);
+ }
+ if (view->denyanswernames != NULL) {
+ dns_rbt_destroy(&view->denyanswernames);
+ }
+ if (view->answernames_exclude != NULL) {
+ dns_rbt_destroy(&view->answernames_exclude);
+ }
+ if (view->sfd != NULL) {
+ dns_rbt_destroy(&view->sfd);
+ }
+ if (view->delonly != NULL) {
+ dns_name_t *name;
+ int i;
+
+ for (i = 0; i < DNS_VIEW_DELONLYHASH; i++) {
+ name = ISC_LIST_HEAD(view->delonly[i]);
+ while (name != NULL) {
+ ISC_LIST_UNLINK(view->delonly[i], name, link);
+ dns_name_free(name, view->mctx);
+ isc_mem_put(view->mctx, name, sizeof(*name));
+ name = ISC_LIST_HEAD(view->delonly[i]);
+ }
+ }
+ isc_mem_put(view->mctx, view->delonly,
+ sizeof(dns_namelist_t) * DNS_VIEW_DELONLYHASH);
+ view->delonly = NULL;
+ }
+ if (view->rootexclude != NULL) {
+ dns_name_t *name;
+ int i;
+
+ for (i = 0; i < DNS_VIEW_DELONLYHASH; i++) {
+ name = ISC_LIST_HEAD(view->rootexclude[i]);
+ while (name != NULL) {
+ ISC_LIST_UNLINK(view->rootexclude[i], name,
+ link);
+ dns_name_free(name, view->mctx);
+ isc_mem_put(view->mctx, name, sizeof(*name));
+ name = ISC_LIST_HEAD(view->rootexclude[i]);
+ }
+ }
+ isc_mem_put(view->mctx, view->rootexclude,
+ sizeof(dns_namelist_t) * DNS_VIEW_DELONLYHASH);
+ view->rootexclude = NULL;
+ }
+ if (view->adbstats != NULL) {
+ isc_stats_detach(&view->adbstats);
+ }
+ if (view->resstats != NULL) {
+ isc_stats_detach(&view->resstats);
+ }
+ if (view->resquerystats != NULL) {
+ dns_stats_detach(&view->resquerystats);
+ }
+ if (view->secroots_priv != NULL) {
+ dns_keytable_detach(&view->secroots_priv);
+ }
+ if (view->ntatable_priv != NULL) {
+ dns_ntatable_detach(&view->ntatable_priv);
+ }
+ for (dns64 = ISC_LIST_HEAD(view->dns64); dns64 != NULL;
+ dns64 = ISC_LIST_HEAD(view->dns64))
+ {
+ dns_dns64_unlink(&view->dns64, dns64);
+ dns_dns64_destroy(&dns64);
+ }
+ if (view->managed_keys != NULL) {
+ dns_zone_detach(&view->managed_keys);
+ }
+ if (view->redirect != NULL) {
+ dns_zone_detach(&view->redirect);
+ }
+#ifdef HAVE_DNSTAP
+ if (view->dtenv != NULL) {
+ dns_dt_detach(&view->dtenv);
+ }
+#endif /* HAVE_DNSTAP */
+ dns_view_setnewzones(view, false, NULL, NULL, 0ULL);
+ if (view->new_zone_file != NULL) {
+ isc_mem_free(view->mctx, view->new_zone_file);
+ view->new_zone_file = NULL;
+ }
+ if (view->new_zone_dir != NULL) {
+ isc_mem_free(view->mctx, view->new_zone_dir);
+ view->new_zone_dir = NULL;
+ }
+#ifdef HAVE_LMDB
+ if (view->new_zone_dbenv != NULL) {
+ mdb_env_close((MDB_env *)view->new_zone_dbenv);
+ view->new_zone_dbenv = NULL;
+ }
+ if (view->new_zone_db != NULL) {
+ isc_mem_free(view->mctx, view->new_zone_db);
+ view->new_zone_db = NULL;
+ }
+#endif /* HAVE_LMDB */
+ dns_fwdtable_destroy(&view->fwdtable);
+ dns_aclenv_detach(&view->aclenv);
+ if (view->failcache != NULL) {
+ dns_badcache_destroy(&view->failcache);
+ }
+ isc_mutex_destroy(&view->new_zone_lock);
+ isc_rwlock_destroy(&view->sfd_lock);
+ isc_mutex_destroy(&view->lock);
+ isc_refcount_destroy(&view->references);
+ isc_refcount_destroy(&view->weakrefs);
+ isc_mem_free(view->mctx, view->nta_file);
+ isc_mem_free(view->mctx, view->name);
+ if (view->hooktable != NULL && view->hooktable_free != NULL) {
+ view->hooktable_free(view->mctx, &view->hooktable);
+ }
+ if (view->plugins != NULL && view->plugins_free != NULL) {
+ view->plugins_free(view->mctx, &view->plugins);
+ }
+ isc_mem_putanddetach(&view->mctx, view, sizeof(*view));
+}
+
+void
+dns_view_attach(dns_view_t *source, dns_view_t **targetp) {
+ REQUIRE(DNS_VIEW_VALID(source));
+ REQUIRE(targetp != NULL && *targetp == NULL);
+
+ isc_refcount_increment(&source->references);
+
+ *targetp = source;
+}
+
+static void
+view_flushanddetach(dns_view_t **viewp, bool flush) {
+ REQUIRE(viewp != NULL && DNS_VIEW_VALID(*viewp));
+ dns_view_t *view = *viewp;
+ *viewp = NULL;
+
+ if (flush) {
+ view->flush = flush;
+ }
+
+ if (isc_refcount_decrement(&view->references) == 1) {
+ dns_zone_t *mkzone = NULL, *rdzone = NULL;
+ dns_zt_t *zt = NULL;
+
+ isc_refcount_destroy(&view->references);
+
+ if (!RESSHUTDOWN(view)) {
+ dns_resolver_shutdown(view->resolver);
+ }
+ if (!ADBSHUTDOWN(view)) {
+ dns_adb_shutdown(view->adb);
+ }
+ if (!REQSHUTDOWN(view)) {
+ dns_requestmgr_shutdown(view->requestmgr);
+ }
+
+ LOCK(&view->lock);
+
+ if (view->zonetable != NULL) {
+ zt = view->zonetable;
+ view->zonetable = NULL;
+ if (view->flush) {
+ dns_zt_flush(zt);
+ }
+ }
+
+ if (view->managed_keys != NULL) {
+ mkzone = view->managed_keys;
+ view->managed_keys = NULL;
+ if (view->flush) {
+ dns_zone_flush(mkzone);
+ }
+ }
+ if (view->redirect != NULL) {
+ rdzone = view->redirect;
+ view->redirect = NULL;
+ if (view->flush) {
+ dns_zone_flush(rdzone);
+ }
+ }
+ if (view->catzs != NULL) {
+ dns_catz_shutdown_catzs(view->catzs);
+ dns_catz_detach_catzs(&view->catzs);
+ }
+ if (view->ntatable_priv != NULL) {
+ dns_ntatable_shutdown(view->ntatable_priv);
+ }
+ UNLOCK(&view->lock);
+
+ /* Need to detach zt and zones outside view lock */
+ if (zt != NULL) {
+ dns_zt_detach(&zt);
+ }
+
+ if (mkzone != NULL) {
+ dns_zone_detach(&mkzone);
+ }
+
+ if (rdzone != NULL) {
+ dns_zone_detach(&rdzone);
+ }
+
+ dns_view_weakdetach(&view);
+ }
+}
+
+void
+dns_view_flushanddetach(dns_view_t **viewp) {
+ view_flushanddetach(viewp, true);
+}
+
+void
+dns_view_detach(dns_view_t **viewp) {
+ view_flushanddetach(viewp, false);
+}
+
+static isc_result_t
+dialup(dns_zone_t *zone, void *dummy) {
+ UNUSED(dummy);
+ dns_zone_dialup(zone);
+ return (ISC_R_SUCCESS);
+}
+
+void
+dns_view_dialup(dns_view_t *view) {
+ REQUIRE(DNS_VIEW_VALID(view));
+ REQUIRE(view->zonetable != NULL);
+
+ (void)dns_zt_apply(view->zonetable, isc_rwlocktype_read, false, NULL,
+ dialup, NULL);
+}
+
+void
+dns_view_weakattach(dns_view_t *source, dns_view_t **targetp) {
+ REQUIRE(DNS_VIEW_VALID(source));
+ REQUIRE(targetp != NULL && *targetp == NULL);
+
+ isc_refcount_increment(&source->weakrefs);
+
+ *targetp = source;
+}
+
+void
+dns_view_weakdetach(dns_view_t **viewp) {
+ dns_view_t *view;
+
+ REQUIRE(viewp != NULL);
+ view = *viewp;
+ *viewp = NULL;
+ REQUIRE(DNS_VIEW_VALID(view));
+
+ if (isc_refcount_decrement(&view->weakrefs) == 1) {
+ destroy(view);
+ }
+}
+
+static void
+resolver_shutdown(isc_task_t *task, isc_event_t *event) {
+ dns_view_t *view = event->ev_arg;
+
+ REQUIRE(event->ev_type == DNS_EVENT_VIEWRESSHUTDOWN);
+ REQUIRE(DNS_VIEW_VALID(view));
+ REQUIRE(view->task == task);
+
+ UNUSED(task);
+
+ isc_event_free(&event);
+
+ atomic_fetch_or(&view->attributes, DNS_VIEWATTR_RESSHUTDOWN);
+ dns_view_weakdetach(&view);
+}
+
+static void
+adb_shutdown(isc_task_t *task, isc_event_t *event) {
+ dns_view_t *view = event->ev_arg;
+
+ REQUIRE(event->ev_type == DNS_EVENT_VIEWADBSHUTDOWN);
+ REQUIRE(DNS_VIEW_VALID(view));
+ REQUIRE(view->task == task);
+
+ UNUSED(task);
+
+ isc_event_free(&event);
+
+ atomic_fetch_or(&view->attributes, DNS_VIEWATTR_ADBSHUTDOWN);
+
+ dns_view_weakdetach(&view);
+}
+
+static void
+req_shutdown(isc_task_t *task, isc_event_t *event) {
+ dns_view_t *view = event->ev_arg;
+
+ REQUIRE(event->ev_type == DNS_EVENT_VIEWREQSHUTDOWN);
+ REQUIRE(DNS_VIEW_VALID(view));
+ REQUIRE(view->task == task);
+
+ UNUSED(task);
+
+ isc_event_free(&event);
+
+ atomic_fetch_or(&view->attributes, DNS_VIEWATTR_REQSHUTDOWN);
+
+ dns_view_weakdetach(&view);
+}
+
+isc_result_t
+dns_view_createzonetable(dns_view_t *view) {
+ REQUIRE(DNS_VIEW_VALID(view));
+ REQUIRE(!view->frozen);
+ REQUIRE(view->zonetable == NULL);
+
+ return (dns_zt_create(view->mctx, view->rdclass, &view->zonetable));
+}
+
+isc_result_t
+dns_view_createresolver(dns_view_t *view, isc_taskmgr_t *taskmgr,
+ unsigned int ntasks, unsigned int ndisp, isc_nm_t *nm,
+ isc_timermgr_t *timermgr, unsigned int options,
+ dns_dispatchmgr_t *dispatchmgr,
+ dns_dispatch_t *dispatchv4,
+ dns_dispatch_t *dispatchv6) {
+ isc_result_t result;
+ isc_event_t *event;
+ isc_mem_t *mctx = NULL;
+
+ REQUIRE(DNS_VIEW_VALID(view));
+ REQUIRE(!view->frozen);
+ REQUIRE(view->resolver == NULL);
+
+ result = isc_task_create(taskmgr, 0, &view->task);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+ isc_task_setname(view->task, "view", view);
+
+ result = dns_resolver_create(view, taskmgr, ntasks, ndisp, nm, timermgr,
+ options, dispatchmgr, dispatchv4,
+ dispatchv6, &view->resolver);
+ if (result != ISC_R_SUCCESS) {
+ isc_task_detach(&view->task);
+ return (result);
+ }
+ event = &view->resevent;
+ dns_resolver_whenshutdown(view->resolver, view->task, &event);
+ atomic_fetch_and(&view->attributes, ~DNS_VIEWATTR_RESSHUTDOWN);
+ isc_refcount_increment(&view->weakrefs);
+
+ isc_mem_create(&mctx);
+ isc_mem_setname(mctx, "ADB");
+
+ result = dns_adb_create(mctx, view, timermgr, taskmgr, &view->adb);
+ isc_mem_detach(&mctx);
+ if (result != ISC_R_SUCCESS) {
+ dns_resolver_shutdown(view->resolver);
+ return (result);
+ }
+ event = &view->adbevent;
+ dns_adb_whenshutdown(view->adb, view->task, &event);
+ atomic_fetch_and(&view->attributes, ~DNS_VIEWATTR_ADBSHUTDOWN);
+ isc_refcount_increment(&view->weakrefs);
+
+ result = dns_requestmgr_create(
+ view->mctx, dns_resolver_taskmgr(view->resolver),
+ dns_resolver_dispatchmgr(view->resolver), dispatchv4,
+ dispatchv6, &view->requestmgr);
+ if (result != ISC_R_SUCCESS) {
+ dns_adb_shutdown(view->adb);
+ dns_resolver_shutdown(view->resolver);
+ return (result);
+ }
+ event = &view->reqevent;
+ dns_requestmgr_whenshutdown(view->requestmgr, view->task, &event);
+ atomic_fetch_and(&view->attributes, ~DNS_VIEWATTR_REQSHUTDOWN);
+ isc_refcount_increment(&view->weakrefs);
+
+ return (ISC_R_SUCCESS);
+}
+
+void
+dns_view_setcache(dns_view_t *view, dns_cache_t *cache, bool shared) {
+ REQUIRE(DNS_VIEW_VALID(view));
+ REQUIRE(!view->frozen);
+
+ view->cacheshared = shared;
+ if (view->cache != NULL) {
+ dns_db_detach(&view->cachedb);
+ dns_cache_detach(&view->cache);
+ }
+ dns_cache_attach(cache, &view->cache);
+ dns_cache_attachdb(cache, &view->cachedb);
+ INSIST(DNS_DB_VALID(view->cachedb));
+}
+
+bool
+dns_view_iscacheshared(dns_view_t *view) {
+ REQUIRE(DNS_VIEW_VALID(view));
+
+ return (view->cacheshared);
+}
+
+void
+dns_view_sethints(dns_view_t *view, dns_db_t *hints) {
+ REQUIRE(DNS_VIEW_VALID(view));
+ REQUIRE(!view->frozen);
+ REQUIRE(view->hints == NULL);
+ REQUIRE(dns_db_iszone(hints));
+
+ dns_db_attach(hints, &view->hints);
+}
+
+void
+dns_view_settransports(dns_view_t *view, dns_transport_list_t *list) {
+ REQUIRE(DNS_VIEW_VALID(view));
+ REQUIRE(list != NULL);
+ if (view->transports != NULL) {
+ dns_transport_list_detach(&view->transports);
+ }
+ dns_transport_list_attach(list, &view->transports);
+}
+
+void
+dns_view_setkeyring(dns_view_t *view, dns_tsig_keyring_t *ring) {
+ REQUIRE(DNS_VIEW_VALID(view));
+ REQUIRE(ring != NULL);
+ if (view->statickeys != NULL) {
+ dns_tsigkeyring_detach(&view->statickeys);
+ }
+ dns_tsigkeyring_attach(ring, &view->statickeys);
+}
+
+void
+dns_view_setdynamickeyring(dns_view_t *view, dns_tsig_keyring_t *ring) {
+ REQUIRE(DNS_VIEW_VALID(view));
+ REQUIRE(ring != NULL);
+ if (view->dynamickeys != NULL) {
+ dns_tsigkeyring_detach(&view->dynamickeys);
+ }
+ dns_tsigkeyring_attach(ring, &view->dynamickeys);
+}
+
+void
+dns_view_getdynamickeyring(dns_view_t *view, dns_tsig_keyring_t **ringp) {
+ REQUIRE(DNS_VIEW_VALID(view));
+ REQUIRE(ringp != NULL && *ringp == NULL);
+ if (view->dynamickeys != NULL) {
+ dns_tsigkeyring_attach(view->dynamickeys, ringp);
+ }
+}
+
+void
+dns_view_restorekeyring(dns_view_t *view) {
+ FILE *fp;
+ char keyfile[PATH_MAX];
+ isc_result_t result;
+
+ REQUIRE(DNS_VIEW_VALID(view));
+
+ if (view->dynamickeys != NULL) {
+ result = isc_file_sanitize(NULL, view->name, "tsigkeys",
+ keyfile, sizeof(keyfile));
+ if (result == ISC_R_SUCCESS) {
+ fp = fopen(keyfile, "r");
+ if (fp != NULL) {
+ dns_keyring_restore(view->dynamickeys, fp);
+ (void)fclose(fp);
+ }
+ }
+ }
+}
+
+void
+dns_view_setdstport(dns_view_t *view, in_port_t dstport) {
+ REQUIRE(DNS_VIEW_VALID(view));
+ view->dstport = dstport;
+}
+
+void
+dns_view_freeze(dns_view_t *view) {
+ REQUIRE(DNS_VIEW_VALID(view));
+ REQUIRE(!view->frozen);
+
+ if (view->resolver != NULL) {
+ INSIST(view->cachedb != NULL);
+ dns_resolver_freeze(view->resolver);
+ }
+ view->frozen = true;
+}
+
+void
+dns_view_thaw(dns_view_t *view) {
+ REQUIRE(DNS_VIEW_VALID(view));
+ REQUIRE(view->frozen);
+
+ view->frozen = false;
+}
+
+isc_result_t
+dns_view_addzone(dns_view_t *view, dns_zone_t *zone) {
+ isc_result_t result;
+
+ REQUIRE(DNS_VIEW_VALID(view));
+ REQUIRE(!view->frozen);
+ REQUIRE(view->zonetable != NULL);
+
+ result = dns_zt_mount(view->zonetable, zone);
+
+ return (result);
+}
+
+isc_result_t
+dns_view_findzone(dns_view_t *view, const dns_name_t *name,
+ dns_zone_t **zonep) {
+ isc_result_t result;
+
+ REQUIRE(DNS_VIEW_VALID(view));
+
+ LOCK(&view->lock);
+ if (view->zonetable != NULL) {
+ result = dns_zt_find(view->zonetable, name, 0, NULL, zonep);
+ if (result == DNS_R_PARTIALMATCH) {
+ dns_zone_detach(zonep);
+ result = ISC_R_NOTFOUND;
+ }
+ } else {
+ result = ISC_R_NOTFOUND;
+ }
+ UNLOCK(&view->lock);
+
+ return (result);
+}
+
+isc_result_t
+dns_view_find(dns_view_t *view, const dns_name_t *name, dns_rdatatype_t type,
+ isc_stdtime_t now, unsigned int options, bool use_hints,
+ bool use_static_stub, dns_db_t **dbp, dns_dbnode_t **nodep,
+ dns_name_t *foundname, dns_rdataset_t *rdataset,
+ dns_rdataset_t *sigrdataset) {
+ isc_result_t result;
+ dns_db_t *db, *zdb;
+ dns_dbnode_t *node, *znode;
+ bool is_cache, is_staticstub_zone;
+ dns_rdataset_t zrdataset, zsigrdataset;
+ dns_zone_t *zone;
+
+ /*
+ * Find an rdataset whose owner name is 'name', and whose type is
+ * 'type'.
+ */
+
+ REQUIRE(DNS_VIEW_VALID(view));
+ REQUIRE(view->frozen);
+ REQUIRE(type != dns_rdatatype_rrsig);
+ REQUIRE(rdataset != NULL); /* XXXBEW - remove this */
+ REQUIRE(nodep == NULL || *nodep == NULL);
+
+ /*
+ * Initialize.
+ */
+ dns_rdataset_init(&zrdataset);
+ dns_rdataset_init(&zsigrdataset);
+ zdb = NULL;
+ znode = NULL;
+
+ /*
+ * Find a database to answer the query.
+ */
+ db = NULL;
+ node = NULL;
+ is_staticstub_zone = false;
+ zone = NULL;
+ LOCK(&view->lock);
+ if (view->zonetable != NULL) {
+ result = dns_zt_find(view->zonetable, name, DNS_ZTFIND_MIRROR,
+ NULL, &zone);
+ } else {
+ result = ISC_R_NOTFOUND;
+ }
+ UNLOCK(&view->lock);
+ if (zone != NULL && dns_zone_gettype(zone) == dns_zone_staticstub &&
+ !use_static_stub)
+ {
+ result = ISC_R_NOTFOUND;
+ }
+ if (result == ISC_R_SUCCESS || result == DNS_R_PARTIALMATCH) {
+ result = dns_zone_getdb(zone, &db);
+ if (result != ISC_R_SUCCESS && view->cachedb != NULL) {
+ dns_db_attach(view->cachedb, &db);
+ } else if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+ if (dns_zone_gettype(zone) == dns_zone_staticstub &&
+ dns_name_equal(name, dns_zone_getorigin(zone)))
+ {
+ is_staticstub_zone = true;
+ }
+ } else if (result == ISC_R_NOTFOUND && view->cachedb != NULL) {
+ dns_db_attach(view->cachedb, &db);
+ } else {
+ goto cleanup;
+ }
+
+ is_cache = dns_db_iscache(db);
+
+db_find:
+ /*
+ * Now look for an answer in the database.
+ */
+ result = dns_db_find(db, name, NULL, type, options, now, &node,
+ foundname, rdataset, sigrdataset);
+
+ if (result == DNS_R_DELEGATION || result == ISC_R_NOTFOUND) {
+ if (dns_rdataset_isassociated(rdataset)) {
+ dns_rdataset_disassociate(rdataset);
+ }
+ if (sigrdataset != NULL &&
+ dns_rdataset_isassociated(sigrdataset))
+ {
+ dns_rdataset_disassociate(sigrdataset);
+ }
+ if (node != NULL) {
+ dns_db_detachnode(db, &node);
+ }
+ if (!is_cache) {
+ dns_db_detach(&db);
+ if (view->cachedb != NULL && !is_staticstub_zone) {
+ /*
+ * Either the answer is in the cache, or we
+ * don't know it.
+ * Note that if the result comes from a
+ * static-stub zone we stop the search here
+ * (see the function description in view.h).
+ */
+ is_cache = true;
+ dns_db_attach(view->cachedb, &db);
+ goto db_find;
+ }
+ } else {
+ /*
+ * We don't have the data in the cache. If we've got
+ * glue from the zone, use it.
+ */
+ if (dns_rdataset_isassociated(&zrdataset)) {
+ dns_rdataset_clone(&zrdataset, rdataset);
+ if (sigrdataset != NULL &&
+ dns_rdataset_isassociated(&zsigrdataset))
+ {
+ dns_rdataset_clone(&zsigrdataset,
+ sigrdataset);
+ }
+ result = DNS_R_GLUE;
+ if (db != NULL) {
+ dns_db_detach(&db);
+ }
+ dns_db_attach(zdb, &db);
+ dns_db_attachnode(db, znode, &node);
+ goto cleanup;
+ }
+ }
+ /*
+ * We don't know the answer.
+ */
+ result = ISC_R_NOTFOUND;
+ } else if (result == DNS_R_GLUE) {
+ if (view->cachedb != NULL && !is_staticstub_zone) {
+ /*
+ * We found an answer, but the cache may be better.
+ * Remember what we've got and go look in the cache.
+ */
+ is_cache = true;
+ dns_rdataset_clone(rdataset, &zrdataset);
+ dns_rdataset_disassociate(rdataset);
+ if (sigrdataset != NULL &&
+ dns_rdataset_isassociated(sigrdataset))
+ {
+ dns_rdataset_clone(sigrdataset, &zsigrdataset);
+ dns_rdataset_disassociate(sigrdataset);
+ }
+ dns_db_attach(db, &zdb);
+ dns_db_attachnode(zdb, node, &znode);
+ dns_db_detachnode(db, &node);
+ dns_db_detach(&db);
+ dns_db_attach(view->cachedb, &db);
+ goto db_find;
+ }
+ /*
+ * Otherwise, the glue is the best answer.
+ */
+ result = ISC_R_SUCCESS;
+ }
+
+ if (result == ISC_R_NOTFOUND && use_hints && view->hints != NULL) {
+ if (dns_rdataset_isassociated(rdataset)) {
+ dns_rdataset_disassociate(rdataset);
+ }
+ if (sigrdataset != NULL &&
+ dns_rdataset_isassociated(sigrdataset))
+ {
+ dns_rdataset_disassociate(sigrdataset);
+ }
+ if (db != NULL) {
+ if (node != NULL) {
+ dns_db_detachnode(db, &node);
+ }
+ dns_db_detach(&db);
+ }
+ result = dns_db_find(view->hints, name, NULL, type, options,
+ now, &node, foundname, rdataset,
+ sigrdataset);
+ if (result == ISC_R_SUCCESS || result == DNS_R_GLUE) {
+ /*
+ * We just used a hint. Let the resolver know it
+ * should consider priming.
+ */
+ dns_resolver_prime(view->resolver);
+ dns_db_attach(view->hints, &db);
+ result = DNS_R_HINT;
+ } else if (result == DNS_R_NXRRSET) {
+ dns_db_attach(view->hints, &db);
+ result = DNS_R_HINTNXRRSET;
+ } else if (result == DNS_R_NXDOMAIN) {
+ result = ISC_R_NOTFOUND;
+ }
+
+ /*
+ * Cleanup if non-standard hints are used.
+ */
+ if (db == NULL && node != NULL) {
+ dns_db_detachnode(view->hints, &node);
+ }
+ }
+
+cleanup:
+ if (dns_rdataset_isassociated(&zrdataset)) {
+ dns_rdataset_disassociate(&zrdataset);
+ if (dns_rdataset_isassociated(&zsigrdataset)) {
+ dns_rdataset_disassociate(&zsigrdataset);
+ }
+ }
+
+ if (zdb != NULL) {
+ if (znode != NULL) {
+ dns_db_detachnode(zdb, &znode);
+ }
+ dns_db_detach(&zdb);
+ }
+
+ if (db != NULL) {
+ if (node != NULL) {
+ if (nodep != NULL) {
+ *nodep = node;
+ } else {
+ dns_db_detachnode(db, &node);
+ }
+ }
+ if (dbp != NULL) {
+ *dbp = db;
+ } else {
+ dns_db_detach(&db);
+ }
+ } else {
+ INSIST(node == NULL);
+ }
+
+ if (zone != NULL) {
+ dns_zone_detach(&zone);
+ }
+
+ return (result);
+}
+
+isc_result_t
+dns_view_simplefind(dns_view_t *view, const dns_name_t *name,
+ dns_rdatatype_t type, isc_stdtime_t now,
+ unsigned int options, bool use_hints,
+ dns_rdataset_t *rdataset, dns_rdataset_t *sigrdataset) {
+ isc_result_t result;
+ dns_fixedname_t foundname;
+
+ dns_fixedname_init(&foundname);
+ result = dns_view_find(view, name, type, now, options, use_hints, false,
+ NULL, NULL, dns_fixedname_name(&foundname),
+ rdataset, sigrdataset);
+ if (result == DNS_R_NXDOMAIN) {
+ /*
+ * The rdataset and sigrdataset of the relevant NSEC record
+ * may be returned, but the caller cannot use them because
+ * foundname is not returned by this simplified API. We
+ * disassociate them here to prevent any misuse by the caller.
+ */
+ if (dns_rdataset_isassociated(rdataset)) {
+ dns_rdataset_disassociate(rdataset);
+ }
+ if (sigrdataset != NULL &&
+ dns_rdataset_isassociated(sigrdataset))
+ {
+ dns_rdataset_disassociate(sigrdataset);
+ }
+ } else if (result != ISC_R_SUCCESS && result != DNS_R_GLUE &&
+ result != DNS_R_HINT && result != DNS_R_NCACHENXDOMAIN &&
+ result != DNS_R_NCACHENXRRSET && result != DNS_R_NXRRSET &&
+ result != DNS_R_HINTNXRRSET && result != ISC_R_NOTFOUND)
+ {
+ if (dns_rdataset_isassociated(rdataset)) {
+ dns_rdataset_disassociate(rdataset);
+ }
+ if (sigrdataset != NULL &&
+ dns_rdataset_isassociated(sigrdataset))
+ {
+ dns_rdataset_disassociate(sigrdataset);
+ }
+ result = ISC_R_NOTFOUND;
+ }
+
+ return (result);
+}
+
+isc_result_t
+dns_view_findzonecut(dns_view_t *view, const dns_name_t *name,
+ dns_name_t *fname, dns_name_t *dcname, isc_stdtime_t now,
+ unsigned int options, bool use_hints, bool use_cache,
+ dns_rdataset_t *rdataset, dns_rdataset_t *sigrdataset) {
+ isc_result_t result;
+ dns_db_t *db;
+ bool is_cache, use_zone, try_hints;
+ dns_zone_t *zone;
+ dns_name_t *zfname;
+ dns_rdataset_t zrdataset, zsigrdataset;
+ dns_fixedname_t zfixedname;
+ unsigned int ztoptions = DNS_ZTFIND_MIRROR;
+
+ REQUIRE(DNS_VIEW_VALID(view));
+ REQUIRE(view->frozen);
+
+ db = NULL;
+ use_zone = false;
+ try_hints = false;
+ zfname = NULL;
+
+ /*
+ * Initialize.
+ */
+ dns_fixedname_init(&zfixedname);
+ dns_rdataset_init(&zrdataset);
+ dns_rdataset_init(&zsigrdataset);
+
+ /*
+ * Find the right database.
+ */
+ zone = NULL;
+ LOCK(&view->lock);
+ if (view->zonetable != NULL) {
+ if ((options & DNS_DBFIND_NOEXACT) != 0) {
+ ztoptions |= DNS_ZTFIND_NOEXACT;
+ }
+ result = dns_zt_find(view->zonetable, name, ztoptions, NULL,
+ &zone);
+ } else {
+ result = ISC_R_NOTFOUND;
+ }
+ UNLOCK(&view->lock);
+ if (result == ISC_R_SUCCESS || result == DNS_R_PARTIALMATCH) {
+ result = dns_zone_getdb(zone, &db);
+ }
+ if (result == ISC_R_NOTFOUND) {
+ /*
+ * We're not directly authoritative for this query name, nor
+ * is it a subdomain of any zone for which we're
+ * authoritative.
+ */
+ if (use_cache && view->cachedb != NULL) {
+ /*
+ * We have a cache; try it.
+ */
+ dns_db_attach(view->cachedb, &db);
+ } else if (use_hints && view->hints != NULL) {
+ /*
+ * Maybe we have hints...
+ */
+ try_hints = true;
+ goto finish;
+ } else {
+ result = DNS_R_NXDOMAIN;
+ goto cleanup;
+ }
+ } else if (result != ISC_R_SUCCESS) {
+ /*
+ * Something is broken.
+ */
+ goto cleanup;
+ }
+ is_cache = dns_db_iscache(db);
+
+db_find:
+ /*
+ * Look for the zonecut.
+ */
+ if (!is_cache) {
+ result = dns_db_find(db, name, NULL, dns_rdatatype_ns, options,
+ now, NULL, fname, rdataset, sigrdataset);
+ if (result == DNS_R_DELEGATION) {
+ result = ISC_R_SUCCESS;
+ } else if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+
+ if (use_cache && view->cachedb != NULL && db != view->hints) {
+ /*
+ * We found an answer, but the cache may be better.
+ */
+ zfname = dns_fixedname_name(&zfixedname);
+ dns_name_copy(fname, zfname);
+ dns_rdataset_clone(rdataset, &zrdataset);
+ dns_rdataset_disassociate(rdataset);
+ if (sigrdataset != NULL &&
+ dns_rdataset_isassociated(sigrdataset))
+ {
+ dns_rdataset_clone(sigrdataset, &zsigrdataset);
+ dns_rdataset_disassociate(sigrdataset);
+ }
+ dns_db_detach(&db);
+ dns_db_attach(view->cachedb, &db);
+ is_cache = true;
+ goto db_find;
+ }
+ } else {
+ result = dns_db_findzonecut(db, name, options, now, NULL, fname,
+ dcname, rdataset, sigrdataset);
+ if (result == ISC_R_SUCCESS) {
+ if (zfname != NULL &&
+ (!dns_name_issubdomain(fname, zfname) ||
+ (dns_zone_gettype(zone) == dns_zone_staticstub &&
+ dns_name_equal(fname, zfname))))
+ {
+ /*
+ * We found a zonecut in the cache, but our
+ * zone delegation is better.
+ */
+ use_zone = true;
+ }
+ } else if (result == ISC_R_NOTFOUND) {
+ if (zfname != NULL) {
+ /*
+ * We didn't find anything in the cache, but we
+ * have a zone delegation, so use it.
+ */
+ use_zone = true;
+ result = ISC_R_SUCCESS;
+ } else if (use_hints && view->hints != NULL) {
+ /*
+ * Maybe we have hints...
+ */
+ try_hints = true;
+ result = ISC_R_SUCCESS;
+ } else {
+ result = DNS_R_NXDOMAIN;
+ }
+ } else {
+ /*
+ * Something bad happened.
+ */
+ goto cleanup;
+ }
+ }
+
+finish:
+ if (use_zone) {
+ if (dns_rdataset_isassociated(rdataset)) {
+ dns_rdataset_disassociate(rdataset);
+ if (sigrdataset != NULL &&
+ dns_rdataset_isassociated(sigrdataset))
+ {
+ dns_rdataset_disassociate(sigrdataset);
+ }
+ }
+ dns_name_copy(zfname, fname);
+ if (dcname != NULL) {
+ dns_name_copy(zfname, dcname);
+ }
+ dns_rdataset_clone(&zrdataset, rdataset);
+ if (sigrdataset != NULL &&
+ dns_rdataset_isassociated(&zrdataset))
+ {
+ dns_rdataset_clone(&zsigrdataset, sigrdataset);
+ }
+ } else if (try_hints) {
+ /*
+ * We've found nothing so far, but we have hints.
+ */
+ result = dns_db_find(view->hints, dns_rootname, NULL,
+ dns_rdatatype_ns, 0, now, NULL, fname,
+ rdataset, NULL);
+ if (result != ISC_R_SUCCESS) {
+ /*
+ * We can't even find the hints for the root
+ * nameservers!
+ */
+ if (dns_rdataset_isassociated(rdataset)) {
+ dns_rdataset_disassociate(rdataset);
+ }
+ result = ISC_R_NOTFOUND;
+ } else if (dcname != NULL) {
+ dns_name_copy(fname, dcname);
+ }
+ }
+
+cleanup:
+ if (dns_rdataset_isassociated(&zrdataset)) {
+ dns_rdataset_disassociate(&zrdataset);
+ if (dns_rdataset_isassociated(&zsigrdataset)) {
+ dns_rdataset_disassociate(&zsigrdataset);
+ }
+ }
+ if (db != NULL) {
+ dns_db_detach(&db);
+ }
+ if (zone != NULL) {
+ dns_zone_detach(&zone);
+ }
+
+ return (result);
+}
+
+isc_result_t
+dns_viewlist_find(dns_viewlist_t *list, const char *name,
+ dns_rdataclass_t rdclass, dns_view_t **viewp) {
+ dns_view_t *view;
+
+ REQUIRE(list != NULL);
+
+ for (view = ISC_LIST_HEAD(*list); view != NULL;
+ view = ISC_LIST_NEXT(view, link))
+ {
+ if (strcmp(view->name, name) == 0 && view->rdclass == rdclass) {
+ break;
+ }
+ }
+ if (view == NULL) {
+ return (ISC_R_NOTFOUND);
+ }
+
+ dns_view_attach(view, viewp);
+
+ return (ISC_R_SUCCESS);
+}
+
+isc_result_t
+dns_viewlist_findzone(dns_viewlist_t *list, const dns_name_t *name,
+ bool allclasses, dns_rdataclass_t rdclass,
+ dns_zone_t **zonep) {
+ dns_view_t *view;
+ isc_result_t result;
+ dns_zone_t *zone1 = NULL, *zone2 = NULL;
+ dns_zone_t **zp = NULL;
+
+ REQUIRE(list != NULL);
+ REQUIRE(zonep != NULL && *zonep == NULL);
+
+ for (view = ISC_LIST_HEAD(*list); view != NULL;
+ view = ISC_LIST_NEXT(view, link))
+ {
+ if (!allclasses && view->rdclass != rdclass) {
+ continue;
+ }
+
+ /*
+ * If the zone is defined in more than one view,
+ * treat it as not found.
+ */
+ zp = (zone1 == NULL) ? &zone1 : &zone2;
+ LOCK(&view->lock);
+ if (view->zonetable != NULL) {
+ result = dns_zt_find(view->zonetable, name, 0, NULL,
+ zp);
+ } else {
+ result = ISC_R_NOTFOUND;
+ }
+ UNLOCK(&view->lock);
+ INSIST(result == ISC_R_SUCCESS || result == ISC_R_NOTFOUND ||
+ result == DNS_R_PARTIALMATCH);
+
+ /* Treat a partial match as no match */
+ if (result == DNS_R_PARTIALMATCH) {
+ dns_zone_detach(zp);
+ result = ISC_R_NOTFOUND;
+ POST(result);
+ }
+
+ if (zone2 != NULL) {
+ dns_zone_detach(&zone1);
+ dns_zone_detach(&zone2);
+ return (ISC_R_MULTIPLE);
+ }
+ }
+
+ if (zone1 != NULL) {
+ dns_zone_attach(zone1, zonep);
+ dns_zone_detach(&zone1);
+ return (ISC_R_SUCCESS);
+ }
+
+ return (ISC_R_NOTFOUND);
+}
+
+isc_result_t
+dns_view_load(dns_view_t *view, bool stop, bool newonly) {
+ REQUIRE(DNS_VIEW_VALID(view));
+ REQUIRE(view->zonetable != NULL);
+
+ return (dns_zt_load(view->zonetable, stop, newonly));
+}
+
+isc_result_t
+dns_view_asyncload(dns_view_t *view, bool newonly, dns_zt_allloaded_t callback,
+ void *arg) {
+ REQUIRE(DNS_VIEW_VALID(view));
+ REQUIRE(view->zonetable != NULL);
+
+ return (dns_zt_asyncload(view->zonetable, newonly, callback, arg));
+}
+
+isc_result_t
+dns_view_gettsig(dns_view_t *view, const dns_name_t *keyname,
+ dns_tsigkey_t **keyp) {
+ isc_result_t result;
+ REQUIRE(keyp != NULL && *keyp == NULL);
+
+ result = dns_tsigkey_find(keyp, keyname, NULL, view->statickeys);
+ if (result == ISC_R_NOTFOUND) {
+ result = dns_tsigkey_find(keyp, keyname, NULL,
+ view->dynamickeys);
+ }
+ return (result);
+}
+
+isc_result_t
+dns_view_gettransport(dns_view_t *view, const dns_transport_type_t type,
+ const dns_name_t *name, dns_transport_t **transportp) {
+ REQUIRE(DNS_VIEW_VALID(view));
+ REQUIRE(transportp != NULL && *transportp == NULL);
+
+ dns_transport_t *transport = dns_transport_find(type, name,
+ view->transports);
+ if (transport == NULL) {
+ return (ISC_R_NOTFOUND);
+ }
+
+ *transportp = transport;
+ return (ISC_R_SUCCESS);
+}
+
+isc_result_t
+dns_view_getpeertsig(dns_view_t *view, const isc_netaddr_t *peeraddr,
+ dns_tsigkey_t **keyp) {
+ isc_result_t result;
+ dns_name_t *keyname = NULL;
+ dns_peer_t *peer = NULL;
+
+ result = dns_peerlist_peerbyaddr(view->peers, peeraddr, &peer);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+
+ result = dns_peer_getkey(peer, &keyname);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+
+ result = dns_view_gettsig(view, keyname, keyp);
+ return ((result == ISC_R_NOTFOUND) ? ISC_R_FAILURE : result);
+}
+
+isc_result_t
+dns_view_checksig(dns_view_t *view, isc_buffer_t *source, dns_message_t *msg) {
+ REQUIRE(DNS_VIEW_VALID(view));
+ REQUIRE(source != NULL);
+
+ return (dns_tsig_verify(source, msg, view->statickeys,
+ view->dynamickeys));
+}
+
+isc_result_t
+dns_view_dumpdbtostream(dns_view_t *view, FILE *fp) {
+ isc_result_t result;
+
+ REQUIRE(DNS_VIEW_VALID(view));
+
+ (void)fprintf(fp, ";\n; Cache dump of view '%s'\n;\n", view->name);
+ result = dns_master_dumptostream(view->mctx, view->cachedb, NULL,
+ &dns_master_style_cache,
+ dns_masterformat_text, NULL, fp);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+ dns_adb_dump(view->adb, fp);
+ dns_resolver_printbadcache(view->resolver, fp);
+ dns_badcache_print(view->failcache, "SERVFAIL cache", fp);
+ return (ISC_R_SUCCESS);
+}
+
+isc_result_t
+dns_view_flushcache(dns_view_t *view, bool fixuponly) {
+ isc_result_t result;
+
+ REQUIRE(DNS_VIEW_VALID(view));
+
+ if (view->cachedb == NULL) {
+ return (ISC_R_SUCCESS);
+ }
+ if (!fixuponly) {
+ result = dns_cache_flush(view->cache);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+ }
+ dns_db_detach(&view->cachedb);
+ dns_cache_attachdb(view->cache, &view->cachedb);
+ if (view->resolver != NULL) {
+ dns_resolver_flushbadcache(view->resolver, NULL);
+ }
+ if (view->failcache != NULL) {
+ dns_badcache_flush(view->failcache);
+ }
+
+ dns_adb_flush(view->adb);
+ return (ISC_R_SUCCESS);
+}
+
+isc_result_t
+dns_view_flushname(dns_view_t *view, const dns_name_t *name) {
+ return (dns_view_flushnode(view, name, false));
+}
+
+isc_result_t
+dns_view_flushnode(dns_view_t *view, const dns_name_t *name, bool tree) {
+ isc_result_t result = ISC_R_SUCCESS;
+
+ REQUIRE(DNS_VIEW_VALID(view));
+
+ if (tree) {
+ if (view->adb != NULL) {
+ dns_adb_flushnames(view->adb, name);
+ }
+ if (view->resolver != NULL) {
+ dns_resolver_flushbadnames(view->resolver, name);
+ }
+ if (view->failcache != NULL) {
+ dns_badcache_flushtree(view->failcache, name);
+ }
+ } else {
+ if (view->adb != NULL) {
+ dns_adb_flushname(view->adb, name);
+ }
+ if (view->resolver != NULL) {
+ dns_resolver_flushbadcache(view->resolver, name);
+ }
+ if (view->failcache != NULL) {
+ dns_badcache_flushname(view->failcache, name);
+ }
+ }
+
+ if (view->cache != NULL) {
+ result = dns_cache_flushnode(view->cache, name, tree);
+ }
+
+ return (result);
+}
+
+void
+dns_view_adddelegationonly(dns_view_t *view, const dns_name_t *name) {
+ dns_name_t *item;
+ unsigned int hash;
+
+ REQUIRE(DNS_VIEW_VALID(view));
+
+ if (view->delonly == NULL) {
+ view->delonly = isc_mem_get(view->mctx,
+ sizeof(dns_namelist_t) *
+ DNS_VIEW_DELONLYHASH);
+ for (hash = 0; hash < DNS_VIEW_DELONLYHASH; hash++) {
+ ISC_LIST_INIT(view->delonly[hash]);
+ }
+ }
+ hash = dns_name_hash(name, false) % DNS_VIEW_DELONLYHASH;
+ item = ISC_LIST_HEAD(view->delonly[hash]);
+ while (item != NULL && !dns_name_equal(item, name)) {
+ item = ISC_LIST_NEXT(item, link);
+ }
+ if (item != NULL) {
+ return;
+ }
+ item = isc_mem_get(view->mctx, sizeof(*item));
+ dns_name_init(item, NULL);
+ dns_name_dup(name, view->mctx, item);
+ ISC_LIST_APPEND(view->delonly[hash], item, link);
+}
+
+void
+dns_view_excludedelegationonly(dns_view_t *view, const dns_name_t *name) {
+ dns_name_t *item;
+ unsigned int hash;
+
+ REQUIRE(DNS_VIEW_VALID(view));
+
+ if (view->rootexclude == NULL) {
+ view->rootexclude = isc_mem_get(view->mctx,
+ sizeof(dns_namelist_t) *
+ DNS_VIEW_DELONLYHASH);
+ for (hash = 0; hash < DNS_VIEW_DELONLYHASH; hash++) {
+ ISC_LIST_INIT(view->rootexclude[hash]);
+ }
+ }
+ hash = dns_name_hash(name, false) % DNS_VIEW_DELONLYHASH;
+ item = ISC_LIST_HEAD(view->rootexclude[hash]);
+ while (item != NULL && !dns_name_equal(item, name)) {
+ item = ISC_LIST_NEXT(item, link);
+ }
+ if (item != NULL) {
+ return;
+ }
+ item = isc_mem_get(view->mctx, sizeof(*item));
+ dns_name_init(item, NULL);
+ dns_name_dup(name, view->mctx, item);
+ ISC_LIST_APPEND(view->rootexclude[hash], item, link);
+}
+
+bool
+dns_view_isdelegationonly(dns_view_t *view, const dns_name_t *name) {
+ dns_name_t *item;
+ unsigned int hash;
+
+ REQUIRE(DNS_VIEW_VALID(view));
+
+ if (!view->rootdelonly && view->delonly == NULL) {
+ return (false);
+ }
+
+ hash = dns_name_hash(name, false) % DNS_VIEW_DELONLYHASH;
+ if (view->rootdelonly && dns_name_countlabels(name) <= 2) {
+ if (view->rootexclude == NULL) {
+ return (true);
+ }
+ item = ISC_LIST_HEAD(view->rootexclude[hash]);
+ while (item != NULL && !dns_name_equal(item, name)) {
+ item = ISC_LIST_NEXT(item, link);
+ }
+ if (item == NULL) {
+ return (true);
+ }
+ }
+
+ if (view->delonly == NULL) {
+ return (false);
+ }
+
+ item = ISC_LIST_HEAD(view->delonly[hash]);
+ while (item != NULL && !dns_name_equal(item, name)) {
+ item = ISC_LIST_NEXT(item, link);
+ }
+ if (item == NULL) {
+ return (false);
+ }
+ return (true);
+}
+
+void
+dns_view_setrootdelonly(dns_view_t *view, bool value) {
+ REQUIRE(DNS_VIEW_VALID(view));
+ view->rootdelonly = value;
+}
+
+bool
+dns_view_getrootdelonly(dns_view_t *view) {
+ REQUIRE(DNS_VIEW_VALID(view));
+ return (view->rootdelonly);
+}
+
+isc_result_t
+dns_view_freezezones(dns_view_t *view, bool value) {
+ REQUIRE(DNS_VIEW_VALID(view));
+ REQUIRE(view->zonetable != NULL);
+
+ return (dns_zt_freezezones(view->zonetable, view, value));
+}
+
+void
+dns_view_setadbstats(dns_view_t *view, isc_stats_t *stats) {
+ REQUIRE(DNS_VIEW_VALID(view));
+ REQUIRE(!view->frozen);
+ REQUIRE(view->adbstats == NULL);
+
+ isc_stats_attach(stats, &view->adbstats);
+}
+
+void
+dns_view_getadbstats(dns_view_t *view, isc_stats_t **statsp) {
+ REQUIRE(DNS_VIEW_VALID(view));
+ REQUIRE(statsp != NULL && *statsp == NULL);
+
+ if (view->adbstats != NULL) {
+ isc_stats_attach(view->adbstats, statsp);
+ }
+}
+
+void
+dns_view_setresstats(dns_view_t *view, isc_stats_t *stats) {
+ REQUIRE(DNS_VIEW_VALID(view));
+ REQUIRE(!view->frozen);
+ REQUIRE(view->resstats == NULL);
+
+ isc_stats_attach(stats, &view->resstats);
+}
+
+void
+dns_view_getresstats(dns_view_t *view, isc_stats_t **statsp) {
+ REQUIRE(DNS_VIEW_VALID(view));
+ REQUIRE(statsp != NULL && *statsp == NULL);
+
+ if (view->resstats != NULL) {
+ isc_stats_attach(view->resstats, statsp);
+ }
+}
+
+void
+dns_view_setresquerystats(dns_view_t *view, dns_stats_t *stats) {
+ REQUIRE(DNS_VIEW_VALID(view));
+ REQUIRE(!view->frozen);
+ REQUIRE(view->resquerystats == NULL);
+
+ dns_stats_attach(stats, &view->resquerystats);
+}
+
+void
+dns_view_getresquerystats(dns_view_t *view, dns_stats_t **statsp) {
+ REQUIRE(DNS_VIEW_VALID(view));
+ REQUIRE(statsp != NULL && *statsp == NULL);
+
+ if (view->resquerystats != NULL) {
+ dns_stats_attach(view->resquerystats, statsp);
+ }
+}
+
+isc_result_t
+dns_view_initntatable(dns_view_t *view, isc_taskmgr_t *taskmgr,
+ isc_timermgr_t *timermgr) {
+ REQUIRE(DNS_VIEW_VALID(view));
+ if (view->ntatable_priv != NULL) {
+ dns_ntatable_detach(&view->ntatable_priv);
+ }
+ return (dns_ntatable_create(view, taskmgr, timermgr,
+ &view->ntatable_priv));
+}
+
+isc_result_t
+dns_view_getntatable(dns_view_t *view, dns_ntatable_t **ntp) {
+ REQUIRE(DNS_VIEW_VALID(view));
+ REQUIRE(ntp != NULL && *ntp == NULL);
+ if (view->ntatable_priv == NULL) {
+ return (ISC_R_NOTFOUND);
+ }
+ dns_ntatable_attach(view->ntatable_priv, ntp);
+ return (ISC_R_SUCCESS);
+}
+
+isc_result_t
+dns_view_initsecroots(dns_view_t *view, isc_mem_t *mctx) {
+ REQUIRE(DNS_VIEW_VALID(view));
+ if (view->secroots_priv != NULL) {
+ dns_keytable_detach(&view->secroots_priv);
+ }
+ return (dns_keytable_create(mctx, &view->secroots_priv));
+}
+
+isc_result_t
+dns_view_getsecroots(dns_view_t *view, dns_keytable_t **ktp) {
+ REQUIRE(DNS_VIEW_VALID(view));
+ REQUIRE(ktp != NULL && *ktp == NULL);
+ if (view->secroots_priv == NULL) {
+ return (ISC_R_NOTFOUND);
+ }
+ dns_keytable_attach(view->secroots_priv, ktp);
+ return (ISC_R_SUCCESS);
+}
+
+bool
+dns_view_ntacovers(dns_view_t *view, isc_stdtime_t now, const dns_name_t *name,
+ const dns_name_t *anchor) {
+ REQUIRE(DNS_VIEW_VALID(view));
+
+ if (view->ntatable_priv == NULL) {
+ return (false);
+ }
+
+ return (dns_ntatable_covered(view->ntatable_priv, now, name, anchor));
+}
+
+isc_result_t
+dns_view_issecuredomain(dns_view_t *view, const dns_name_t *name,
+ isc_stdtime_t now, bool checknta, bool *ntap,
+ bool *secure_domain) {
+ isc_result_t result;
+ bool secure = false;
+ dns_fixedname_t fn;
+ dns_name_t *anchor;
+
+ REQUIRE(DNS_VIEW_VALID(view));
+
+ if (view->secroots_priv == NULL) {
+ return (ISC_R_NOTFOUND);
+ }
+
+ anchor = dns_fixedname_initname(&fn);
+
+ result = dns_keytable_issecuredomain(view->secroots_priv, name, anchor,
+ &secure);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+
+ if (ntap != NULL) {
+ *ntap = false;
+ }
+ if (checknta && secure && view->ntatable_priv != NULL &&
+ dns_ntatable_covered(view->ntatable_priv, now, name, anchor))
+ {
+ if (ntap != NULL) {
+ *ntap = true;
+ }
+ secure = false;
+ }
+
+ *secure_domain = secure;
+ return (ISC_R_SUCCESS);
+}
+
+void
+dns_view_untrust(dns_view_t *view, const dns_name_t *keyname,
+ const dns_rdata_dnskey_t *dnskey) {
+ isc_result_t result;
+ dns_keytable_t *sr = NULL;
+ dns_rdata_dnskey_t tmpkey;
+
+ REQUIRE(DNS_VIEW_VALID(view));
+ REQUIRE(keyname != NULL);
+ REQUIRE(dnskey != NULL);
+
+ result = dns_view_getsecroots(view, &sr);
+ if (result != ISC_R_SUCCESS) {
+ return;
+ }
+
+ /*
+ * Clear the revoke bit, if set, so that the key will match what's
+ * in secroots now.
+ */
+ tmpkey = *dnskey;
+ tmpkey.flags &= ~DNS_KEYFLAG_REVOKE;
+
+ result = dns_keytable_deletekey(sr, keyname, &tmpkey);
+ if (result == ISC_R_SUCCESS) {
+ /*
+ * If key was found in secroots, then it was a
+ * configured trust anchor, and we want to fail
+ * secure. If there are no other configured keys,
+ * then leave a null key so that we can't validate
+ * anymore.
+ */
+ dns_keytable_marksecure(sr, keyname);
+ }
+
+ dns_keytable_detach(&sr);
+}
+
+bool
+dns_view_istrusted(dns_view_t *view, const dns_name_t *keyname,
+ const dns_rdata_dnskey_t *dnskey) {
+ isc_result_t result;
+ dns_keytable_t *sr = NULL;
+ dns_keynode_t *knode = NULL;
+ bool answer = false;
+ dns_rdataset_t dsset;
+
+ REQUIRE(DNS_VIEW_VALID(view));
+ REQUIRE(keyname != NULL);
+ REQUIRE(dnskey != NULL);
+
+ result = dns_view_getsecroots(view, &sr);
+ if (result != ISC_R_SUCCESS) {
+ return (false);
+ }
+
+ dns_rdataset_init(&dsset);
+ result = dns_keytable_find(sr, keyname, &knode);
+ if (result == ISC_R_SUCCESS) {
+ if (dns_keynode_dsset(knode, &dsset)) {
+ dns_rdata_t rdata = DNS_RDATA_INIT;
+ unsigned char data[4096], digest[DNS_DS_BUFFERSIZE];
+ dns_rdata_dnskey_t tmpkey = *dnskey;
+ dns_rdata_ds_t ds;
+ isc_buffer_t b;
+ dns_rdataclass_t rdclass = tmpkey.common.rdclass;
+
+ /*
+ * Clear the revoke bit, if set, so that the key
+ * will match what's in secroots now.
+ */
+ tmpkey.flags &= ~DNS_KEYFLAG_REVOKE;
+
+ isc_buffer_init(&b, data, sizeof(data));
+ result = dns_rdata_fromstruct(&rdata, rdclass,
+ dns_rdatatype_dnskey,
+ &tmpkey, &b);
+ if (result != ISC_R_SUCCESS) {
+ goto finish;
+ }
+
+ result = dns_ds_fromkeyrdata(keyname, &rdata,
+ DNS_DSDIGEST_SHA256,
+ digest, &ds);
+ if (result != ISC_R_SUCCESS) {
+ goto finish;
+ }
+
+ dns_rdata_reset(&rdata);
+ isc_buffer_init(&b, data, sizeof(data));
+ result = dns_rdata_fromstruct(
+ &rdata, rdclass, dns_rdatatype_ds, &ds, &b);
+ if (result != ISC_R_SUCCESS) {
+ goto finish;
+ }
+
+ result = dns_rdataset_first(&dsset);
+ while (result == ISC_R_SUCCESS) {
+ dns_rdata_t this = DNS_RDATA_INIT;
+ dns_rdataset_current(&dsset, &this);
+ if (dns_rdata_compare(&rdata, &this) == 0) {
+ answer = true;
+ break;
+ }
+ result = dns_rdataset_next(&dsset);
+ }
+ }
+ }
+
+finish:
+ if (dns_rdataset_isassociated(&dsset)) {
+ dns_rdataset_disassociate(&dsset);
+ }
+ if (knode != NULL) {
+ dns_keytable_detachkeynode(sr, &knode);
+ }
+ dns_keytable_detach(&sr);
+ return (answer);
+}
+
+/*
+ * Create path to a directory and a filename constructed from viewname.
+ * This is a front-end to isc_file_sanitize(), allowing backward
+ * compatibility to older versions when a file couldn't be expected
+ * to be in the specified directory but might be in the current working
+ * directory instead.
+ *
+ * It first tests for the existence of a file <viewname>.<suffix> in
+ * 'directory'. If the file does not exist, it checks again in the
+ * current working directory. If it does not exist there either,
+ * return the path inside the directory.
+ *
+ * Returns ISC_R_SUCCESS if a path to an existing file is found or
+ * a new path is created; returns ISC_R_NOSPACE if the path won't
+ * fit in 'buflen'.
+ */
+
+static isc_result_t
+nz_legacy(const char *directory, const char *viewname, const char *suffix,
+ char *buffer, size_t buflen) {
+ isc_result_t result;
+ char newbuf[PATH_MAX];
+
+ result = isc_file_sanitize(directory, viewname, suffix, buffer, buflen);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ } else if (directory == NULL || isc_file_exists(buffer)) {
+ return (ISC_R_SUCCESS);
+ } else {
+ /* Save buffer */
+ strlcpy(newbuf, buffer, sizeof(newbuf));
+ }
+
+ /*
+ * It isn't in the specified directory; check CWD.
+ */
+ result = isc_file_sanitize(NULL, viewname, suffix, buffer, buflen);
+ if (result != ISC_R_SUCCESS || isc_file_exists(buffer)) {
+ return (result);
+ }
+
+ /*
+ * File does not exist in either 'directory' or CWD,
+ * so use the path in 'directory'.
+ */
+ strlcpy(buffer, newbuf, buflen);
+ return (ISC_R_SUCCESS);
+}
+
+isc_result_t
+dns_view_setnewzones(dns_view_t *view, bool allow, void *cfgctx,
+ void (*cfg_destroy)(void **), uint64_t mapsize) {
+ isc_result_t result = ISC_R_SUCCESS;
+ char buffer[1024];
+#ifdef HAVE_LMDB
+ MDB_env *env = NULL;
+ int status;
+#endif /* ifdef HAVE_LMDB */
+
+#ifndef HAVE_LMDB
+ UNUSED(mapsize);
+#endif /* ifndef HAVE_LMDB */
+
+ REQUIRE(DNS_VIEW_VALID(view));
+ REQUIRE((cfgctx != NULL && cfg_destroy != NULL) || !allow);
+
+ if (view->new_zone_file != NULL) {
+ isc_mem_free(view->mctx, view->new_zone_file);
+ view->new_zone_file = NULL;
+ }
+
+#ifdef HAVE_LMDB
+ if (view->new_zone_dbenv != NULL) {
+ mdb_env_close((MDB_env *)view->new_zone_dbenv);
+ view->new_zone_dbenv = NULL;
+ }
+
+ if (view->new_zone_db != NULL) {
+ isc_mem_free(view->mctx, view->new_zone_db);
+ view->new_zone_db = NULL;
+ }
+#endif /* HAVE_LMDB */
+
+ if (view->new_zone_config != NULL) {
+ view->cfg_destroy(&view->new_zone_config);
+ view->cfg_destroy = NULL;
+ }
+
+ if (!allow) {
+ return (ISC_R_SUCCESS);
+ }
+
+ CHECK(nz_legacy(view->new_zone_dir, view->name, "nzf", buffer,
+ sizeof(buffer)));
+
+ view->new_zone_file = isc_mem_strdup(view->mctx, buffer);
+
+#ifdef HAVE_LMDB
+ CHECK(nz_legacy(view->new_zone_dir, view->name, "nzd", buffer,
+ sizeof(buffer)));
+
+ view->new_zone_db = isc_mem_strdup(view->mctx, buffer);
+
+ status = mdb_env_create(&env);
+ if (status != MDB_SUCCESS) {
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL,
+ ISC_LOGMODULE_OTHER, ISC_LOG_ERROR,
+ "mdb_env_create failed: %s",
+ mdb_strerror(status));
+ CHECK(ISC_R_FAILURE);
+ }
+
+ if (mapsize != 0ULL) {
+ status = mdb_env_set_mapsize(env, mapsize);
+ if (status != MDB_SUCCESS) {
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL,
+ ISC_LOGMODULE_OTHER, ISC_LOG_ERROR,
+ "mdb_env_set_mapsize failed: %s",
+ mdb_strerror(status));
+ CHECK(ISC_R_FAILURE);
+ }
+ view->new_zone_mapsize = mapsize;
+ }
+
+ status = mdb_env_open(env, view->new_zone_db, DNS_LMDB_FLAGS, 0600);
+ if (status != MDB_SUCCESS) {
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL,
+ ISC_LOGMODULE_OTHER, ISC_LOG_ERROR,
+ "mdb_env_open of '%s' failed: %s",
+ view->new_zone_db, mdb_strerror(status));
+ CHECK(ISC_R_FAILURE);
+ }
+
+ view->new_zone_dbenv = env;
+ env = NULL;
+#endif /* HAVE_LMDB */
+
+ view->new_zone_config = cfgctx;
+ view->cfg_destroy = cfg_destroy;
+
+cleanup:
+ if (result != ISC_R_SUCCESS) {
+ if (view->new_zone_file != NULL) {
+ isc_mem_free(view->mctx, view->new_zone_file);
+ view->new_zone_file = NULL;
+ }
+
+#ifdef HAVE_LMDB
+ if (view->new_zone_db != NULL) {
+ isc_mem_free(view->mctx, view->new_zone_db);
+ view->new_zone_db = NULL;
+ }
+ if (env != NULL) {
+ mdb_env_close(env);
+ }
+#endif /* HAVE_LMDB */
+ view->new_zone_config = NULL;
+ view->cfg_destroy = NULL;
+ }
+
+ return (result);
+}
+
+void
+dns_view_setnewzonedir(dns_view_t *view, const char *dir) {
+ REQUIRE(DNS_VIEW_VALID(view));
+
+ if (view->new_zone_dir != NULL) {
+ isc_mem_free(view->mctx, view->new_zone_dir);
+ view->new_zone_dir = NULL;
+ }
+
+ if (dir == NULL) {
+ return;
+ }
+
+ view->new_zone_dir = isc_mem_strdup(view->mctx, dir);
+}
+
+const char *
+dns_view_getnewzonedir(dns_view_t *view) {
+ REQUIRE(DNS_VIEW_VALID(view));
+
+ return (view->new_zone_dir);
+}
+
+isc_result_t
+dns_view_searchdlz(dns_view_t *view, const dns_name_t *name,
+ unsigned int minlabels, dns_clientinfomethods_t *methods,
+ dns_clientinfo_t *clientinfo, dns_db_t **dbp) {
+ dns_fixedname_t fname;
+ dns_name_t *zonename;
+ unsigned int namelabels;
+ unsigned int i;
+ isc_result_t result;
+ dns_dlzfindzone_t findzone;
+ dns_dlzdb_t *dlzdb;
+ dns_db_t *db, *best = NULL;
+
+ /*
+ * Performs checks to make sure data is as we expect it to be.
+ */
+ REQUIRE(DNS_VIEW_VALID(view));
+ REQUIRE(name != NULL);
+ REQUIRE(dbp != NULL && *dbp == NULL);
+
+ /* setup a "fixed" dns name */
+ zonename = dns_fixedname_initname(&fname);
+
+ /* count the number of labels in the name */
+ namelabels = dns_name_countlabels(name);
+
+ for (dlzdb = ISC_LIST_HEAD(view->dlz_searched); dlzdb != NULL;
+ dlzdb = ISC_LIST_NEXT(dlzdb, link))
+ {
+ REQUIRE(DNS_DLZ_VALID(dlzdb));
+
+ /*
+ * loop through starting with the longest domain name and
+ * trying shorter names portions of the name until we find a
+ * match, have an error, or are below the 'minlabels'
+ * threshold. minlabels is 0, if neither the standard
+ * database nor any previous DLZ database had a zone name
+ * match. Otherwise minlabels is the number of labels
+ * in that name. We need to beat that for a "better"
+ * match for this DLZ database to be authoritative.
+ */
+ for (i = namelabels; i > minlabels && i > 1; i--) {
+ if (i == namelabels) {
+ dns_name_copy(name, zonename);
+ } else {
+ dns_name_split(name, i, NULL, zonename);
+ }
+
+ /* ask SDLZ driver if the zone is supported */
+ db = NULL;
+ findzone = dlzdb->implementation->methods->findzone;
+ result = (*findzone)(dlzdb->implementation->driverarg,
+ dlzdb->dbdata, dlzdb->mctx,
+ view->rdclass, zonename, methods,
+ clientinfo, &db);
+
+ if (result != ISC_R_NOTFOUND) {
+ if (best != NULL) {
+ dns_db_detach(&best);
+ }
+ if (result == ISC_R_SUCCESS) {
+ INSIST(db != NULL);
+ dns_db_attach(db, &best);
+ dns_db_detach(&db);
+ minlabels = i;
+ } else {
+ if (db != NULL) {
+ dns_db_detach(&db);
+ }
+ break;
+ }
+ } else if (db != NULL) {
+ dns_db_detach(&db);
+ }
+ }
+ }
+
+ if (best != NULL) {
+ dns_db_attach(best, dbp);
+ dns_db_detach(&best);
+ return (ISC_R_SUCCESS);
+ }
+
+ return (ISC_R_NOTFOUND);
+}
+
+uint32_t
+dns_view_getfailttl(dns_view_t *view) {
+ REQUIRE(DNS_VIEW_VALID(view));
+ return (view->fail_ttl);
+}
+
+void
+dns_view_setfailttl(dns_view_t *view, uint32_t fail_ttl) {
+ REQUIRE(DNS_VIEW_VALID(view));
+ view->fail_ttl = fail_ttl;
+}
+
+isc_result_t
+dns_view_saventa(dns_view_t *view) {
+ isc_result_t result;
+ bool removefile = false;
+ dns_ntatable_t *ntatable = NULL;
+ FILE *fp = NULL;
+
+ REQUIRE(DNS_VIEW_VALID(view));
+
+ if (view->nta_lifetime == 0) {
+ return (ISC_R_SUCCESS);
+ }
+
+ /* Open NTA save file for overwrite. */
+ CHECK(isc_stdio_open(view->nta_file, "w", &fp));
+
+ result = dns_view_getntatable(view, &ntatable);
+ if (result == ISC_R_NOTFOUND) {
+ removefile = true;
+ result = ISC_R_SUCCESS;
+ goto cleanup;
+ } else {
+ CHECK(result);
+ }
+
+ result = dns_ntatable_save(ntatable, fp);
+ if (result == ISC_R_NOTFOUND) {
+ removefile = true;
+ result = ISC_R_SUCCESS;
+ } else if (result == ISC_R_SUCCESS) {
+ result = isc_stdio_close(fp);
+ fp = NULL;
+ }
+
+cleanup:
+ if (ntatable != NULL) {
+ dns_ntatable_detach(&ntatable);
+ }
+
+ if (fp != NULL) {
+ (void)isc_stdio_close(fp);
+ }
+
+ /* Don't leave half-baked NTA save files lying around. */
+ if (result != ISC_R_SUCCESS || removefile) {
+ (void)isc_file_remove(view->nta_file);
+ }
+
+ return (result);
+}
+
+#define TSTR(t) ((t).value.as_textregion.base)
+#define TLEN(t) ((t).value.as_textregion.length)
+
+isc_result_t
+dns_view_loadnta(dns_view_t *view) {
+ isc_result_t result;
+ dns_ntatable_t *ntatable = NULL;
+ isc_lex_t *lex = NULL;
+ isc_token_t token;
+ isc_stdtime_t now;
+
+ REQUIRE(DNS_VIEW_VALID(view));
+
+ if (view->nta_lifetime == 0) {
+ return (ISC_R_SUCCESS);
+ }
+
+ CHECK(isc_lex_create(view->mctx, 1025, &lex));
+ CHECK(isc_lex_openfile(lex, view->nta_file));
+ CHECK(dns_view_getntatable(view, &ntatable));
+ isc_stdtime_get(&now);
+
+ for (;;) {
+ int options = (ISC_LEXOPT_EOL | ISC_LEXOPT_EOF);
+ char *name, *type, *timestamp;
+ size_t len;
+ dns_fixedname_t fn;
+ const dns_name_t *ntaname;
+ isc_buffer_t b;
+ isc_stdtime_t t;
+ bool forced;
+
+ CHECK(isc_lex_gettoken(lex, options, &token));
+ if (token.type == isc_tokentype_eof) {
+ break;
+ } else if (token.type != isc_tokentype_string) {
+ CHECK(ISC_R_UNEXPECTEDTOKEN);
+ }
+ name = TSTR(token);
+ len = TLEN(token);
+
+ if (strcmp(name, ".") == 0) {
+ ntaname = dns_rootname;
+ } else {
+ dns_name_t *fname;
+ fname = dns_fixedname_initname(&fn);
+
+ isc_buffer_init(&b, name, (unsigned int)len);
+ isc_buffer_add(&b, (unsigned int)len);
+ CHECK(dns_name_fromtext(fname, &b, dns_rootname, 0,
+ NULL));
+ ntaname = fname;
+ }
+
+ CHECK(isc_lex_gettoken(lex, options, &token));
+ if (token.type != isc_tokentype_string) {
+ CHECK(ISC_R_UNEXPECTEDTOKEN);
+ }
+ type = TSTR(token);
+
+ if (strcmp(type, "regular") == 0) {
+ forced = false;
+ } else if (strcmp(type, "forced") == 0) {
+ forced = true;
+ } else {
+ CHECK(ISC_R_UNEXPECTEDTOKEN);
+ }
+
+ CHECK(isc_lex_gettoken(lex, options, &token));
+ if (token.type != isc_tokentype_string) {
+ CHECK(ISC_R_UNEXPECTEDTOKEN);
+ }
+ timestamp = TSTR(token);
+ CHECK(dns_time32_fromtext(timestamp, &t));
+
+ CHECK(isc_lex_gettoken(lex, options, &token));
+ if (token.type != isc_tokentype_eol &&
+ token.type != isc_tokentype_eof)
+ {
+ CHECK(ISC_R_UNEXPECTEDTOKEN);
+ }
+
+ if (now <= t) {
+ if (t > (now + 604800)) {
+ t = now + 604800;
+ }
+
+ (void)dns_ntatable_add(ntatable, ntaname, forced, 0, t);
+ } else {
+ char nb[DNS_NAME_FORMATSIZE];
+ dns_name_format(ntaname, nb, sizeof(nb));
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_DNSSEC,
+ DNS_LOGMODULE_NTA, ISC_LOG_INFO,
+ "ignoring expired NTA at %s", nb);
+ }
+ }
+
+cleanup:
+ if (ntatable != NULL) {
+ dns_ntatable_detach(&ntatable);
+ }
+
+ if (lex != NULL) {
+ isc_lex_close(lex);
+ isc_lex_destroy(&lex);
+ }
+
+ return (result);
+}
+
+void
+dns_view_setviewcommit(dns_view_t *view) {
+ dns_zone_t *redirect = NULL, *managed_keys = NULL;
+
+ REQUIRE(DNS_VIEW_VALID(view));
+
+ LOCK(&view->lock);
+
+ if (view->redirect != NULL) {
+ dns_zone_attach(view->redirect, &redirect);
+ }
+ if (view->managed_keys != NULL) {
+ dns_zone_attach(view->managed_keys, &managed_keys);
+ }
+
+ UNLOCK(&view->lock);
+
+ if (view->zonetable != NULL) {
+ dns_zt_setviewcommit(view->zonetable);
+ }
+ if (redirect != NULL) {
+ dns_zone_setviewcommit(redirect);
+ dns_zone_detach(&redirect);
+ }
+ if (managed_keys != NULL) {
+ dns_zone_setviewcommit(managed_keys);
+ dns_zone_detach(&managed_keys);
+ }
+}
+
+void
+dns_view_setviewrevert(dns_view_t *view) {
+ dns_zone_t *redirect = NULL, *managed_keys = NULL;
+ dns_zt_t *zonetable;
+
+ REQUIRE(DNS_VIEW_VALID(view));
+
+ /*
+ * dns_zt_setviewrevert() attempts to lock this view, so we must
+ * release the lock.
+ */
+ LOCK(&view->lock);
+ if (view->redirect != NULL) {
+ dns_zone_attach(view->redirect, &redirect);
+ }
+ if (view->managed_keys != NULL) {
+ dns_zone_attach(view->managed_keys, &managed_keys);
+ }
+ zonetable = view->zonetable;
+ UNLOCK(&view->lock);
+
+ if (redirect != NULL) {
+ dns_zone_setviewrevert(redirect);
+ dns_zone_detach(&redirect);
+ }
+ if (managed_keys != NULL) {
+ dns_zone_setviewrevert(managed_keys);
+ dns_zone_detach(&managed_keys);
+ }
+ if (zonetable != NULL) {
+ dns_zt_setviewrevert(zonetable);
+ }
+}
+
+bool
+dns_view_staleanswerenabled(dns_view_t *view) {
+ uint32_t stale_ttl = 0;
+ bool result = false;
+
+ REQUIRE(DNS_VIEW_VALID(view));
+
+ if (dns_db_getservestalettl(view->cachedb, &stale_ttl) != ISC_R_SUCCESS)
+ {
+ return (false);
+ }
+ if (stale_ttl > 0) {
+ if (view->staleanswersok == dns_stale_answer_yes) {
+ result = true;
+ } else if (view->staleanswersok == dns_stale_answer_conf) {
+ result = view->staleanswersenable;
+ }
+ }
+
+ return (result);
+}
+
+static void
+free_sfd(void *data, void *arg) {
+ isc_mem_put(arg, data, sizeof(unsigned int));
+}
+
+void
+dns_view_sfd_add(dns_view_t *view, const dns_name_t *name) {
+ isc_result_t result;
+ dns_rbtnode_t *node = NULL;
+
+ REQUIRE(DNS_VIEW_VALID(view));
+
+ RWLOCK(&view->sfd_lock, isc_rwlocktype_write);
+ if (view->sfd == NULL) {
+ result = dns_rbt_create(view->mctx, free_sfd, view->mctx,
+ &view->sfd);
+ RUNTIME_CHECK(result == ISC_R_SUCCESS);
+ }
+
+ result = dns_rbt_addnode(view->sfd, name, &node);
+ RUNTIME_CHECK(result == ISC_R_SUCCESS || result == ISC_R_EXISTS);
+ if (node->data != NULL) {
+ unsigned int *count = node->data;
+ (*count)++;
+ } else {
+ unsigned int *count = isc_mem_get(view->mctx,
+ sizeof(unsigned int));
+ *count = 1;
+ node->data = count;
+ }
+ RWUNLOCK(&view->sfd_lock, isc_rwlocktype_write);
+}
+
+void
+dns_view_sfd_del(dns_view_t *view, const dns_name_t *name) {
+ isc_result_t result;
+ void *data = NULL;
+
+ REQUIRE(DNS_VIEW_VALID(view));
+
+ RWLOCK(&view->sfd_lock, isc_rwlocktype_write);
+ INSIST(view->sfd != NULL);
+ result = dns_rbt_findname(view->sfd, name, 0, NULL, &data);
+ if (result == ISC_R_SUCCESS) {
+ unsigned int *count = data;
+ INSIST(count != NULL);
+ if (--(*count) == 0U) {
+ result = dns_rbt_deletename(view->sfd, name, false);
+ RUNTIME_CHECK(result == ISC_R_SUCCESS);
+ }
+ }
+ RWUNLOCK(&view->sfd_lock, isc_rwlocktype_write);
+}
+
+void
+dns_view_sfd_find(dns_view_t *view, const dns_name_t *name,
+ dns_name_t *foundname) {
+ REQUIRE(DNS_VIEW_VALID(view));
+
+ if (view->sfd != NULL) {
+ isc_result_t result;
+ void *data = NULL;
+
+ RWLOCK(&view->sfd_lock, isc_rwlocktype_read);
+ result = dns_rbt_findname(view->sfd, name, 0, foundname, &data);
+ RWUNLOCK(&view->sfd_lock, isc_rwlocktype_read);
+ if (result != ISC_R_SUCCESS && result != DNS_R_PARTIALMATCH) {
+ dns_name_copy(dns_rootname, foundname);
+ }
+ } else {
+ dns_name_copy(dns_rootname, foundname);
+ }
+}
diff --git a/lib/dns/xfrin.c b/lib/dns/xfrin.c
new file mode 100644
index 0000000..393b557
--- /dev/null
+++ b/lib/dns/xfrin.c
@@ -0,0 +1,2034 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/*! \file */
+
+#include <inttypes.h>
+#include <stdbool.h>
+
+#include <isc/mem.h>
+#include <isc/netmgr.h>
+#include <isc/print.h>
+#include <isc/random.h>
+#include <isc/result.h>
+#include <isc/string.h> /* Required for HP/UX (and others?) */
+#include <isc/task.h>
+#include <isc/timer.h>
+#include <isc/util.h>
+
+#include <dns/callbacks.h>
+#include <dns/catz.h>
+#include <dns/db.h>
+#include <dns/diff.h>
+#include <dns/events.h>
+#include <dns/journal.h>
+#include <dns/log.h>
+#include <dns/message.h>
+#include <dns/rdataclass.h>
+#include <dns/rdatalist.h>
+#include <dns/rdataset.h>
+#include <dns/result.h>
+#include <dns/soa.h>
+#include <dns/transport.h>
+#include <dns/tsig.h>
+#include <dns/view.h>
+#include <dns/xfrin.h>
+#include <dns/zone.h>
+
+#include <dst/dst.h>
+
+/*
+ * Incoming AXFR and IXFR.
+ */
+
+/*%
+ * It would be non-sensical (or at least obtuse) to use FAIL() with an
+ * ISC_R_SUCCESS code, but the test is there to keep the Solaris compiler
+ * from complaining about "end-of-loop code not reached".
+ */
+#define FAIL(code) \
+ do { \
+ result = (code); \
+ if (result != ISC_R_SUCCESS) \
+ goto failure; \
+ } while (0)
+
+#define CHECK(op) \
+ do { \
+ result = (op); \
+ if (result != ISC_R_SUCCESS) \
+ goto failure; \
+ } while (0)
+
+/*%
+ * The states of the *XFR state machine. We handle both IXFR and AXFR
+ * with a single integrated state machine because they cannot be distinguished
+ * immediately - an AXFR response to an IXFR request can only be detected
+ * when the first two (2) response RRs have already been received.
+ */
+typedef enum {
+ XFRST_SOAQUERY,
+ XFRST_GOTSOA,
+ XFRST_INITIALSOA,
+ XFRST_FIRSTDATA,
+ XFRST_IXFR_DELSOA,
+ XFRST_IXFR_DEL,
+ XFRST_IXFR_ADDSOA,
+ XFRST_IXFR_ADD,
+ XFRST_IXFR_END,
+ XFRST_AXFR,
+ XFRST_AXFR_END
+} xfrin_state_t;
+
+/*%
+ * Incoming zone transfer context.
+ */
+
+struct dns_xfrin_ctx {
+ unsigned int magic;
+ isc_mem_t *mctx;
+ dns_zone_t *zone;
+
+ isc_refcount_t references;
+
+ isc_nm_t *netmgr;
+
+ isc_refcount_t connects; /*%< Connect in progress */
+ isc_refcount_t sends; /*%< Send in progress */
+ isc_refcount_t recvs; /*%< Receive in progress */
+
+ atomic_bool shuttingdown;
+
+ isc_result_t shutdown_result;
+
+ dns_name_t name; /*%< Name of zone to transfer */
+ dns_rdataclass_t rdclass;
+
+ dns_messageid_t id;
+
+ /*%
+ * Requested transfer type (dns_rdatatype_axfr or
+ * dns_rdatatype_ixfr). The actual transfer type
+ * may differ due to IXFR->AXFR fallback.
+ */
+ dns_rdatatype_t reqtype;
+
+ isc_sockaddr_t primaryaddr;
+ isc_sockaddr_t sourceaddr;
+
+ isc_nmhandle_t *handle;
+ isc_nmhandle_t *readhandle;
+ isc_nmhandle_t *sendhandle;
+
+ /*% Buffer for IXFR/AXFR request message */
+ isc_buffer_t qbuffer;
+ unsigned char qbuffer_data[512];
+
+ /*%
+ * Whether the zone originally had a database attached at the time this
+ * transfer context was created. Used by xfrin_destroy() when making
+ * logging decisions.
+ */
+ bool zone_had_db;
+
+ dns_db_t *db;
+ dns_dbversion_t *ver;
+ dns_diff_t diff; /*%< Pending database changes */
+ int difflen; /*%< Number of pending tuples */
+
+ xfrin_state_t state;
+ uint32_t end_serial;
+ bool is_ixfr;
+
+ unsigned int nmsg; /*%< Number of messages recvd */
+ unsigned int nrecs; /*%< Number of records recvd */
+ uint64_t nbytes; /*%< Number of bytes received */
+
+ unsigned int maxrecords; /*%< The maximum number of
+ * records set for the zone */
+
+ isc_time_t start; /*%< Start time of the transfer */
+ isc_time_t end; /*%< End time of the transfer */
+
+ dns_tsigkey_t *tsigkey; /*%< Key used to create TSIG */
+ isc_buffer_t *lasttsig; /*%< The last TSIG */
+ dst_context_t *tsigctx; /*%< TSIG verification context */
+ unsigned int sincetsig; /*%< recvd since the last TSIG */
+
+ dns_transport_t *transport;
+
+ dns_xfrindone_t done;
+
+ /*%
+ * AXFR- and IXFR-specific data. Only one is used at a time
+ * according to the is_ixfr flag, so this could be a union,
+ * but keeping them separate makes it a bit simpler to clean
+ * things up when destroying the context.
+ */
+ dns_rdatacallbacks_t axfr;
+
+ struct {
+ uint32_t request_serial;
+ uint32_t current_serial;
+ dns_journal_t *journal;
+ } ixfr;
+
+ dns_rdata_t firstsoa;
+ unsigned char *firstsoa_data;
+
+ isc_tlsctx_cache_t *tlsctx_cache;
+
+ isc_timer_t *max_time_timer;
+ isc_timer_t *max_idle_timer;
+};
+
+#define XFRIN_MAGIC ISC_MAGIC('X', 'f', 'r', 'I')
+#define VALID_XFRIN(x) ISC_MAGIC_VALID(x, XFRIN_MAGIC)
+
+/**************************************************************************/
+/*
+ * Forward declarations.
+ */
+
+static void
+xfrin_create(isc_mem_t *mctx, dns_zone_t *zone, dns_db_t *db, isc_nm_t *netmgr,
+ dns_name_t *zonename, dns_rdataclass_t rdclass,
+ dns_rdatatype_t reqtype, const isc_sockaddr_t *primaryaddr,
+ const isc_sockaddr_t *sourceaddr, dns_tsigkey_t *tsigkey,
+ dns_transport_t *transport, isc_tlsctx_cache_t *tlsctx_cache,
+ dns_xfrin_ctx_t **xfrp);
+
+static isc_result_t
+axfr_init(dns_xfrin_ctx_t *xfr);
+static isc_result_t
+axfr_makedb(dns_xfrin_ctx_t *xfr, dns_db_t **dbp);
+static isc_result_t
+axfr_putdata(dns_xfrin_ctx_t *xfr, dns_diffop_t op, dns_name_t *name,
+ dns_ttl_t ttl, dns_rdata_t *rdata);
+static isc_result_t
+axfr_apply(dns_xfrin_ctx_t *xfr);
+static isc_result_t
+axfr_commit(dns_xfrin_ctx_t *xfr);
+static isc_result_t
+axfr_finalize(dns_xfrin_ctx_t *xfr);
+
+static isc_result_t
+ixfr_init(dns_xfrin_ctx_t *xfr);
+static isc_result_t
+ixfr_apply(dns_xfrin_ctx_t *xfr);
+static isc_result_t
+ixfr_putdata(dns_xfrin_ctx_t *xfr, dns_diffop_t op, dns_name_t *name,
+ dns_ttl_t ttl, dns_rdata_t *rdata);
+static isc_result_t
+ixfr_commit(dns_xfrin_ctx_t *xfr);
+
+static isc_result_t
+xfr_rr(dns_xfrin_ctx_t *xfr, dns_name_t *name, uint32_t ttl,
+ dns_rdata_t *rdata);
+
+static isc_result_t
+xfrin_start(dns_xfrin_ctx_t *xfr);
+
+static void
+xfrin_connect_done(isc_nmhandle_t *handle, isc_result_t result, void *cbarg);
+static isc_result_t
+xfrin_send_request(dns_xfrin_ctx_t *xfr);
+static void
+xfrin_send_done(isc_nmhandle_t *handle, isc_result_t result, void *cbarg);
+static void
+xfrin_recv_done(isc_nmhandle_t *handle, isc_result_t result,
+ isc_region_t *region, void *cbarg);
+
+static void
+xfrin_destroy(dns_xfrin_ctx_t *xfr);
+
+static void
+xfrin_timedout(struct isc_task *, struct isc_event *);
+static void
+xfrin_idledout(struct isc_task *, struct isc_event *);
+static void
+xfrin_fail(dns_xfrin_ctx_t *xfr, isc_result_t result, const char *msg);
+static isc_result_t
+render(dns_message_t *msg, isc_mem_t *mctx, isc_buffer_t *buf);
+
+static void
+xfrin_logv(int level, const char *zonetext, const isc_sockaddr_t *primaryaddr,
+ const char *fmt, va_list ap) ISC_FORMAT_PRINTF(4, 0);
+
+static void
+xfrin_log1(int level, const char *zonetext, const isc_sockaddr_t *primaryaddr,
+ const char *fmt, ...) ISC_FORMAT_PRINTF(4, 5);
+
+static void
+xfrin_log(dns_xfrin_ctx_t *xfr, int level, const char *fmt, ...)
+ ISC_FORMAT_PRINTF(3, 4);
+
+/**************************************************************************/
+/*
+ * AXFR handling
+ */
+
+static isc_result_t
+axfr_init(dns_xfrin_ctx_t *xfr) {
+ isc_result_t result;
+
+ xfr->is_ixfr = false;
+
+ if (xfr->db != NULL) {
+ dns_db_detach(&xfr->db);
+ }
+
+ CHECK(axfr_makedb(xfr, &xfr->db));
+ dns_rdatacallbacks_init(&xfr->axfr);
+ CHECK(dns_db_beginload(xfr->db, &xfr->axfr));
+ result = ISC_R_SUCCESS;
+failure:
+ return (result);
+}
+
+static isc_result_t
+axfr_makedb(dns_xfrin_ctx_t *xfr, dns_db_t **dbp) {
+ isc_result_t result;
+
+ result = dns_db_create(xfr->mctx, /* XXX */
+ "rbt", /* XXX guess */
+ &xfr->name, dns_dbtype_zone, xfr->rdclass, 0,
+ NULL, /* XXX guess */
+ dbp);
+ if (result == ISC_R_SUCCESS) {
+ dns_zone_rpz_enable_db(xfr->zone, *dbp);
+ dns_zone_catz_enable_db(xfr->zone, *dbp);
+ }
+ return (result);
+}
+
+static isc_result_t
+axfr_putdata(dns_xfrin_ctx_t *xfr, dns_diffop_t op, dns_name_t *name,
+ dns_ttl_t ttl, dns_rdata_t *rdata) {
+ isc_result_t result;
+
+ dns_difftuple_t *tuple = NULL;
+
+ if (rdata->rdclass != xfr->rdclass) {
+ return (DNS_R_BADCLASS);
+ }
+
+ CHECK(dns_zone_checknames(xfr->zone, name, rdata));
+ CHECK(dns_difftuple_create(xfr->diff.mctx, op, name, ttl, rdata,
+ &tuple));
+ dns_diff_append(&xfr->diff, &tuple);
+ if (++xfr->difflen > 100) {
+ CHECK(axfr_apply(xfr));
+ }
+ result = ISC_R_SUCCESS;
+failure:
+ return (result);
+}
+
+/*
+ * Store a set of AXFR RRs in the database.
+ */
+static isc_result_t
+axfr_apply(dns_xfrin_ctx_t *xfr) {
+ isc_result_t result;
+ uint64_t records;
+
+ CHECK(dns_diff_load(&xfr->diff, xfr->axfr.add, xfr->axfr.add_private));
+ xfr->difflen = 0;
+ dns_diff_clear(&xfr->diff);
+ if (xfr->maxrecords != 0U) {
+ result = dns_db_getsize(xfr->db, xfr->ver, &records, NULL);
+ if (result == ISC_R_SUCCESS && records > xfr->maxrecords) {
+ result = DNS_R_TOOMANYRECORDS;
+ goto failure;
+ }
+ }
+ result = ISC_R_SUCCESS;
+failure:
+ return (result);
+}
+
+static isc_result_t
+axfr_commit(dns_xfrin_ctx_t *xfr) {
+ isc_result_t result;
+
+ CHECK(axfr_apply(xfr));
+ CHECK(dns_db_endload(xfr->db, &xfr->axfr));
+ CHECK(dns_zone_verifydb(xfr->zone, xfr->db, NULL));
+
+ result = ISC_R_SUCCESS;
+failure:
+ return (result);
+}
+
+static isc_result_t
+axfr_finalize(dns_xfrin_ctx_t *xfr) {
+ isc_result_t result;
+
+ CHECK(dns_zone_replacedb(xfr->zone, xfr->db, true));
+
+ result = ISC_R_SUCCESS;
+failure:
+ return (result);
+}
+
+/**************************************************************************/
+/*
+ * IXFR handling
+ */
+
+static isc_result_t
+ixfr_init(dns_xfrin_ctx_t *xfr) {
+ isc_result_t result;
+ char *journalfile = NULL;
+
+ if (xfr->reqtype != dns_rdatatype_ixfr) {
+ xfrin_log(xfr, ISC_LOG_NOTICE,
+ "got incremental response to AXFR request");
+ return (DNS_R_FORMERR);
+ }
+
+ xfr->is_ixfr = true;
+ INSIST(xfr->db != NULL);
+ xfr->difflen = 0;
+
+ journalfile = dns_zone_getjournal(xfr->zone);
+ if (journalfile != NULL) {
+ CHECK(dns_journal_open(xfr->mctx, journalfile,
+ DNS_JOURNAL_CREATE, &xfr->ixfr.journal));
+ }
+
+ result = ISC_R_SUCCESS;
+failure:
+ return (result);
+}
+
+static isc_result_t
+ixfr_putdata(dns_xfrin_ctx_t *xfr, dns_diffop_t op, dns_name_t *name,
+ dns_ttl_t ttl, dns_rdata_t *rdata) {
+ isc_result_t result;
+ dns_difftuple_t *tuple = NULL;
+
+ if (rdata->rdclass != xfr->rdclass) {
+ return (DNS_R_BADCLASS);
+ }
+
+ if (op == DNS_DIFFOP_ADD) {
+ CHECK(dns_zone_checknames(xfr->zone, name, rdata));
+ }
+ CHECK(dns_difftuple_create(xfr->diff.mctx, op, name, ttl, rdata,
+ &tuple));
+ dns_diff_append(&xfr->diff, &tuple);
+ if (++xfr->difflen > 100) {
+ CHECK(ixfr_apply(xfr));
+ }
+ result = ISC_R_SUCCESS;
+failure:
+ return (result);
+}
+
+/*
+ * Apply a set of IXFR changes to the database.
+ */
+static isc_result_t
+ixfr_apply(dns_xfrin_ctx_t *xfr) {
+ isc_result_t result;
+ uint64_t records;
+
+ if (xfr->ver == NULL) {
+ CHECK(dns_db_newversion(xfr->db, &xfr->ver));
+ if (xfr->ixfr.journal != NULL) {
+ CHECK(dns_journal_begin_transaction(xfr->ixfr.journal));
+ }
+ }
+ CHECK(dns_diff_apply(&xfr->diff, xfr->db, xfr->ver));
+ if (xfr->maxrecords != 0U) {
+ result = dns_db_getsize(xfr->db, xfr->ver, &records, NULL);
+ if (result == ISC_R_SUCCESS && records > xfr->maxrecords) {
+ result = DNS_R_TOOMANYRECORDS;
+ goto failure;
+ }
+ }
+ if (xfr->ixfr.journal != NULL) {
+ result = dns_journal_writediff(xfr->ixfr.journal, &xfr->diff);
+ if (result != ISC_R_SUCCESS) {
+ goto failure;
+ }
+ }
+ dns_diff_clear(&xfr->diff);
+ xfr->difflen = 0;
+ result = ISC_R_SUCCESS;
+failure:
+ return (result);
+}
+
+static isc_result_t
+ixfr_commit(dns_xfrin_ctx_t *xfr) {
+ isc_result_t result;
+
+ CHECK(ixfr_apply(xfr));
+ if (xfr->ver != NULL) {
+ CHECK(dns_zone_verifydb(xfr->zone, xfr->db, xfr->ver));
+ /* XXX enter ready-to-commit state here */
+ if (xfr->ixfr.journal != NULL) {
+ CHECK(dns_journal_commit(xfr->ixfr.journal));
+ }
+ dns_db_closeversion(xfr->db, &xfr->ver, true);
+ dns_zone_markdirty(xfr->zone);
+ }
+ result = ISC_R_SUCCESS;
+failure:
+ return (result);
+}
+
+/**************************************************************************/
+/*
+ * Common AXFR/IXFR protocol code
+ */
+
+/*
+ * Handle a single incoming resource record according to the current
+ * state.
+ */
+static isc_result_t
+xfr_rr(dns_xfrin_ctx_t *xfr, dns_name_t *name, uint32_t ttl,
+ dns_rdata_t *rdata) {
+ isc_result_t result;
+
+ xfr->nrecs++;
+
+ if (rdata->type == dns_rdatatype_none ||
+ dns_rdatatype_ismeta(rdata->type))
+ {
+ char buf[64];
+ dns_rdatatype_format(rdata->type, buf, sizeof(buf));
+ xfrin_log(xfr, ISC_LOG_NOTICE,
+ "Unexpected %s record in zone transfer", buf);
+ FAIL(DNS_R_FORMERR);
+ }
+
+ /*
+ * Immediately reject the entire transfer if the RR that is currently
+ * being processed is an SOA record that is not placed at the zone
+ * apex.
+ */
+ if (rdata->type == dns_rdatatype_soa &&
+ !dns_name_equal(&xfr->name, name))
+ {
+ char namebuf[DNS_NAME_FORMATSIZE];
+ dns_name_format(name, namebuf, sizeof(namebuf));
+ xfrin_log(xfr, ISC_LOG_DEBUG(3), "SOA name mismatch: '%s'",
+ namebuf);
+ FAIL(DNS_R_NOTZONETOP);
+ }
+
+redo:
+ switch (xfr->state) {
+ case XFRST_SOAQUERY:
+ if (rdata->type != dns_rdatatype_soa) {
+ xfrin_log(xfr, ISC_LOG_NOTICE,
+ "non-SOA response to SOA query");
+ FAIL(DNS_R_FORMERR);
+ }
+ xfr->end_serial = dns_soa_getserial(rdata);
+ if (!DNS_SERIAL_GT(xfr->end_serial, xfr->ixfr.request_serial) &&
+ !dns_zone_isforced(xfr->zone))
+ {
+ xfrin_log(xfr, ISC_LOG_DEBUG(3),
+ "requested serial %u, "
+ "primary has %u, not updating",
+ xfr->ixfr.request_serial, xfr->end_serial);
+ FAIL(DNS_R_UPTODATE);
+ }
+ xfr->state = XFRST_GOTSOA;
+ break;
+
+ case XFRST_GOTSOA:
+ /*
+ * Skip other records in the answer section.
+ */
+ break;
+
+ case XFRST_INITIALSOA:
+ if (rdata->type != dns_rdatatype_soa) {
+ xfrin_log(xfr, ISC_LOG_NOTICE,
+ "first RR in zone transfer must be SOA");
+ FAIL(DNS_R_FORMERR);
+ }
+ /*
+ * Remember the serial number in the initial SOA.
+ * We need it to recognize the end of an IXFR.
+ */
+ xfr->end_serial = dns_soa_getserial(rdata);
+ if (xfr->reqtype == dns_rdatatype_ixfr &&
+ !DNS_SERIAL_GT(xfr->end_serial, xfr->ixfr.request_serial) &&
+ !dns_zone_isforced(xfr->zone))
+ {
+ /*
+ * This must be the single SOA record that is
+ * sent when the current version on the primary
+ * is not newer than the version in the request.
+ */
+ xfrin_log(xfr, ISC_LOG_DEBUG(3),
+ "requested serial %u, "
+ "primary has %u, not updating",
+ xfr->ixfr.request_serial, xfr->end_serial);
+ FAIL(DNS_R_UPTODATE);
+ }
+ xfr->firstsoa = *rdata;
+ if (xfr->firstsoa_data != NULL) {
+ isc_mem_free(xfr->mctx, xfr->firstsoa_data);
+ }
+ xfr->firstsoa_data = isc_mem_allocate(xfr->mctx, rdata->length);
+ memcpy(xfr->firstsoa_data, rdata->data, rdata->length);
+ xfr->firstsoa.data = xfr->firstsoa_data;
+ xfr->state = XFRST_FIRSTDATA;
+ break;
+
+ case XFRST_FIRSTDATA:
+ /*
+ * If the transfer begins with one SOA record, it is an AXFR,
+ * if it begins with two SOAs, it is an IXFR.
+ */
+ if (xfr->reqtype == dns_rdatatype_ixfr &&
+ rdata->type == dns_rdatatype_soa &&
+ xfr->ixfr.request_serial == dns_soa_getserial(rdata))
+ {
+ xfrin_log(xfr, ISC_LOG_DEBUG(3),
+ "got incremental response");
+ CHECK(ixfr_init(xfr));
+ xfr->state = XFRST_IXFR_DELSOA;
+ } else {
+ xfrin_log(xfr, ISC_LOG_DEBUG(3),
+ "got nonincremental response");
+ CHECK(axfr_init(xfr));
+ xfr->state = XFRST_AXFR;
+ }
+ goto redo;
+
+ case XFRST_IXFR_DELSOA:
+ INSIST(rdata->type == dns_rdatatype_soa);
+ CHECK(ixfr_putdata(xfr, DNS_DIFFOP_DEL, name, ttl, rdata));
+ xfr->state = XFRST_IXFR_DEL;
+ break;
+
+ case XFRST_IXFR_DEL:
+ if (rdata->type == dns_rdatatype_soa) {
+ uint32_t soa_serial = dns_soa_getserial(rdata);
+ xfr->state = XFRST_IXFR_ADDSOA;
+ xfr->ixfr.current_serial = soa_serial;
+ goto redo;
+ }
+ CHECK(ixfr_putdata(xfr, DNS_DIFFOP_DEL, name, ttl, rdata));
+ break;
+
+ case XFRST_IXFR_ADDSOA:
+ INSIST(rdata->type == dns_rdatatype_soa);
+ CHECK(ixfr_putdata(xfr, DNS_DIFFOP_ADD, name, ttl, rdata));
+ xfr->state = XFRST_IXFR_ADD;
+ break;
+
+ case XFRST_IXFR_ADD:
+ if (rdata->type == dns_rdatatype_soa) {
+ uint32_t soa_serial = dns_soa_getserial(rdata);
+ if (soa_serial == xfr->end_serial) {
+ CHECK(ixfr_commit(xfr));
+ xfr->state = XFRST_IXFR_END;
+ break;
+ } else if (soa_serial != xfr->ixfr.current_serial) {
+ xfrin_log(xfr, ISC_LOG_NOTICE,
+ "IXFR out of sync: "
+ "expected serial %u, got %u",
+ xfr->ixfr.current_serial, soa_serial);
+ FAIL(DNS_R_FORMERR);
+ } else {
+ CHECK(ixfr_commit(xfr));
+ xfr->state = XFRST_IXFR_DELSOA;
+ goto redo;
+ }
+ }
+ if (rdata->type == dns_rdatatype_ns &&
+ dns_name_iswildcard(name))
+ {
+ FAIL(DNS_R_INVALIDNS);
+ }
+ CHECK(ixfr_putdata(xfr, DNS_DIFFOP_ADD, name, ttl, rdata));
+ break;
+
+ case XFRST_AXFR:
+ /*
+ * Old BINDs sent cross class A records for non IN classes.
+ */
+ if (rdata->type == dns_rdatatype_a &&
+ rdata->rdclass != xfr->rdclass &&
+ xfr->rdclass != dns_rdataclass_in)
+ {
+ break;
+ }
+ CHECK(axfr_putdata(xfr, DNS_DIFFOP_ADD, name, ttl, rdata));
+ if (rdata->type == dns_rdatatype_soa) {
+ /*
+ * Use dns_rdata_compare instead of memcmp to
+ * allow for case differences.
+ */
+ if (dns_rdata_compare(rdata, &xfr->firstsoa) != 0) {
+ xfrin_log(xfr, ISC_LOG_NOTICE,
+ "start and ending SOA records "
+ "mismatch");
+ FAIL(DNS_R_FORMERR);
+ }
+ CHECK(axfr_commit(xfr));
+ xfr->state = XFRST_AXFR_END;
+ break;
+ }
+ break;
+ case XFRST_AXFR_END:
+ case XFRST_IXFR_END:
+ FAIL(DNS_R_EXTRADATA);
+ FALLTHROUGH;
+ default:
+ UNREACHABLE();
+ }
+ result = ISC_R_SUCCESS;
+failure:
+ return (result);
+}
+
+isc_result_t
+dns_xfrin_create(dns_zone_t *zone, dns_rdatatype_t xfrtype,
+ const isc_sockaddr_t *primaryaddr,
+ const isc_sockaddr_t *sourceaddr, dns_tsigkey_t *tsigkey,
+ dns_transport_t *transport, isc_tlsctx_cache_t *tlsctx_cache,
+ isc_mem_t *mctx, isc_nm_t *netmgr, dns_xfrindone_t done,
+ dns_xfrin_ctx_t **xfrp) {
+ dns_name_t *zonename = dns_zone_getorigin(zone);
+ dns_xfrin_ctx_t *xfr = NULL;
+ isc_result_t result;
+ dns_db_t *db = NULL;
+
+ REQUIRE(xfrp != NULL && *xfrp == NULL);
+ REQUIRE(done != NULL);
+ REQUIRE(isc_sockaddr_getport(primaryaddr) != 0);
+
+ (void)dns_zone_getdb(zone, &db);
+
+ if (xfrtype == dns_rdatatype_soa || xfrtype == dns_rdatatype_ixfr) {
+ REQUIRE(db != NULL);
+ }
+
+ xfrin_create(mctx, zone, db, netmgr, zonename, dns_zone_getclass(zone),
+ xfrtype, primaryaddr, sourceaddr, tsigkey, transport,
+ tlsctx_cache, &xfr);
+
+ if (db != NULL) {
+ xfr->zone_had_db = true;
+ }
+
+ xfr->done = done;
+
+ isc_refcount_init(&xfr->references, 1);
+
+ /*
+ * Set *xfrp now, before calling xfrin_start(). Asynchronous
+ * netmgr processing could cause the 'done' callback to run in
+ * another thread before we reached the end of the present
+ * function. In that case, if *xfrp hadn't already been
+ * attached, the 'done' function would be unable to detach it.
+ */
+ *xfrp = xfr;
+
+ result = xfrin_start(xfr);
+ if (result != ISC_R_SUCCESS) {
+ atomic_store(&xfr->shuttingdown, true);
+ xfr->shutdown_result = result;
+ dns_xfrin_detach(xfrp);
+ }
+
+ if (db != NULL) {
+ dns_db_detach(&db);
+ }
+
+ if (result != ISC_R_SUCCESS) {
+ char zonetext[DNS_NAME_MAXTEXT + 32];
+ dns_zone_name(zone, zonetext, sizeof(zonetext));
+ xfrin_log1(ISC_LOG_ERROR, zonetext, primaryaddr,
+ "zone transfer setup failed");
+ }
+
+ return (result);
+}
+
+static void
+xfrin_cancelio(dns_xfrin_ctx_t *xfr);
+
+static void
+xfrin_timedout(struct isc_task *task, struct isc_event *event) {
+ UNUSED(task);
+
+ dns_xfrin_ctx_t *xfr = event->ev_arg;
+ REQUIRE(VALID_XFRIN(xfr));
+
+ xfrin_fail(xfr, ISC_R_TIMEDOUT, "maximum transfer time exceeded");
+ isc_event_free(&event);
+}
+
+static void
+xfrin_idledout(struct isc_task *task, struct isc_event *event) {
+ UNUSED(task);
+
+ dns_xfrin_ctx_t *xfr = event->ev_arg;
+ REQUIRE(VALID_XFRIN(xfr));
+
+ xfrin_fail(xfr, ISC_R_TIMEDOUT, "maximum idle time exceeded");
+ isc_event_free(&event);
+}
+
+void
+dns_xfrin_shutdown(dns_xfrin_ctx_t *xfr) {
+ REQUIRE(VALID_XFRIN(xfr));
+
+ xfrin_fail(xfr, ISC_R_CANCELED, "shut down");
+}
+
+void
+dns_xfrin_attach(dns_xfrin_ctx_t *source, dns_xfrin_ctx_t **target) {
+ REQUIRE(VALID_XFRIN(source));
+ REQUIRE(target != NULL && *target == NULL);
+ (void)isc_refcount_increment(&source->references);
+
+ *target = source;
+}
+
+void
+dns_xfrin_detach(dns_xfrin_ctx_t **xfrp) {
+ dns_xfrin_ctx_t *xfr = NULL;
+
+ REQUIRE(xfrp != NULL && VALID_XFRIN(*xfrp));
+
+ xfr = *xfrp;
+ *xfrp = NULL;
+
+ if (isc_refcount_decrement(&xfr->references) == 1) {
+ xfrin_destroy(xfr);
+ }
+}
+
+static void
+xfrin_cancelio(dns_xfrin_ctx_t *xfr) {
+ if (xfr->readhandle == NULL) {
+ return;
+ }
+
+ isc_nm_cancelread(xfr->readhandle);
+ /* The xfr->readhandle detach will happen in xfrin_recv_done callback */
+}
+
+static void
+xfrin_reset(dns_xfrin_ctx_t *xfr) {
+ REQUIRE(VALID_XFRIN(xfr));
+
+ xfrin_log(xfr, ISC_LOG_INFO, "resetting");
+
+ REQUIRE(xfr->readhandle == NULL);
+ REQUIRE(xfr->sendhandle == NULL);
+
+ if (xfr->lasttsig != NULL) {
+ isc_buffer_free(&xfr->lasttsig);
+ }
+
+ dns_diff_clear(&xfr->diff);
+ xfr->difflen = 0;
+
+ if (xfr->ixfr.journal != NULL) {
+ dns_journal_destroy(&xfr->ixfr.journal);
+ }
+
+ if (xfr->axfr.add_private != NULL) {
+ (void)dns_db_endload(xfr->db, &xfr->axfr);
+ }
+
+ if (xfr->ver != NULL) {
+ dns_db_closeversion(xfr->db, &xfr->ver, false);
+ }
+}
+
+static void
+xfrin_fail(dns_xfrin_ctx_t *xfr, isc_result_t result, const char *msg) {
+ /* Make sure only the first xfrin_fail() trumps */
+ if (atomic_compare_exchange_strong(&xfr->shuttingdown, &(bool){ false },
+ true))
+ {
+ (void)isc_timer_reset(xfr->max_time_timer,
+ isc_timertype_inactive, NULL, NULL, true);
+ (void)isc_timer_reset(xfr->max_idle_timer,
+ isc_timertype_inactive, NULL, NULL, true);
+
+ if (result != DNS_R_UPTODATE && result != DNS_R_TOOMANYRECORDS)
+ {
+ xfrin_log(xfr, ISC_LOG_ERROR, "%s: %s", msg,
+ isc_result_totext(result));
+ if (xfr->is_ixfr) {
+ /* Pass special result code to force AXFR retry
+ */
+ result = DNS_R_BADIXFR;
+ }
+ }
+ xfrin_cancelio(xfr);
+ /*
+ * Close the journal.
+ */
+ if (xfr->ixfr.journal != NULL) {
+ dns_journal_destroy(&xfr->ixfr.journal);
+ }
+ if (xfr->done != NULL) {
+ (xfr->done)(xfr->zone, result);
+ xfr->done = NULL;
+ }
+ xfr->shutdown_result = result;
+ }
+}
+
+static void
+xfrin_create(isc_mem_t *mctx, dns_zone_t *zone, dns_db_t *db, isc_nm_t *netmgr,
+ dns_name_t *zonename, dns_rdataclass_t rdclass,
+ dns_rdatatype_t reqtype, const isc_sockaddr_t *primaryaddr,
+ const isc_sockaddr_t *sourceaddr, dns_tsigkey_t *tsigkey,
+ dns_transport_t *transport, isc_tlsctx_cache_t *tlsctx_cache,
+ dns_xfrin_ctx_t **xfrp) {
+ dns_xfrin_ctx_t *xfr = NULL;
+ dns_zonemgr_t *zmgr = dns_zone_getmgr(zone);
+ isc_timermgr_t *timermgr = dns_zonemgr_gettimermgr(zmgr);
+ isc_task_t *ztask = NULL;
+
+ xfr = isc_mem_get(mctx, sizeof(*xfr));
+ *xfr = (dns_xfrin_ctx_t){ .netmgr = netmgr,
+ .shutdown_result = ISC_R_UNSET,
+ .rdclass = rdclass,
+ .reqtype = reqtype,
+ .id = (dns_messageid_t)isc_random16(),
+ .maxrecords = dns_zone_getmaxrecords(zone),
+ .primaryaddr = *primaryaddr,
+ .sourceaddr = *sourceaddr,
+ .firstsoa = DNS_RDATA_INIT,
+ .magic = XFRIN_MAGIC };
+
+ isc_mem_attach(mctx, &xfr->mctx);
+ dns_zone_iattach(zone, &xfr->zone);
+ dns_name_init(&xfr->name, NULL);
+
+ isc_refcount_init(&xfr->connects, 0);
+ isc_refcount_init(&xfr->sends, 0);
+ isc_refcount_init(&xfr->recvs, 0);
+
+ atomic_init(&xfr->shuttingdown, false);
+
+ if (db != NULL) {
+ dns_db_attach(db, &xfr->db);
+ }
+
+ dns_diff_init(xfr->mctx, &xfr->diff);
+
+ if (reqtype == dns_rdatatype_soa) {
+ xfr->state = XFRST_SOAQUERY;
+ } else {
+ xfr->state = XFRST_INITIALSOA;
+ }
+
+ isc_time_now(&xfr->start);
+
+ if (tsigkey != NULL) {
+ dns_tsigkey_attach(tsigkey, &xfr->tsigkey);
+ }
+
+ if (transport != NULL) {
+ dns_transport_attach(transport, &xfr->transport);
+ }
+
+ dns_name_dup(zonename, mctx, &xfr->name);
+
+ INSIST(isc_sockaddr_pf(primaryaddr) == isc_sockaddr_pf(sourceaddr));
+ isc_sockaddr_setport(&xfr->sourceaddr, 0);
+
+ /*
+ * Reserve 2 bytes for TCP length at the beginning of the buffer.
+ */
+ isc_buffer_init(&xfr->qbuffer, &xfr->qbuffer_data[2],
+ sizeof(xfr->qbuffer_data) - 2);
+
+ isc_tlsctx_cache_attach(tlsctx_cache, &xfr->tlsctx_cache);
+
+ dns_zone_gettask(zone, &ztask);
+ isc_timer_create(timermgr, isc_timertype_inactive, NULL, NULL, ztask,
+ xfrin_timedout, xfr, &xfr->max_time_timer);
+ isc_timer_create(timermgr, isc_timertype_inactive, NULL, NULL, ztask,
+ xfrin_idledout, xfr, &xfr->max_idle_timer);
+ isc_task_detach(&ztask); /* dns_zone_task() attaches to the task */
+
+ *xfrp = xfr;
+}
+
+static isc_result_t
+get_create_tlsctx(const dns_xfrin_ctx_t *xfr, isc_tlsctx_t **pctx,
+ isc_tlsctx_client_session_cache_t **psess_cache) {
+ isc_result_t result = ISC_R_FAILURE;
+ isc_tlsctx_t *tlsctx = NULL, *found = NULL;
+ isc_tls_cert_store_t *store = NULL, *found_store = NULL;
+ isc_tlsctx_client_session_cache_t *sess_cache = NULL,
+ *found_sess_cache = NULL;
+ uint32_t tls_versions;
+ const char *ciphers = NULL;
+ bool prefer_server_ciphers;
+ const uint16_t family = isc_sockaddr_pf(&xfr->primaryaddr) == PF_INET6
+ ? AF_INET6
+ : AF_INET;
+ const char *tlsname = NULL;
+
+ REQUIRE(psess_cache != NULL && *psess_cache == NULL);
+ REQUIRE(pctx != NULL && *pctx == NULL);
+
+ INSIST(xfr->transport != NULL);
+ tlsname = dns_transport_get_tlsname(xfr->transport);
+ INSIST(tlsname != NULL && *tlsname != '\0');
+
+ /*
+ * Let's try to re-use the already created context. This way
+ * we have a chance to resume the TLS session, bypassing the
+ * full TLS handshake procedure, making establishing
+ * subsequent TLS connections for XoT faster.
+ */
+ result = isc_tlsctx_cache_find(xfr->tlsctx_cache, tlsname,
+ isc_tlsctx_cache_tls, family, &found,
+ &found_store, &found_sess_cache);
+ if (result != ISC_R_SUCCESS) {
+ const char *hostname =
+ dns_transport_get_remote_hostname(xfr->transport);
+ const char *ca_file = dns_transport_get_cafile(xfr->transport);
+ const char *cert_file =
+ dns_transport_get_certfile(xfr->transport);
+ const char *key_file =
+ dns_transport_get_keyfile(xfr->transport);
+ char primary_addr_str[INET6_ADDRSTRLEN] = { 0 };
+ isc_netaddr_t primary_netaddr = { 0 };
+ bool hostname_ignore_subject;
+ /*
+ * So, no context exists. Let's create one using the
+ * parameters from the configuration file and try to
+ * store it for further reuse.
+ */
+ result = isc_tlsctx_createclient(&tlsctx);
+ if (result != ISC_R_SUCCESS) {
+ goto failure;
+ }
+ tls_versions = dns_transport_get_tls_versions(xfr->transport);
+ if (tls_versions != 0) {
+ isc_tlsctx_set_protocols(tlsctx, tls_versions);
+ }
+ ciphers = dns_transport_get_ciphers(xfr->transport);
+ if (ciphers != NULL) {
+ isc_tlsctx_set_cipherlist(tlsctx, ciphers);
+ }
+
+ if (dns_transport_get_prefer_server_ciphers(
+ xfr->transport, &prefer_server_ciphers))
+ {
+ isc_tlsctx_prefer_server_ciphers(tlsctx,
+ prefer_server_ciphers);
+ }
+
+ if (hostname != NULL || ca_file != NULL) {
+ /*
+ * The situation when 'found_store != NULL' while 'found
+ * == NULL' might appear as there is one to many
+ * relation between per transport TLS contexts and cert
+ * stores. That is, there could be one store shared
+ * between multiple contexts.
+ */
+ if (found_store == NULL) {
+ /*
+ * 'ca_file' can equal 'NULL' here, in
+ * that case the store with system-wide
+ * CA certificates will be created, just
+ * as planned.
+ */
+ result = isc_tls_cert_store_create(ca_file,
+ &store);
+
+ if (result != ISC_R_SUCCESS) {
+ goto failure;
+ }
+ } else {
+ store = found_store;
+ }
+
+ INSIST(store != NULL);
+ if (hostname == NULL) {
+ /*
+ * If CA bundle file is specified, but
+ * hostname is not, then use the primary
+ * IP address for validation, just like
+ * dig does.
+ */
+ INSIST(ca_file != NULL);
+ isc_netaddr_fromsockaddr(&primary_netaddr,
+ &xfr->primaryaddr);
+ isc_netaddr_format(&primary_netaddr,
+ primary_addr_str,
+ sizeof(primary_addr_str));
+ hostname = primary_addr_str;
+ }
+ /*
+ * According to RFC 8310, Subject field MUST NOT
+ * be inspected when verifying hostname for DoT.
+ * Only SubjectAltName must be checked.
+ */
+ hostname_ignore_subject = true;
+ result = isc_tlsctx_enable_peer_verification(
+ tlsctx, false, store, hostname,
+ hostname_ignore_subject);
+ if (result != ISC_R_SUCCESS) {
+ goto failure;
+ }
+
+ /*
+ * Let's load client certificate and enable
+ * Mutual TLS. We do that only in the case when
+ * Strict TLS is enabled, because Mutual TLS is
+ * an extension of it.
+ */
+ if (cert_file != NULL) {
+ INSIST(key_file != NULL);
+
+ result = isc_tlsctx_load_certificate(
+ tlsctx, key_file, cert_file);
+ if (result != ISC_R_SUCCESS) {
+ goto failure;
+ }
+ }
+ }
+
+ isc_tlsctx_enable_dot_client_alpn(tlsctx);
+
+ isc_tlsctx_client_session_cache_create(
+ xfr->mctx, tlsctx,
+ ISC_TLSCTX_CLIENT_SESSION_CACHE_DEFAULT_SIZE,
+ &sess_cache);
+
+ found_store = NULL;
+ result = isc_tlsctx_cache_add(xfr->tlsctx_cache, tlsname,
+ isc_tlsctx_cache_tls, family,
+ tlsctx, store, sess_cache, &found,
+ &found_store, &found_sess_cache);
+ if (result == ISC_R_EXISTS) {
+ /*
+ * It seems the entry has just been created from within
+ * another thread while we were initialising
+ * ours. Although this is unlikely, it could happen
+ * after startup/re-initialisation. In such a case,
+ * discard the new context and associated data and use
+ * the already established one from now on.
+ *
+ * Such situation will not occur after the
+ * initial 'warm-up', so it is not critical
+ * performance-wise.
+ */
+ INSIST(found != NULL);
+ isc_tlsctx_free(&tlsctx);
+ isc_tls_cert_store_free(&store);
+ isc_tlsctx_client_session_cache_detach(&sess_cache);
+ /* Let's return the data from the cache. */
+ *psess_cache = found_sess_cache;
+ *pctx = found;
+ } else {
+ /*
+ * Adding the fresh values into the cache has been
+ * successful, let's return them
+ */
+ INSIST(result == ISC_R_SUCCESS);
+ *psess_cache = sess_cache;
+ *pctx = tlsctx;
+ }
+ } else {
+ /*
+ * The cache lookup has been successful, let's return the
+ * results.
+ */
+ INSIST(result == ISC_R_SUCCESS);
+ *psess_cache = found_sess_cache;
+ *pctx = found;
+ }
+
+ return (ISC_R_SUCCESS);
+
+failure:
+ if (tlsctx != NULL) {
+ isc_tlsctx_free(&tlsctx);
+ }
+
+ /*
+ * The 'found_store' is being managed by the TLS context
+ * cache. Thus, we should keep it as it is, as it will get
+ * destroyed alongside the cache. As there is one store per
+ * multiple TLS contexts, we need to handle store deletion in a
+ * special way.
+ */
+ if (store != NULL && store != found_store) {
+ isc_tls_cert_store_free(&store);
+ }
+
+ return (result);
+}
+
+static isc_result_t
+xfrin_start(dns_xfrin_ctx_t *xfr) {
+ isc_result_t result;
+ dns_xfrin_ctx_t *connect_xfr = NULL;
+ dns_transport_type_t transport_type = DNS_TRANSPORT_TCP;
+ isc_tlsctx_t *tlsctx = NULL;
+ isc_tlsctx_client_session_cache_t *sess_cache = NULL;
+ isc_interval_t interval;
+ isc_time_t next;
+
+ (void)isc_refcount_increment0(&xfr->connects);
+ dns_xfrin_attach(xfr, &connect_xfr);
+
+ if (xfr->transport != NULL) {
+ transport_type = dns_transport_get_type(xfr->transport);
+ }
+
+ /* Set the maximum timer */
+ isc_interval_set(&interval, dns_zone_getmaxxfrin(xfr->zone), 0);
+ isc_time_nowplusinterval(&next, &interval);
+ result = isc_timer_reset(xfr->max_time_timer, isc_timertype_once, &next,
+ NULL, true);
+ RUNTIME_CHECK(result == ISC_R_SUCCESS);
+
+ /* Set the idle timer */
+ isc_interval_set(&interval, dns_zone_getidlein(xfr->zone), 0);
+ isc_time_nowplusinterval(&next, &interval);
+ result = isc_timer_reset(xfr->max_idle_timer, isc_timertype_once, &next,
+ NULL, true);
+ RUNTIME_CHECK(result == ISC_R_SUCCESS);
+
+ /*
+ * XXX: timeouts are hard-coded to 30 seconds; this needs to be
+ * configurable.
+ */
+ switch (transport_type) {
+ case DNS_TRANSPORT_TCP:
+ isc_nm_tcpdnsconnect(xfr->netmgr, &xfr->sourceaddr,
+ &xfr->primaryaddr, xfrin_connect_done,
+ connect_xfr, 30000, 0);
+ break;
+ case DNS_TRANSPORT_TLS: {
+ result = get_create_tlsctx(xfr, &tlsctx, &sess_cache);
+ if (result != ISC_R_SUCCESS) {
+ goto failure;
+ }
+ INSIST(tlsctx != NULL);
+ isc_nm_tlsdnsconnect(xfr->netmgr, &xfr->sourceaddr,
+ &xfr->primaryaddr, xfrin_connect_done,
+ connect_xfr, 30000, 0, tlsctx, sess_cache);
+ } break;
+ default:
+ UNREACHABLE();
+ }
+
+ return (ISC_R_SUCCESS);
+
+failure:
+ isc_refcount_decrement0(&xfr->connects);
+ dns_xfrin_detach(&connect_xfr);
+ return (result);
+}
+
+/* XXX the resolver could use this, too */
+
+static isc_result_t
+render(dns_message_t *msg, isc_mem_t *mctx, isc_buffer_t *buf) {
+ dns_compress_t cctx;
+ bool cleanup_cctx = false;
+ isc_result_t result;
+
+ CHECK(dns_compress_init(&cctx, -1, mctx));
+ cleanup_cctx = true;
+ CHECK(dns_message_renderbegin(msg, &cctx, buf));
+ CHECK(dns_message_rendersection(msg, DNS_SECTION_QUESTION, 0));
+ CHECK(dns_message_rendersection(msg, DNS_SECTION_ANSWER, 0));
+ CHECK(dns_message_rendersection(msg, DNS_SECTION_AUTHORITY, 0));
+ CHECK(dns_message_rendersection(msg, DNS_SECTION_ADDITIONAL, 0));
+ CHECK(dns_message_renderend(msg));
+ result = ISC_R_SUCCESS;
+failure:
+ if (cleanup_cctx) {
+ dns_compress_invalidate(&cctx);
+ }
+ return (result);
+}
+
+/*
+ * A connection has been established.
+ */
+static void
+xfrin_connect_done(isc_nmhandle_t *handle, isc_result_t result, void *cbarg) {
+ dns_xfrin_ctx_t *xfr = (dns_xfrin_ctx_t *)cbarg;
+ char sourcetext[ISC_SOCKADDR_FORMATSIZE];
+ char signerbuf[DNS_NAME_FORMATSIZE];
+ const char *signer = "", *sep = "";
+ isc_sockaddr_t sockaddr;
+ dns_zonemgr_t *zmgr = NULL;
+
+ REQUIRE(VALID_XFRIN(xfr));
+
+ isc_refcount_decrement0(&xfr->connects);
+
+ if (atomic_load(&xfr->shuttingdown)) {
+ result = ISC_R_SHUTTINGDOWN;
+ }
+
+ if (result != ISC_R_SUCCESS) {
+ xfrin_fail(xfr, result, "failed to connect");
+ goto failure;
+ }
+
+ result = isc_nm_xfr_checkperm(handle);
+ if (result != ISC_R_SUCCESS) {
+ xfrin_fail(xfr, result, "connected but unable to transfer");
+ goto failure;
+ }
+
+ zmgr = dns_zone_getmgr(xfr->zone);
+ if (zmgr != NULL) {
+ dns_zonemgr_unreachabledel(zmgr, &xfr->primaryaddr,
+ &xfr->sourceaddr);
+ }
+
+ xfr->handle = handle;
+ sockaddr = isc_nmhandle_peeraddr(handle);
+ isc_sockaddr_format(&sockaddr, sourcetext, sizeof(sourcetext));
+
+ if (xfr->tsigkey != NULL && xfr->tsigkey->key != NULL) {
+ dns_name_format(dst_key_name(xfr->tsigkey->key), signerbuf,
+ sizeof(signerbuf));
+ sep = " TSIG ";
+ signer = signerbuf;
+ }
+
+ xfrin_log(xfr, ISC_LOG_INFO, "connected using %s%s%s", sourcetext, sep,
+ signer);
+
+ result = xfrin_send_request(xfr);
+ if (result != ISC_R_SUCCESS) {
+ xfrin_fail(xfr, result, "connected but unable to send");
+ }
+
+failure:
+ switch (result) {
+ case ISC_R_SUCCESS:
+ break;
+ case ISC_R_NETDOWN:
+ case ISC_R_HOSTDOWN:
+ case ISC_R_NETUNREACH:
+ case ISC_R_HOSTUNREACH:
+ case ISC_R_CONNREFUSED:
+ case ISC_R_TIMEDOUT:
+ /*
+ * Add the server to unreachable primaries table if
+ * the server has a permanent networking error or
+ * the connection attempt as timed out.
+ */
+ zmgr = dns_zone_getmgr(xfr->zone);
+ if (zmgr != NULL) {
+ isc_time_t now;
+
+ TIME_NOW(&now);
+
+ dns_zonemgr_unreachableadd(zmgr, &xfr->primaryaddr,
+ &xfr->sourceaddr, &now);
+ }
+ break;
+ default:
+ /* Retry sooner than in 10 minutes */
+ break;
+ }
+
+ dns_xfrin_detach(&xfr);
+}
+
+/*
+ * Convert a tuple into a dns_name_t suitable for inserting
+ * into the given dns_message_t.
+ */
+static isc_result_t
+tuple2msgname(dns_difftuple_t *tuple, dns_message_t *msg, dns_name_t **target) {
+ isc_result_t result;
+ dns_rdata_t *rdata = NULL;
+ dns_rdatalist_t *rdl = NULL;
+ dns_rdataset_t *rds = NULL;
+ dns_name_t *name = NULL;
+
+ REQUIRE(target != NULL && *target == NULL);
+
+ CHECK(dns_message_gettemprdata(msg, &rdata));
+ dns_rdata_init(rdata);
+ dns_rdata_clone(&tuple->rdata, rdata);
+
+ CHECK(dns_message_gettemprdatalist(msg, &rdl));
+ dns_rdatalist_init(rdl);
+ rdl->type = tuple->rdata.type;
+ rdl->rdclass = tuple->rdata.rdclass;
+ rdl->ttl = tuple->ttl;
+ ISC_LIST_APPEND(rdl->rdata, rdata, link);
+
+ CHECK(dns_message_gettemprdataset(msg, &rds));
+ CHECK(dns_rdatalist_tordataset(rdl, rds));
+
+ CHECK(dns_message_gettempname(msg, &name));
+ dns_name_clone(&tuple->name, name);
+ ISC_LIST_APPEND(name->list, rds, link);
+
+ *target = name;
+ return (ISC_R_SUCCESS);
+
+failure:
+
+ if (rds != NULL) {
+ dns_rdataset_disassociate(rds);
+ dns_message_puttemprdataset(msg, &rds);
+ }
+ if (rdl != NULL) {
+ ISC_LIST_UNLINK(rdl->rdata, rdata, link);
+ dns_message_puttemprdatalist(msg, &rdl);
+ }
+ if (rdata != NULL) {
+ dns_message_puttemprdata(msg, &rdata);
+ }
+
+ return (result);
+}
+
+/*
+ * Build an *XFR request and send its length prefix.
+ */
+static isc_result_t
+xfrin_send_request(dns_xfrin_ctx_t *xfr) {
+ isc_result_t result;
+ isc_region_t region;
+ dns_rdataset_t *qrdataset = NULL;
+ dns_message_t *msg = NULL;
+ dns_difftuple_t *soatuple = NULL;
+ dns_name_t *qname = NULL;
+ dns_dbversion_t *ver = NULL;
+ dns_name_t *msgsoaname = NULL;
+ dns_xfrin_ctx_t *send_xfr = NULL;
+
+ /* Create the request message */
+ dns_message_create(xfr->mctx, DNS_MESSAGE_INTENTRENDER, &msg);
+ CHECK(dns_message_settsigkey(msg, xfr->tsigkey));
+
+ /* Create a name for the question section. */
+ CHECK(dns_message_gettempname(msg, &qname));
+ dns_name_clone(&xfr->name, qname);
+
+ /* Formulate the question and attach it to the question name. */
+ CHECK(dns_message_gettemprdataset(msg, &qrdataset));
+ dns_rdataset_makequestion(qrdataset, xfr->rdclass, xfr->reqtype);
+ ISC_LIST_APPEND(qname->list, qrdataset, link);
+ qrdataset = NULL;
+
+ dns_message_addname(msg, qname, DNS_SECTION_QUESTION);
+ qname = NULL;
+
+ if (xfr->reqtype == dns_rdatatype_ixfr) {
+ /* Get the SOA and add it to the authority section. */
+ /* XXX is using the current version the right thing? */
+ dns_db_currentversion(xfr->db, &ver);
+ CHECK(dns_db_createsoatuple(xfr->db, ver, xfr->mctx,
+ DNS_DIFFOP_EXISTS, &soatuple));
+ xfr->ixfr.request_serial = dns_soa_getserial(&soatuple->rdata);
+ xfr->ixfr.current_serial = xfr->ixfr.request_serial;
+ xfrin_log(xfr, ISC_LOG_DEBUG(3),
+ "requesting IXFR for serial %u",
+ xfr->ixfr.request_serial);
+
+ CHECK(tuple2msgname(soatuple, msg, &msgsoaname));
+ dns_message_addname(msg, msgsoaname, DNS_SECTION_AUTHORITY);
+ } else if (xfr->reqtype == dns_rdatatype_soa) {
+ CHECK(dns_db_getsoaserial(xfr->db, NULL,
+ &xfr->ixfr.request_serial));
+ }
+
+ xfr->id++;
+ xfr->nmsg = 0;
+ xfr->nrecs = 0;
+ xfr->nbytes = 0;
+ isc_time_now(&xfr->start);
+ msg->id = xfr->id;
+ if (xfr->tsigctx != NULL) {
+ dst_context_destroy(&xfr->tsigctx);
+ }
+
+ CHECK(render(msg, xfr->mctx, &xfr->qbuffer));
+
+ /*
+ * Free the last tsig, if there is one.
+ */
+ if (xfr->lasttsig != NULL) {
+ isc_buffer_free(&xfr->lasttsig);
+ }
+
+ /*
+ * Save the query TSIG and don't let message_destroy free it.
+ */
+ CHECK(dns_message_getquerytsig(msg, xfr->mctx, &xfr->lasttsig));
+
+ isc_buffer_usedregion(&xfr->qbuffer, &region);
+ INSIST(region.length <= 65535);
+
+ dns_xfrin_attach(xfr, &send_xfr);
+ isc_nmhandle_attach(send_xfr->handle, &xfr->sendhandle);
+ isc_refcount_increment0(&send_xfr->sends);
+ isc_nm_send(xfr->handle, &region, xfrin_send_done, send_xfr);
+
+failure:
+ if (qname != NULL) {
+ dns_message_puttempname(msg, &qname);
+ }
+ if (qrdataset != NULL) {
+ dns_message_puttemprdataset(msg, &qrdataset);
+ }
+ if (msg != NULL) {
+ dns_message_detach(&msg);
+ }
+ if (soatuple != NULL) {
+ dns_difftuple_free(&soatuple);
+ }
+ if (ver != NULL) {
+ dns_db_closeversion(xfr->db, &ver, false);
+ }
+
+ return (result);
+}
+
+static void
+xfrin_send_done(isc_nmhandle_t *handle, isc_result_t result, void *cbarg) {
+ dns_xfrin_ctx_t *xfr = (dns_xfrin_ctx_t *)cbarg;
+ dns_xfrin_ctx_t *recv_xfr = NULL;
+
+ REQUIRE(VALID_XFRIN(xfr));
+
+ isc_refcount_decrement0(&xfr->sends);
+ if (atomic_load(&xfr->shuttingdown)) {
+ result = ISC_R_SHUTTINGDOWN;
+ }
+
+ CHECK(result);
+
+ xfrin_log(xfr, ISC_LOG_DEBUG(3), "sent request data");
+
+ dns_xfrin_attach(xfr, &recv_xfr);
+ isc_nmhandle_attach(handle, &recv_xfr->readhandle);
+ isc_refcount_increment0(&recv_xfr->recvs);
+ isc_nm_read(recv_xfr->handle, xfrin_recv_done, recv_xfr);
+
+failure:
+ if (result != ISC_R_SUCCESS) {
+ xfrin_fail(xfr, result, "failed sending request data");
+ }
+
+ isc_nmhandle_detach(&xfr->sendhandle);
+ dns_xfrin_detach(&xfr); /* send_xfr */
+}
+
+static void
+xfrin_recv_done(isc_nmhandle_t *handle, isc_result_t result,
+ isc_region_t *region, void *cbarg) {
+ dns_xfrin_ctx_t *xfr = (dns_xfrin_ctx_t *)cbarg;
+ dns_message_t *msg = NULL;
+ dns_name_t *name = NULL;
+ const dns_name_t *tsigowner = NULL;
+ isc_buffer_t buffer;
+ isc_sockaddr_t peer;
+
+ REQUIRE(VALID_XFRIN(xfr));
+
+ isc_refcount_decrement0(&xfr->recvs);
+
+ if (atomic_load(&xfr->shuttingdown)) {
+ result = ISC_R_SHUTTINGDOWN;
+ }
+
+ /* Stop the idle timer */
+ (void)isc_timer_reset(xfr->max_idle_timer, isc_timertype_inactive, NULL,
+ NULL, true);
+
+ CHECK(result);
+
+ xfrin_log(xfr, ISC_LOG_DEBUG(7), "received %u bytes", region->length);
+
+ dns_message_create(xfr->mctx, DNS_MESSAGE_INTENTPARSE, &msg);
+
+ CHECK(dns_message_settsigkey(msg, xfr->tsigkey));
+ CHECK(dns_message_setquerytsig(msg, xfr->lasttsig));
+
+ msg->tsigctx = xfr->tsigctx;
+ xfr->tsigctx = NULL;
+
+ dns_message_setclass(msg, xfr->rdclass);
+
+ if (xfr->nmsg > 0) {
+ msg->tcp_continuation = 1;
+ }
+
+ isc_buffer_init(&buffer, region->base, region->length);
+ isc_buffer_add(&buffer, region->length);
+ peer = isc_nmhandle_peeraddr(handle);
+
+ result = dns_message_parse(msg, &buffer,
+ DNS_MESSAGEPARSE_PRESERVEORDER);
+ if (result == ISC_R_SUCCESS) {
+ dns_message_logpacket(msg, "received message from", &peer,
+ DNS_LOGCATEGORY_XFER_IN,
+ DNS_LOGMODULE_XFER_IN, ISC_LOG_DEBUG(10),
+ xfr->mctx);
+ } else {
+ xfrin_log(xfr, ISC_LOG_DEBUG(10), "dns_message_parse: %s",
+ isc_result_totext(result));
+ }
+
+ if (result != ISC_R_SUCCESS || msg->rcode != dns_rcode_noerror ||
+ msg->opcode != dns_opcode_query || msg->rdclass != xfr->rdclass ||
+ msg->id != xfr->id)
+ {
+ if (result == ISC_R_SUCCESS && msg->rcode != dns_rcode_noerror)
+ {
+ result = dns_result_fromrcode(msg->rcode);
+ } else if (result == ISC_R_SUCCESS &&
+ msg->opcode != dns_opcode_query)
+ {
+ result = DNS_R_UNEXPECTEDOPCODE;
+ } else if (result == ISC_R_SUCCESS &&
+ msg->rdclass != xfr->rdclass)
+ {
+ result = DNS_R_BADCLASS;
+ } else if (result == ISC_R_SUCCESS || result == DNS_R_NOERROR) {
+ result = DNS_R_UNEXPECTEDID;
+ }
+
+ if (xfr->reqtype == dns_rdatatype_axfr ||
+ xfr->reqtype == dns_rdatatype_soa)
+ {
+ goto failure;
+ }
+
+ xfrin_log(xfr, ISC_LOG_DEBUG(3), "got %s, retrying with AXFR",
+ isc_result_totext(result));
+ try_axfr:
+ isc_nmhandle_detach(&xfr->readhandle);
+ dns_message_detach(&msg);
+ xfrin_reset(xfr);
+ xfr->reqtype = dns_rdatatype_soa;
+ xfr->state = XFRST_SOAQUERY;
+ result = xfrin_start(xfr);
+ if (result != ISC_R_SUCCESS) {
+ xfrin_fail(xfr, result, "failed setting up socket");
+ }
+ dns_xfrin_detach(&xfr); /* recv_xfr */
+ return;
+ }
+
+ /*
+ * The question section should exist for SOA and in the first
+ * message of a AXFR or IXFR response. The question section
+ * may exist in the 2nd and subsequent messages in a AXFR or
+ * IXFR response. If the question section exists it should
+ * match the question that was sent.
+ */
+ if (msg->counts[DNS_SECTION_QUESTION] > 1) {
+ xfrin_log(xfr, ISC_LOG_NOTICE, "too many questions (%u)",
+ msg->counts[DNS_SECTION_QUESTION]);
+ result = DNS_R_FORMERR;
+ goto failure;
+ }
+
+ if ((xfr->state == XFRST_SOAQUERY || xfr->state == XFRST_INITIALSOA) &&
+ msg->counts[DNS_SECTION_QUESTION] != 1)
+ {
+ xfrin_log(xfr, ISC_LOG_NOTICE, "missing question section");
+ result = DNS_R_FORMERR;
+ goto failure;
+ }
+
+ for (result = dns_message_firstname(msg, DNS_SECTION_QUESTION);
+ result == ISC_R_SUCCESS;
+ result = dns_message_nextname(msg, DNS_SECTION_QUESTION))
+ {
+ dns_rdataset_t *rds = NULL;
+
+ name = NULL;
+ dns_message_currentname(msg, DNS_SECTION_QUESTION, &name);
+ if (!dns_name_equal(name, &xfr->name)) {
+ result = DNS_R_FORMERR;
+ xfrin_log(xfr, ISC_LOG_NOTICE,
+ "question name mismatch");
+ goto failure;
+ }
+ rds = ISC_LIST_HEAD(name->list);
+ INSIST(rds != NULL);
+ if (rds->type != xfr->reqtype) {
+ result = DNS_R_FORMERR;
+ xfrin_log(xfr, ISC_LOG_NOTICE,
+ "question type mismatch");
+ goto failure;
+ }
+ if (rds->rdclass != xfr->rdclass) {
+ result = DNS_R_FORMERR;
+ xfrin_log(xfr, ISC_LOG_NOTICE,
+ "question class mismatch");
+ goto failure;
+ }
+ }
+ if (result != ISC_R_NOMORE) {
+ goto failure;
+ }
+
+ /*
+ * Does the server know about IXFR? If it doesn't we will get
+ * a message with a empty answer section or a potentially a CNAME /
+ * DNAME, the later is handled by xfr_rr() which will return FORMERR
+ * if the first RR in the answer section is not a SOA record.
+ */
+ if (xfr->reqtype == dns_rdatatype_ixfr &&
+ xfr->state == XFRST_INITIALSOA &&
+ msg->counts[DNS_SECTION_ANSWER] == 0)
+ {
+ xfrin_log(xfr, ISC_LOG_DEBUG(3),
+ "empty answer section, retrying with AXFR");
+ goto try_axfr;
+ }
+
+ if (xfr->reqtype == dns_rdatatype_soa &&
+ (msg->flags & DNS_MESSAGEFLAG_AA) == 0)
+ {
+ FAIL(DNS_R_NOTAUTHORITATIVE);
+ }
+
+ result = dns_message_checksig(msg, dns_zone_getview(xfr->zone));
+ if (result != ISC_R_SUCCESS) {
+ xfrin_log(xfr, ISC_LOG_DEBUG(3), "TSIG check failed: %s",
+ isc_result_totext(result));
+ goto failure;
+ }
+
+ for (result = dns_message_firstname(msg, DNS_SECTION_ANSWER);
+ result == ISC_R_SUCCESS;
+ result = dns_message_nextname(msg, DNS_SECTION_ANSWER))
+ {
+ dns_rdataset_t *rds = NULL;
+
+ name = NULL;
+ dns_message_currentname(msg, DNS_SECTION_ANSWER, &name);
+ for (rds = ISC_LIST_HEAD(name->list); rds != NULL;
+ rds = ISC_LIST_NEXT(rds, link))
+ {
+ for (result = dns_rdataset_first(rds);
+ result == ISC_R_SUCCESS;
+ result = dns_rdataset_next(rds))
+ {
+ dns_rdata_t rdata = DNS_RDATA_INIT;
+ dns_rdataset_current(rds, &rdata);
+ CHECK(xfr_rr(xfr, name, rds->ttl, &rdata));
+ }
+ }
+ }
+ if (result != ISC_R_NOMORE) {
+ goto failure;
+ }
+
+ if (dns_message_gettsig(msg, &tsigowner) != NULL) {
+ /*
+ * Reset the counter.
+ */
+ xfr->sincetsig = 0;
+
+ /*
+ * Free the last tsig, if there is one.
+ */
+ if (xfr->lasttsig != NULL) {
+ isc_buffer_free(&xfr->lasttsig);
+ }
+
+ /*
+ * Update the last tsig pointer.
+ */
+ CHECK(dns_message_getquerytsig(msg, xfr->mctx, &xfr->lasttsig));
+ } else if (dns_message_gettsigkey(msg) != NULL) {
+ xfr->sincetsig++;
+ if (xfr->sincetsig > 100 || xfr->nmsg == 0 ||
+ xfr->state == XFRST_AXFR_END ||
+ xfr->state == XFRST_IXFR_END)
+ {
+ result = DNS_R_EXPECTEDTSIG;
+ goto failure;
+ }
+ }
+
+ /*
+ * Update the number of messages received.
+ */
+ xfr->nmsg++;
+
+ /*
+ * Update the number of bytes received.
+ */
+ xfr->nbytes += buffer.used;
+
+ /*
+ * Take the context back.
+ */
+ INSIST(xfr->tsigctx == NULL);
+ xfr->tsigctx = msg->tsigctx;
+ msg->tsigctx = NULL;
+
+ switch (xfr->state) {
+ case XFRST_GOTSOA:
+ xfr->reqtype = dns_rdatatype_axfr;
+ xfr->state = XFRST_INITIALSOA;
+ CHECK(xfrin_send_request(xfr));
+ break;
+ case XFRST_AXFR_END:
+ CHECK(axfr_finalize(xfr));
+ FALLTHROUGH;
+ case XFRST_IXFR_END:
+ /*
+ * Close the journal.
+ */
+ if (xfr->ixfr.journal != NULL) {
+ dns_journal_destroy(&xfr->ixfr.journal);
+ }
+
+ /*
+ * Inform the caller we succeeded.
+ */
+ if (xfr->done != NULL) {
+ (xfr->done)(xfr->zone, ISC_R_SUCCESS);
+ xfr->done = NULL;
+ }
+
+ atomic_store(&xfr->shuttingdown, true);
+ (void)isc_timer_reset(xfr->max_time_timer,
+ isc_timertype_inactive, NULL, NULL, true);
+ xfr->shutdown_result = ISC_R_SUCCESS;
+ break;
+ default:
+ /*
+ * Read the next message.
+ */
+ /* The readhandle is still attached */
+ /* The recv_xfr is still attached */
+ dns_message_detach(&msg);
+ isc_refcount_increment0(&xfr->recvs);
+ isc_nm_read(xfr->handle, xfrin_recv_done, xfr);
+ isc_time_t next;
+ isc_interval_t interval;
+ isc_interval_set(&interval, dns_zone_getidlein(xfr->zone), 0);
+ isc_time_nowplusinterval(&next, &interval);
+ result = isc_timer_reset(xfr->max_idle_timer,
+ isc_timertype_once, &next, NULL, true);
+ RUNTIME_CHECK(result == ISC_R_SUCCESS);
+ return;
+ }
+
+failure:
+ if (result != ISC_R_SUCCESS) {
+ xfrin_fail(xfr, result, "failed while receiving responses");
+ }
+
+ if (msg != NULL) {
+ dns_message_detach(&msg);
+ }
+ isc_nmhandle_detach(&xfr->readhandle);
+ dns_xfrin_detach(&xfr); /* recv_xfr */
+}
+
+static void
+xfrin_destroy(dns_xfrin_ctx_t *xfr) {
+ uint64_t msecs;
+ uint64_t persec;
+ const char *result_str;
+
+ REQUIRE(VALID_XFRIN(xfr));
+
+ /* Safe-guards */
+ REQUIRE(atomic_load(&xfr->shuttingdown));
+ isc_refcount_destroy(&xfr->references);
+ isc_refcount_destroy(&xfr->connects);
+ isc_refcount_destroy(&xfr->recvs);
+ isc_refcount_destroy(&xfr->sends);
+
+ INSIST(xfr->shutdown_result != ISC_R_UNSET);
+
+ /*
+ * If we're called through dns_xfrin_detach() and are not
+ * shutting down, we can't know what the transfer status is as
+ * we are only called when the last reference is lost.
+ */
+ result_str = isc_result_totext(xfr->shutdown_result);
+ xfrin_log(xfr, ISC_LOG_INFO, "Transfer status: %s", result_str);
+
+ /*
+ * Calculate the length of time the transfer took,
+ * and print a log message with the bytes and rate.
+ */
+ isc_time_now(&xfr->end);
+ msecs = isc_time_microdiff(&xfr->end, &xfr->start) / 1000;
+ if (msecs == 0) {
+ msecs = 1;
+ }
+ persec = (xfr->nbytes * 1000) / msecs;
+ xfrin_log(xfr, ISC_LOG_INFO,
+ "Transfer completed: %d messages, %d records, "
+ "%" PRIu64 " bytes, "
+ "%u.%03u secs (%u bytes/sec) (serial %u)",
+ xfr->nmsg, xfr->nrecs, xfr->nbytes,
+ (unsigned int)(msecs / 1000), (unsigned int)(msecs % 1000),
+ (unsigned int)persec, xfr->end_serial);
+
+ if (xfr->readhandle != NULL) {
+ isc_nmhandle_detach(&xfr->readhandle);
+ }
+ if (xfr->sendhandle != NULL) {
+ isc_nmhandle_detach(&xfr->sendhandle);
+ }
+
+ if (xfr->transport != NULL) {
+ dns_transport_detach(&xfr->transport);
+ }
+
+ if (xfr->tsigkey != NULL) {
+ dns_tsigkey_detach(&xfr->tsigkey);
+ }
+
+ if (xfr->lasttsig != NULL) {
+ isc_buffer_free(&xfr->lasttsig);
+ }
+
+ dns_diff_clear(&xfr->diff);
+
+ if (xfr->ixfr.journal != NULL) {
+ dns_journal_destroy(&xfr->ixfr.journal);
+ }
+
+ if (xfr->axfr.add_private != NULL) {
+ (void)dns_db_endload(xfr->db, &xfr->axfr);
+ }
+
+ if (xfr->tsigctx != NULL) {
+ dst_context_destroy(&xfr->tsigctx);
+ }
+
+ if ((xfr->name.attributes & DNS_NAMEATTR_DYNAMIC) != 0) {
+ dns_name_free(&xfr->name, xfr->mctx);
+ }
+
+ if (xfr->ver != NULL) {
+ dns_db_closeversion(xfr->db, &xfr->ver, false);
+ }
+
+ if (xfr->db != NULL) {
+ dns_db_detach(&xfr->db);
+ }
+
+ if (xfr->zone != NULL) {
+ if (!xfr->zone_had_db &&
+ xfr->shutdown_result == ISC_R_SUCCESS &&
+ dns_zone_gettype(xfr->zone) == dns_zone_mirror)
+ {
+ dns_zone_log(xfr->zone, ISC_LOG_INFO,
+ "mirror zone is now in use");
+ }
+ xfrin_log(xfr, ISC_LOG_DEBUG(99), "freeing transfer context");
+ /*
+ * xfr->zone must not be detached before xfrin_log() is called.
+ */
+ dns_zone_idetach(&xfr->zone);
+ }
+
+ if (xfr->firstsoa_data != NULL) {
+ isc_mem_free(xfr->mctx, xfr->firstsoa_data);
+ }
+
+ if (xfr->tlsctx_cache != NULL) {
+ isc_tlsctx_cache_detach(&xfr->tlsctx_cache);
+ }
+
+ isc_timer_destroy(&xfr->max_idle_timer);
+ isc_timer_destroy(&xfr->max_time_timer);
+
+ isc_mem_putanddetach(&xfr->mctx, xfr, sizeof(*xfr));
+}
+
+/*
+ * Log incoming zone transfer messages in a format like
+ * transfer of <zone> from <address>: <message>
+ */
+static void
+xfrin_logv(int level, const char *zonetext, const isc_sockaddr_t *primaryaddr,
+ const char *fmt, va_list ap) {
+ char primarytext[ISC_SOCKADDR_FORMATSIZE];
+ char msgtext[2048];
+
+ isc_sockaddr_format(primaryaddr, primarytext, sizeof(primarytext));
+ vsnprintf(msgtext, sizeof(msgtext), fmt, ap);
+
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_XFER_IN, DNS_LOGMODULE_XFER_IN,
+ level, "transfer of '%s' from %s: %s", zonetext,
+ primarytext, msgtext);
+}
+
+/*
+ * Logging function for use when a xfrin_ctx_t has not yet been created.
+ */
+
+static void
+xfrin_log1(int level, const char *zonetext, const isc_sockaddr_t *primaryaddr,
+ const char *fmt, ...) {
+ va_list ap;
+
+ if (!isc_log_wouldlog(dns_lctx, level)) {
+ return;
+ }
+
+ va_start(ap, fmt);
+ xfrin_logv(level, zonetext, primaryaddr, fmt, ap);
+ va_end(ap);
+}
+
+/*
+ * Logging function for use when there is a xfrin_ctx_t.
+ */
+
+static void
+xfrin_log(dns_xfrin_ctx_t *xfr, int level, const char *fmt, ...) {
+ va_list ap;
+ char zonetext[DNS_NAME_MAXTEXT + 32];
+
+ if (!isc_log_wouldlog(dns_lctx, level)) {
+ return;
+ }
+
+ dns_zone_name(xfr->zone, zonetext, sizeof(zonetext));
+
+ va_start(ap, fmt);
+ xfrin_logv(level, zonetext, &xfr->primaryaddr, fmt, ap);
+ va_end(ap);
+}
diff --git a/lib/dns/zone.c b/lib/dns/zone.c
new file mode 100644
index 0000000..4428e3d
--- /dev/null
+++ b/lib/dns/zone.c
@@ -0,0 +1,23706 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/*! \file */
+
+#include <errno.h>
+#include <inttypes.h>
+#include <stdbool.h>
+
+#include <isc/atomic.h>
+#include <isc/file.h>
+#include <isc/hex.h>
+#include <isc/md.h>
+#include <isc/mutex.h>
+#include <isc/pool.h>
+#include <isc/print.h>
+#include <isc/random.h>
+#include <isc/ratelimiter.h>
+#include <isc/refcount.h>
+#include <isc/result.h>
+#include <isc/rwlock.h>
+#include <isc/serial.h>
+#include <isc/stats.h>
+#include <isc/stdtime.h>
+#include <isc/strerr.h>
+#include <isc/string.h>
+#include <isc/taskpool.h>
+#include <isc/thread.h>
+#include <isc/timer.h>
+#include <isc/tls.h>
+#include <isc/util.h>
+
+#include <dns/acl.h>
+#include <dns/adb.h>
+#include <dns/callbacks.h>
+#include <dns/catz.h>
+#include <dns/db.h>
+#include <dns/dbiterator.h>
+#include <dns/dlz.h>
+#include <dns/dnssec.h>
+#include <dns/events.h>
+#include <dns/journal.h>
+#include <dns/kasp.h>
+#include <dns/keydata.h>
+#include <dns/keymgr.h>
+#include <dns/keytable.h>
+#include <dns/keyvalues.h>
+#include <dns/log.h>
+#include <dns/master.h>
+#include <dns/masterdump.h>
+#include <dns/message.h>
+#include <dns/name.h>
+#include <dns/nsec.h>
+#include <dns/nsec3.h>
+#include <dns/opcode.h>
+#include <dns/peer.h>
+#include <dns/private.h>
+#include <dns/rcode.h>
+#include <dns/rdata.h>
+#include <dns/rdataclass.h>
+#include <dns/rdatalist.h>
+#include <dns/rdataset.h>
+#include <dns/rdatasetiter.h>
+#include <dns/rdatastruct.h>
+#include <dns/rdatatype.h>
+#include <dns/request.h>
+#include <dns/resolver.h>
+#include <dns/rriterator.h>
+#include <dns/soa.h>
+#include <dns/ssu.h>
+#include <dns/stats.h>
+#include <dns/time.h>
+#include <dns/tsig.h>
+#include <dns/update.h>
+#include <dns/xfrin.h>
+#include <dns/zone.h>
+#include <dns/zoneverify.h>
+#include <dns/zt.h>
+
+#include <dst/dst.h>
+
+#include "zone_p.h"
+
+#define ZONE_MAGIC ISC_MAGIC('Z', 'O', 'N', 'E')
+#define DNS_ZONE_VALID(zone) ISC_MAGIC_VALID(zone, ZONE_MAGIC)
+
+#define NOTIFY_MAGIC ISC_MAGIC('N', 't', 'f', 'y')
+#define DNS_NOTIFY_VALID(notify) ISC_MAGIC_VALID(notify, NOTIFY_MAGIC)
+
+#define CHECKDS_MAGIC ISC_MAGIC('C', 'h', 'D', 'S')
+#define DNS_CHECKDS_VALID(checkds) ISC_MAGIC_VALID(checkds, CHECKDS_MAGIC)
+
+#define STUB_MAGIC ISC_MAGIC('S', 't', 'u', 'b')
+#define DNS_STUB_VALID(stub) ISC_MAGIC_VALID(stub, STUB_MAGIC)
+
+#define ZONEMGR_MAGIC ISC_MAGIC('Z', 'm', 'g', 'r')
+#define DNS_ZONEMGR_VALID(stub) ISC_MAGIC_VALID(stub, ZONEMGR_MAGIC)
+
+#define LOAD_MAGIC ISC_MAGIC('L', 'o', 'a', 'd')
+#define DNS_LOAD_VALID(load) ISC_MAGIC_VALID(load, LOAD_MAGIC)
+
+#define FORWARD_MAGIC ISC_MAGIC('F', 'o', 'r', 'w')
+#define DNS_FORWARD_VALID(load) ISC_MAGIC_VALID(load, FORWARD_MAGIC)
+
+#define IO_MAGIC ISC_MAGIC('Z', 'm', 'I', 'O')
+#define DNS_IO_VALID(load) ISC_MAGIC_VALID(load, IO_MAGIC)
+
+#define KEYMGMT_MAGIC ISC_MAGIC('M', 'g', 'm', 't')
+#define DNS_KEYMGMT_VALID(load) ISC_MAGIC_VALID(load, KEYMGMT_MAGIC)
+
+#define KEYFILEIO_MAGIC ISC_MAGIC('K', 'y', 'I', 'O')
+#define DNS_KEYFILEIO_VALID(kfio) ISC_MAGIC_VALID(kfio, KEYFILEIO_MAGIC)
+
+/*%
+ * Ensure 'a' is at least 'min' but not more than 'max'.
+ */
+#define RANGE(a, min, max) (((a) < (min)) ? (min) : ((a) < (max) ? (a) : (max)))
+
+#define NSEC3REMOVE(x) (((x)&DNS_NSEC3FLAG_REMOVE) != 0)
+
+/*%
+ * Key flags
+ */
+#define REVOKE(x) ((dst_key_flags(x) & DNS_KEYFLAG_REVOKE) != 0)
+#define KSK(x) ((dst_key_flags(x) & DNS_KEYFLAG_KSK) != 0)
+#define ID(x) dst_key_id(x)
+#define ALG(x) dst_key_alg(x)
+
+/*%
+ * KASP flags
+ */
+#define KASP_LOCK(k) \
+ if ((k) != NULL) { \
+ LOCK((&((k)->lock))); \
+ }
+
+#define KASP_UNLOCK(k) \
+ if ((k) != NULL) { \
+ UNLOCK((&((k)->lock))); \
+ }
+
+/*
+ * Default values.
+ */
+#define DNS_DEFAULT_IDLEIN 3600 /*%< 1 hour */
+#define DNS_DEFAULT_IDLEOUT 3600 /*%< 1 hour */
+#define MAX_XFER_TIME (2 * 3600) /*%< Documented default is 2 hours */
+#define RESIGN_DELAY 3600 /*%< 1 hour */
+
+#ifndef DNS_MAX_EXPIRE
+#define DNS_MAX_EXPIRE 14515200 /*%< 24 weeks */
+#endif /* ifndef DNS_MAX_EXPIRE */
+
+#ifndef DNS_DUMP_DELAY
+#define DNS_DUMP_DELAY 900 /*%< 15 minutes */
+#endif /* ifndef DNS_DUMP_DELAY */
+
+typedef struct dns_notify dns_notify_t;
+typedef struct dns_checkds dns_checkds_t;
+typedef struct dns_stub dns_stub_t;
+typedef struct dns_load dns_load_t;
+typedef struct dns_forward dns_forward_t;
+typedef ISC_LIST(dns_forward_t) dns_forwardlist_t;
+typedef struct dns_io dns_io_t;
+typedef ISC_LIST(dns_io_t) dns_iolist_t;
+typedef struct dns_keymgmt dns_keymgmt_t;
+typedef struct dns_signing dns_signing_t;
+typedef ISC_LIST(dns_signing_t) dns_signinglist_t;
+typedef struct dns_nsec3chain dns_nsec3chain_t;
+typedef ISC_LIST(dns_nsec3chain_t) dns_nsec3chainlist_t;
+typedef struct dns_keyfetch dns_keyfetch_t;
+typedef struct dns_asyncload dns_asyncload_t;
+typedef struct dns_include dns_include_t;
+
+#define DNS_ZONE_CHECKLOCK
+#ifdef DNS_ZONE_CHECKLOCK
+#define LOCK_ZONE(z) \
+ do { \
+ LOCK(&(z)->lock); \
+ INSIST(!(z)->locked); \
+ (z)->locked = true; \
+ } while (0)
+#define UNLOCK_ZONE(z) \
+ do { \
+ (z)->locked = false; \
+ UNLOCK(&(z)->lock); \
+ } while (0)
+#define LOCKED_ZONE(z) ((z)->locked)
+#define TRYLOCK_ZONE(result, z) \
+ do { \
+ result = isc_mutex_trylock(&(z)->lock); \
+ if (result == ISC_R_SUCCESS) { \
+ INSIST(!(z)->locked); \
+ (z)->locked = true; \
+ } \
+ } while (0)
+#else /* ifdef DNS_ZONE_CHECKLOCK */
+#define LOCK_ZONE(z) LOCK(&(z)->lock)
+#define UNLOCK_ZONE(z) UNLOCK(&(z)->lock)
+#define LOCKED_ZONE(z) true
+#define TRYLOCK_ZONE(result, z) \
+ do { \
+ result = isc_mutex_trylock(&(z)->lock); \
+ } while (0)
+#endif /* ifdef DNS_ZONE_CHECKLOCK */
+
+#define ZONEDB_INITLOCK(l) isc_rwlock_init((l), 0, 0)
+#define ZONEDB_DESTROYLOCK(l) isc_rwlock_destroy(l)
+#define ZONEDB_LOCK(l, t) RWLOCK((l), (t))
+#define ZONEDB_UNLOCK(l, t) RWUNLOCK((l), (t))
+
+#ifdef ENABLE_AFL
+extern bool dns_fuzzing_resolver;
+#endif /* ifdef ENABLE_AFL */
+
+/*%
+ * Hold key file IO locks.
+ */
+typedef struct dns_keyfileio {
+ unsigned int magic;
+ struct dns_keyfileio *next;
+ uint32_t hashval;
+ dns_fixedname_t fname;
+ dns_name_t *name;
+ isc_refcount_t references;
+ isc_mutex_t lock;
+} dns_keyfileio_t;
+
+struct dns_keymgmt {
+ unsigned int magic;
+ isc_rwlock_t lock;
+ isc_mem_t *mctx;
+
+ dns_keyfileio_t **table;
+
+ atomic_uint_fast32_t count;
+
+ uint32_t bits;
+};
+
+struct dns_zone {
+ /* Unlocked */
+ unsigned int magic;
+ isc_mutex_t lock;
+#ifdef DNS_ZONE_CHECKLOCK
+ bool locked;
+#endif /* ifdef DNS_ZONE_CHECKLOCK */
+ isc_mem_t *mctx;
+ isc_refcount_t erefs;
+
+ isc_rwlock_t dblock;
+ dns_db_t *db; /* Locked by dblock */
+
+ /* Locked */
+ dns_zonemgr_t *zmgr;
+ ISC_LINK(dns_zone_t) link; /* Used by zmgr. */
+ isc_timer_t *timer;
+ isc_refcount_t irefs;
+ dns_name_t origin;
+ char *masterfile;
+ const FILE *stream; /* loading from a stream? */
+ ISC_LIST(dns_include_t) includes; /* Include files */
+ ISC_LIST(dns_include_t) newincludes; /* Loading */
+ unsigned int nincludes;
+ dns_masterformat_t masterformat;
+ const dns_master_style_t *masterstyle;
+ char *journal;
+ int32_t journalsize;
+ dns_rdataclass_t rdclass;
+ dns_zonetype_t type;
+ atomic_uint_fast64_t flags;
+ atomic_uint_fast64_t options;
+ unsigned int db_argc;
+ char **db_argv;
+ isc_time_t expiretime;
+ isc_time_t refreshtime;
+ isc_time_t dumptime;
+ isc_time_t loadtime;
+ isc_time_t notifytime;
+ isc_time_t resigntime;
+ isc_time_t keywarntime;
+ isc_time_t signingtime;
+ isc_time_t nsec3chaintime;
+ isc_time_t refreshkeytime;
+ uint32_t refreshkeyinterval;
+ uint32_t refreshkeycount;
+ uint32_t refresh;
+ uint32_t retry;
+ uint32_t expire;
+ uint32_t minimum;
+ isc_stdtime_t key_expiry;
+ isc_stdtime_t log_key_expired_timer;
+ char *keydirectory;
+ dns_keyfileio_t *kfio;
+
+ uint32_t maxrefresh;
+ uint32_t minrefresh;
+ uint32_t maxretry;
+ uint32_t minretry;
+
+ uint32_t maxrecords;
+
+ isc_sockaddr_t *primaries;
+ dns_name_t **primarykeynames;
+ dns_name_t **primarytlsnames;
+ bool *primariesok;
+ unsigned int primariescnt;
+ unsigned int curprimary;
+ isc_sockaddr_t primaryaddr;
+
+ isc_sockaddr_t *parentals;
+ dns_name_t **parentalkeynames;
+ dns_name_t **parentaltlsnames;
+ dns_dnsseckeylist_t checkds_ok;
+ unsigned int parentalscnt;
+ isc_sockaddr_t parentaladdr;
+
+ dns_notifytype_t notifytype;
+ isc_sockaddr_t *notify;
+ dns_name_t **notifykeynames;
+ dns_name_t **notifytlsnames;
+ unsigned int notifycnt;
+ isc_sockaddr_t notifyfrom;
+ isc_task_t *task;
+ isc_task_t *loadtask;
+ isc_sockaddr_t notifysrc4;
+ isc_sockaddr_t notifysrc6;
+ isc_sockaddr_t parentalsrc4;
+ isc_sockaddr_t parentalsrc6;
+ isc_sockaddr_t xfrsource4;
+ isc_sockaddr_t xfrsource6;
+ isc_sockaddr_t altxfrsource4;
+ isc_sockaddr_t altxfrsource6;
+ isc_sockaddr_t sourceaddr;
+ dns_xfrin_ctx_t *xfr; /* task locked */
+ dns_tsigkey_t *tsigkey; /* key used for xfr */
+ dns_transport_t *transport; /* transport used for xfr */
+ /* Access Control Lists */
+ dns_acl_t *update_acl;
+ dns_acl_t *forward_acl;
+ dns_acl_t *notify_acl;
+ dns_acl_t *query_acl;
+ dns_acl_t *queryon_acl;
+ dns_acl_t *xfr_acl;
+ bool update_disabled;
+ bool zero_no_soa_ttl;
+ dns_severity_t check_names;
+ ISC_LIST(dns_notify_t) notifies;
+ ISC_LIST(dns_checkds_t) checkds_requests;
+ dns_request_t *request;
+ dns_loadctx_t *lctx;
+ dns_io_t *readio;
+ dns_dumpctx_t *dctx;
+ dns_io_t *writeio;
+ uint32_t maxxfrin;
+ uint32_t maxxfrout;
+ uint32_t idlein;
+ uint32_t idleout;
+ isc_event_t ctlevent;
+ dns_ssutable_t *ssutable;
+ uint32_t sigvalidityinterval;
+ uint32_t keyvalidityinterval;
+ uint32_t sigresigninginterval;
+ dns_view_t *view;
+ dns_view_t *prev_view;
+ dns_kasp_t *kasp;
+ dns_checkmxfunc_t checkmx;
+ dns_checksrvfunc_t checksrv;
+ dns_checknsfunc_t checkns;
+ /*%
+ * Zones in certain states such as "waiting for zone transfer"
+ * or "zone transfer in progress" are kept on per-state linked lists
+ * in the zone manager using the 'statelink' field. The 'statelist'
+ * field points at the list the zone is currently on. It the zone
+ * is not on any such list, statelist is NULL.
+ */
+ ISC_LINK(dns_zone_t) statelink;
+ dns_zonelist_t *statelist;
+ /*%
+ * Statistics counters about zone management.
+ */
+ isc_stats_t *stats;
+ /*%
+ * Optional per-zone statistics counters. Counted outside of this
+ * module.
+ */
+ dns_zonestat_level_t statlevel;
+ bool requeststats_on;
+ isc_stats_t *requeststats;
+ dns_stats_t *rcvquerystats;
+ dns_stats_t *dnssecsignstats;
+ uint32_t notifydelay;
+ dns_isselffunc_t isself;
+ void *isselfarg;
+
+ char *strnamerd;
+ char *strname;
+ char *strrdclass;
+ char *strviewname;
+
+ /*%
+ * Serial number for deferred journal compaction.
+ */
+ uint32_t compact_serial;
+ /*%
+ * Keys that are signing the zone for the first time.
+ */
+ dns_signinglist_t signing;
+ dns_nsec3chainlist_t nsec3chain;
+ /*%
+ * List of outstanding NSEC3PARAM change requests.
+ */
+ isc_eventlist_t setnsec3param_queue;
+ /*%
+ * Signing / re-signing quantum stopping parameters.
+ */
+ uint32_t signatures;
+ uint32_t nodes;
+ dns_rdatatype_t privatetype;
+
+ /*%
+ * Autosigning/key-maintenance options
+ */
+ atomic_uint_fast64_t keyopts;
+
+ /*%
+ * True if added by "rndc addzone"
+ */
+ bool added;
+
+ /*%
+ * True if added by automatically by named.
+ */
+ bool automatic;
+
+ /*%
+ * response policy data to be relayed to the database
+ */
+ dns_rpz_zones_t *rpzs;
+ dns_rpz_num_t rpz_num;
+
+ /*%
+ * catalog zone data
+ */
+ dns_catz_zones_t *catzs;
+
+ /*%
+ * parent catalog zone
+ */
+ dns_catz_zone_t *parentcatz;
+
+ /*%
+ * Serial number update method.
+ */
+ dns_updatemethod_t updatemethod;
+
+ /*%
+ * whether ixfr is requested
+ */
+ bool requestixfr;
+ uint32_t ixfr_ratio;
+
+ /*%
+ * whether EDNS EXPIRE is requested
+ */
+ bool requestexpire;
+
+ /*%
+ * Outstanding forwarded UPDATE requests.
+ */
+ dns_forwardlist_t forwards;
+
+ dns_zone_t *raw;
+ dns_zone_t *secure;
+
+ bool sourceserialset;
+ uint32_t sourceserial;
+
+ /*%
+ * soa and maximum zone ttl
+ */
+ dns_ttl_t soattl;
+ dns_ttl_t maxttl;
+
+ /*
+ * Inline zone signing state.
+ */
+ dns_diff_t rss_diff;
+ isc_eventlist_t rss_events;
+ isc_eventlist_t rss_post;
+ dns_dbversion_t *rss_newver;
+ dns_dbversion_t *rss_oldver;
+ dns_db_t *rss_db;
+ dns_zone_t *rss_raw;
+ isc_event_t *rss_event;
+ dns_update_state_t *rss_state;
+
+ isc_stats_t *gluecachestats;
+};
+
+#define zonediff_init(z, d) \
+ do { \
+ dns__zonediff_t *_z = (z); \
+ (_z)->diff = (d); \
+ (_z)->offline = false; \
+ } while (0)
+
+#define DNS_ZONE_FLAG(z, f) ((atomic_load_relaxed(&(z)->flags) & (f)) != 0)
+#define DNS_ZONE_SETFLAG(z, f) atomic_fetch_or(&(z)->flags, (f))
+#define DNS_ZONE_CLRFLAG(z, f) atomic_fetch_and(&(z)->flags, ~(f))
+typedef enum {
+ DNS_ZONEFLG_REFRESH = 0x00000001U, /*%< refresh check in progress */
+ DNS_ZONEFLG_NEEDDUMP = 0x00000002U, /*%< zone need consolidation */
+ DNS_ZONEFLG_USEVC = 0x00000004U, /*%< use tcp for refresh query */
+ DNS_ZONEFLG_DUMPING = 0x00000008U, /*%< a dump is in progress */
+ DNS_ZONEFLG_HASINCLUDE = 0x00000010U, /*%< $INCLUDE in zone file */
+ DNS_ZONEFLG_LOADED = 0x00000020U, /*%< database has loaded */
+ DNS_ZONEFLG_EXITING = 0x00000040U, /*%< zone is being destroyed */
+ DNS_ZONEFLG_EXPIRED = 0x00000080U, /*%< zone has expired */
+ DNS_ZONEFLG_NEEDREFRESH = 0x00000100U, /*%< refresh check needed */
+ DNS_ZONEFLG_UPTODATE = 0x00000200U, /*%< zone contents are
+ * up-to-date */
+ DNS_ZONEFLG_NEEDNOTIFY = 0x00000400U, /*%< need to send out notify
+ * messages */
+ DNS_ZONEFLG_FIXJOURNAL = 0x00000800U, /*%< journal file had
+ * recoverable error,
+ * needs rewriting */
+ DNS_ZONEFLG_NOPRIMARIES = 0x00001000U, /*%< an attempt to refresh a
+ * zone with no primaries
+ * occurred */
+ DNS_ZONEFLG_LOADING = 0x00002000U, /*%< load from disk in progress*/
+ DNS_ZONEFLG_HAVETIMERS = 0x00004000U, /*%< timer values have been set
+ * from SOA (if not set, we
+ * are still using
+ * default timer values) */
+ DNS_ZONEFLG_FORCEXFER = 0x00008000U, /*%< Force a zone xfer */
+ DNS_ZONEFLG_NOREFRESH = 0x00010000U,
+ DNS_ZONEFLG_DIALNOTIFY = 0x00020000U,
+ DNS_ZONEFLG_DIALREFRESH = 0x00040000U,
+ DNS_ZONEFLG_SHUTDOWN = 0x00080000U,
+ DNS_ZONEFLG_NOIXFR = 0x00100000U, /*%< IXFR failed, force AXFR */
+ DNS_ZONEFLG_FLUSH = 0x00200000U,
+ DNS_ZONEFLG_NOEDNS = 0x00400000U,
+ DNS_ZONEFLG_USEALTXFRSRC = 0x00800000U,
+ DNS_ZONEFLG_SOABEFOREAXFR = 0x01000000U,
+ DNS_ZONEFLG_NEEDCOMPACT = 0x02000000U,
+ DNS_ZONEFLG_REFRESHING = 0x04000000U, /*%< Refreshing keydata */
+ DNS_ZONEFLG_THAW = 0x08000000U,
+ DNS_ZONEFLG_LOADPENDING = 0x10000000U, /*%< Loading scheduled */
+ DNS_ZONEFLG_NODELAY = 0x20000000U,
+ DNS_ZONEFLG_SENDSECURE = 0x40000000U,
+ DNS_ZONEFLG_NEEDSTARTUPNOTIFY = 0x80000000U, /*%< need to send out
+ * notify due to the zone
+ * just being loaded for
+ * the first time. */
+ /*
+ * DO NOT add any new zone flags here until all platforms
+ * support 64-bit enum values. Currently they fail on
+ * Windows.
+ */
+ DNS_ZONEFLG___MAX = UINT64_MAX, /* trick to make the ENUM 64-bit wide */
+} dns_zoneflg_t;
+
+#define DNS_ZONE_OPTION(z, o) ((atomic_load_relaxed(&(z)->options) & (o)) != 0)
+#define DNS_ZONE_SETOPTION(z, o) atomic_fetch_or(&(z)->options, (o))
+#define DNS_ZONE_CLROPTION(z, o) atomic_fetch_and(&(z)->options, ~(o))
+
+#define DNS_ZONEKEY_OPTION(z, o) \
+ ((atomic_load_relaxed(&(z)->keyopts) & (o)) != 0)
+#define DNS_ZONEKEY_SETOPTION(z, o) atomic_fetch_or(&(z)->keyopts, (o))
+#define DNS_ZONEKEY_CLROPTION(z, o) atomic_fetch_and(&(z)->keyopts, ~(o))
+
+/* Flags for zone_load() */
+typedef enum {
+ DNS_ZONELOADFLAG_NOSTAT = 0x00000001U, /* Do not stat() master files */
+ DNS_ZONELOADFLAG_THAW = 0x00000002U, /* Thaw the zone on successful
+ * load. */
+} dns_zoneloadflag_t;
+
+#define UNREACH_CACHE_SIZE 10U
+#define UNREACH_HOLD_TIME 600 /* 10 minutes */
+
+#define CHECK(op) \
+ do { \
+ result = (op); \
+ if (result != ISC_R_SUCCESS) \
+ goto failure; \
+ } while (0)
+
+struct dns_unreachable {
+ isc_sockaddr_t remote;
+ isc_sockaddr_t local;
+ atomic_uint_fast32_t expire;
+ atomic_uint_fast32_t last;
+ uint32_t count;
+};
+
+struct dns_zonemgr {
+ unsigned int magic;
+ isc_mem_t *mctx;
+ isc_refcount_t refs;
+ isc_taskmgr_t *taskmgr;
+ isc_timermgr_t *timermgr;
+ isc_nm_t *netmgr;
+ isc_taskpool_t *zonetasks;
+ isc_taskpool_t *loadtasks;
+ isc_task_t *task;
+ isc_pool_t *mctxpool;
+ isc_ratelimiter_t *checkdsrl;
+ isc_ratelimiter_t *notifyrl;
+ isc_ratelimiter_t *refreshrl;
+ isc_ratelimiter_t *startupnotifyrl;
+ isc_ratelimiter_t *startuprefreshrl;
+ isc_rwlock_t rwlock;
+ isc_mutex_t iolock;
+ isc_rwlock_t urlock;
+
+ /* Locked by rwlock. */
+ dns_zonelist_t zones;
+ dns_zonelist_t waiting_for_xfrin;
+ dns_zonelist_t xfrin_in_progress;
+
+ /* Configuration data. */
+ uint32_t transfersin;
+ uint32_t transfersperns;
+ unsigned int checkdsrate;
+ unsigned int notifyrate;
+ unsigned int startupnotifyrate;
+ unsigned int serialqueryrate;
+ unsigned int startupserialqueryrate;
+
+ /* Locked by iolock */
+ uint32_t iolimit;
+ uint32_t ioactive;
+ dns_iolist_t high;
+ dns_iolist_t low;
+
+ /* Locked by urlock. */
+ /* LRU cache */
+ struct dns_unreachable unreachable[UNREACH_CACHE_SIZE];
+
+ dns_keymgmt_t *keymgmt;
+
+ isc_tlsctx_cache_t *tlsctx_cache;
+ isc_rwlock_t tlsctx_cache_rwlock;
+};
+
+/*%
+ * Hold notify state.
+ */
+struct dns_notify {
+ unsigned int magic;
+ unsigned int flags;
+ isc_mem_t *mctx;
+ dns_zone_t *zone;
+ dns_adbfind_t *find;
+ dns_request_t *request;
+ dns_name_t ns;
+ isc_sockaddr_t dst;
+ dns_tsigkey_t *key;
+ dns_transport_t *transport;
+ ISC_LINK(dns_notify_t) link;
+ isc_event_t *event;
+};
+
+#define DNS_NOTIFY_NOSOA 0x0001U
+#define DNS_NOTIFY_STARTUP 0x0002U
+
+/*%
+ * Hold checkds state.
+ */
+struct dns_checkds {
+ unsigned int magic;
+ unsigned int flags;
+ isc_mem_t *mctx;
+ dns_zone_t *zone;
+ dns_request_t *request;
+ isc_sockaddr_t dst;
+ dns_tsigkey_t *key;
+ dns_transport_t *transport;
+ ISC_LINK(dns_checkds_t) link;
+ isc_event_t *event;
+};
+
+/*%
+ * dns_stub holds state while performing a 'stub' transfer.
+ * 'db' is the zone's 'db' or a new one if this is the initial
+ * transfer.
+ */
+
+struct dns_stub {
+ unsigned int magic;
+ isc_mem_t *mctx;
+ dns_zone_t *zone;
+ dns_db_t *db;
+ dns_dbversion_t *version;
+ atomic_uint_fast32_t pending_requests;
+};
+
+/*%
+ * Hold load state.
+ */
+struct dns_load {
+ unsigned int magic;
+ isc_mem_t *mctx;
+ dns_zone_t *zone;
+ dns_db_t *db;
+ isc_time_t loadtime;
+ dns_rdatacallbacks_t callbacks;
+};
+
+/*%
+ * Hold forward state.
+ */
+struct dns_forward {
+ unsigned int magic;
+ isc_mem_t *mctx;
+ dns_zone_t *zone;
+ isc_buffer_t *msgbuf;
+ dns_request_t *request;
+ uint32_t which;
+ isc_sockaddr_t addr;
+ dns_updatecallback_t callback;
+ void *callback_arg;
+ unsigned int options;
+ ISC_LINK(dns_forward_t) link;
+};
+
+/*%
+ * Hold IO request state.
+ */
+struct dns_io {
+ unsigned int magic;
+ dns_zonemgr_t *zmgr;
+ bool high;
+ isc_task_t *task;
+ ISC_LINK(dns_io_t) link;
+ isc_event_t *event;
+};
+
+/*%
+ * Hold state for when we are signing a zone with a new
+ * DNSKEY as result of an update.
+ */
+struct dns_signing {
+ unsigned int magic;
+ dns_db_t *db;
+ dns_dbiterator_t *dbiterator;
+ dns_secalg_t algorithm;
+ uint16_t keyid;
+ bool deleteit;
+ bool done;
+ ISC_LINK(dns_signing_t) link;
+};
+
+struct dns_nsec3chain {
+ unsigned int magic;
+ dns_db_t *db;
+ dns_dbiterator_t *dbiterator;
+ dns_rdata_nsec3param_t nsec3param;
+ unsigned char salt[255];
+ bool done;
+ bool seen_nsec;
+ bool delete_nsec;
+ bool save_delete_nsec;
+ ISC_LINK(dns_nsec3chain_t) link;
+};
+
+/*%<
+ * 'dbiterator' contains a iterator for the database. If we are creating
+ * a NSEC3 chain only the non-NSEC3 nodes will be iterated. If we are
+ * removing a NSEC3 chain then both NSEC3 and non-NSEC3 nodes will be
+ * iterated.
+ *
+ * 'nsec3param' contains the parameters of the NSEC3 chain being created
+ * or removed.
+ *
+ * 'salt' is buffer space and is referenced via 'nsec3param.salt'.
+ *
+ * 'seen_nsec' will be set to true if, while iterating the zone to create a
+ * NSEC3 chain, a NSEC record is seen.
+ *
+ * 'delete_nsec' will be set to true if, at the completion of the creation
+ * of a NSEC3 chain, 'seen_nsec' is true. If 'delete_nsec' is true then we
+ * are in the process of deleting the NSEC chain.
+ *
+ * 'save_delete_nsec' is used to store the initial state of 'delete_nsec'
+ * so it can be recovered in the event of a error.
+ */
+
+struct dns_keyfetch {
+ isc_mem_t *mctx;
+ dns_fixedname_t name;
+ dns_rdataset_t keydataset;
+ dns_rdataset_t dnskeyset;
+ dns_rdataset_t dnskeysigset;
+ dns_zone_t *zone;
+ dns_db_t *db;
+ dns_fetch_t *fetch;
+};
+
+/*%
+ * Hold state for an asynchronous load
+ */
+struct dns_asyncload {
+ dns_zone_t *zone;
+ unsigned int flags;
+ dns_zt_zoneloaded_t loaded;
+ void *loaded_arg;
+};
+
+/*%
+ * Reference to an include file encountered during loading
+ */
+struct dns_include {
+ char *name;
+ isc_time_t filetime;
+ ISC_LINK(dns_include_t) link;
+};
+
+/*
+ * These can be overridden by the -T mkeytimers option on the command
+ * line, so that we can test with shorter periods than specified in
+ * RFC 5011.
+ */
+#define HOUR 3600
+#define DAY (24 * HOUR)
+#define MONTH (30 * DAY)
+unsigned int dns_zone_mkey_hour = HOUR;
+unsigned int dns_zone_mkey_day = DAY;
+unsigned int dns_zone_mkey_month = MONTH;
+
+#define SEND_BUFFER_SIZE 2048
+
+static void
+zone_settimer(dns_zone_t *, isc_time_t *);
+static void
+cancel_refresh(dns_zone_t *);
+static void
+zone_debuglog(dns_zone_t *zone, const char *, int debuglevel, const char *msg,
+ ...) ISC_FORMAT_PRINTF(4, 5);
+static void
+notify_log(dns_zone_t *zone, int level, const char *fmt, ...)
+ ISC_FORMAT_PRINTF(3, 4);
+static void
+dnssec_log(dns_zone_t *zone, int level, const char *fmt, ...)
+ ISC_FORMAT_PRINTF(3, 4);
+static void
+queue_xfrin(dns_zone_t *zone);
+static isc_result_t
+update_one_rr(dns_db_t *db, dns_dbversion_t *ver, dns_diff_t *diff,
+ dns_diffop_t op, dns_name_t *name, dns_ttl_t ttl,
+ dns_rdata_t *rdata);
+static void
+zone_unload(dns_zone_t *zone);
+static void
+zone_expire(dns_zone_t *zone);
+static void
+zone_refresh(dns_zone_t *zone);
+static void
+zone_iattach(dns_zone_t *source, dns_zone_t **target);
+static void
+zone_idetach(dns_zone_t **zonep);
+static isc_result_t
+zone_replacedb(dns_zone_t *zone, dns_db_t *db, bool dump);
+static void
+zone_attachdb(dns_zone_t *zone, dns_db_t *db);
+static void
+zone_detachdb(dns_zone_t *zone);
+static void
+zone_catz_enable(dns_zone_t *zone, dns_catz_zones_t *catzs);
+static void
+zone_catz_disable(dns_zone_t *zone);
+static isc_result_t
+default_journal(dns_zone_t *zone);
+static void
+zone_xfrdone(dns_zone_t *zone, isc_result_t result);
+static isc_result_t
+zone_postload(dns_zone_t *zone, dns_db_t *db, isc_time_t loadtime,
+ isc_result_t result);
+static void
+zone_needdump(dns_zone_t *zone, unsigned int delay);
+static void
+zone_shutdown(isc_task_t *, isc_event_t *);
+static void
+zone_loaddone(void *arg, isc_result_t result);
+static isc_result_t
+zone_startload(dns_db_t *db, dns_zone_t *zone, isc_time_t loadtime);
+static void
+zone_namerd_tostr(dns_zone_t *zone, char *buf, size_t length);
+static void
+zone_name_tostr(dns_zone_t *zone, char *buf, size_t length);
+static void
+zone_rdclass_tostr(dns_zone_t *zone, char *buf, size_t length);
+static void
+zone_viewname_tostr(dns_zone_t *zone, char *buf, size_t length);
+static isc_result_t
+zone_send_secureserial(dns_zone_t *zone, uint32_t serial);
+static void
+refresh_callback(isc_task_t *, isc_event_t *);
+static void
+stub_callback(isc_task_t *, isc_event_t *);
+static void
+queue_soa_query(dns_zone_t *zone);
+static void
+soa_query(isc_task_t *, isc_event_t *);
+static void
+ns_query(dns_zone_t *zone, dns_rdataset_t *soardataset, dns_stub_t *stub);
+static int
+message_count(dns_message_t *msg, dns_section_t section, dns_rdatatype_t type);
+static void
+checkds_cancel(dns_zone_t *zone);
+static void
+checkds_send(dns_zone_t *zone);
+static isc_result_t
+checkds_createmessage(dns_zone_t *zone, dns_message_t **messagep);
+static void
+checkds_done(isc_task_t *task, isc_event_t *event);
+static void
+checkds_send_toaddr(isc_task_t *task, isc_event_t *event);
+static void
+notify_cancel(dns_zone_t *zone);
+static void
+notify_find_address(dns_notify_t *notify);
+static void
+notify_send(dns_notify_t *notify);
+static isc_result_t
+notify_createmessage(dns_zone_t *zone, unsigned int flags,
+ dns_message_t **messagep);
+static void
+notify_done(isc_task_t *task, isc_event_t *event);
+static void
+notify_send_toaddr(isc_task_t *task, isc_event_t *event);
+static isc_result_t
+zone_dump(dns_zone_t *, bool);
+static void
+got_transfer_quota(isc_task_t *task, isc_event_t *event);
+static isc_result_t
+zmgr_start_xfrin_ifquota(dns_zonemgr_t *zmgr, dns_zone_t *zone);
+static void
+zmgr_resume_xfrs(dns_zonemgr_t *zmgr, bool multi);
+static void
+zonemgr_free(dns_zonemgr_t *zmgr);
+static isc_result_t
+zonemgr_getio(dns_zonemgr_t *zmgr, bool high, isc_task_t *task,
+ isc_taskaction_t action, void *arg, dns_io_t **iop);
+static void
+zonemgr_putio(dns_io_t **iop);
+static void
+zonemgr_cancelio(dns_io_t *io);
+static void
+rss_post(dns_zone_t *, isc_event_t *);
+
+static isc_result_t
+zone_get_from_db(dns_zone_t *zone, dns_db_t *db, unsigned int *nscount,
+ unsigned int *soacount, uint32_t *soattl, uint32_t *serial,
+ uint32_t *refresh, uint32_t *retry, uint32_t *expire,
+ uint32_t *minimum, unsigned int *errors);
+
+static void
+zone_freedbargs(dns_zone_t *zone);
+static void
+forward_callback(isc_task_t *task, isc_event_t *event);
+static void
+zone_saveunique(dns_zone_t *zone, const char *path, const char *templat);
+static void
+zone_maintenance(dns_zone_t *zone);
+static void
+zone_notify(dns_zone_t *zone, isc_time_t *now);
+static void
+dump_done(void *arg, isc_result_t result);
+static isc_result_t
+zone_signwithkey(dns_zone_t *zone, dns_secalg_t algorithm, uint16_t keyid,
+ bool deleteit);
+static isc_result_t
+delete_nsec(dns_db_t *db, dns_dbversion_t *ver, dns_dbnode_t *node,
+ dns_name_t *name, dns_diff_t *diff);
+static void
+zone_rekey(dns_zone_t *zone);
+static isc_result_t
+zone_send_securedb(dns_zone_t *zone, dns_db_t *db);
+static dns_ttl_t
+zone_nsecttl(dns_zone_t *zone);
+static void
+setrl(isc_ratelimiter_t *rl, unsigned int *rate, unsigned int value);
+static void
+zone_journal_compact(dns_zone_t *zone, dns_db_t *db, uint32_t serial);
+static isc_result_t
+zone_journal_rollforward(dns_zone_t *zone, dns_db_t *db, bool *needdump,
+ bool *fixjournal);
+
+#define ENTER zone_debuglog(zone, me, 1, "enter")
+
+static void
+zmgr_tlsctx_attach(dns_zonemgr_t *zmgr, isc_tlsctx_cache_t **ptlsctx_cache);
+/*%<
+ * Attach to TLS client context cache used for zone transfers via
+ * encrypted transports (e.g. XoT).
+ *
+ * The obtained reference needs to be detached by a call to
+ * 'isc_tlsctx_cache_detach()' when not needed anymore.
+ *
+ * Requires:
+ *\li 'zmgr' is a valid zone manager.
+ *\li 'ptlsctx_cache' is not 'NULL' and points to 'NULL'.
+ */
+
+static const unsigned int dbargc_default = 1;
+static const char *dbargv_default[] = { "rbt" };
+
+#define DNS_ZONE_JITTER_ADD(a, b, c) \
+ do { \
+ isc_interval_t _i; \
+ uint32_t _j; \
+ _j = (b)-isc_random_uniform((b) / 4); \
+ isc_interval_set(&_i, _j, 0); \
+ if (isc_time_add((a), &_i, (c)) != ISC_R_SUCCESS) { \
+ dns_zone_log(zone, ISC_LOG_WARNING, \
+ "epoch approaching: upgrade required: " \
+ "now + %s failed", \
+ #b); \
+ isc_interval_set(&_i, _j / 2, 0); \
+ (void)isc_time_add((a), &_i, (c)); \
+ } \
+ } while (0)
+
+#define DNS_ZONE_TIME_ADD(a, b, c) \
+ do { \
+ isc_interval_t _i; \
+ isc_interval_set(&_i, (b), 0); \
+ if (isc_time_add((a), &_i, (c)) != ISC_R_SUCCESS) { \
+ dns_zone_log(zone, ISC_LOG_WARNING, \
+ "epoch approaching: upgrade required: " \
+ "now + %s failed", \
+ #b); \
+ isc_interval_set(&_i, (b) / 2, 0); \
+ (void)isc_time_add((a), &_i, (c)); \
+ } \
+ } while (0)
+
+typedef struct nsec3param nsec3param_t;
+struct nsec3param {
+ dns_rdata_nsec3param_t rdata;
+ unsigned char data[DNS_NSEC3PARAM_BUFFERSIZE + 1];
+ unsigned int length;
+ bool nsec;
+ bool replace;
+ bool resalt;
+ bool lookup;
+ ISC_LINK(nsec3param_t) link;
+};
+typedef ISC_LIST(nsec3param_t) nsec3paramlist_t;
+struct np3event {
+ isc_event_t event;
+ nsec3param_t params;
+};
+
+struct ssevent {
+ isc_event_t event;
+ uint32_t serial;
+};
+
+struct stub_cb_args {
+ dns_stub_t *stub;
+ dns_tsigkey_t *tsig_key;
+ uint16_t udpsize;
+ int timeout;
+ bool reqnsid;
+};
+
+struct stub_glue_request {
+ dns_request_t *request;
+ dns_name_t name;
+ struct stub_cb_args *args;
+ bool ipv4;
+};
+
+/*%
+ * Increment resolver-related statistics counters. Zone must be locked.
+ */
+static void
+inc_stats(dns_zone_t *zone, isc_statscounter_t counter) {
+ if (zone->stats != NULL) {
+ isc_stats_increment(zone->stats, counter);
+ }
+}
+
+/***
+ *** Public functions.
+ ***/
+
+isc_result_t
+dns_zone_create(dns_zone_t **zonep, isc_mem_t *mctx) {
+ isc_result_t result;
+ isc_time_t now;
+ dns_zone_t *zone = NULL;
+ dns_zone_t z = { .masterformat = dns_masterformat_none,
+ .journalsize = -1,
+ .rdclass = dns_rdataclass_none,
+ .type = dns_zone_none,
+ .refresh = DNS_ZONE_DEFAULTREFRESH,
+ .retry = DNS_ZONE_DEFAULTRETRY,
+ .maxrefresh = DNS_ZONE_MAXREFRESH,
+ .minrefresh = DNS_ZONE_MINREFRESH,
+ .maxretry = DNS_ZONE_MAXRETRY,
+ .minretry = DNS_ZONE_MINRETRY,
+ .notifytype = dns_notifytype_yes,
+ .zero_no_soa_ttl = true,
+ .check_names = dns_severity_ignore,
+ .idlein = DNS_DEFAULT_IDLEIN,
+ .idleout = DNS_DEFAULT_IDLEOUT,
+ .maxxfrin = MAX_XFER_TIME,
+ .maxxfrout = MAX_XFER_TIME,
+ .sigvalidityinterval = 30 * 24 * 3600,
+ .sigresigninginterval = 7 * 24 * 3600,
+ .statlevel = dns_zonestat_none,
+ .notifydelay = 5,
+ .signatures = 10,
+ .nodes = 100,
+ .privatetype = (dns_rdatatype_t)0xffffU,
+ .rpz_num = DNS_RPZ_INVALID_NUM,
+ .requestixfr = true,
+ .ixfr_ratio = 100,
+ .requestexpire = true,
+ .updatemethod = dns_updatemethod_increment,
+ .magic = ZONE_MAGIC };
+
+ REQUIRE(zonep != NULL && *zonep == NULL);
+ REQUIRE(mctx != NULL);
+
+ TIME_NOW(&now);
+ zone = isc_mem_get(mctx, sizeof(*zone));
+ *zone = z;
+
+ zone->mctx = NULL;
+ isc_mem_attach(mctx, &zone->mctx);
+ isc_mutex_init(&zone->lock);
+ ZONEDB_INITLOCK(&zone->dblock);
+ /* XXX MPA check that all elements are initialised */
+#ifdef DNS_ZONE_CHECKLOCK
+ zone->locked = false;
+#endif /* ifdef DNS_ZONE_CHECKLOCK */
+
+ zone->notifytime = now;
+
+ ISC_LINK_INIT(zone, link);
+ isc_refcount_init(&zone->erefs, 1);
+ isc_refcount_init(&zone->irefs, 0);
+ dns_name_init(&zone->origin, NULL);
+ ISC_LIST_INIT(zone->includes);
+ ISC_LIST_INIT(zone->newincludes);
+ atomic_init(&zone->flags, 0);
+ atomic_init(&zone->options, 0);
+ atomic_init(&zone->keyopts, 0);
+ isc_time_settoepoch(&zone->expiretime);
+ isc_time_settoepoch(&zone->refreshtime);
+ isc_time_settoepoch(&zone->dumptime);
+ isc_time_settoepoch(&zone->loadtime);
+ isc_time_settoepoch(&zone->resigntime);
+ isc_time_settoepoch(&zone->keywarntime);
+ isc_time_settoepoch(&zone->signingtime);
+ isc_time_settoepoch(&zone->nsec3chaintime);
+ isc_time_settoepoch(&zone->refreshkeytime);
+ ISC_LIST_INIT(zone->notifies);
+ ISC_LIST_INIT(zone->checkds_requests);
+ isc_sockaddr_any(&zone->notifysrc4);
+ isc_sockaddr_any6(&zone->notifysrc6);
+ isc_sockaddr_any(&zone->parentalsrc4);
+ isc_sockaddr_any6(&zone->parentalsrc6);
+ isc_sockaddr_any(&zone->xfrsource4);
+ isc_sockaddr_any6(&zone->xfrsource6);
+ isc_sockaddr_any(&zone->altxfrsource4);
+ isc_sockaddr_any6(&zone->altxfrsource6);
+ ISC_LINK_INIT(zone, statelink);
+ ISC_LIST_INIT(zone->signing);
+ ISC_LIST_INIT(zone->nsec3chain);
+ ISC_LIST_INIT(zone->setnsec3param_queue);
+ ISC_LIST_INIT(zone->forwards);
+ ISC_LIST_INIT(zone->rss_events);
+ ISC_LIST_INIT(zone->rss_post);
+
+ result = isc_stats_create(mctx, &zone->gluecachestats,
+ dns_gluecachestatscounter_max);
+ if (result != ISC_R_SUCCESS) {
+ goto free_refs;
+ }
+
+ /* Must be after magic is set. */
+ dns_zone_setdbtype(zone, dbargc_default, dbargv_default);
+
+ ISC_EVENT_INIT(&zone->ctlevent, sizeof(zone->ctlevent), 0, NULL,
+ DNS_EVENT_ZONECONTROL, zone_shutdown, zone, zone, NULL,
+ NULL);
+ *zonep = zone;
+ return (ISC_R_SUCCESS);
+
+free_refs:
+ isc_refcount_decrement0(&zone->erefs);
+ isc_refcount_destroy(&zone->erefs);
+ isc_refcount_destroy(&zone->irefs);
+ ZONEDB_DESTROYLOCK(&zone->dblock);
+ isc_mutex_destroy(&zone->lock);
+ isc_mem_putanddetach(&zone->mctx, zone, sizeof(*zone));
+ return (result);
+}
+
+static void
+clear_keylist(dns_dnsseckeylist_t *list, isc_mem_t *mctx) {
+ dns_dnsseckey_t *key;
+ while (!ISC_LIST_EMPTY(*list)) {
+ key = ISC_LIST_HEAD(*list);
+ ISC_LIST_UNLINK(*list, key, link);
+ dns_dnsseckey_destroy(mctx, &key);
+ }
+}
+
+/*
+ * Free a zone. Because we require that there be no more
+ * outstanding events or references, no locking is necessary.
+ */
+static void
+zone_free(dns_zone_t *zone) {
+ dns_signing_t *signing;
+ dns_nsec3chain_t *nsec3chain;
+ isc_event_t *event;
+ dns_include_t *include;
+
+ REQUIRE(DNS_ZONE_VALID(zone));
+ isc_refcount_destroy(&zone->erefs);
+ isc_refcount_destroy(&zone->irefs);
+ REQUIRE(!LOCKED_ZONE(zone));
+ REQUIRE(zone->timer == NULL);
+ REQUIRE(zone->zmgr == NULL);
+
+ /*
+ * Managed objects. Order is important.
+ */
+ if (zone->request != NULL) {
+ dns_request_destroy(&zone->request); /* XXXMPA */
+ }
+ INSIST(zone->readio == NULL);
+ INSIST(zone->statelist == NULL);
+ INSIST(zone->writeio == NULL);
+ INSIST(zone->view == NULL);
+ INSIST(zone->prev_view == NULL);
+
+ if (zone->task != NULL) {
+ isc_task_detach(&zone->task);
+ }
+ if (zone->loadtask != NULL) {
+ isc_task_detach(&zone->loadtask);
+ }
+
+ /* Unmanaged objects */
+ while (!ISC_LIST_EMPTY(zone->setnsec3param_queue)) {
+ event = ISC_LIST_HEAD(zone->setnsec3param_queue);
+ ISC_LIST_UNLINK(zone->setnsec3param_queue, event, ev_link);
+ isc_event_free(&event);
+ }
+ while (!ISC_LIST_EMPTY(zone->rss_post)) {
+ event = ISC_LIST_HEAD(zone->rss_post);
+ ISC_LIST_UNLINK(zone->rss_post, event, ev_link);
+ isc_event_free(&event);
+ }
+ for (signing = ISC_LIST_HEAD(zone->signing); signing != NULL;
+ signing = ISC_LIST_HEAD(zone->signing))
+ {
+ ISC_LIST_UNLINK(zone->signing, signing, link);
+ dns_db_detach(&signing->db);
+ dns_dbiterator_destroy(&signing->dbiterator);
+ isc_mem_put(zone->mctx, signing, sizeof *signing);
+ }
+ for (nsec3chain = ISC_LIST_HEAD(zone->nsec3chain); nsec3chain != NULL;
+ nsec3chain = ISC_LIST_HEAD(zone->nsec3chain))
+ {
+ ISC_LIST_UNLINK(zone->nsec3chain, nsec3chain, link);
+ dns_db_detach(&nsec3chain->db);
+ dns_dbiterator_destroy(&nsec3chain->dbiterator);
+ isc_mem_put(zone->mctx, nsec3chain, sizeof *nsec3chain);
+ }
+ for (include = ISC_LIST_HEAD(zone->includes); include != NULL;
+ include = ISC_LIST_HEAD(zone->includes))
+ {
+ ISC_LIST_UNLINK(zone->includes, include, link);
+ isc_mem_free(zone->mctx, include->name);
+ isc_mem_put(zone->mctx, include, sizeof *include);
+ }
+ for (include = ISC_LIST_HEAD(zone->newincludes); include != NULL;
+ include = ISC_LIST_HEAD(zone->newincludes))
+ {
+ ISC_LIST_UNLINK(zone->newincludes, include, link);
+ isc_mem_free(zone->mctx, include->name);
+ isc_mem_put(zone->mctx, include, sizeof *include);
+ }
+ if (zone->masterfile != NULL) {
+ isc_mem_free(zone->mctx, zone->masterfile);
+ }
+ zone->masterfile = NULL;
+ if (zone->keydirectory != NULL) {
+ isc_mem_free(zone->mctx, zone->keydirectory);
+ }
+ zone->keydirectory = NULL;
+ if (zone->kasp != NULL) {
+ dns_kasp_detach(&zone->kasp);
+ }
+ if (!ISC_LIST_EMPTY(zone->checkds_ok)) {
+ clear_keylist(&zone->checkds_ok, zone->mctx);
+ }
+
+ zone->journalsize = -1;
+ if (zone->journal != NULL) {
+ isc_mem_free(zone->mctx, zone->journal);
+ }
+ zone->journal = NULL;
+ if (zone->stats != NULL) {
+ isc_stats_detach(&zone->stats);
+ }
+ if (zone->requeststats != NULL) {
+ isc_stats_detach(&zone->requeststats);
+ }
+ if (zone->rcvquerystats != NULL) {
+ dns_stats_detach(&zone->rcvquerystats);
+ }
+ if (zone->dnssecsignstats != NULL) {
+ dns_stats_detach(&zone->dnssecsignstats);
+ }
+ if (zone->db != NULL) {
+ zone_detachdb(zone);
+ }
+ if (zone->rpzs != NULL) {
+ REQUIRE(zone->rpz_num < zone->rpzs->p.num_zones);
+ dns_rpz_detach_rpzs(&zone->rpzs);
+ zone->rpz_num = DNS_RPZ_INVALID_NUM;
+ }
+ if (zone->catzs != NULL) {
+ dns_catz_detach_catzs(&zone->catzs);
+ }
+ zone_freedbargs(zone);
+ dns_zone_setparentals(zone, NULL, NULL, NULL, 0);
+ dns_zone_setprimaries(zone, NULL, NULL, NULL, 0);
+ dns_zone_setalsonotify(zone, NULL, NULL, NULL, 0);
+
+ zone->check_names = dns_severity_ignore;
+ if (zone->update_acl != NULL) {
+ dns_acl_detach(&zone->update_acl);
+ }
+ if (zone->forward_acl != NULL) {
+ dns_acl_detach(&zone->forward_acl);
+ }
+ if (zone->notify_acl != NULL) {
+ dns_acl_detach(&zone->notify_acl);
+ }
+ if (zone->query_acl != NULL) {
+ dns_acl_detach(&zone->query_acl);
+ }
+ if (zone->queryon_acl != NULL) {
+ dns_acl_detach(&zone->queryon_acl);
+ }
+ if (zone->xfr_acl != NULL) {
+ dns_acl_detach(&zone->xfr_acl);
+ }
+ if (dns_name_dynamic(&zone->origin)) {
+ dns_name_free(&zone->origin, zone->mctx);
+ }
+ if (zone->strnamerd != NULL) {
+ isc_mem_free(zone->mctx, zone->strnamerd);
+ }
+ if (zone->strname != NULL) {
+ isc_mem_free(zone->mctx, zone->strname);
+ }
+ if (zone->strrdclass != NULL) {
+ isc_mem_free(zone->mctx, zone->strrdclass);
+ }
+ if (zone->strviewname != NULL) {
+ isc_mem_free(zone->mctx, zone->strviewname);
+ }
+ if (zone->ssutable != NULL) {
+ dns_ssutable_detach(&zone->ssutable);
+ }
+ if (zone->gluecachestats != NULL) {
+ isc_stats_detach(&zone->gluecachestats);
+ }
+
+ /* last stuff */
+ ZONEDB_DESTROYLOCK(&zone->dblock);
+ isc_mutex_destroy(&zone->lock);
+ zone->magic = 0;
+ isc_mem_putanddetach(&zone->mctx, zone, sizeof(*zone));
+}
+
+/*
+ * Returns true iff this the signed side of an inline-signing zone.
+ * Caller should hold zone lock.
+ */
+static bool
+inline_secure(dns_zone_t *zone) {
+ REQUIRE(DNS_ZONE_VALID(zone));
+ if (zone->raw != NULL) {
+ return (true);
+ }
+ return (false);
+}
+
+/*
+ * Returns true iff this the unsigned side of an inline-signing zone
+ * Caller should hold zone lock.
+ */
+static bool
+inline_raw(dns_zone_t *zone) {
+ REQUIRE(DNS_ZONE_VALID(zone));
+ if (zone->secure != NULL) {
+ return (true);
+ }
+ return (false);
+}
+
+/*
+ * Single shot.
+ */
+void
+dns_zone_setclass(dns_zone_t *zone, dns_rdataclass_t rdclass) {
+ char namebuf[1024];
+
+ REQUIRE(DNS_ZONE_VALID(zone));
+ REQUIRE(rdclass != dns_rdataclass_none);
+
+ /*
+ * Test and set.
+ */
+ LOCK_ZONE(zone);
+ INSIST(zone != zone->raw);
+ REQUIRE(zone->rdclass == dns_rdataclass_none ||
+ zone->rdclass == rdclass);
+ zone->rdclass = rdclass;
+
+ if (zone->strnamerd != NULL) {
+ isc_mem_free(zone->mctx, zone->strnamerd);
+ }
+ if (zone->strrdclass != NULL) {
+ isc_mem_free(zone->mctx, zone->strrdclass);
+ }
+
+ zone_namerd_tostr(zone, namebuf, sizeof namebuf);
+ zone->strnamerd = isc_mem_strdup(zone->mctx, namebuf);
+ zone_rdclass_tostr(zone, namebuf, sizeof namebuf);
+ zone->strrdclass = isc_mem_strdup(zone->mctx, namebuf);
+
+ if (inline_secure(zone)) {
+ dns_zone_setclass(zone->raw, rdclass);
+ }
+ UNLOCK_ZONE(zone);
+}
+
+dns_rdataclass_t
+dns_zone_getclass(dns_zone_t *zone) {
+ REQUIRE(DNS_ZONE_VALID(zone));
+
+ return (zone->rdclass);
+}
+
+void
+dns_zone_setnotifytype(dns_zone_t *zone, dns_notifytype_t notifytype) {
+ REQUIRE(DNS_ZONE_VALID(zone));
+
+ LOCK_ZONE(zone);
+ zone->notifytype = notifytype;
+ UNLOCK_ZONE(zone);
+}
+
+isc_result_t
+dns_zone_getserial(dns_zone_t *zone, uint32_t *serialp) {
+ isc_result_t result;
+ unsigned int soacount;
+
+ REQUIRE(DNS_ZONE_VALID(zone));
+ REQUIRE(serialp != NULL);
+
+ LOCK_ZONE(zone);
+ ZONEDB_LOCK(&zone->dblock, isc_rwlocktype_read);
+ if (zone->db != NULL) {
+ result = zone_get_from_db(zone, zone->db, NULL, &soacount, NULL,
+ serialp, NULL, NULL, NULL, NULL,
+ NULL);
+ if (result == ISC_R_SUCCESS && soacount == 0) {
+ result = ISC_R_FAILURE;
+ }
+ } else {
+ result = DNS_R_NOTLOADED;
+ }
+ ZONEDB_UNLOCK(&zone->dblock, isc_rwlocktype_read);
+ UNLOCK_ZONE(zone);
+
+ return (result);
+}
+
+/*
+ * Single shot.
+ */
+void
+dns_zone_settype(dns_zone_t *zone, dns_zonetype_t type) {
+ char namebuf[1024];
+
+ REQUIRE(DNS_ZONE_VALID(zone));
+ REQUIRE(type != dns_zone_none);
+
+ /*
+ * Test and set.
+ */
+ LOCK_ZONE(zone);
+ REQUIRE(zone->type == dns_zone_none || zone->type == type);
+ zone->type = type;
+
+ if (zone->strnamerd != NULL) {
+ isc_mem_free(zone->mctx, zone->strnamerd);
+ }
+
+ zone_namerd_tostr(zone, namebuf, sizeof namebuf);
+ zone->strnamerd = isc_mem_strdup(zone->mctx, namebuf);
+ UNLOCK_ZONE(zone);
+}
+
+static void
+zone_freedbargs(dns_zone_t *zone) {
+ unsigned int i;
+
+ /* Free the old database argument list. */
+ if (zone->db_argv != NULL) {
+ for (i = 0; i < zone->db_argc; i++) {
+ isc_mem_free(zone->mctx, zone->db_argv[i]);
+ }
+ isc_mem_put(zone->mctx, zone->db_argv,
+ zone->db_argc * sizeof(*zone->db_argv));
+ }
+ zone->db_argc = 0;
+ zone->db_argv = NULL;
+}
+
+isc_result_t
+dns_zone_getdbtype(dns_zone_t *zone, char ***argv, isc_mem_t *mctx) {
+ size_t size = 0;
+ unsigned int i;
+ isc_result_t result = ISC_R_SUCCESS;
+ void *mem;
+ char **tmp, *tmp2, *base;
+
+ REQUIRE(DNS_ZONE_VALID(zone));
+ REQUIRE(argv != NULL && *argv == NULL);
+
+ LOCK_ZONE(zone);
+ size = (zone->db_argc + 1) * sizeof(char *);
+ for (i = 0; i < zone->db_argc; i++) {
+ size += strlen(zone->db_argv[i]) + 1;
+ }
+ mem = isc_mem_allocate(mctx, size);
+ {
+ tmp = mem;
+ tmp2 = mem;
+ base = mem;
+ tmp2 += (zone->db_argc + 1) * sizeof(char *);
+ for (i = 0; i < zone->db_argc; i++) {
+ *tmp++ = tmp2;
+ strlcpy(tmp2, zone->db_argv[i], size - (tmp2 - base));
+ tmp2 += strlen(tmp2) + 1;
+ }
+ *tmp = NULL;
+ }
+ UNLOCK_ZONE(zone);
+ *argv = mem;
+ return (result);
+}
+
+void
+dns_zone_setdbtype(dns_zone_t *zone, unsigned int dbargc,
+ const char *const *dbargv) {
+ char **argv = NULL;
+ unsigned int i;
+
+ REQUIRE(DNS_ZONE_VALID(zone));
+ REQUIRE(dbargc >= 1);
+ REQUIRE(dbargv != NULL);
+
+ LOCK_ZONE(zone);
+
+ /* Set up a new database argument list. */
+ argv = isc_mem_get(zone->mctx, dbargc * sizeof(*argv));
+ for (i = 0; i < dbargc; i++) {
+ argv[i] = NULL;
+ }
+ for (i = 0; i < dbargc; i++) {
+ argv[i] = isc_mem_strdup(zone->mctx, dbargv[i]);
+ }
+
+ /* Free the old list. */
+ zone_freedbargs(zone);
+
+ zone->db_argc = dbargc;
+ zone->db_argv = argv;
+
+ UNLOCK_ZONE(zone);
+}
+
+static void
+dns_zone_setview_helper(dns_zone_t *zone, dns_view_t *view) {
+ char namebuf[1024];
+
+ if (zone->prev_view == NULL && zone->view != NULL) {
+ dns_view_weakattach(zone->view, &zone->prev_view);
+ }
+
+ INSIST(zone != zone->raw);
+ if (zone->view != NULL) {
+ dns_view_sfd_del(zone->view, &zone->origin);
+ dns_view_weakdetach(&zone->view);
+ }
+ dns_view_weakattach(view, &zone->view);
+ dns_view_sfd_add(view, &zone->origin);
+
+ if (zone->strviewname != NULL) {
+ isc_mem_free(zone->mctx, zone->strviewname);
+ }
+ if (zone->strnamerd != NULL) {
+ isc_mem_free(zone->mctx, zone->strnamerd);
+ }
+
+ zone_namerd_tostr(zone, namebuf, sizeof namebuf);
+ zone->strnamerd = isc_mem_strdup(zone->mctx, namebuf);
+ zone_viewname_tostr(zone, namebuf, sizeof namebuf);
+ zone->strviewname = isc_mem_strdup(zone->mctx, namebuf);
+
+ if (inline_secure(zone)) {
+ dns_zone_setview(zone->raw, view);
+ }
+}
+
+void
+dns_zone_setview(dns_zone_t *zone, dns_view_t *view) {
+ REQUIRE(DNS_ZONE_VALID(zone));
+
+ LOCK_ZONE(zone);
+ dns_zone_setview_helper(zone, view);
+ UNLOCK_ZONE(zone);
+}
+
+dns_view_t *
+dns_zone_getview(dns_zone_t *zone) {
+ REQUIRE(DNS_ZONE_VALID(zone));
+
+ return (zone->view);
+}
+
+void
+dns_zone_setviewcommit(dns_zone_t *zone) {
+ REQUIRE(DNS_ZONE_VALID(zone));
+
+ LOCK_ZONE(zone);
+ if (zone->prev_view != NULL) {
+ dns_view_weakdetach(&zone->prev_view);
+ }
+ if (inline_secure(zone)) {
+ dns_zone_setviewcommit(zone->raw);
+ }
+ UNLOCK_ZONE(zone);
+}
+
+void
+dns_zone_setviewrevert(dns_zone_t *zone) {
+ REQUIRE(DNS_ZONE_VALID(zone));
+
+ LOCK_ZONE(zone);
+ if (zone->prev_view != NULL) {
+ dns_zone_setview_helper(zone, zone->prev_view);
+ dns_view_weakdetach(&zone->prev_view);
+ }
+ if (zone->catzs != NULL) {
+ zone_catz_enable(zone, zone->catzs);
+ }
+ if (inline_secure(zone)) {
+ dns_zone_setviewrevert(zone->raw);
+ }
+ UNLOCK_ZONE(zone);
+}
+
+isc_result_t
+dns_zone_setorigin(dns_zone_t *zone, const dns_name_t *origin) {
+ isc_result_t result = ISC_R_SUCCESS;
+ char namebuf[1024];
+
+ REQUIRE(DNS_ZONE_VALID(zone));
+ REQUIRE(origin != NULL);
+
+ LOCK_ZONE(zone);
+ INSIST(zone != zone->raw);
+ if (dns_name_dynamic(&zone->origin)) {
+ dns_name_free(&zone->origin, zone->mctx);
+ dns_name_init(&zone->origin, NULL);
+ }
+ dns_name_dup(origin, zone->mctx, &zone->origin);
+
+ if (zone->strnamerd != NULL) {
+ isc_mem_free(zone->mctx, zone->strnamerd);
+ }
+ if (zone->strname != NULL) {
+ isc_mem_free(zone->mctx, zone->strname);
+ }
+
+ zone_namerd_tostr(zone, namebuf, sizeof namebuf);
+ zone->strnamerd = isc_mem_strdup(zone->mctx, namebuf);
+ zone_name_tostr(zone, namebuf, sizeof namebuf);
+ zone->strname = isc_mem_strdup(zone->mctx, namebuf);
+
+ if (inline_secure(zone)) {
+ result = dns_zone_setorigin(zone->raw, origin);
+ }
+ UNLOCK_ZONE(zone);
+ return (result);
+}
+
+static isc_result_t
+dns_zone_setstring(dns_zone_t *zone, char **field, const char *value) {
+ char *copy;
+
+ if (value != NULL) {
+ copy = isc_mem_strdup(zone->mctx, value);
+ } else {
+ copy = NULL;
+ }
+
+ if (*field != NULL) {
+ isc_mem_free(zone->mctx, *field);
+ }
+
+ *field = copy;
+ return (ISC_R_SUCCESS);
+}
+
+isc_result_t
+dns_zone_setfile(dns_zone_t *zone, const char *file, dns_masterformat_t format,
+ const dns_master_style_t *style) {
+ isc_result_t result = ISC_R_SUCCESS;
+
+ REQUIRE(DNS_ZONE_VALID(zone));
+ REQUIRE(zone->stream == NULL);
+
+ LOCK_ZONE(zone);
+ result = dns_zone_setstring(zone, &zone->masterfile, file);
+ if (result == ISC_R_SUCCESS) {
+ zone->masterformat = format;
+ if (format == dns_masterformat_text) {
+ zone->masterstyle = style;
+ }
+ result = default_journal(zone);
+ }
+ UNLOCK_ZONE(zone);
+
+ return (result);
+}
+
+const char *
+dns_zone_getfile(dns_zone_t *zone) {
+ REQUIRE(DNS_ZONE_VALID(zone));
+
+ return (zone->masterfile);
+}
+
+isc_result_t
+dns_zone_setstream(dns_zone_t *zone, const FILE *stream,
+ dns_masterformat_t format, const dns_master_style_t *style) {
+ isc_result_t result = ISC_R_SUCCESS;
+
+ REQUIRE(DNS_ZONE_VALID(zone));
+ REQUIRE(stream != NULL);
+ REQUIRE(zone->masterfile == NULL);
+
+ LOCK_ZONE(zone);
+ zone->stream = stream;
+ zone->masterformat = format;
+ if (format == dns_masterformat_text) {
+ zone->masterstyle = style;
+ }
+ result = default_journal(zone);
+ UNLOCK_ZONE(zone);
+
+ return (result);
+}
+
+dns_ttl_t
+dns_zone_getmaxttl(dns_zone_t *zone) {
+ REQUIRE(DNS_ZONE_VALID(zone));
+
+ return (zone->maxttl);
+}
+
+void
+dns_zone_setmaxttl(dns_zone_t *zone, dns_ttl_t maxttl) {
+ REQUIRE(DNS_ZONE_VALID(zone));
+
+ LOCK_ZONE(zone);
+ if (maxttl != 0) {
+ DNS_ZONE_SETOPTION(zone, DNS_ZONEOPT_CHECKTTL);
+ } else {
+ DNS_ZONE_CLROPTION(zone, DNS_ZONEOPT_CHECKTTL);
+ }
+ zone->maxttl = maxttl;
+ UNLOCK_ZONE(zone);
+
+ return;
+}
+
+static isc_result_t
+default_journal(dns_zone_t *zone) {
+ isc_result_t result;
+ char *journal;
+
+ REQUIRE(DNS_ZONE_VALID(zone));
+ REQUIRE(LOCKED_ZONE(zone));
+
+ if (zone->masterfile != NULL) {
+ /* Calculate string length including '\0'. */
+ int len = strlen(zone->masterfile) + sizeof(".jnl");
+ journal = isc_mem_allocate(zone->mctx, len);
+ strlcpy(journal, zone->masterfile, len);
+ strlcat(journal, ".jnl", len);
+ } else {
+ journal = NULL;
+ }
+ result = dns_zone_setstring(zone, &zone->journal, journal);
+ if (journal != NULL) {
+ isc_mem_free(zone->mctx, journal);
+ }
+ return (result);
+}
+
+isc_result_t
+dns_zone_setjournal(dns_zone_t *zone, const char *myjournal) {
+ isc_result_t result = ISC_R_SUCCESS;
+
+ REQUIRE(DNS_ZONE_VALID(zone));
+
+ LOCK_ZONE(zone);
+ result = dns_zone_setstring(zone, &zone->journal, myjournal);
+ UNLOCK_ZONE(zone);
+
+ return (result);
+}
+
+char *
+dns_zone_getjournal(dns_zone_t *zone) {
+ REQUIRE(DNS_ZONE_VALID(zone));
+
+ return (zone->journal);
+}
+
+/*
+ * Return true iff the zone is "dynamic", in the sense that the zone's
+ * master file (if any) is written by the server, rather than being
+ * updated manually and read by the server.
+ *
+ * This is true for secondary zones, mirror zones, stub zones, key zones,
+ * and zones that allow dynamic updates either by having an update
+ * policy ("ssutable") or an "allow-update" ACL with a value other than
+ * exactly "{ none; }".
+ */
+bool
+dns_zone_isdynamic(dns_zone_t *zone, bool ignore_freeze) {
+ REQUIRE(DNS_ZONE_VALID(zone));
+
+ if (zone->type == dns_zone_secondary || zone->type == dns_zone_mirror ||
+ zone->type == dns_zone_stub || zone->type == dns_zone_key ||
+ (zone->type == dns_zone_redirect && zone->primaries != NULL))
+ {
+ return (true);
+ }
+
+ /* Inline zones are always dynamic. */
+ if (zone->type == dns_zone_primary && zone->raw != NULL) {
+ return (true);
+ }
+
+ /* If !ignore_freeze, we need check whether updates are disabled. */
+ if (zone->type == dns_zone_primary &&
+ (!zone->update_disabled || ignore_freeze) &&
+ ((zone->ssutable != NULL) ||
+ (zone->update_acl != NULL && !dns_acl_isnone(zone->update_acl))))
+ {
+ return (true);
+ }
+
+ return (false);
+}
+
+/*
+ * Set the response policy index and information for a zone.
+ */
+isc_result_t
+dns_zone_rpz_enable(dns_zone_t *zone, dns_rpz_zones_t *rpzs,
+ dns_rpz_num_t rpz_num) {
+ /*
+ * Only RBTDB zones can be used for response policy zones,
+ * because only they have the code to create the summary data.
+ * Only zones that are loaded instead of mmap()ed create the
+ * summary data and so can be policy zones.
+ */
+ if (strcmp(zone->db_argv[0], "rbt") != 0 &&
+ strcmp(zone->db_argv[0], "rbt64") != 0)
+ {
+ return (ISC_R_NOTIMPLEMENTED);
+ }
+
+ /*
+ * This must happen only once or be redundant.
+ */
+ LOCK_ZONE(zone);
+ if (zone->rpzs != NULL) {
+ REQUIRE(zone->rpzs == rpzs && zone->rpz_num == rpz_num);
+ } else {
+ REQUIRE(zone->rpz_num == DNS_RPZ_INVALID_NUM);
+ dns_rpz_attach_rpzs(rpzs, &zone->rpzs);
+ zone->rpz_num = rpz_num;
+ }
+ rpzs->defined |= DNS_RPZ_ZBIT(rpz_num);
+ UNLOCK_ZONE(zone);
+
+ return (ISC_R_SUCCESS);
+}
+
+dns_rpz_num_t
+dns_zone_get_rpz_num(dns_zone_t *zone) {
+ return (zone->rpz_num);
+}
+
+/*
+ * If a zone is a response policy zone, mark its new database.
+ */
+void
+dns_zone_rpz_enable_db(dns_zone_t *zone, dns_db_t *db) {
+ isc_result_t result;
+ if (zone->rpz_num == DNS_RPZ_INVALID_NUM) {
+ return;
+ }
+ REQUIRE(zone->rpzs != NULL);
+ result = dns_db_updatenotify_register(db, dns_rpz_dbupdate_callback,
+ zone->rpzs->zones[zone->rpz_num]);
+ REQUIRE(result == ISC_R_SUCCESS);
+}
+
+static void
+dns_zone_rpz_disable_db(dns_zone_t *zone, dns_db_t *db) {
+ if (zone->rpz_num == DNS_RPZ_INVALID_NUM) {
+ return;
+ }
+ REQUIRE(zone->rpzs != NULL);
+ (void)dns_db_updatenotify_unregister(db, dns_rpz_dbupdate_callback,
+ zone->rpzs->zones[zone->rpz_num]);
+}
+
+/*
+ * If a zone is a catalog zone, attach it to update notification in database.
+ */
+void
+dns_zone_catz_enable_db(dns_zone_t *zone, dns_db_t *db) {
+ REQUIRE(DNS_ZONE_VALID(zone));
+ REQUIRE(db != NULL);
+
+ if (zone->catzs != NULL) {
+ dns_catz_dbupdate_register(db, zone->catzs);
+ }
+}
+
+static void
+dns_zone_catz_disable_db(dns_zone_t *zone, dns_db_t *db) {
+ REQUIRE(DNS_ZONE_VALID(zone));
+ REQUIRE(db != NULL);
+
+ if (zone->catzs != NULL) {
+ dns_catz_dbupdate_unregister(db, zone->catzs);
+ }
+}
+
+static void
+zone_catz_enable(dns_zone_t *zone, dns_catz_zones_t *catzs) {
+ REQUIRE(DNS_ZONE_VALID(zone));
+ REQUIRE(catzs != NULL);
+
+ INSIST(zone->catzs == NULL || zone->catzs == catzs);
+ dns_catz_catzs_set_view(catzs, zone->view);
+ if (zone->catzs == NULL) {
+ dns_catz_attach_catzs(catzs, &zone->catzs);
+ }
+}
+
+void
+dns_zone_catz_enable(dns_zone_t *zone, dns_catz_zones_t *catzs) {
+ REQUIRE(DNS_ZONE_VALID(zone));
+
+ LOCK_ZONE(zone);
+ zone_catz_enable(zone, catzs);
+ UNLOCK_ZONE(zone);
+}
+
+static void
+zone_catz_disable(dns_zone_t *zone) {
+ REQUIRE(DNS_ZONE_VALID(zone));
+
+ if (zone->catzs != NULL) {
+ if (zone->db != NULL) {
+ dns_zone_catz_disable_db(zone, zone->db);
+ }
+ dns_catz_detach_catzs(&zone->catzs);
+ }
+}
+
+void
+dns_zone_catz_disable(dns_zone_t *zone) {
+ REQUIRE(DNS_ZONE_VALID(zone));
+
+ LOCK_ZONE(zone);
+ zone_catz_disable(zone);
+ UNLOCK_ZONE(zone);
+}
+
+bool
+dns_zone_catz_is_enabled(dns_zone_t *zone) {
+ REQUIRE(DNS_ZONE_VALID(zone));
+
+ return (zone->catzs != NULL);
+}
+
+/*
+ * Set catalog zone ownership of the zone
+ */
+void
+dns_zone_set_parentcatz(dns_zone_t *zone, dns_catz_zone_t *catz) {
+ REQUIRE(DNS_ZONE_VALID(zone));
+ REQUIRE(catz != NULL);
+ LOCK_ZONE(zone);
+ INSIST(zone->parentcatz == NULL || zone->parentcatz == catz);
+ zone->parentcatz = catz;
+ UNLOCK_ZONE(zone);
+}
+
+dns_catz_zone_t *
+dns_zone_get_parentcatz(const dns_zone_t *zone) {
+ REQUIRE(DNS_ZONE_VALID(zone));
+ return (zone->parentcatz);
+}
+
+static bool
+zone_touched(dns_zone_t *zone) {
+ isc_result_t result;
+ isc_time_t modtime;
+ dns_include_t *include;
+
+ REQUIRE(DNS_ZONE_VALID(zone));
+
+ result = isc_file_getmodtime(zone->masterfile, &modtime);
+ if (result != ISC_R_SUCCESS ||
+ isc_time_compare(&modtime, &zone->loadtime) > 0)
+ {
+ return (true);
+ }
+
+ for (include = ISC_LIST_HEAD(zone->includes); include != NULL;
+ include = ISC_LIST_NEXT(include, link))
+ {
+ result = isc_file_getmodtime(include->name, &modtime);
+ if (result != ISC_R_SUCCESS ||
+ isc_time_compare(&modtime, &include->filetime) > 0)
+ {
+ return (true);
+ }
+ }
+
+ return (false);
+}
+
+/*
+ * Note: when dealing with inline-signed zones, external callers will always
+ * call zone_load() for the secure zone; zone_load() calls itself recursively
+ * in order to load the raw zone.
+ */
+static isc_result_t
+zone_load(dns_zone_t *zone, unsigned int flags, bool locked) {
+ isc_result_t result;
+ isc_time_t now;
+ isc_time_t loadtime;
+ dns_db_t *db = NULL;
+ bool rbt, hasraw, is_dynamic;
+
+ REQUIRE(DNS_ZONE_VALID(zone));
+
+ if (!locked) {
+ LOCK_ZONE(zone);
+ }
+
+ INSIST(zone != zone->raw);
+ hasraw = inline_secure(zone);
+ if (hasraw) {
+ /*
+ * We are trying to load an inline-signed zone. First call
+ * self recursively to try loading the raw version of the zone.
+ * Assuming the raw zone file is readable, there are two
+ * possibilities:
+ *
+ * a) the raw zone was not yet loaded and thus it will be
+ * loaded now, synchronously; if this succeeds, a
+ * subsequent attempt to load the signed zone file will
+ * take place and thus zone_postload() will be called
+ * twice: first for the raw zone and then for the secure
+ * zone; the latter call will take care of syncing the raw
+ * version with the secure version,
+ *
+ * b) the raw zone was already loaded and we are trying to
+ * reload it, which will happen asynchronously; this means
+ * zone_postload() will only be called for the raw zone
+ * because "result" returned by the zone_load() call below
+ * will not be ISC_R_SUCCESS but rather DNS_R_CONTINUE;
+ * zone_postload() called for the raw zone will take care
+ * of syncing the raw version with the secure version.
+ */
+ result = zone_load(zone->raw, flags, false);
+ if (result != ISC_R_SUCCESS) {
+ if (!locked) {
+ UNLOCK_ZONE(zone);
+ }
+ return (result);
+ }
+ LOCK_ZONE(zone->raw);
+ }
+
+ TIME_NOW(&now);
+
+ INSIST(zone->type != dns_zone_none);
+
+ if (DNS_ZONE_FLAG(zone, DNS_ZONEFLG_LOADING)) {
+ if ((flags & DNS_ZONELOADFLAG_THAW) != 0) {
+ DNS_ZONE_SETFLAG(zone, DNS_ZONEFLG_THAW);
+ }
+ result = DNS_R_CONTINUE;
+ goto cleanup;
+ }
+
+ INSIST(zone->db_argc >= 1);
+
+ rbt = strcmp(zone->db_argv[0], "rbt") == 0 ||
+ strcmp(zone->db_argv[0], "rbt64") == 0;
+
+ if (zone->db != NULL && zone->masterfile == NULL && rbt) {
+ /*
+ * The zone has no master file configured.
+ */
+ result = ISC_R_SUCCESS;
+ goto cleanup;
+ }
+
+ is_dynamic = dns_zone_isdynamic(zone, false);
+ if (zone->db != NULL && is_dynamic) {
+ /*
+ * This is a secondary, stub, or dynamically updated zone
+ * being reloaded. Do nothing - the database we already
+ * have is guaranteed to be up-to-date.
+ */
+ if (zone->type == dns_zone_primary && !hasraw) {
+ result = DNS_R_DYNAMIC;
+ } else {
+ result = ISC_R_SUCCESS;
+ }
+ goto cleanup;
+ }
+
+ /*
+ * Store the current time before the zone is loaded, so that if the
+ * file changes between the time of the load and the time that
+ * zone->loadtime is set, then the file will still be reloaded
+ * the next time dns_zone_load is called.
+ */
+ TIME_NOW(&loadtime);
+
+ /*
+ * Don't do the load if the file that stores the zone is older
+ * than the last time the zone was loaded. If the zone has not
+ * been loaded yet, zone->loadtime will be the epoch.
+ */
+ if (zone->masterfile != NULL) {
+ isc_time_t filetime;
+
+ /*
+ * The file is already loaded. If we are just doing a
+ * "rndc reconfig", we are done.
+ */
+ if (!isc_time_isepoch(&zone->loadtime) &&
+ (flags & DNS_ZONELOADFLAG_NOSTAT) != 0)
+ {
+ result = ISC_R_SUCCESS;
+ goto cleanup;
+ }
+
+ if (DNS_ZONE_FLAG(zone, DNS_ZONEFLG_LOADED) &&
+ !zone_touched(zone))
+ {
+ dns_zone_logc(zone, DNS_LOGCATEGORY_ZONELOAD,
+ ISC_LOG_DEBUG(1),
+ "skipping load: master file "
+ "older than last load");
+ result = DNS_R_UPTODATE;
+ goto cleanup;
+ }
+
+ /*
+ * If the file modification time is in the past
+ * set loadtime to that value.
+ */
+ result = isc_file_getmodtime(zone->masterfile, &filetime);
+ if (result == ISC_R_SUCCESS &&
+ isc_time_compare(&loadtime, &filetime) > 0)
+ {
+ loadtime = filetime;
+ }
+ }
+
+ /*
+ * Built in zones (with the exception of empty zones) don't need
+ * to be reloaded.
+ */
+ if (zone->type == dns_zone_primary &&
+ strcmp(zone->db_argv[0], "_builtin") == 0 &&
+ (zone->db_argc < 2 || strcmp(zone->db_argv[1], "empty") != 0) &&
+ DNS_ZONE_FLAG(zone, DNS_ZONEFLG_LOADED))
+ {
+ result = ISC_R_SUCCESS;
+ goto cleanup;
+ }
+
+ /*
+ * Zones associated with a DLZ don't need to be loaded either,
+ * but we need to associate the database with the zone object.
+ */
+ if (strcmp(zone->db_argv[0], "dlz") == 0) {
+ dns_dlzdb_t *dlzdb;
+ dns_dlzfindzone_t findzone;
+
+ for (dlzdb = ISC_LIST_HEAD(zone->view->dlz_unsearched);
+ dlzdb != NULL; dlzdb = ISC_LIST_NEXT(dlzdb, link))
+ {
+ INSIST(DNS_DLZ_VALID(dlzdb));
+ if (strcmp(zone->db_argv[1], dlzdb->dlzname) == 0) {
+ break;
+ }
+ }
+
+ if (dlzdb == NULL) {
+ dns_zone_logc(zone, DNS_LOGCATEGORY_ZONELOAD,
+ ISC_LOG_ERROR,
+ "DLZ %s does not exist or is set "
+ "to 'search yes;'",
+ zone->db_argv[1]);
+ result = ISC_R_NOTFOUND;
+ goto cleanup;
+ }
+
+ ZONEDB_LOCK(&zone->dblock, isc_rwlocktype_write);
+ /* ask SDLZ driver if the zone is supported */
+ findzone = dlzdb->implementation->methods->findzone;
+ result = (*findzone)(dlzdb->implementation->driverarg,
+ dlzdb->dbdata, dlzdb->mctx,
+ zone->view->rdclass, &zone->origin, NULL,
+ NULL, &db);
+ if (result != ISC_R_NOTFOUND) {
+ if (zone->db != NULL) {
+ zone_detachdb(zone);
+ }
+ zone_attachdb(zone, db);
+ dns_db_detach(&db);
+ result = ISC_R_SUCCESS;
+ }
+ ZONEDB_UNLOCK(&zone->dblock, isc_rwlocktype_write);
+
+ if (result == ISC_R_SUCCESS) {
+ if (dlzdb->configure_callback == NULL) {
+ goto cleanup;
+ }
+
+ result = (*dlzdb->configure_callback)(zone->view, dlzdb,
+ zone);
+ if (result != ISC_R_SUCCESS) {
+ dns_zone_logc(zone, DNS_LOGCATEGORY_ZONELOAD,
+ ISC_LOG_ERROR,
+ "DLZ configuration callback: %s",
+ isc_result_totext(result));
+ }
+ }
+ goto cleanup;
+ }
+
+ if ((zone->type == dns_zone_secondary ||
+ zone->type == dns_zone_mirror || zone->type == dns_zone_stub ||
+ (zone->type == dns_zone_redirect && zone->primaries != NULL)) &&
+ rbt)
+ {
+ if (zone->stream == NULL &&
+ (zone->masterfile == NULL ||
+ !isc_file_exists(zone->masterfile)))
+ {
+ if (zone->masterfile != NULL) {
+ dns_zone_logc(zone, DNS_LOGCATEGORY_ZONELOAD,
+ ISC_LOG_DEBUG(1),
+ "no master file");
+ }
+ zone->refreshtime = now;
+ if (zone->task != NULL) {
+ zone_settimer(zone, &now);
+ }
+ result = ISC_R_SUCCESS;
+ goto cleanup;
+ }
+ }
+
+ dns_zone_logc(zone, DNS_LOGCATEGORY_ZONELOAD, ISC_LOG_DEBUG(1),
+ "starting load");
+
+ result = dns_db_create(zone->mctx, zone->db_argv[0], &zone->origin,
+ (zone->type == dns_zone_stub) ? dns_dbtype_stub
+ : dns_dbtype_zone,
+ zone->rdclass, zone->db_argc - 1,
+ zone->db_argv + 1, &db);
+
+ if (result != ISC_R_SUCCESS) {
+ dns_zone_logc(zone, DNS_LOGCATEGORY_ZONELOAD, ISC_LOG_ERROR,
+ "loading zone: creating database: %s",
+ isc_result_totext(result));
+ goto cleanup;
+ }
+ dns_db_settask(db, zone->task);
+
+ if (zone->type == dns_zone_primary ||
+ zone->type == dns_zone_secondary || zone->type == dns_zone_mirror)
+ {
+ result = dns_db_setgluecachestats(db, zone->gluecachestats);
+ if (result == ISC_R_NOTIMPLEMENTED) {
+ result = ISC_R_SUCCESS;
+ }
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+ }
+
+ if (!dns_db_ispersistent(db)) {
+ if (zone->masterfile != NULL || zone->stream != NULL) {
+ result = zone_startload(db, zone, loadtime);
+ } else {
+ result = DNS_R_NOMASTERFILE;
+ if (zone->type == dns_zone_primary ||
+ (zone->type == dns_zone_redirect &&
+ zone->primaries == NULL))
+ {
+ dns_zone_logc(zone, DNS_LOGCATEGORY_ZONELOAD,
+ ISC_LOG_ERROR,
+ "loading zone: "
+ "no master file configured");
+ goto cleanup;
+ }
+ dns_zone_logc(zone, DNS_LOGCATEGORY_ZONELOAD,
+ ISC_LOG_INFO,
+ "loading zone: "
+ "no master file configured: continuing");
+ }
+ }
+
+ if (result == DNS_R_CONTINUE) {
+ DNS_ZONE_SETFLAG(zone, DNS_ZONEFLG_LOADING);
+ if ((flags & DNS_ZONELOADFLAG_THAW) != 0) {
+ DNS_ZONE_SETFLAG(zone, DNS_ZONEFLG_THAW);
+ }
+ goto cleanup;
+ }
+
+ result = zone_postload(zone, db, loadtime, result);
+
+cleanup:
+ if (hasraw) {
+ UNLOCK_ZONE(zone->raw);
+ }
+ if (!locked) {
+ UNLOCK_ZONE(zone);
+ }
+ if (db != NULL) {
+ dns_db_detach(&db);
+ }
+ return (result);
+}
+
+isc_result_t
+dns_zone_load(dns_zone_t *zone, bool newonly) {
+ return (zone_load(zone, newonly ? DNS_ZONELOADFLAG_NOSTAT : 0, false));
+}
+
+static void
+zone_asyncload(isc_task_t *task, isc_event_t *event) {
+ dns_asyncload_t *asl = event->ev_arg;
+ dns_zone_t *zone = asl->zone;
+ isc_result_t result;
+
+ UNUSED(task);
+
+ REQUIRE(DNS_ZONE_VALID(zone));
+
+ isc_event_free(&event);
+
+ LOCK_ZONE(zone);
+ result = zone_load(zone, asl->flags, true);
+ if (result != DNS_R_CONTINUE) {
+ DNS_ZONE_CLRFLAG(zone, DNS_ZONEFLG_LOADPENDING);
+ }
+ UNLOCK_ZONE(zone);
+
+ /* Inform the zone table we've finished loading */
+ if (asl->loaded != NULL) {
+ (asl->loaded)(asl->loaded_arg, zone, task);
+ }
+
+ /* Reduce the quantum */
+ isc_task_setquantum(zone->loadtask, 1);
+
+ isc_mem_put(zone->mctx, asl, sizeof(*asl));
+ dns_zone_idetach(&zone);
+}
+
+isc_result_t
+dns_zone_asyncload(dns_zone_t *zone, bool newonly, dns_zt_zoneloaded_t done,
+ void *arg) {
+ isc_event_t *e;
+ dns_asyncload_t *asl = NULL;
+
+ REQUIRE(DNS_ZONE_VALID(zone));
+
+ if (zone->zmgr == NULL) {
+ return (ISC_R_FAILURE);
+ }
+
+ /* If we already have a load pending, stop now */
+ LOCK_ZONE(zone);
+ if (DNS_ZONE_FLAG(zone, DNS_ZONEFLG_LOADPENDING)) {
+ UNLOCK_ZONE(zone);
+ return (ISC_R_ALREADYRUNNING);
+ }
+
+ asl = isc_mem_get(zone->mctx, sizeof(*asl));
+
+ asl->zone = NULL;
+ asl->flags = newonly ? DNS_ZONELOADFLAG_NOSTAT : 0;
+ asl->loaded = done;
+ asl->loaded_arg = arg;
+
+ e = isc_event_allocate(zone->zmgr->mctx, zone->zmgr, DNS_EVENT_ZONELOAD,
+ zone_asyncload, asl, sizeof(isc_event_t));
+
+ zone_iattach(zone, &asl->zone);
+ DNS_ZONE_SETFLAG(zone, DNS_ZONEFLG_LOADPENDING);
+ isc_task_send(zone->loadtask, &e);
+ UNLOCK_ZONE(zone);
+
+ return (ISC_R_SUCCESS);
+}
+
+bool
+dns__zone_loadpending(dns_zone_t *zone) {
+ REQUIRE(DNS_ZONE_VALID(zone));
+
+ return (DNS_ZONE_FLAG(zone, DNS_ZONEFLG_LOADPENDING));
+}
+
+isc_result_t
+dns_zone_loadandthaw(dns_zone_t *zone) {
+ isc_result_t result;
+
+ if (inline_raw(zone)) {
+ result = zone_load(zone->secure, DNS_ZONELOADFLAG_THAW, false);
+ } else {
+ /*
+ * When thawing a zone, we don't know what changes
+ * have been made. If we do DNSSEC maintenance on this
+ * zone, schedule a full sign for this zone.
+ */
+ if (zone->type == dns_zone_primary &&
+ DNS_ZONEKEY_OPTION(zone, DNS_ZONEKEY_MAINTAIN))
+ {
+ DNS_ZONEKEY_SETOPTION(zone, DNS_ZONEKEY_FULLSIGN);
+ }
+ result = zone_load(zone, DNS_ZONELOADFLAG_THAW, false);
+ }
+
+ switch (result) {
+ case DNS_R_CONTINUE:
+ /* Deferred thaw. */
+ break;
+ case DNS_R_UPTODATE:
+ case ISC_R_SUCCESS:
+ case DNS_R_SEENINCLUDE:
+ zone->update_disabled = false;
+ break;
+ case DNS_R_NOMASTERFILE:
+ zone->update_disabled = false;
+ break;
+ default:
+ /* Error, remain in disabled state. */
+ break;
+ }
+ return (result);
+}
+
+static unsigned int
+get_primary_options(dns_zone_t *zone) {
+ unsigned int options;
+
+ options = DNS_MASTER_ZONE | DNS_MASTER_RESIGN;
+ if (zone->type == dns_zone_secondary || zone->type == dns_zone_mirror ||
+ (zone->type == dns_zone_redirect && zone->primaries == NULL))
+ {
+ options |= DNS_MASTER_SECONDARY;
+ }
+ if (zone->type == dns_zone_key) {
+ options |= DNS_MASTER_KEY;
+ }
+ if (DNS_ZONE_OPTION(zone, DNS_ZONEOPT_CHECKNS)) {
+ options |= DNS_MASTER_CHECKNS;
+ }
+ if (DNS_ZONE_OPTION(zone, DNS_ZONEOPT_FATALNS)) {
+ options |= DNS_MASTER_FATALNS;
+ }
+ if (DNS_ZONE_OPTION(zone, DNS_ZONEOPT_CHECKNAMES)) {
+ options |= DNS_MASTER_CHECKNAMES;
+ }
+ if (DNS_ZONE_OPTION(zone, DNS_ZONEOPT_CHECKNAMESFAIL)) {
+ options |= DNS_MASTER_CHECKNAMESFAIL;
+ }
+ if (DNS_ZONE_OPTION(zone, DNS_ZONEOPT_CHECKMX)) {
+ options |= DNS_MASTER_CHECKMX;
+ }
+ if (DNS_ZONE_OPTION(zone, DNS_ZONEOPT_CHECKMXFAIL)) {
+ options |= DNS_MASTER_CHECKMXFAIL;
+ }
+ if (DNS_ZONE_OPTION(zone, DNS_ZONEOPT_CHECKWILDCARD)) {
+ options |= DNS_MASTER_CHECKWILDCARD;
+ }
+ if (DNS_ZONE_OPTION(zone, DNS_ZONEOPT_CHECKTTL)) {
+ options |= DNS_MASTER_CHECKTTL;
+ }
+
+ return (options);
+}
+
+static void
+zone_registerinclude(const char *filename, void *arg) {
+ isc_result_t result;
+ dns_zone_t *zone = (dns_zone_t *)arg;
+ dns_include_t *inc = NULL;
+
+ REQUIRE(DNS_ZONE_VALID(zone));
+
+ if (filename == NULL) {
+ return;
+ }
+
+ /*
+ * Suppress duplicates.
+ */
+ for (inc = ISC_LIST_HEAD(zone->newincludes); inc != NULL;
+ inc = ISC_LIST_NEXT(inc, link))
+ {
+ if (strcmp(filename, inc->name) == 0) {
+ return;
+ }
+ }
+
+ inc = isc_mem_get(zone->mctx, sizeof(dns_include_t));
+ inc->name = isc_mem_strdup(zone->mctx, filename);
+ ISC_LINK_INIT(inc, link);
+
+ result = isc_file_getmodtime(filename, &inc->filetime);
+ if (result != ISC_R_SUCCESS) {
+ isc_time_settoepoch(&inc->filetime);
+ }
+
+ ISC_LIST_APPEND(zone->newincludes, inc, link);
+}
+
+static void
+zone_gotreadhandle(isc_task_t *task, isc_event_t *event) {
+ dns_load_t *load = event->ev_arg;
+ isc_result_t result = ISC_R_SUCCESS;
+ unsigned int options;
+
+ REQUIRE(DNS_LOAD_VALID(load));
+
+ if ((event->ev_attributes & ISC_EVENTATTR_CANCELED) != 0) {
+ result = ISC_R_CANCELED;
+ }
+ isc_event_free(&event);
+ if (result == ISC_R_CANCELED) {
+ goto fail;
+ }
+
+ options = get_primary_options(load->zone);
+
+ result = dns_master_loadfileinc(
+ load->zone->masterfile, dns_db_origin(load->db),
+ dns_db_origin(load->db), load->zone->rdclass, options, 0,
+ &load->callbacks, task, zone_loaddone, load, &load->zone->lctx,
+ zone_registerinclude, load->zone, load->zone->mctx,
+ load->zone->masterformat, load->zone->maxttl);
+ if (result != ISC_R_SUCCESS && result != DNS_R_CONTINUE &&
+ result != DNS_R_SEENINCLUDE)
+ {
+ goto fail;
+ }
+ return;
+
+fail:
+ zone_loaddone(load, result);
+}
+
+static void
+get_raw_serial(dns_zone_t *raw, dns_masterrawheader_t *rawdata) {
+ isc_result_t result;
+ unsigned int soacount;
+
+ LOCK(&raw->lock);
+ if (raw->db != NULL) {
+ result = zone_get_from_db(raw, raw->db, NULL, &soacount, NULL,
+ &rawdata->sourceserial, NULL, NULL,
+ NULL, NULL, NULL);
+ if (result == ISC_R_SUCCESS && soacount > 0U) {
+ rawdata->flags |= DNS_MASTERRAW_SOURCESERIALSET;
+ }
+ }
+ UNLOCK(&raw->lock);
+}
+
+static void
+zone_gotwritehandle(isc_task_t *task, isc_event_t *event) {
+ const char me[] = "zone_gotwritehandle";
+ dns_zone_t *zone = event->ev_arg;
+ isc_result_t result = ISC_R_SUCCESS;
+ dns_dbversion_t *version = NULL;
+ dns_masterrawheader_t rawdata;
+ dns_db_t *db = NULL;
+
+ REQUIRE(DNS_ZONE_VALID(zone));
+ INSIST(task == zone->task);
+ ENTER;
+
+ if ((event->ev_attributes & ISC_EVENTATTR_CANCELED) != 0) {
+ result = ISC_R_CANCELED;
+ }
+ isc_event_free(&event);
+ if (result == ISC_R_CANCELED) {
+ goto fail;
+ }
+
+ LOCK_ZONE(zone);
+ INSIST(zone != zone->raw);
+ ZONEDB_LOCK(&zone->dblock, isc_rwlocktype_read);
+ if (zone->db != NULL) {
+ dns_db_attach(zone->db, &db);
+ }
+ ZONEDB_UNLOCK(&zone->dblock, isc_rwlocktype_read);
+ if (db != NULL) {
+ const dns_master_style_t *output_style;
+ dns_db_currentversion(db, &version);
+ dns_master_initrawheader(&rawdata);
+ if (inline_secure(zone)) {
+ get_raw_serial(zone->raw, &rawdata);
+ }
+ if (zone->type == dns_zone_key) {
+ output_style = &dns_master_style_keyzone;
+ } else if (zone->masterstyle != NULL) {
+ output_style = zone->masterstyle;
+ } else {
+ output_style = &dns_master_style_default;
+ }
+ result = dns_master_dumpasync(
+ zone->mctx, db, version, output_style, zone->masterfile,
+ zone->task, dump_done, zone, &zone->dctx,
+ zone->masterformat, &rawdata);
+ dns_db_closeversion(db, &version, false);
+ } else {
+ result = ISC_R_CANCELED;
+ }
+ if (db != NULL) {
+ dns_db_detach(&db);
+ }
+ UNLOCK_ZONE(zone);
+ if (result != DNS_R_CONTINUE) {
+ goto fail;
+ }
+ return;
+
+fail:
+ dump_done(zone, result);
+}
+
+/*
+ * Save the raw serial number for inline-signing zones.
+ * (XXX: Other information from the header will be used
+ * for other purposes in the future, but for now this is
+ * all we're interested in.)
+ */
+static void
+zone_setrawdata(dns_zone_t *zone, dns_masterrawheader_t *header) {
+ if ((header->flags & DNS_MASTERRAW_SOURCESERIALSET) == 0) {
+ return;
+ }
+
+ zone->sourceserial = header->sourceserial;
+ zone->sourceserialset = true;
+}
+
+void
+dns_zone_setrawdata(dns_zone_t *zone, dns_masterrawheader_t *header) {
+ if (zone == NULL) {
+ return;
+ }
+
+ LOCK_ZONE(zone);
+ zone_setrawdata(zone, header);
+ UNLOCK_ZONE(zone);
+}
+
+static isc_result_t
+zone_startload(dns_db_t *db, dns_zone_t *zone, isc_time_t loadtime) {
+ const char me[] = "zone_startload";
+ dns_load_t *load;
+ isc_result_t result;
+ isc_result_t tresult;
+ unsigned int options;
+
+ ENTER;
+
+ dns_zone_rpz_enable_db(zone, db);
+ dns_zone_catz_enable_db(zone, db);
+
+ options = get_primary_options(zone);
+ if (DNS_ZONE_OPTION(zone, DNS_ZONEOPT_MANYERRORS)) {
+ options |= DNS_MASTER_MANYERRORS;
+ }
+
+ if (zone->zmgr != NULL && zone->db != NULL && zone->loadtask != NULL) {
+ load = isc_mem_get(zone->mctx, sizeof(*load));
+
+ load->mctx = NULL;
+ load->zone = NULL;
+ load->db = NULL;
+ load->loadtime = loadtime;
+ load->magic = LOAD_MAGIC;
+
+ isc_mem_attach(zone->mctx, &load->mctx);
+ zone_iattach(zone, &load->zone);
+ dns_db_attach(db, &load->db);
+ dns_rdatacallbacks_init(&load->callbacks);
+ load->callbacks.rawdata = zone_setrawdata;
+ zone_iattach(zone, &load->callbacks.zone);
+ result = dns_db_beginload(db, &load->callbacks);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+ result = zonemgr_getio(zone->zmgr, true, zone->loadtask,
+ zone_gotreadhandle, load, &zone->readio);
+ if (result != ISC_R_SUCCESS) {
+ /*
+ * We can't report multiple errors so ignore
+ * the result of dns_db_endload().
+ */
+ (void)dns_db_endload(load->db, &load->callbacks);
+ goto cleanup;
+ } else {
+ result = DNS_R_CONTINUE;
+ }
+ } else {
+ dns_rdatacallbacks_t callbacks;
+
+ dns_rdatacallbacks_init(&callbacks);
+ callbacks.rawdata = zone_setrawdata;
+ zone_iattach(zone, &callbacks.zone);
+ result = dns_db_beginload(db, &callbacks);
+ if (result != ISC_R_SUCCESS) {
+ zone_idetach(&callbacks.zone);
+ return (result);
+ }
+
+ if (zone->stream != NULL) {
+ FILE *stream = NULL;
+ DE_CONST(zone->stream, stream);
+ result = dns_master_loadstream(
+ stream, &zone->origin, &zone->origin,
+ zone->rdclass, options, &callbacks, zone->mctx);
+ } else {
+ result = dns_master_loadfile(
+ zone->masterfile, &zone->origin, &zone->origin,
+ zone->rdclass, options, 0, &callbacks,
+ zone_registerinclude, zone, zone->mctx,
+ zone->masterformat, zone->maxttl);
+ }
+
+ tresult = dns_db_endload(db, &callbacks);
+ if (result == ISC_R_SUCCESS) {
+ result = tresult;
+ }
+ zone_idetach(&callbacks.zone);
+ }
+
+ return (result);
+
+cleanup:
+ load->magic = 0;
+ dns_db_detach(&load->db);
+ zone_idetach(&load->zone);
+ zone_idetach(&load->callbacks.zone);
+ isc_mem_detach(&load->mctx);
+ isc_mem_put(zone->mctx, load, sizeof(*load));
+ return (result);
+}
+
+static bool
+zone_check_mx(dns_zone_t *zone, dns_db_t *db, dns_name_t *name,
+ dns_name_t *owner) {
+ isc_result_t result;
+ char ownerbuf[DNS_NAME_FORMATSIZE];
+ char namebuf[DNS_NAME_FORMATSIZE];
+ char altbuf[DNS_NAME_FORMATSIZE];
+ dns_fixedname_t fixed;
+ dns_name_t *foundname;
+ int level;
+
+ /*
+ * "." means the services does not exist.
+ */
+ if (dns_name_equal(name, dns_rootname)) {
+ return (true);
+ }
+
+ /*
+ * Outside of zone.
+ */
+ if (!dns_name_issubdomain(name, &zone->origin)) {
+ if (zone->checkmx != NULL) {
+ return ((zone->checkmx)(zone, name, owner));
+ }
+ return (true);
+ }
+
+ if (zone->type == dns_zone_primary) {
+ level = ISC_LOG_ERROR;
+ } else {
+ level = ISC_LOG_WARNING;
+ }
+
+ foundname = dns_fixedname_initname(&fixed);
+
+ result = dns_db_find(db, name, NULL, dns_rdatatype_a, 0, 0, NULL,
+ foundname, NULL, NULL);
+ if (result == ISC_R_SUCCESS) {
+ return (true);
+ }
+
+ if (result == DNS_R_NXRRSET) {
+ result = dns_db_find(db, name, NULL, dns_rdatatype_aaaa, 0, 0,
+ NULL, foundname, NULL, NULL);
+ if (result == ISC_R_SUCCESS) {
+ return (true);
+ }
+ }
+
+ dns_name_format(owner, ownerbuf, sizeof ownerbuf);
+ dns_name_format(name, namebuf, sizeof namebuf);
+ if (result == DNS_R_NXRRSET || result == DNS_R_NXDOMAIN ||
+ result == DNS_R_EMPTYNAME)
+ {
+ if (!DNS_ZONE_OPTION(zone, DNS_ZONEOPT_CHECKMXFAIL)) {
+ level = ISC_LOG_WARNING;
+ }
+ dns_zone_log(zone, level,
+ "%s/MX '%s' has no address records (A or AAAA)",
+ ownerbuf, namebuf);
+ return ((level == ISC_LOG_WARNING) ? true : false);
+ }
+
+ if (result == DNS_R_CNAME) {
+ if (DNS_ZONE_OPTION(zone, DNS_ZONEOPT_WARNMXCNAME) ||
+ DNS_ZONE_OPTION(zone, DNS_ZONEOPT_IGNOREMXCNAME))
+ {
+ level = ISC_LOG_WARNING;
+ }
+ if (!DNS_ZONE_OPTION(zone, DNS_ZONEOPT_IGNOREMXCNAME)) {
+ dns_zone_log(zone, level,
+ "%s/MX '%s' is a CNAME (illegal)",
+ ownerbuf, namebuf);
+ }
+ return ((level == ISC_LOG_WARNING) ? true : false);
+ }
+
+ if (result == DNS_R_DNAME) {
+ if (DNS_ZONE_OPTION(zone, DNS_ZONEOPT_WARNMXCNAME) ||
+ DNS_ZONE_OPTION(zone, DNS_ZONEOPT_IGNOREMXCNAME))
+ {
+ level = ISC_LOG_WARNING;
+ }
+ if (!DNS_ZONE_OPTION(zone, DNS_ZONEOPT_IGNOREMXCNAME)) {
+ dns_name_format(foundname, altbuf, sizeof altbuf);
+ dns_zone_log(zone, level,
+ "%s/MX '%s' is below a DNAME"
+ " '%s' (illegal)",
+ ownerbuf, namebuf, altbuf);
+ }
+ return ((level == ISC_LOG_WARNING) ? true : false);
+ }
+
+ if (zone->checkmx != NULL && result == DNS_R_DELEGATION) {
+ return ((zone->checkmx)(zone, name, owner));
+ }
+
+ return (true);
+}
+
+static bool
+zone_check_srv(dns_zone_t *zone, dns_db_t *db, dns_name_t *name,
+ dns_name_t *owner) {
+ isc_result_t result;
+ char ownerbuf[DNS_NAME_FORMATSIZE];
+ char namebuf[DNS_NAME_FORMATSIZE];
+ char altbuf[DNS_NAME_FORMATSIZE];
+ dns_fixedname_t fixed;
+ dns_name_t *foundname;
+ int level;
+
+ /*
+ * "." means the services does not exist.
+ */
+ if (dns_name_equal(name, dns_rootname)) {
+ return (true);
+ }
+
+ /*
+ * Outside of zone.
+ */
+ if (!dns_name_issubdomain(name, &zone->origin)) {
+ if (zone->checksrv != NULL) {
+ return ((zone->checksrv)(zone, name, owner));
+ }
+ return (true);
+ }
+
+ if (zone->type == dns_zone_primary) {
+ level = ISC_LOG_ERROR;
+ } else {
+ level = ISC_LOG_WARNING;
+ }
+
+ foundname = dns_fixedname_initname(&fixed);
+
+ result = dns_db_find(db, name, NULL, dns_rdatatype_a, 0, 0, NULL,
+ foundname, NULL, NULL);
+ if (result == ISC_R_SUCCESS) {
+ return (true);
+ }
+
+ if (result == DNS_R_NXRRSET) {
+ result = dns_db_find(db, name, NULL, dns_rdatatype_aaaa, 0, 0,
+ NULL, foundname, NULL, NULL);
+ if (result == ISC_R_SUCCESS) {
+ return (true);
+ }
+ }
+
+ dns_name_format(owner, ownerbuf, sizeof ownerbuf);
+ dns_name_format(name, namebuf, sizeof namebuf);
+ if (result == DNS_R_NXRRSET || result == DNS_R_NXDOMAIN ||
+ result == DNS_R_EMPTYNAME)
+ {
+ dns_zone_log(zone, level,
+ "%s/SRV '%s' has no address records (A or AAAA)",
+ ownerbuf, namebuf);
+ /* XXX950 make fatal for 9.5.0. */
+ return (true);
+ }
+
+ if (result == DNS_R_CNAME) {
+ if (DNS_ZONE_OPTION(zone, DNS_ZONEOPT_WARNSRVCNAME) ||
+ DNS_ZONE_OPTION(zone, DNS_ZONEOPT_IGNORESRVCNAME))
+ {
+ level = ISC_LOG_WARNING;
+ }
+ if (!DNS_ZONE_OPTION(zone, DNS_ZONEOPT_IGNORESRVCNAME)) {
+ dns_zone_log(zone, level,
+ "%s/SRV '%s' is a CNAME (illegal)",
+ ownerbuf, namebuf);
+ }
+ return ((level == ISC_LOG_WARNING) ? true : false);
+ }
+
+ if (result == DNS_R_DNAME) {
+ if (DNS_ZONE_OPTION(zone, DNS_ZONEOPT_WARNSRVCNAME) ||
+ DNS_ZONE_OPTION(zone, DNS_ZONEOPT_IGNORESRVCNAME))
+ {
+ level = ISC_LOG_WARNING;
+ }
+ if (!DNS_ZONE_OPTION(zone, DNS_ZONEOPT_IGNORESRVCNAME)) {
+ dns_name_format(foundname, altbuf, sizeof altbuf);
+ dns_zone_log(zone, level,
+ "%s/SRV '%s' is below a "
+ "DNAME '%s' (illegal)",
+ ownerbuf, namebuf, altbuf);
+ }
+ return ((level == ISC_LOG_WARNING) ? true : false);
+ }
+
+ if (zone->checksrv != NULL && result == DNS_R_DELEGATION) {
+ return ((zone->checksrv)(zone, name, owner));
+ }
+
+ return (true);
+}
+
+static bool
+zone_check_glue(dns_zone_t *zone, dns_db_t *db, dns_name_t *name,
+ dns_name_t *owner) {
+ bool answer = true;
+ isc_result_t result, tresult;
+ char ownerbuf[DNS_NAME_FORMATSIZE];
+ char namebuf[DNS_NAME_FORMATSIZE];
+ char altbuf[DNS_NAME_FORMATSIZE];
+ dns_fixedname_t fixed;
+ dns_name_t *foundname;
+ dns_rdataset_t a;
+ dns_rdataset_t aaaa;
+ int level;
+
+ /*
+ * Outside of zone.
+ */
+ if (!dns_name_issubdomain(name, &zone->origin)) {
+ if (zone->checkns != NULL) {
+ return ((zone->checkns)(zone, name, owner, NULL, NULL));
+ }
+ return (true);
+ }
+
+ if (zone->type == dns_zone_primary) {
+ level = ISC_LOG_ERROR;
+ } else {
+ level = ISC_LOG_WARNING;
+ }
+
+ foundname = dns_fixedname_initname(&fixed);
+ dns_rdataset_init(&a);
+ dns_rdataset_init(&aaaa);
+
+ /*
+ * Perform a regular lookup to catch DNAME records then look
+ * for glue.
+ */
+ result = dns_db_find(db, name, NULL, dns_rdatatype_a, 0, 0, NULL,
+ foundname, &a, NULL);
+ switch (result) {
+ case ISC_R_SUCCESS:
+ case DNS_R_DNAME:
+ case DNS_R_CNAME:
+ break;
+ default:
+ if (dns_rdataset_isassociated(&a)) {
+ dns_rdataset_disassociate(&a);
+ }
+ result = dns_db_find(db, name, NULL, dns_rdatatype_a,
+ DNS_DBFIND_GLUEOK, 0, NULL, foundname, &a,
+ NULL);
+ }
+ if (result == ISC_R_SUCCESS) {
+ dns_rdataset_disassociate(&a);
+ return (true);
+ } else if (result == DNS_R_DELEGATION) {
+ dns_rdataset_disassociate(&a);
+ }
+
+ if (result == DNS_R_NXRRSET || result == DNS_R_DELEGATION ||
+ result == DNS_R_GLUE)
+ {
+ tresult = dns_db_find(db, name, NULL, dns_rdatatype_aaaa,
+ DNS_DBFIND_GLUEOK, 0, NULL, foundname,
+ &aaaa, NULL);
+ if (tresult == ISC_R_SUCCESS) {
+ if (dns_rdataset_isassociated(&a)) {
+ dns_rdataset_disassociate(&a);
+ }
+ dns_rdataset_disassociate(&aaaa);
+ return (true);
+ }
+ if (tresult == DNS_R_DELEGATION || tresult == DNS_R_DNAME) {
+ dns_rdataset_disassociate(&aaaa);
+ }
+ if (result == DNS_R_GLUE || tresult == DNS_R_GLUE) {
+ /*
+ * Check glue against child zone.
+ */
+ if (zone->checkns != NULL) {
+ answer = (zone->checkns)(zone, name, owner, &a,
+ &aaaa);
+ }
+ if (dns_rdataset_isassociated(&a)) {
+ dns_rdataset_disassociate(&a);
+ }
+ if (dns_rdataset_isassociated(&aaaa)) {
+ dns_rdataset_disassociate(&aaaa);
+ }
+ return (answer);
+ }
+ }
+
+ dns_name_format(owner, ownerbuf, sizeof ownerbuf);
+ dns_name_format(name, namebuf, sizeof namebuf);
+ if (result == DNS_R_NXRRSET || result == DNS_R_NXDOMAIN ||
+ result == DNS_R_EMPTYNAME || result == DNS_R_DELEGATION)
+ {
+ const char *what;
+ bool required = false;
+ if (dns_name_issubdomain(name, owner)) {
+ what = "REQUIRED GLUE ";
+ required = true;
+ } else if (result == DNS_R_DELEGATION) {
+ what = "SIBLING GLUE ";
+ } else {
+ what = "";
+ }
+
+ if (result != DNS_R_DELEGATION || required ||
+ DNS_ZONE_OPTION(zone, DNS_ZONEOPT_CHECKSIBLING))
+ {
+ dns_zone_log(zone, level,
+ "%s/NS '%s' has no %s"
+ "address records (A or AAAA)",
+ ownerbuf, namebuf, what);
+ /*
+ * Log missing address record.
+ */
+ if (result == DNS_R_DELEGATION && zone->checkns != NULL)
+ {
+ (void)(zone->checkns)(zone, name, owner, &a,
+ &aaaa);
+ }
+ /* XXX950 make fatal for 9.5.0. */
+ /* answer = false; */
+ }
+ } else if (result == DNS_R_CNAME) {
+ dns_zone_log(zone, level, "%s/NS '%s' is a CNAME (illegal)",
+ ownerbuf, namebuf);
+ /* XXX950 make fatal for 9.5.0. */
+ /* answer = false; */
+ } else if (result == DNS_R_DNAME) {
+ dns_name_format(foundname, altbuf, sizeof altbuf);
+ dns_zone_log(zone, level,
+ "%s/NS '%s' is below a DNAME '%s' (illegal)",
+ ownerbuf, namebuf, altbuf);
+ /* XXX950 make fatal for 9.5.0. */
+ /* answer = false; */
+ }
+
+ if (dns_rdataset_isassociated(&a)) {
+ dns_rdataset_disassociate(&a);
+ }
+ if (dns_rdataset_isassociated(&aaaa)) {
+ dns_rdataset_disassociate(&aaaa);
+ }
+ return (answer);
+}
+
+static bool
+zone_rrset_check_dup(dns_zone_t *zone, dns_name_t *owner,
+ dns_rdataset_t *rdataset) {
+ dns_rdataset_t tmprdataset;
+ isc_result_t result;
+ bool answer = true;
+ bool format = true;
+ int level = ISC_LOG_WARNING;
+ char ownerbuf[DNS_NAME_FORMATSIZE];
+ char typebuf[DNS_RDATATYPE_FORMATSIZE];
+ unsigned int count1 = 0;
+
+ if (DNS_ZONE_OPTION(zone, DNS_ZONEOPT_CHECKDUPRRFAIL)) {
+ level = ISC_LOG_ERROR;
+ }
+
+ dns_rdataset_init(&tmprdataset);
+ for (result = dns_rdataset_first(rdataset); result == ISC_R_SUCCESS;
+ result = dns_rdataset_next(rdataset))
+ {
+ dns_rdata_t rdata1 = DNS_RDATA_INIT;
+ unsigned int count2 = 0;
+
+ count1++;
+ dns_rdataset_current(rdataset, &rdata1);
+ dns_rdataset_clone(rdataset, &tmprdataset);
+ for (result = dns_rdataset_first(&tmprdataset);
+ result == ISC_R_SUCCESS;
+ result = dns_rdataset_next(&tmprdataset))
+ {
+ dns_rdata_t rdata2 = DNS_RDATA_INIT;
+ count2++;
+ if (count1 >= count2) {
+ continue;
+ }
+ dns_rdataset_current(&tmprdataset, &rdata2);
+ if (dns_rdata_casecompare(&rdata1, &rdata2) == 0) {
+ if (format) {
+ dns_name_format(owner, ownerbuf,
+ sizeof ownerbuf);
+ dns_rdatatype_format(rdata1.type,
+ typebuf,
+ sizeof(typebuf));
+ format = false;
+ }
+ dns_zone_log(zone, level,
+ "%s/%s has "
+ "semantically identical records",
+ ownerbuf, typebuf);
+ if (level == ISC_LOG_ERROR) {
+ answer = false;
+ }
+ break;
+ }
+ }
+ dns_rdataset_disassociate(&tmprdataset);
+ if (!format) {
+ break;
+ }
+ }
+ return (answer);
+}
+
+static bool
+zone_check_dup(dns_zone_t *zone, dns_db_t *db) {
+ dns_dbiterator_t *dbiterator = NULL;
+ dns_dbnode_t *node = NULL;
+ dns_fixedname_t fixed;
+ dns_name_t *name;
+ dns_rdataset_t rdataset;
+ dns_rdatasetiter_t *rdsit = NULL;
+ bool ok = true;
+ isc_result_t result;
+
+ name = dns_fixedname_initname(&fixed);
+ dns_rdataset_init(&rdataset);
+
+ result = dns_db_createiterator(db, 0, &dbiterator);
+ if (result != ISC_R_SUCCESS) {
+ return (true);
+ }
+
+ for (result = dns_dbiterator_first(dbiterator); result == ISC_R_SUCCESS;
+ result = dns_dbiterator_next(dbiterator))
+ {
+ result = dns_dbiterator_current(dbiterator, &node, name);
+ if (result != ISC_R_SUCCESS) {
+ continue;
+ }
+
+ result = dns_db_allrdatasets(db, node, NULL, 0, 0, &rdsit);
+ if (result != ISC_R_SUCCESS) {
+ continue;
+ }
+
+ for (result = dns_rdatasetiter_first(rdsit);
+ result == ISC_R_SUCCESS;
+ result = dns_rdatasetiter_next(rdsit))
+ {
+ dns_rdatasetiter_current(rdsit, &rdataset);
+ if (!zone_rrset_check_dup(zone, name, &rdataset)) {
+ ok = false;
+ }
+ dns_rdataset_disassociate(&rdataset);
+ }
+ dns_rdatasetiter_destroy(&rdsit);
+ dns_db_detachnode(db, &node);
+ }
+
+ if (node != NULL) {
+ dns_db_detachnode(db, &node);
+ }
+ dns_dbiterator_destroy(&dbiterator);
+
+ return (ok);
+}
+
+static bool
+isspf(const dns_rdata_t *rdata) {
+ char buf[1024];
+ const unsigned char *data = rdata->data;
+ unsigned int rdl = rdata->length, i = 0, tl, len;
+
+ while (rdl > 0U) {
+ len = tl = *data;
+ ++data;
+ --rdl;
+ INSIST(tl <= rdl);
+ if (len > sizeof(buf) - i - 1) {
+ len = sizeof(buf) - i - 1;
+ }
+ memmove(buf + i, data, len);
+ i += len;
+ data += tl;
+ rdl -= tl;
+ }
+
+ if (i < 6U) {
+ return (false);
+ }
+
+ buf[i] = 0;
+ if (strncmp(buf, "v=spf1", 6) == 0 && (buf[6] == 0 || buf[6] == ' ')) {
+ return (true);
+ }
+ return (false);
+}
+
+static bool
+integrity_checks(dns_zone_t *zone, dns_db_t *db) {
+ dns_dbiterator_t *dbiterator = NULL;
+ dns_dbnode_t *node = NULL;
+ dns_rdataset_t rdataset;
+ dns_fixedname_t fixed;
+ dns_fixedname_t fixedbottom;
+ dns_rdata_mx_t mx;
+ dns_rdata_ns_t ns;
+ dns_rdata_in_srv_t srv;
+ dns_rdata_t rdata;
+ dns_name_t *name;
+ dns_name_t *bottom;
+ isc_result_t result;
+ bool ok = true, have_spf, have_txt;
+
+ name = dns_fixedname_initname(&fixed);
+ bottom = dns_fixedname_initname(&fixedbottom);
+ dns_rdataset_init(&rdataset);
+ dns_rdata_init(&rdata);
+
+ result = dns_db_createiterator(db, 0, &dbiterator);
+ if (result != ISC_R_SUCCESS) {
+ return (true);
+ }
+
+ result = dns_dbiterator_first(dbiterator);
+ while (result == ISC_R_SUCCESS) {
+ result = dns_dbiterator_current(dbiterator, &node, name);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+
+ /*
+ * Is this name visible in the zone?
+ */
+ if (!dns_name_issubdomain(name, &zone->origin) ||
+ (dns_name_countlabels(bottom) > 0 &&
+ dns_name_issubdomain(name, bottom)))
+ {
+ goto next;
+ }
+
+ dns_dbiterator_pause(dbiterator);
+
+ /*
+ * Don't check the NS records at the origin.
+ */
+ if (dns_name_equal(name, &zone->origin)) {
+ goto checkfordname;
+ }
+
+ result = dns_db_findrdataset(db, node, NULL, dns_rdatatype_ns,
+ 0, 0, &rdataset, NULL);
+ if (result != ISC_R_SUCCESS) {
+ goto checkfordname;
+ }
+ /*
+ * Remember bottom of zone due to NS.
+ */
+ dns_name_copy(name, bottom);
+
+ result = dns_rdataset_first(&rdataset);
+ while (result == ISC_R_SUCCESS) {
+ dns_rdataset_current(&rdataset, &rdata);
+ result = dns_rdata_tostruct(&rdata, &ns, NULL);
+ RUNTIME_CHECK(result == ISC_R_SUCCESS);
+ if (!zone_check_glue(zone, db, &ns.name, name)) {
+ ok = false;
+ }
+ dns_rdata_reset(&rdata);
+ result = dns_rdataset_next(&rdataset);
+ }
+ dns_rdataset_disassociate(&rdataset);
+ goto next;
+
+ checkfordname:
+ result = dns_db_findrdataset(db, node, NULL,
+ dns_rdatatype_dname, 0, 0,
+ &rdataset, NULL);
+ if (result == ISC_R_SUCCESS) {
+ /*
+ * Remember bottom of zone due to DNAME.
+ */
+ dns_name_copy(name, bottom);
+ dns_rdataset_disassociate(&rdataset);
+ }
+
+ result = dns_db_findrdataset(db, node, NULL, dns_rdatatype_mx,
+ 0, 0, &rdataset, NULL);
+ if (result != ISC_R_SUCCESS) {
+ goto checksrv;
+ }
+ result = dns_rdataset_first(&rdataset);
+ while (result == ISC_R_SUCCESS) {
+ dns_rdataset_current(&rdataset, &rdata);
+ result = dns_rdata_tostruct(&rdata, &mx, NULL);
+ RUNTIME_CHECK(result == ISC_R_SUCCESS);
+ if (!zone_check_mx(zone, db, &mx.mx, name)) {
+ ok = false;
+ }
+ dns_rdata_reset(&rdata);
+ result = dns_rdataset_next(&rdataset);
+ }
+ dns_rdataset_disassociate(&rdataset);
+
+ checksrv:
+ if (zone->rdclass != dns_rdataclass_in) {
+ goto next;
+ }
+ result = dns_db_findrdataset(db, node, NULL, dns_rdatatype_srv,
+ 0, 0, &rdataset, NULL);
+ if (result != ISC_R_SUCCESS) {
+ goto checkspf;
+ }
+ result = dns_rdataset_first(&rdataset);
+ while (result == ISC_R_SUCCESS) {
+ dns_rdataset_current(&rdataset, &rdata);
+ result = dns_rdata_tostruct(&rdata, &srv, NULL);
+ RUNTIME_CHECK(result == ISC_R_SUCCESS);
+ if (!zone_check_srv(zone, db, &srv.target, name)) {
+ ok = false;
+ }
+ dns_rdata_reset(&rdata);
+ result = dns_rdataset_next(&rdataset);
+ }
+ dns_rdataset_disassociate(&rdataset);
+
+ checkspf:
+ /*
+ * Check if there is a type SPF record without an
+ * SPF-formatted type TXT record also being present.
+ */
+ if (!DNS_ZONE_OPTION(zone, DNS_ZONEOPT_CHECKSPF)) {
+ goto next;
+ }
+ if (zone->rdclass != dns_rdataclass_in) {
+ goto next;
+ }
+ have_spf = have_txt = false;
+ result = dns_db_findrdataset(db, node, NULL, dns_rdatatype_spf,
+ 0, 0, &rdataset, NULL);
+ if (result == ISC_R_SUCCESS) {
+ dns_rdataset_disassociate(&rdataset);
+ have_spf = true;
+ }
+ result = dns_db_findrdataset(db, node, NULL, dns_rdatatype_txt,
+ 0, 0, &rdataset, NULL);
+ if (result != ISC_R_SUCCESS) {
+ goto notxt;
+ }
+ result = dns_rdataset_first(&rdataset);
+ while (result == ISC_R_SUCCESS) {
+ dns_rdataset_current(&rdataset, &rdata);
+ have_txt = isspf(&rdata);
+ dns_rdata_reset(&rdata);
+ if (have_txt) {
+ break;
+ }
+ result = dns_rdataset_next(&rdataset);
+ }
+ dns_rdataset_disassociate(&rdataset);
+
+ notxt:
+ if (have_spf && !have_txt) {
+ char namebuf[DNS_NAME_FORMATSIZE];
+
+ dns_name_format(name, namebuf, sizeof(namebuf));
+ dns_zone_log(zone, ISC_LOG_WARNING,
+ "'%s' found type "
+ "SPF record but no SPF TXT record found, "
+ "add matching type TXT record",
+ namebuf);
+ }
+
+ next:
+ dns_db_detachnode(db, &node);
+ result = dns_dbiterator_next(dbiterator);
+ }
+
+cleanup:
+ if (node != NULL) {
+ dns_db_detachnode(db, &node);
+ }
+ dns_dbiterator_destroy(&dbiterator);
+
+ return (ok);
+}
+
+/*
+ * OpenSSL verification of RSA keys with exponent 3 is known to be
+ * broken prior OpenSSL 0.9.8c/0.9.7k. Look for such keys and warn
+ * if they are in use.
+ */
+static void
+zone_check_dnskeys(dns_zone_t *zone, dns_db_t *db) {
+ dns_dbnode_t *node = NULL;
+ dns_dbversion_t *version = NULL;
+ dns_rdata_dnskey_t dnskey;
+ dns_rdata_t rdata = DNS_RDATA_INIT;
+ dns_rdataset_t rdataset;
+ isc_result_t result;
+
+ result = dns_db_findnode(db, &zone->origin, false, &node);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+
+ dns_db_currentversion(db, &version);
+ dns_rdataset_init(&rdataset);
+ result = dns_db_findrdataset(db, node, version, dns_rdatatype_dnskey,
+ dns_rdatatype_none, 0, &rdataset, NULL);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+
+ for (result = dns_rdataset_first(&rdataset); result == ISC_R_SUCCESS;
+ result = dns_rdataset_next(&rdataset))
+ {
+ dns_rdataset_current(&rdataset, &rdata);
+ result = dns_rdata_tostruct(&rdata, &dnskey, NULL);
+ INSIST(result == ISC_R_SUCCESS);
+
+ /*
+ * RFC 3110, section 4: Performance Considerations:
+ *
+ * A public exponent of 3 minimizes the effort needed to verify
+ * a signature. Use of 3 as the public exponent is weak for
+ * confidentiality uses since, if the same data can be collected
+ * encrypted under three different keys with an exponent of 3
+ * then, using the Chinese Remainder Theorem [NETSEC], the
+ * original plain text can be easily recovered. If a key is
+ * known to be used only for authentication, as is the case with
+ * DNSSEC, then an exponent of 3 is acceptable. However other
+ * applications in the future may wish to leverage DNS
+ * distributed keys for applications that do require
+ * confidentiality. For keys which might have such other uses,
+ * a more conservative choice would be 65537 (F4, the fourth
+ * fermat number).
+ */
+ if (dnskey.datalen > 1 && dnskey.data[0] == 1 &&
+ dnskey.data[1] == 3 &&
+ (dnskey.algorithm == DNS_KEYALG_RSAMD5 ||
+ dnskey.algorithm == DNS_KEYALG_RSASHA1 ||
+ dnskey.algorithm == DNS_KEYALG_NSEC3RSASHA1 ||
+ dnskey.algorithm == DNS_KEYALG_RSASHA256 ||
+ dnskey.algorithm == DNS_KEYALG_RSASHA512))
+ {
+ char algorithm[DNS_SECALG_FORMATSIZE];
+ isc_region_t r;
+
+ dns_rdata_toregion(&rdata, &r);
+ dns_secalg_format(dnskey.algorithm, algorithm,
+ sizeof(algorithm));
+
+ dnssec_log(zone, ISC_LOG_WARNING,
+ "weak %s (%u) key found (exponent=3, id=%u)",
+ algorithm, dnskey.algorithm,
+ dst_region_computeid(&r));
+ }
+ dns_rdata_reset(&rdata);
+ }
+ dns_rdataset_disassociate(&rdataset);
+
+cleanup:
+ if (node != NULL) {
+ dns_db_detachnode(db, &node);
+ }
+ if (version != NULL) {
+ dns_db_closeversion(db, &version, false);
+ }
+}
+
+static void
+resume_signingwithkey(dns_zone_t *zone) {
+ dns_dbnode_t *node = NULL;
+ dns_dbversion_t *version = NULL;
+ dns_rdata_t rdata = DNS_RDATA_INIT;
+ dns_rdataset_t rdataset;
+ isc_result_t result;
+ dns_db_t *db = NULL;
+
+ ZONEDB_LOCK(&zone->dblock, isc_rwlocktype_read);
+ if (zone->db != NULL) {
+ dns_db_attach(zone->db, &db);
+ }
+ ZONEDB_UNLOCK(&zone->dblock, isc_rwlocktype_read);
+ if (db == NULL) {
+ goto cleanup;
+ }
+
+ result = dns_db_findnode(db, &zone->origin, false, &node);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+
+ dns_db_currentversion(db, &version);
+ dns_rdataset_init(&rdataset);
+ result = dns_db_findrdataset(db, node, version, zone->privatetype,
+ dns_rdatatype_none, 0, &rdataset, NULL);
+ if (result != ISC_R_SUCCESS) {
+ INSIST(!dns_rdataset_isassociated(&rdataset));
+ goto cleanup;
+ }
+
+ for (result = dns_rdataset_first(&rdataset); result == ISC_R_SUCCESS;
+ result = dns_rdataset_next(&rdataset))
+ {
+ dns_rdataset_current(&rdataset, &rdata);
+ if (rdata.length != 5 || rdata.data[0] == 0 ||
+ rdata.data[4] != 0)
+ {
+ dns_rdata_reset(&rdata);
+ continue;
+ }
+
+ result = zone_signwithkey(zone, rdata.data[0],
+ (rdata.data[1] << 8) | rdata.data[2],
+ rdata.data[3]);
+ if (result != ISC_R_SUCCESS) {
+ dnssec_log(zone, ISC_LOG_ERROR,
+ "zone_signwithkey failed: %s",
+ isc_result_totext(result));
+ }
+ dns_rdata_reset(&rdata);
+ }
+ dns_rdataset_disassociate(&rdataset);
+
+cleanup:
+ if (db != NULL) {
+ if (node != NULL) {
+ dns_db_detachnode(db, &node);
+ }
+ if (version != NULL) {
+ dns_db_closeversion(db, &version, false);
+ }
+ dns_db_detach(&db);
+ }
+}
+
+/*
+ * Initiate adding/removing NSEC3 records belonging to the chain defined by the
+ * supplied NSEC3PARAM RDATA.
+ *
+ * Zone must be locked by caller.
+ */
+static isc_result_t
+zone_addnsec3chain(dns_zone_t *zone, dns_rdata_nsec3param_t *nsec3param) {
+ dns_nsec3chain_t *nsec3chain, *current;
+ dns_dbversion_t *version = NULL;
+ bool nseconly = false, nsec3ok = false;
+ isc_result_t result;
+ isc_time_t now;
+ unsigned int options = 0;
+ char saltbuf[255 * 2 + 1];
+ char flags[sizeof("INITIAL|REMOVE|CREATE|NONSEC|OPTOUT")];
+ dns_db_t *db = NULL;
+
+ ZONEDB_LOCK(&zone->dblock, isc_rwlocktype_read);
+ if (zone->db != NULL) {
+ dns_db_attach(zone->db, &db);
+ }
+ ZONEDB_UNLOCK(&zone->dblock, isc_rwlocktype_read);
+
+ if (db == NULL) {
+ result = ISC_R_SUCCESS;
+ goto cleanup;
+ }
+
+ /*
+ * If this zone is not NSEC3-capable, attempting to remove any NSEC3
+ * chain from it is pointless as it would not be possible for the
+ * latter to exist in the first place.
+ */
+ dns_db_currentversion(db, &version);
+ result = dns_nsec_nseconly(db, version, NULL, &nseconly);
+ nsec3ok = (result == ISC_R_SUCCESS && !nseconly);
+ dns_db_closeversion(db, &version, false);
+ if (!nsec3ok && (nsec3param->flags & DNS_NSEC3FLAG_REMOVE) == 0) {
+ result = ISC_R_SUCCESS;
+ goto cleanup;
+ }
+
+ /*
+ * Allocate and initialize structure preserving state of
+ * adding/removing records belonging to this NSEC3 chain between
+ * separate zone_nsec3chain() calls.
+ */
+ nsec3chain = isc_mem_get(zone->mctx, sizeof *nsec3chain);
+
+ nsec3chain->magic = 0;
+ nsec3chain->done = false;
+ nsec3chain->db = NULL;
+ nsec3chain->dbiterator = NULL;
+ nsec3chain->nsec3param.common.rdclass = nsec3param->common.rdclass;
+ nsec3chain->nsec3param.common.rdtype = nsec3param->common.rdtype;
+ nsec3chain->nsec3param.hash = nsec3param->hash;
+ nsec3chain->nsec3param.iterations = nsec3param->iterations;
+ nsec3chain->nsec3param.flags = nsec3param->flags;
+ nsec3chain->nsec3param.salt_length = nsec3param->salt_length;
+ memmove(nsec3chain->salt, nsec3param->salt, nsec3param->salt_length);
+ nsec3chain->nsec3param.salt = nsec3chain->salt;
+ nsec3chain->seen_nsec = false;
+ nsec3chain->delete_nsec = false;
+ nsec3chain->save_delete_nsec = false;
+
+ /*
+ * Log NSEC3 parameters defined by supplied NSEC3PARAM RDATA.
+ */
+ if (nsec3param->flags == 0) {
+ strlcpy(flags, "NONE", sizeof(flags));
+ } else {
+ flags[0] = '\0';
+ if ((nsec3param->flags & DNS_NSEC3FLAG_REMOVE) != 0) {
+ strlcat(flags, "REMOVE", sizeof(flags));
+ }
+ if ((nsec3param->flags & DNS_NSEC3FLAG_INITIAL) != 0) {
+ if (flags[0] == '\0') {
+ strlcpy(flags, "INITIAL", sizeof(flags));
+ } else {
+ strlcat(flags, "|INITIAL", sizeof(flags));
+ }
+ }
+ if ((nsec3param->flags & DNS_NSEC3FLAG_CREATE) != 0) {
+ if (flags[0] == '\0') {
+ strlcpy(flags, "CREATE", sizeof(flags));
+ } else {
+ strlcat(flags, "|CREATE", sizeof(flags));
+ }
+ }
+ if ((nsec3param->flags & DNS_NSEC3FLAG_NONSEC) != 0) {
+ if (flags[0] == '\0') {
+ strlcpy(flags, "NONSEC", sizeof(flags));
+ } else {
+ strlcat(flags, "|NONSEC", sizeof(flags));
+ }
+ }
+ if ((nsec3param->flags & DNS_NSEC3FLAG_OPTOUT) != 0) {
+ if (flags[0] == '\0') {
+ strlcpy(flags, "OPTOUT", sizeof(flags));
+ } else {
+ strlcat(flags, "|OPTOUT", sizeof(flags));
+ }
+ }
+ }
+ result = dns_nsec3param_salttotext(nsec3param, saltbuf,
+ sizeof(saltbuf));
+ RUNTIME_CHECK(result == ISC_R_SUCCESS);
+ dnssec_log(zone, ISC_LOG_INFO, "zone_addnsec3chain(%u,%s,%u,%s)",
+ nsec3param->hash, flags, nsec3param->iterations, saltbuf);
+
+ /*
+ * If the NSEC3 chain defined by the supplied NSEC3PARAM RDATA is
+ * currently being processed, interrupt its processing to avoid
+ * simultaneously adding and removing records for the same NSEC3 chain.
+ */
+ for (current = ISC_LIST_HEAD(zone->nsec3chain); current != NULL;
+ current = ISC_LIST_NEXT(current, link))
+ {
+ if ((current->db == db) &&
+ (current->nsec3param.hash == nsec3param->hash) &&
+ (current->nsec3param.iterations ==
+ nsec3param->iterations) &&
+ (current->nsec3param.salt_length ==
+ nsec3param->salt_length) &&
+ memcmp(current->nsec3param.salt, nsec3param->salt,
+ nsec3param->salt_length) == 0)
+ {
+ current->done = true;
+ }
+ }
+
+ /*
+ * Attach zone database to the structure initialized above and create
+ * an iterator for it with appropriate options in order to avoid
+ * creating NSEC3 records for NSEC3 records.
+ */
+ dns_db_attach(db, &nsec3chain->db);
+ if ((nsec3chain->nsec3param.flags & DNS_NSEC3FLAG_CREATE) != 0) {
+ options = DNS_DB_NONSEC3;
+ }
+ result = dns_db_createiterator(nsec3chain->db, options,
+ &nsec3chain->dbiterator);
+ if (result == ISC_R_SUCCESS) {
+ result = dns_dbiterator_first(nsec3chain->dbiterator);
+ }
+ if (result == ISC_R_SUCCESS) {
+ /*
+ * Database iterator initialization succeeded. We are now
+ * ready to kick off adding/removing records belonging to this
+ * NSEC3 chain. Append the structure initialized above to the
+ * "nsec3chain" list for the zone and set the appropriate zone
+ * timer so that zone_nsec3chain() is called as soon as
+ * possible.
+ */
+ dns_dbiterator_pause(nsec3chain->dbiterator);
+ ISC_LIST_INITANDAPPEND(zone->nsec3chain, nsec3chain, link);
+ nsec3chain = NULL;
+ if (isc_time_isepoch(&zone->nsec3chaintime)) {
+ TIME_NOW(&now);
+ zone->nsec3chaintime = now;
+ if (zone->task != NULL) {
+ zone_settimer(zone, &now);
+ }
+ }
+ }
+
+ if (nsec3chain != NULL) {
+ if (nsec3chain->db != NULL) {
+ dns_db_detach(&nsec3chain->db);
+ }
+ if (nsec3chain->dbiterator != NULL) {
+ dns_dbiterator_destroy(&nsec3chain->dbiterator);
+ }
+ isc_mem_put(zone->mctx, nsec3chain, sizeof *nsec3chain);
+ }
+
+cleanup:
+ if (db != NULL) {
+ dns_db_detach(&db);
+ }
+ return (result);
+}
+
+/*
+ * Find private-type records at the zone apex which signal that an NSEC3 chain
+ * should be added or removed. For each such record, extract NSEC3PARAM RDATA
+ * and pass it to zone_addnsec3chain().
+ *
+ * Zone must be locked by caller.
+ */
+static void
+resume_addnsec3chain(dns_zone_t *zone) {
+ dns_dbnode_t *node = NULL;
+ dns_dbversion_t *version = NULL;
+ dns_rdataset_t rdataset;
+ isc_result_t result;
+ dns_rdata_nsec3param_t nsec3param;
+ bool nseconly = false, nsec3ok = false;
+ dns_db_t *db = NULL;
+
+ INSIST(LOCKED_ZONE(zone));
+
+ if (zone->privatetype == 0) {
+ return;
+ }
+
+ ZONEDB_LOCK(&zone->dblock, isc_rwlocktype_read);
+ if (zone->db != NULL) {
+ dns_db_attach(zone->db, &db);
+ }
+ ZONEDB_UNLOCK(&zone->dblock, isc_rwlocktype_read);
+ if (db == NULL) {
+ goto cleanup;
+ }
+
+ result = dns_db_findnode(db, &zone->origin, false, &node);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+
+ dns_db_currentversion(db, &version);
+
+ /*
+ * In order to create NSEC3 chains we need the DNSKEY RRset at zone
+ * apex to exist and contain no keys using NSEC-only algorithms.
+ */
+ result = dns_nsec_nseconly(db, version, NULL, &nseconly);
+ nsec3ok = (result == ISC_R_SUCCESS && !nseconly);
+
+ /*
+ * Get the RRset containing all private-type records at the zone apex.
+ */
+ dns_rdataset_init(&rdataset);
+ result = dns_db_findrdataset(db, node, version, zone->privatetype,
+ dns_rdatatype_none, 0, &rdataset, NULL);
+ if (result != ISC_R_SUCCESS) {
+ INSIST(!dns_rdataset_isassociated(&rdataset));
+ goto cleanup;
+ }
+
+ for (result = dns_rdataset_first(&rdataset); result == ISC_R_SUCCESS;
+ result = dns_rdataset_next(&rdataset))
+ {
+ unsigned char buf[DNS_NSEC3PARAM_BUFFERSIZE];
+ dns_rdata_t rdata = DNS_RDATA_INIT;
+ dns_rdata_t private = DNS_RDATA_INIT;
+
+ dns_rdataset_current(&rdataset, &private);
+ /*
+ * Try extracting NSEC3PARAM RDATA from this private-type
+ * record. Failure means this private-type record does not
+ * represent an NSEC3PARAM record, so skip it.
+ */
+ if (!dns_nsec3param_fromprivate(&private, &rdata, buf,
+ sizeof(buf)))
+ {
+ continue;
+ }
+ result = dns_rdata_tostruct(&rdata, &nsec3param, NULL);
+ RUNTIME_CHECK(result == ISC_R_SUCCESS);
+ if (((nsec3param.flags & DNS_NSEC3FLAG_REMOVE) != 0) ||
+ ((nsec3param.flags & DNS_NSEC3FLAG_CREATE) != 0 && nsec3ok))
+ {
+ /*
+ * Pass the NSEC3PARAM RDATA contained in this
+ * private-type record to zone_addnsec3chain() so that
+ * it can kick off adding or removing NSEC3 records.
+ */
+ result = zone_addnsec3chain(zone, &nsec3param);
+ if (result != ISC_R_SUCCESS) {
+ dnssec_log(zone, ISC_LOG_ERROR,
+ "zone_addnsec3chain failed: %s",
+ isc_result_totext(result));
+ }
+ }
+ }
+ dns_rdataset_disassociate(&rdataset);
+
+cleanup:
+ if (db != NULL) {
+ if (node != NULL) {
+ dns_db_detachnode(db, &node);
+ }
+ if (version != NULL) {
+ dns_db_closeversion(db, &version, false);
+ }
+ dns_db_detach(&db);
+ }
+}
+
+static void
+set_resigntime(dns_zone_t *zone) {
+ dns_rdataset_t rdataset;
+ dns_fixedname_t fixed;
+ unsigned int resign;
+ isc_result_t result;
+ uint32_t nanosecs;
+ dns_db_t *db = NULL;
+
+ INSIST(LOCKED_ZONE(zone));
+
+ /* We only re-sign zones that can be dynamically updated */
+ if (zone->update_disabled) {
+ return;
+ }
+
+ if (!inline_secure(zone) &&
+ (zone->type != dns_zone_primary ||
+ (zone->ssutable == NULL &&
+ (zone->update_acl == NULL || dns_acl_isnone(zone->update_acl)))))
+ {
+ return;
+ }
+
+ dns_rdataset_init(&rdataset);
+ dns_fixedname_init(&fixed);
+
+ ZONEDB_LOCK(&zone->dblock, isc_rwlocktype_read);
+ if (zone->db != NULL) {
+ dns_db_attach(zone->db, &db);
+ }
+ ZONEDB_UNLOCK(&zone->dblock, isc_rwlocktype_read);
+ if (db == NULL) {
+ isc_time_settoepoch(&zone->resigntime);
+ return;
+ }
+
+ result = dns_db_getsigningtime(db, &rdataset,
+ dns_fixedname_name(&fixed));
+ if (result != ISC_R_SUCCESS) {
+ isc_time_settoepoch(&zone->resigntime);
+ goto cleanup;
+ }
+
+ resign = rdataset.resign - dns_zone_getsigresigninginterval(zone);
+ dns_rdataset_disassociate(&rdataset);
+ nanosecs = isc_random_uniform(1000000000);
+ isc_time_set(&zone->resigntime, resign, nanosecs);
+
+cleanup:
+ dns_db_detach(&db);
+ return;
+}
+
+static isc_result_t
+check_nsec3param(dns_zone_t *zone, dns_db_t *db) {
+ bool ok = false;
+ dns_dbnode_t *node = NULL;
+ dns_dbversion_t *version = NULL;
+ dns_rdata_nsec3param_t nsec3param;
+ dns_rdataset_t rdataset;
+ isc_result_t result;
+ bool dynamic = (zone->type == dns_zone_primary)
+ ? dns_zone_isdynamic(zone, false)
+ : false;
+
+ dns_rdataset_init(&rdataset);
+ result = dns_db_findnode(db, &zone->origin, false, &node);
+ if (result != ISC_R_SUCCESS) {
+ dns_zone_log(zone, ISC_LOG_ERROR,
+ "nsec3param lookup failure: %s",
+ isc_result_totext(result));
+ return (result);
+ }
+ dns_db_currentversion(db, &version);
+
+ result = dns_db_findrdataset(db, node, version,
+ dns_rdatatype_nsec3param,
+ dns_rdatatype_none, 0, &rdataset, NULL);
+ if (result == ISC_R_NOTFOUND) {
+ INSIST(!dns_rdataset_isassociated(&rdataset));
+ result = ISC_R_SUCCESS;
+ goto cleanup;
+ }
+ if (result != ISC_R_SUCCESS) {
+ INSIST(!dns_rdataset_isassociated(&rdataset));
+ dns_zone_log(zone, ISC_LOG_ERROR,
+ "nsec3param lookup failure: %s",
+ isc_result_totext(result));
+ goto cleanup;
+ }
+
+ for (result = dns_rdataset_first(&rdataset); result == ISC_R_SUCCESS;
+ result = dns_rdataset_next(&rdataset))
+ {
+ dns_rdata_t rdata = DNS_RDATA_INIT;
+
+ dns_rdataset_current(&rdataset, &rdata);
+ result = dns_rdata_tostruct(&rdata, &nsec3param, NULL);
+ RUNTIME_CHECK(result == ISC_R_SUCCESS);
+
+ /*
+ * For dynamic zones we must support every algorithm so we
+ * can regenerate all the NSEC3 chains.
+ * For non-dynamic zones we only need to find a supported
+ * algorithm.
+ */
+ if (DNS_ZONE_OPTION(zone, DNS_ZONEOPT_NSEC3TESTZONE) &&
+ nsec3param.hash == DNS_NSEC3_UNKNOWNALG && !dynamic)
+ {
+ dns_zone_log(zone, ISC_LOG_WARNING,
+ "nsec3 test \"unknown\" hash algorithm "
+ "found: %u",
+ nsec3param.hash);
+ ok = true;
+ } else if (!dns_nsec3_supportedhash(nsec3param.hash)) {
+ if (dynamic) {
+ dns_zone_log(zone, ISC_LOG_ERROR,
+ "unsupported nsec3 hash algorithm"
+ " in dynamic zone: %u",
+ nsec3param.hash);
+ result = DNS_R_BADZONE;
+ /* Stop second error message. */
+ ok = true;
+ break;
+ } else {
+ dns_zone_log(zone, ISC_LOG_WARNING,
+ "unsupported nsec3 hash "
+ "algorithm: %u",
+ nsec3param.hash);
+ }
+ } else {
+ ok = true;
+ }
+
+ /*
+ * Warn if the zone has excessive NSEC3 iterations.
+ */
+ if (nsec3param.iterations > dns_nsec3_maxiterations()) {
+ dnssec_log(zone, ISC_LOG_WARNING,
+ "excessive NSEC3PARAM iterations %u > %u",
+ nsec3param.iterations,
+ dns_nsec3_maxiterations());
+ }
+ }
+ if (result == ISC_R_NOMORE) {
+ result = ISC_R_SUCCESS;
+ }
+
+ if (!ok) {
+ result = DNS_R_BADZONE;
+ dns_zone_log(zone, ISC_LOG_ERROR,
+ "no supported nsec3 hash algorithm");
+ }
+
+cleanup:
+ if (dns_rdataset_isassociated(&rdataset)) {
+ dns_rdataset_disassociate(&rdataset);
+ }
+ dns_db_closeversion(db, &version, false);
+ dns_db_detachnode(db, &node);
+ return (result);
+}
+
+/*
+ * Set the timer for refreshing the key zone to the soonest future time
+ * of the set (current timer, keydata->refresh, keydata->addhd,
+ * keydata->removehd).
+ */
+static void
+set_refreshkeytimer(dns_zone_t *zone, dns_rdata_keydata_t *key,
+ isc_stdtime_t now, bool force) {
+ const char me[] = "set_refreshkeytimer";
+ isc_stdtime_t then;
+ isc_time_t timenow, timethen;
+ char timebuf[80];
+
+ ENTER;
+ then = key->refresh;
+ if (force) {
+ then = now;
+ }
+ if (key->addhd > now && key->addhd < then) {
+ then = key->addhd;
+ }
+ if (key->removehd > now && key->removehd < then) {
+ then = key->removehd;
+ }
+
+ TIME_NOW(&timenow);
+ if (then > now) {
+ DNS_ZONE_TIME_ADD(&timenow, then - now, &timethen);
+ } else {
+ timethen = timenow;
+ }
+ if (isc_time_compare(&zone->refreshkeytime, &timenow) < 0 ||
+ isc_time_compare(&timethen, &zone->refreshkeytime) < 0)
+ {
+ zone->refreshkeytime = timethen;
+ }
+
+ isc_time_formattimestamp(&zone->refreshkeytime, timebuf, 80);
+ dns_zone_log(zone, ISC_LOG_DEBUG(1), "next key refresh: %s", timebuf);
+ zone_settimer(zone, &timenow);
+}
+
+/*
+ * If keynode references a key or a DS rdataset, and if the key
+ * zone does not contain a KEYDATA record for the corresponding name,
+ * then create an empty KEYDATA and push it into the zone as a placeholder,
+ * then schedule a key refresh immediately. This new KEYDATA record will be
+ * updated during the refresh.
+ *
+ * If the key zone is changed, set '*changed' to true.
+ */
+static isc_result_t
+create_keydata(dns_zone_t *zone, dns_db_t *db, dns_dbversion_t *ver,
+ dns_diff_t *diff, dns_keynode_t *keynode, dns_name_t *keyname,
+ bool *changed) {
+ const char me[] = "create_keydata";
+ isc_result_t result = ISC_R_SUCCESS;
+ dns_rdata_t rdata = DNS_RDATA_INIT;
+ dns_rdata_keydata_t kd;
+ unsigned char rrdata[4096];
+ isc_buffer_t rrdatabuf;
+ isc_stdtime_t now;
+
+ REQUIRE(keynode != NULL);
+
+ ENTER;
+ isc_stdtime_get(&now);
+
+ /*
+ * If the keynode has no trust anchor set, we shouldn't be here.
+ */
+ if (!dns_keynode_dsset(keynode, NULL)) {
+ return (ISC_R_FAILURE);
+ }
+
+ memset(&kd, 0, sizeof(kd));
+ kd.common.rdclass = zone->rdclass;
+ kd.common.rdtype = dns_rdatatype_keydata;
+ ISC_LINK_INIT(&kd.common, link);
+
+ isc_buffer_init(&rrdatabuf, rrdata, sizeof(rrdata));
+
+ CHECK(dns_rdata_fromstruct(&rdata, zone->rdclass, dns_rdatatype_keydata,
+ &kd, &rrdatabuf));
+ /* Add rdata to zone. */
+ CHECK(update_one_rr(db, ver, diff, DNS_DIFFOP_ADD, keyname, 0, &rdata));
+ *changed = true;
+
+ /* Refresh new keys from the zone apex as soon as possible. */
+ set_refreshkeytimer(zone, &kd, now, true);
+ return (ISC_R_SUCCESS);
+
+failure:
+ return (result);
+}
+
+/*
+ * Remove from the key zone all the KEYDATA records found in rdataset.
+ */
+static isc_result_t
+delete_keydata(dns_db_t *db, dns_dbversion_t *ver, dns_diff_t *diff,
+ dns_name_t *name, dns_rdataset_t *rdataset) {
+ dns_rdata_t rdata = DNS_RDATA_INIT;
+ isc_result_t result, uresult;
+
+ for (result = dns_rdataset_first(rdataset); result == ISC_R_SUCCESS;
+ result = dns_rdataset_next(rdataset))
+ {
+ dns_rdata_reset(&rdata);
+ dns_rdataset_current(rdataset, &rdata);
+ uresult = update_one_rr(db, ver, diff, DNS_DIFFOP_DEL, name, 0,
+ &rdata);
+ if (uresult != ISC_R_SUCCESS) {
+ return (uresult);
+ }
+ }
+ if (result == ISC_R_NOMORE) {
+ result = ISC_R_SUCCESS;
+ }
+ return (result);
+}
+
+/*
+ * Compute the DNSSEC key ID for a DNSKEY record.
+ */
+static isc_result_t
+compute_tag(dns_name_t *name, dns_rdata_dnskey_t *dnskey, isc_mem_t *mctx,
+ dns_keytag_t *tag) {
+ isc_result_t result;
+ dns_rdata_t rdata = DNS_RDATA_INIT;
+ unsigned char data[4096];
+ isc_buffer_t buffer;
+ dst_key_t *dstkey = NULL;
+
+ isc_buffer_init(&buffer, data, sizeof(data));
+ dns_rdata_fromstruct(&rdata, dnskey->common.rdclass,
+ dns_rdatatype_dnskey, dnskey, &buffer);
+
+ result = dns_dnssec_keyfromrdata(name, &rdata, mctx, &dstkey);
+ if (result == ISC_R_SUCCESS) {
+ *tag = dst_key_id(dstkey);
+ dst_key_free(&dstkey);
+ }
+
+ return (result);
+}
+
+/*
+ * Synth-from-dnssec callbacks to add/delete names from namespace tree.
+ */
+static void
+sfd_add(const dns_name_t *name, void *arg) {
+ if (arg != NULL) {
+ dns_view_sfd_add(arg, name);
+ }
+}
+
+static void
+sfd_del(const dns_name_t *name, void *arg) {
+ if (arg != NULL) {
+ dns_view_sfd_del(arg, name);
+ }
+}
+
+/*
+ * Add key to the security roots.
+ */
+static void
+trust_key(dns_zone_t *zone, dns_name_t *keyname, dns_rdata_dnskey_t *dnskey,
+ bool initial) {
+ isc_result_t result;
+ dns_rdata_t rdata = DNS_RDATA_INIT;
+ unsigned char data[4096], digest[ISC_MAX_MD_SIZE];
+ isc_buffer_t buffer;
+ dns_keytable_t *sr = NULL;
+ dns_rdata_ds_t ds;
+
+ result = dns_view_getsecroots(zone->view, &sr);
+ if (result != ISC_R_SUCCESS) {
+ return;
+ }
+
+ /* Build DS record for key. */
+ isc_buffer_init(&buffer, data, sizeof(data));
+ dns_rdata_fromstruct(&rdata, dnskey->common.rdclass,
+ dns_rdatatype_dnskey, dnskey, &buffer);
+ CHECK(dns_ds_fromkeyrdata(keyname, &rdata, DNS_DSDIGEST_SHA256, digest,
+ &ds));
+ CHECK(dns_keytable_add(sr, true, initial, keyname, &ds, sfd_add,
+ zone->view));
+
+ dns_keytable_detach(&sr);
+
+failure:
+ if (sr != NULL) {
+ dns_keytable_detach(&sr);
+ }
+ return;
+}
+
+/*
+ * Add a null key to the security roots for so that all queries
+ * to the zone will fail.
+ */
+static void
+fail_secure(dns_zone_t *zone, dns_name_t *keyname) {
+ isc_result_t result;
+ dns_keytable_t *sr = NULL;
+
+ result = dns_view_getsecroots(zone->view, &sr);
+ if (result == ISC_R_SUCCESS) {
+ dns_keytable_marksecure(sr, keyname);
+ dns_keytable_detach(&sr);
+ }
+}
+
+/*
+ * Scan a set of KEYDATA records from the key zone. The ones that are
+ * valid (i.e., the add holddown timer has expired) become trusted keys.
+ */
+static void
+load_secroots(dns_zone_t *zone, dns_name_t *name, dns_rdataset_t *rdataset) {
+ isc_result_t result;
+ dns_rdata_t rdata = DNS_RDATA_INIT;
+ dns_rdata_keydata_t keydata;
+ dns_rdata_dnskey_t dnskey;
+ int trusted = 0, revoked = 0, pending = 0;
+ isc_stdtime_t now;
+ dns_keytable_t *sr = NULL;
+
+ isc_stdtime_get(&now);
+
+ result = dns_view_getsecroots(zone->view, &sr);
+ if (result == ISC_R_SUCCESS) {
+ dns_keytable_delete(sr, name, sfd_del, zone->view);
+ dns_keytable_detach(&sr);
+ }
+
+ /* Now insert all the accepted trust anchors from this keydata set. */
+ for (result = dns_rdataset_first(rdataset); result == ISC_R_SUCCESS;
+ result = dns_rdataset_next(rdataset))
+ {
+ dns_rdata_reset(&rdata);
+ dns_rdataset_current(rdataset, &rdata);
+
+ /* Convert rdata to keydata. */
+ result = dns_rdata_tostruct(&rdata, &keydata, NULL);
+ if (result == ISC_R_UNEXPECTEDEND) {
+ continue;
+ }
+ RUNTIME_CHECK(result == ISC_R_SUCCESS);
+
+ /* Set the key refresh timer to force a fast refresh. */
+ set_refreshkeytimer(zone, &keydata, now, true);
+
+ /* If the removal timer is nonzero, this key was revoked. */
+ if (keydata.removehd != 0) {
+ revoked++;
+ continue;
+ }
+
+ /*
+ * If the add timer is still pending, this key is not
+ * trusted yet.
+ */
+ if (now < keydata.addhd) {
+ pending++;
+ continue;
+ }
+
+ /* Convert keydata to dnskey. */
+ dns_keydata_todnskey(&keydata, &dnskey, NULL);
+
+ /* Add to keytables. */
+ trusted++;
+ trust_key(zone, name, &dnskey, (keydata.addhd == 0));
+ }
+
+ if (trusted == 0 && pending != 0) {
+ char namebuf[DNS_NAME_FORMATSIZE];
+ dns_name_format(name, namebuf, sizeof namebuf);
+ dnssec_log(zone, ISC_LOG_ERROR,
+ "No valid trust anchors for '%s'!", namebuf);
+ dnssec_log(zone, ISC_LOG_ERROR,
+ "%d key(s) revoked, %d still pending", revoked,
+ pending);
+ dnssec_log(zone, ISC_LOG_ERROR, "All queries to '%s' will fail",
+ namebuf);
+ fail_secure(zone, name);
+ }
+}
+
+static isc_result_t
+do_one_tuple(dns_difftuple_t **tuple, dns_db_t *db, dns_dbversion_t *ver,
+ dns_diff_t *diff) {
+ dns_diff_t temp_diff;
+ isc_result_t result;
+
+ /*
+ * Create a singleton diff.
+ */
+ dns_diff_init(diff->mctx, &temp_diff);
+ ISC_LIST_APPEND(temp_diff.tuples, *tuple, link);
+
+ /*
+ * Apply it to the database.
+ */
+ result = dns_diff_apply(&temp_diff, db, ver);
+ ISC_LIST_UNLINK(temp_diff.tuples, *tuple, link);
+ if (result != ISC_R_SUCCESS) {
+ dns_difftuple_free(tuple);
+ return (result);
+ }
+
+ /*
+ * Merge it into the current pending journal entry.
+ */
+ dns_diff_appendminimal(diff, tuple);
+
+ /*
+ * Do not clear temp_diff.
+ */
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+update_one_rr(dns_db_t *db, dns_dbversion_t *ver, dns_diff_t *diff,
+ dns_diffop_t op, dns_name_t *name, dns_ttl_t ttl,
+ dns_rdata_t *rdata) {
+ dns_difftuple_t *tuple = NULL;
+ isc_result_t result;
+ result = dns_difftuple_create(diff->mctx, op, name, ttl, rdata, &tuple);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+ return (do_one_tuple(&tuple, db, ver, diff));
+}
+
+static isc_result_t
+update_soa_serial(dns_zone_t *zone, dns_db_t *db, dns_dbversion_t *ver,
+ dns_diff_t *diff, isc_mem_t *mctx,
+ dns_updatemethod_t method) {
+ dns_difftuple_t *deltuple = NULL;
+ dns_difftuple_t *addtuple = NULL;
+ uint32_t serial;
+ isc_result_t result;
+ dns_updatemethod_t used = dns_updatemethod_none;
+
+ INSIST(method != dns_updatemethod_none);
+
+ CHECK(dns_db_createsoatuple(db, ver, mctx, DNS_DIFFOP_DEL, &deltuple));
+ CHECK(dns_difftuple_copy(deltuple, &addtuple));
+ addtuple->op = DNS_DIFFOP_ADD;
+
+ serial = dns_soa_getserial(&addtuple->rdata);
+ serial = dns_update_soaserial(serial, method, &used);
+ if (method != used) {
+ dns_zone_log(zone, ISC_LOG_WARNING,
+ "update_soa_serial:new serial would be lower than "
+ "old serial, using increment method instead");
+ }
+ dns_soa_setserial(serial, &addtuple->rdata);
+ CHECK(do_one_tuple(&deltuple, db, ver, diff));
+ CHECK(do_one_tuple(&addtuple, db, ver, diff));
+ result = ISC_R_SUCCESS;
+
+failure:
+ if (addtuple != NULL) {
+ dns_difftuple_free(&addtuple);
+ }
+ if (deltuple != NULL) {
+ dns_difftuple_free(&deltuple);
+ }
+ return (result);
+}
+
+/*
+ * Write all transactions in 'diff' to the zone journal file.
+ */
+static isc_result_t
+zone_journal(dns_zone_t *zone, dns_diff_t *diff, uint32_t *sourceserial,
+ const char *caller) {
+ const char me[] = "zone_journal";
+ const char *journalfile;
+ isc_result_t result = ISC_R_SUCCESS;
+ dns_journal_t *journal = NULL;
+ unsigned int mode = DNS_JOURNAL_CREATE | DNS_JOURNAL_WRITE;
+
+ ENTER;
+ journalfile = dns_zone_getjournal(zone);
+ if (journalfile != NULL) {
+ result = dns_journal_open(zone->mctx, journalfile, mode,
+ &journal);
+ if (result != ISC_R_SUCCESS) {
+ dns_zone_log(zone, ISC_LOG_ERROR,
+ "%s:dns_journal_open -> %s", caller,
+ isc_result_totext(result));
+ return (result);
+ }
+
+ if (sourceserial != NULL) {
+ dns_journal_set_sourceserial(journal, *sourceserial);
+ }
+
+ result = dns_journal_write_transaction(journal, diff);
+ if (result != ISC_R_SUCCESS) {
+ dns_zone_log(zone, ISC_LOG_ERROR,
+ "%s:dns_journal_write_transaction -> %s",
+ caller, isc_result_totext(result));
+ }
+ dns_journal_destroy(&journal);
+ }
+
+ return (result);
+}
+
+/*
+ * Create an SOA record for a newly-created zone
+ */
+static isc_result_t
+add_soa(dns_zone_t *zone, dns_db_t *db) {
+ isc_result_t result;
+ dns_rdata_t rdata = DNS_RDATA_INIT;
+ unsigned char buf[DNS_SOA_BUFFERSIZE];
+ dns_dbversion_t *ver = NULL;
+ dns_diff_t diff;
+
+ dns_zone_log(zone, ISC_LOG_DEBUG(1), "creating SOA");
+
+ dns_diff_init(zone->mctx, &diff);
+ result = dns_db_newversion(db, &ver);
+ if (result != ISC_R_SUCCESS) {
+ dns_zone_log(zone, ISC_LOG_ERROR,
+ "add_soa:dns_db_newversion -> %s",
+ isc_result_totext(result));
+ goto failure;
+ }
+
+ /* Build SOA record */
+ result = dns_soa_buildrdata(&zone->origin, dns_rootname, zone->rdclass,
+ 0, 0, 0, 0, 0, buf, &rdata);
+ if (result != ISC_R_SUCCESS) {
+ dns_zone_log(zone, ISC_LOG_ERROR,
+ "add_soa:dns_soa_buildrdata -> %s",
+ isc_result_totext(result));
+ goto failure;
+ }
+
+ result = update_one_rr(db, ver, &diff, DNS_DIFFOP_ADD, &zone->origin, 0,
+ &rdata);
+
+failure:
+ dns_diff_clear(&diff);
+ if (ver != NULL) {
+ dns_db_closeversion(db, &ver, (result == ISC_R_SUCCESS));
+ }
+
+ INSIST(ver == NULL);
+
+ return (result);
+}
+
+struct addifmissing_arg {
+ dns_db_t *db;
+ dns_dbversion_t *ver;
+ dns_diff_t *diff;
+ dns_zone_t *zone;
+ bool *changed;
+ isc_result_t result;
+};
+
+static void
+addifmissing(dns_keytable_t *keytable, dns_keynode_t *keynode,
+ dns_name_t *keyname, void *arg) {
+ dns_db_t *db = ((struct addifmissing_arg *)arg)->db;
+ dns_dbversion_t *ver = ((struct addifmissing_arg *)arg)->ver;
+ dns_diff_t *diff = ((struct addifmissing_arg *)arg)->diff;
+ dns_zone_t *zone = ((struct addifmissing_arg *)arg)->zone;
+ bool *changed = ((struct addifmissing_arg *)arg)->changed;
+ isc_result_t result;
+ dns_fixedname_t fname;
+
+ UNUSED(keytable);
+
+ if (((struct addifmissing_arg *)arg)->result != ISC_R_SUCCESS) {
+ return;
+ }
+
+ if (!dns_keynode_managed(keynode)) {
+ return;
+ }
+
+ /*
+ * If the keynode has no trust anchor set, return.
+ */
+ if (!dns_keynode_dsset(keynode, NULL)) {
+ return;
+ }
+
+ /*
+ * Check whether there's already a KEYDATA entry for this name;
+ * if so, we don't need to add another.
+ */
+ dns_fixedname_init(&fname);
+ result = dns_db_find(db, keyname, ver, dns_rdatatype_keydata,
+ DNS_DBFIND_NOWILD, 0, NULL,
+ dns_fixedname_name(&fname), NULL, NULL);
+ if (result == ISC_R_SUCCESS) {
+ return;
+ }
+
+ /*
+ * Create the keydata.
+ */
+ result = create_keydata(zone, db, ver, diff, keynode, keyname, changed);
+ if (result != ISC_R_SUCCESS && result != ISC_R_NOMORE) {
+ ((struct addifmissing_arg *)arg)->result = result;
+ }
+}
+
+/*
+ * Synchronize the set of initializing keys found in managed-keys {}
+ * statements with the set of trust anchors found in the managed-keys.bind
+ * zone. If a domain is no longer named in managed-keys, delete all keys
+ * from that domain from the key zone. If a domain is configured as an
+ * initial-key in trust-anchors, but there are no references to it in the
+ * key zone, load the key zone with the initializing key(s) for that
+ * domain and schedule a key refresh. If a domain is configured as
+ * an initial-ds in trust-anchors, fetch the DNSKEY RRset, load the key
+ * zone with the matching key, and schedule a key refresh.
+ */
+static isc_result_t
+sync_keyzone(dns_zone_t *zone, dns_db_t *db) {
+ isc_result_t result = ISC_R_SUCCESS;
+ bool changed = false;
+ bool commit = false;
+ dns_keynode_t *keynode = NULL;
+ dns_view_t *view = zone->view;
+ dns_keytable_t *sr = NULL;
+ dns_dbversion_t *ver = NULL;
+ dns_diff_t diff;
+ dns_rriterator_t rrit;
+ struct addifmissing_arg arg;
+
+ dns_zone_log(zone, ISC_LOG_DEBUG(1), "synchronizing trusted keys");
+
+ dns_diff_init(zone->mctx, &diff);
+
+ CHECK(dns_view_getsecroots(view, &sr));
+
+ result = dns_db_newversion(db, &ver);
+ if (result != ISC_R_SUCCESS) {
+ dnssec_log(zone, ISC_LOG_ERROR,
+ "sync_keyzone:dns_db_newversion -> %s",
+ isc_result_totext(result));
+ goto failure;
+ }
+
+ /*
+ * Walk the zone DB. If we find any keys whose names are no longer
+ * in trust-anchors, or which have been changed from initial to static,
+ * (meaning they are permanent and not RFC5011-maintained), delete
+ * them from the zone. Otherwise call load_secroots(), which
+ * loads keys into secroots as appropriate.
+ */
+ dns_rriterator_init(&rrit, db, ver, 0);
+ for (result = dns_rriterator_first(&rrit); result == ISC_R_SUCCESS;
+ result = dns_rriterator_nextrrset(&rrit))
+ {
+ dns_rdataset_t *rdataset = NULL;
+ dns_rdata_t rdata = DNS_RDATA_INIT;
+ dns_rdata_keydata_t keydata;
+ isc_stdtime_t now;
+ bool load = true;
+ dns_name_t *rrname = NULL;
+ uint32_t ttl;
+
+ isc_stdtime_get(&now);
+
+ dns_rriterator_current(&rrit, &rrname, &ttl, &rdataset, NULL);
+ if (!dns_rdataset_isassociated(rdataset)) {
+ dns_rriterator_destroy(&rrit);
+ goto failure;
+ }
+
+ if (rdataset->type != dns_rdatatype_keydata) {
+ continue;
+ }
+
+ /*
+ * The managed-keys zone can contain a placeholder instead of
+ * legitimate data, in which case we will not use it, and we
+ * will try to refresh it.
+ */
+ for (result = dns_rdataset_first(rdataset);
+ result == ISC_R_SUCCESS;
+ result = dns_rdataset_next(rdataset))
+ {
+ isc_result_t iresult;
+
+ dns_rdata_reset(&rdata);
+ dns_rdataset_current(rdataset, &rdata);
+
+ iresult = dns_rdata_tostruct(&rdata, &keydata, NULL);
+ /* Do we have a valid placeholder KEYDATA record? */
+ if (iresult == ISC_R_SUCCESS && keydata.flags == 0 &&
+ keydata.protocol == 0 && keydata.algorithm == 0)
+ {
+ set_refreshkeytimer(zone, &keydata, now, true);
+ load = false;
+ }
+ }
+
+ /*
+ * Release db wrlock to prevent LOR reports against
+ * dns_keytable_forall() call below.
+ */
+ dns_rriterator_pause(&rrit);
+ result = dns_keytable_find(sr, rrname, &keynode);
+ if (result != ISC_R_SUCCESS || !dns_keynode_managed(keynode)) {
+ CHECK(delete_keydata(db, ver, &diff, rrname, rdataset));
+ changed = true;
+ } else if (load) {
+ load_secroots(zone, rrname, rdataset);
+ }
+
+ if (keynode != NULL) {
+ dns_keytable_detachkeynode(sr, &keynode);
+ }
+ }
+ dns_rriterator_destroy(&rrit);
+
+ /*
+ * Walk secroots to find any initial keys that aren't in
+ * the zone. If we find any, add them to the zone directly.
+ * If any DS-style initial keys are found, refresh the key
+ * zone so that they'll be looked up.
+ */
+ arg.db = db;
+ arg.ver = ver;
+ arg.result = ISC_R_SUCCESS;
+ arg.diff = &diff;
+ arg.zone = zone;
+ arg.changed = &changed;
+ dns_keytable_forall(sr, addifmissing, &arg);
+ result = arg.result;
+ if (changed) {
+ /* Write changes to journal file. */
+ CHECK(update_soa_serial(zone, db, ver, &diff, zone->mctx,
+ zone->updatemethod));
+ CHECK(zone_journal(zone, &diff, NULL, "sync_keyzone"));
+
+ DNS_ZONE_SETFLAG(zone, DNS_ZONEFLG_LOADED);
+ zone_needdump(zone, 30);
+ commit = true;
+ }
+
+failure:
+ if (result != ISC_R_SUCCESS) {
+ dnssec_log(zone, ISC_LOG_ERROR,
+ "unable to synchronize managed keys: %s",
+ isc_result_totext(result));
+ isc_time_settoepoch(&zone->refreshkeytime);
+ }
+ if (keynode != NULL) {
+ dns_keytable_detachkeynode(sr, &keynode);
+ }
+ if (sr != NULL) {
+ dns_keytable_detach(&sr);
+ }
+ if (ver != NULL) {
+ dns_db_closeversion(db, &ver, commit);
+ }
+ dns_diff_clear(&diff);
+
+ INSIST(ver == NULL);
+
+ return (result);
+}
+
+isc_result_t
+dns_zone_synckeyzone(dns_zone_t *zone) {
+ isc_result_t result;
+ dns_db_t *db = NULL;
+
+ if (zone->type != dns_zone_key) {
+ return (DNS_R_BADZONE);
+ }
+
+ CHECK(dns_zone_getdb(zone, &db));
+
+ LOCK_ZONE(zone);
+ result = sync_keyzone(zone, db);
+ UNLOCK_ZONE(zone);
+
+failure:
+ if (db != NULL) {
+ dns_db_detach(&db);
+ }
+ return (result);
+}
+
+static void
+maybe_send_secure(dns_zone_t *zone) {
+ isc_result_t result;
+
+ /*
+ * We've finished loading, or else failed to load, an inline-signing
+ * 'secure' zone. We now need information about the status of the
+ * 'raw' zone. If we failed to load, then we need it to send a
+ * copy of its database; if we succeeded, we need it to send its
+ * serial number so that we can sync with it. If it has not yet
+ * loaded, we set a flag so that it will send the necessary
+ * information when it has finished loading.
+ */
+ if (zone->raw->db != NULL) {
+ if (zone->db != NULL) {
+ uint32_t serial;
+ unsigned int soacount;
+
+ result = zone_get_from_db(
+ zone->raw, zone->raw->db, NULL, &soacount, NULL,
+ &serial, NULL, NULL, NULL, NULL, NULL);
+ if (result == ISC_R_SUCCESS && soacount > 0U) {
+ zone_send_secureserial(zone->raw, serial);
+ }
+ } else {
+ zone_send_securedb(zone->raw, zone->raw->db);
+ }
+ } else {
+ DNS_ZONE_SETFLAG(zone->raw, DNS_ZONEFLG_SENDSECURE);
+ }
+}
+
+static bool
+zone_unchanged(dns_db_t *db1, dns_db_t *db2, isc_mem_t *mctx) {
+ isc_result_t result;
+ bool answer = false;
+ dns_diff_t diff;
+
+ dns_diff_init(mctx, &diff);
+ result = dns_db_diffx(&diff, db1, NULL, db2, NULL, NULL);
+ if (result == ISC_R_SUCCESS && ISC_LIST_EMPTY(diff.tuples)) {
+ answer = true;
+ }
+ dns_diff_clear(&diff);
+ return (answer);
+}
+
+/*
+ * The zone is presumed to be locked.
+ * If this is a inline_raw zone the secure version is also locked.
+ */
+static isc_result_t
+zone_postload(dns_zone_t *zone, dns_db_t *db, isc_time_t loadtime,
+ isc_result_t result) {
+ unsigned int soacount = 0;
+ unsigned int nscount = 0;
+ unsigned int errors = 0;
+ uint32_t serial, oldserial, refresh, retry, expire, minimum, soattl;
+ isc_time_t now;
+ bool needdump = false;
+ bool fixjournal = false;
+ bool hasinclude = DNS_ZONE_FLAG(zone, DNS_ZONEFLG_HASINCLUDE);
+ bool noprimary = false;
+ bool had_db = false;
+ dns_include_t *inc;
+ bool is_dynamic = false;
+
+ INSIST(LOCKED_ZONE(zone));
+ if (inline_raw(zone)) {
+ INSIST(LOCKED_ZONE(zone->secure));
+ }
+
+ TIME_NOW(&now);
+
+ /*
+ * Initiate zone transfer? We may need a error code that
+ * indicates that the "permanent" form does not exist.
+ * XXX better error feedback to log.
+ */
+ if (result != ISC_R_SUCCESS && result != DNS_R_SEENINCLUDE) {
+ if (zone->type == dns_zone_secondary ||
+ zone->type == dns_zone_mirror ||
+ zone->type == dns_zone_stub ||
+ (zone->type == dns_zone_redirect &&
+ zone->primaries == NULL))
+ {
+ if (result == ISC_R_FILENOTFOUND) {
+ dns_zone_logc(zone, DNS_LOGCATEGORY_ZONELOAD,
+ ISC_LOG_DEBUG(1),
+ "no master file");
+ } else if (result != DNS_R_NOMASTERFILE) {
+ dns_zone_logc(zone, DNS_LOGCATEGORY_ZONELOAD,
+ ISC_LOG_ERROR,
+ "loading from master file %s "
+ "failed: %s",
+ zone->masterfile,
+ isc_result_totext(result));
+ }
+ } else if (zone->type == dns_zone_primary &&
+ inline_secure(zone) && result == ISC_R_FILENOTFOUND)
+ {
+ dns_zone_logc(zone, DNS_LOGCATEGORY_ZONELOAD,
+ ISC_LOG_DEBUG(1),
+ "no master file, requesting db");
+ maybe_send_secure(zone);
+ } else {
+ int level = ISC_LOG_ERROR;
+ if (zone->type == dns_zone_key &&
+ result == ISC_R_FILENOTFOUND)
+ {
+ level = ISC_LOG_DEBUG(1);
+ }
+ dns_zone_logc(zone, DNS_LOGCATEGORY_ZONELOAD, level,
+ "loading from master file %s failed: %s",
+ zone->masterfile,
+ isc_result_totext(result));
+ noprimary = true;
+ }
+
+ if (zone->type != dns_zone_key) {
+ goto cleanup;
+ }
+ }
+
+ dns_zone_logc(zone, DNS_LOGCATEGORY_ZONELOAD, ISC_LOG_DEBUG(2),
+ "number of nodes in database: %u",
+ dns_db_nodecount(db, dns_dbtree_main));
+
+ if (result == DNS_R_SEENINCLUDE) {
+ DNS_ZONE_SETFLAG(zone, DNS_ZONEFLG_HASINCLUDE);
+ } else {
+ DNS_ZONE_CLRFLAG(zone, DNS_ZONEFLG_HASINCLUDE);
+ }
+
+ /*
+ * If there's no master file for a key zone, then the zone is new:
+ * create an SOA record. (We do this now, instead of later, so that
+ * if there happens to be a journal file, we can roll forward from
+ * a sane starting point.)
+ */
+ if (noprimary && zone->type == dns_zone_key) {
+ result = add_soa(zone, db);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+ }
+
+ /*
+ * Apply update log, if any, on initial load.
+ */
+ if (zone->journal != NULL &&
+ !DNS_ZONE_OPTION(zone, DNS_ZONEOPT_NOMERGE) &&
+ !DNS_ZONE_FLAG(zone, DNS_ZONEFLG_LOADED))
+ {
+ result = zone_journal_rollforward(zone, db, &needdump,
+ &fixjournal);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+ }
+
+ /*
+ * Obtain ns, soa and cname counts for top of zone.
+ */
+ INSIST(db != NULL);
+ result = zone_get_from_db(zone, db, &nscount, &soacount, &soattl,
+ &serial, &refresh, &retry, &expire, &minimum,
+ &errors);
+ if (result != ISC_R_SUCCESS && zone->type != dns_zone_key) {
+ dns_zone_logc(zone, DNS_LOGCATEGORY_ZONELOAD, ISC_LOG_ERROR,
+ "could not find NS and/or SOA records");
+ }
+
+ /*
+ * Process any queued NSEC3PARAM change requests. Only for dynamic
+ * zones, an inline-signing zone will perform this action when
+ * receiving the secure db (receive_secure_db).
+ */
+ is_dynamic = dns_zone_isdynamic(zone, true);
+ if (is_dynamic) {
+ isc_event_t *setnsec3param_event;
+ dns_zone_t *dummy;
+
+ while (!ISC_LIST_EMPTY(zone->setnsec3param_queue)) {
+ setnsec3param_event =
+ ISC_LIST_HEAD(zone->setnsec3param_queue);
+ ISC_LIST_UNLINK(zone->setnsec3param_queue,
+ setnsec3param_event, ev_link);
+ dummy = NULL;
+ zone_iattach(zone, &dummy);
+ isc_task_send(zone->task, &setnsec3param_event);
+ }
+ }
+
+ /*
+ * Check to make sure the journal is up to date, and remove the
+ * journal file if it isn't, as we wouldn't be able to apply
+ * updates otherwise.
+ */
+ if (zone->journal != NULL && is_dynamic &&
+ !DNS_ZONE_OPTION(zone, DNS_ZONEOPT_IXFRFROMDIFFS))
+ {
+ uint32_t jserial;
+ dns_journal_t *journal = NULL;
+ bool empty = false;
+
+ result = dns_journal_open(zone->mctx, zone->journal,
+ DNS_JOURNAL_READ, &journal);
+ if (result == ISC_R_SUCCESS) {
+ jserial = dns_journal_last_serial(journal);
+ empty = dns_journal_empty(journal);
+ dns_journal_destroy(&journal);
+ } else {
+ jserial = serial;
+ result = ISC_R_SUCCESS;
+ }
+
+ if (jserial != serial) {
+ if (!empty) {
+ dns_zone_logc(zone, DNS_LOGCATEGORY_ZONELOAD,
+ ISC_LOG_INFO,
+ "journal file is out of date: "
+ "removing journal file");
+ }
+ if (remove(zone->journal) < 0 && errno != ENOENT) {
+ char strbuf[ISC_STRERRORSIZE];
+ strerror_r(errno, strbuf, sizeof(strbuf));
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL,
+ DNS_LOGMODULE_ZONE,
+ ISC_LOG_WARNING,
+ "unable to remove journal "
+ "'%s': '%s'",
+ zone->journal, strbuf);
+ }
+ }
+ }
+
+ dns_zone_logc(zone, DNS_LOGCATEGORY_ZONELOAD, ISC_LOG_DEBUG(1),
+ "loaded; checking validity");
+
+ /*
+ * Primary / Secondary / Mirror / Stub zones require both NS and SOA
+ * records at the top of the zone.
+ */
+
+ switch (zone->type) {
+ case dns_zone_dlz:
+ case dns_zone_primary:
+ case dns_zone_secondary:
+ case dns_zone_mirror:
+ case dns_zone_stub:
+ case dns_zone_redirect:
+ if (soacount != 1) {
+ dns_zone_logc(zone, DNS_LOGCATEGORY_ZONELOAD,
+ ISC_LOG_ERROR, "has %d SOA records",
+ soacount);
+ result = DNS_R_BADZONE;
+ }
+ if (nscount == 0) {
+ dns_zone_logc(zone, DNS_LOGCATEGORY_ZONELOAD,
+ ISC_LOG_ERROR, "has no NS records");
+ result = DNS_R_BADZONE;
+ }
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+ if (zone->type == dns_zone_primary && errors != 0) {
+ result = DNS_R_BADZONE;
+ goto cleanup;
+ }
+ if (zone->type != dns_zone_stub &&
+ zone->type != dns_zone_redirect)
+ {
+ result = check_nsec3param(zone, db);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+ }
+ if (zone->type == dns_zone_primary &&
+ DNS_ZONE_OPTION(zone, DNS_ZONEOPT_CHECKINTEGRITY) &&
+ !integrity_checks(zone, db))
+ {
+ result = DNS_R_BADZONE;
+ goto cleanup;
+ }
+ if (zone->type == dns_zone_primary &&
+ DNS_ZONE_OPTION(zone, DNS_ZONEOPT_CHECKDUPRR) &&
+ !zone_check_dup(zone, db))
+ {
+ result = DNS_R_BADZONE;
+ goto cleanup;
+ }
+
+ if (zone->type == dns_zone_primary) {
+ result = dns_zone_cdscheck(zone, db, NULL);
+ if (result != ISC_R_SUCCESS) {
+ dns_zone_log(zone, ISC_LOG_ERROR,
+ "CDS/CDNSKEY consistency checks "
+ "failed");
+ goto cleanup;
+ }
+ }
+
+ result = dns_zone_verifydb(zone, db, NULL);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+
+ if (zone->db != NULL) {
+ unsigned int oldsoacount;
+
+ /*
+ * This is checked in zone_replacedb() for
+ * secondary zones as they don't reload from disk.
+ */
+ result = zone_get_from_db(
+ zone, zone->db, NULL, &oldsoacount, NULL,
+ &oldserial, NULL, NULL, NULL, NULL, NULL);
+ RUNTIME_CHECK(result == ISC_R_SUCCESS);
+ RUNTIME_CHECK(oldsoacount > 0U);
+ if (DNS_ZONE_OPTION(zone, DNS_ZONEOPT_IXFRFROMDIFFS) &&
+ !isc_serial_gt(serial, oldserial))
+ {
+ uint32_t serialmin, serialmax;
+
+ INSIST(zone->type == dns_zone_primary);
+ INSIST(zone->raw == NULL);
+
+ if (serial == oldserial &&
+ zone_unchanged(zone->db, db, zone->mctx))
+ {
+ dns_zone_logc(zone,
+ DNS_LOGCATEGORY_ZONELOAD,
+ ISC_LOG_INFO,
+ "ixfr-from-differences: "
+ "unchanged");
+ zone->loadtime = loadtime;
+ goto done;
+ }
+
+ serialmin = (oldserial + 1) & 0xffffffffU;
+ serialmax = (oldserial + 0x7fffffffU) &
+ 0xffffffffU;
+ dns_zone_logc(zone, DNS_LOGCATEGORY_ZONELOAD,
+ ISC_LOG_ERROR,
+ "ixfr-from-differences: "
+ "new serial (%u) out of range "
+ "[%u - %u]",
+ serial, serialmin, serialmax);
+ result = DNS_R_BADZONE;
+ goto cleanup;
+ } else if (!isc_serial_ge(serial, oldserial)) {
+ dns_zone_logc(zone, DNS_LOGCATEGORY_ZONELOAD,
+ ISC_LOG_ERROR,
+ "zone serial (%u/%u) has gone "
+ "backwards",
+ serial, oldserial);
+ } else if (serial == oldserial && !hasinclude &&
+ strcmp(zone->db_argv[0], "_builtin") != 0)
+ {
+ dns_zone_logc(zone, DNS_LOGCATEGORY_ZONELOAD,
+ ISC_LOG_ERROR,
+ "zone serial (%u) unchanged. "
+ "zone may fail to transfer "
+ "to secondaries.",
+ serial);
+ }
+ }
+
+ if (zone->type == dns_zone_primary &&
+ (zone->update_acl != NULL || zone->ssutable != NULL) &&
+ dns_zone_getsigresigninginterval(zone) < (3 * refresh) &&
+ dns_db_issecure(db))
+ {
+ dns_zone_logc(zone, DNS_LOGCATEGORY_ZONELOAD,
+ ISC_LOG_WARNING,
+ "sig-re-signing-interval less than "
+ "3 * refresh.");
+ }
+
+ zone->refresh = RANGE(refresh, zone->minrefresh,
+ zone->maxrefresh);
+ zone->retry = RANGE(retry, zone->minretry, zone->maxretry);
+ zone->expire = RANGE(expire, zone->refresh + zone->retry,
+ DNS_MAX_EXPIRE);
+ zone->soattl = soattl;
+ zone->minimum = minimum;
+ DNS_ZONE_SETFLAG(zone, DNS_ZONEFLG_HAVETIMERS);
+
+ if (zone->type == dns_zone_secondary ||
+ zone->type == dns_zone_mirror ||
+ zone->type == dns_zone_stub ||
+ (zone->type == dns_zone_redirect &&
+ zone->primaries != NULL))
+ {
+ isc_time_t t;
+ uint32_t delay;
+
+ result = isc_file_getmodtime(zone->journal, &t);
+ if (result != ISC_R_SUCCESS) {
+ result = isc_file_getmodtime(zone->masterfile,
+ &t);
+ }
+ if (result == ISC_R_SUCCESS) {
+ DNS_ZONE_TIME_ADD(&t, zone->expire,
+ &zone->expiretime);
+ } else {
+ DNS_ZONE_TIME_ADD(&now, zone->retry,
+ &zone->expiretime);
+ }
+
+ delay = (zone->retry -
+ isc_random_uniform((zone->retry * 3) / 4));
+ DNS_ZONE_TIME_ADD(&now, delay, &zone->refreshtime);
+ if (isc_time_compare(&zone->refreshtime,
+ &zone->expiretime) >= 0)
+ {
+ zone->refreshtime = now;
+ }
+ }
+
+ break;
+
+ case dns_zone_key:
+ /* Nothing needs to be done now */
+ break;
+
+ default:
+ UNEXPECTED_ERROR("unexpected zone type %d", zone->type);
+ result = ISC_R_UNEXPECTED;
+ goto cleanup;
+ }
+
+ /*
+ * Check for weak DNSKEY's.
+ */
+ if (zone->type == dns_zone_primary) {
+ zone_check_dnskeys(zone, db);
+ }
+
+ /*
+ * Schedule DNSSEC key refresh.
+ */
+ if (zone->type == dns_zone_primary &&
+ DNS_ZONEKEY_OPTION(zone, DNS_ZONEKEY_MAINTAIN))
+ {
+ zone->refreshkeytime = now;
+ }
+
+ ZONEDB_LOCK(&zone->dblock, isc_rwlocktype_write);
+ if (zone->db != NULL) {
+ had_db = true;
+ result = zone_replacedb(zone, db, false);
+ ZONEDB_UNLOCK(&zone->dblock, isc_rwlocktype_write);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+ } else {
+ zone_attachdb(zone, db);
+ ZONEDB_UNLOCK(&zone->dblock, isc_rwlocktype_write);
+ DNS_ZONE_SETFLAG(zone, DNS_ZONEFLG_LOADED |
+ DNS_ZONEFLG_NEEDSTARTUPNOTIFY);
+ if (DNS_ZONE_FLAG(zone, DNS_ZONEFLG_SENDSECURE) &&
+ inline_raw(zone))
+ {
+ if (zone->secure->db == NULL) {
+ zone_send_securedb(zone, db);
+ } else {
+ zone_send_secureserial(zone, serial);
+ }
+ }
+ }
+
+ /*
+ * Finished loading inline-signing zone; need to get status
+ * from the raw side now.
+ */
+ if (zone->type == dns_zone_primary && inline_secure(zone)) {
+ maybe_send_secure(zone);
+ }
+
+ result = ISC_R_SUCCESS;
+
+ if (fixjournal) {
+ DNS_ZONE_SETFLAG(zone, DNS_ZONEFLG_FIXJOURNAL);
+ zone_journal_compact(zone, zone->db, 0);
+ }
+ if (needdump) {
+ if (zone->type == dns_zone_key) {
+ zone_needdump(zone, 30);
+ } else {
+ zone_needdump(zone, DNS_DUMP_DELAY);
+ }
+ }
+
+ if (zone->task != NULL) {
+ if (zone->type == dns_zone_primary) {
+ set_resigntime(zone);
+ resume_signingwithkey(zone);
+ resume_addnsec3chain(zone);
+ }
+
+ is_dynamic = dns_zone_isdynamic(zone, false);
+ if (zone->type == dns_zone_primary &&
+ !DNS_ZONEKEY_OPTION(zone, DNS_ZONEKEY_NORESIGN) &&
+ is_dynamic && dns_db_issecure(db))
+ {
+ dns_name_t *name;
+ dns_fixedname_t fixed;
+ dns_rdataset_t next;
+
+ dns_rdataset_init(&next);
+ name = dns_fixedname_initname(&fixed);
+
+ result = dns_db_getsigningtime(db, &next, name);
+ if (result == ISC_R_SUCCESS) {
+ isc_stdtime_t timenow;
+ char namebuf[DNS_NAME_FORMATSIZE];
+ char typebuf[DNS_RDATATYPE_FORMATSIZE];
+
+ isc_stdtime_get(&timenow);
+ dns_name_format(name, namebuf, sizeof(namebuf));
+ dns_rdatatype_format(next.covers, typebuf,
+ sizeof(typebuf));
+ dnssec_log(
+ zone, ISC_LOG_DEBUG(3),
+ "next resign: %s/%s "
+ "in %d seconds",
+ namebuf, typebuf,
+ next.resign - timenow -
+ dns_zone_getsigresigninginterval(
+ zone));
+ dns_rdataset_disassociate(&next);
+ } else {
+ dnssec_log(zone, ISC_LOG_WARNING,
+ "signed dynamic zone has no "
+ "resign event scheduled");
+ }
+ }
+
+ zone_settimer(zone, &now);
+ }
+
+ /*
+ * Clear old include list.
+ */
+ for (inc = ISC_LIST_HEAD(zone->includes); inc != NULL;
+ inc = ISC_LIST_HEAD(zone->includes))
+ {
+ ISC_LIST_UNLINK(zone->includes, inc, link);
+ isc_mem_free(zone->mctx, inc->name);
+ isc_mem_put(zone->mctx, inc, sizeof(*inc));
+ }
+ zone->nincludes = 0;
+
+ /*
+ * Transfer new include list.
+ */
+ for (inc = ISC_LIST_HEAD(zone->newincludes); inc != NULL;
+ inc = ISC_LIST_HEAD(zone->newincludes))
+ {
+ ISC_LIST_UNLINK(zone->newincludes, inc, link);
+ ISC_LIST_APPEND(zone->includes, inc, link);
+ zone->nincludes++;
+ }
+
+ if (!dns_db_ispersistent(db)) {
+ dns_zone_logc(zone, DNS_LOGCATEGORY_ZONELOAD, ISC_LOG_INFO,
+ "loaded serial %u%s", serial,
+ dns_db_issecure(db) ? " (DNSSEC signed)" : "");
+ }
+
+ if (!had_db && zone->type == dns_zone_mirror) {
+ dns_zone_logc(zone, DNS_LOGCATEGORY_ZONELOAD, ISC_LOG_INFO,
+ "mirror zone is now in use");
+ }
+
+ zone->loadtime = loadtime;
+ goto done;
+
+cleanup:
+ if (result != ISC_R_SUCCESS) {
+ dns_zone_rpz_disable_db(zone, db);
+ dns_zone_catz_disable_db(zone, db);
+ }
+
+ for (inc = ISC_LIST_HEAD(zone->newincludes); inc != NULL;
+ inc = ISC_LIST_HEAD(zone->newincludes))
+ {
+ ISC_LIST_UNLINK(zone->newincludes, inc, link);
+ isc_mem_free(zone->mctx, inc->name);
+ isc_mem_put(zone->mctx, inc, sizeof(*inc));
+ }
+ if (zone->type == dns_zone_secondary || zone->type == dns_zone_mirror ||
+ zone->type == dns_zone_stub || zone->type == dns_zone_key ||
+ (zone->type == dns_zone_redirect && zone->primaries != NULL))
+ {
+ if (result != ISC_R_NOMEMORY) {
+ if (zone->journal != NULL) {
+ zone_saveunique(zone, zone->journal,
+ "jn-XXXXXXXX");
+ }
+ if (zone->masterfile != NULL) {
+ zone_saveunique(zone, zone->masterfile,
+ "db-XXXXXXXX");
+ }
+ }
+
+ /* Mark the zone for immediate refresh. */
+ zone->refreshtime = now;
+ if (zone->task != NULL) {
+ zone_settimer(zone, &now);
+ }
+ result = ISC_R_SUCCESS;
+ } else if (zone->type == dns_zone_primary ||
+ zone->type == dns_zone_redirect)
+ {
+ if (!(inline_secure(zone) && result == ISC_R_FILENOTFOUND)) {
+ dns_zone_logc(zone, DNS_LOGCATEGORY_ZONELOAD,
+ ISC_LOG_ERROR,
+ "not loaded due to errors.");
+ } else if (zone->type == dns_zone_primary) {
+ result = ISC_R_SUCCESS;
+ }
+ }
+
+done:
+ DNS_ZONE_CLRFLAG(zone, DNS_ZONEFLG_LOADPENDING);
+ /*
+ * If this is an inline-signed zone and we were called for the raw
+ * zone, we need to clear DNS_ZONEFLG_LOADPENDING for the secure zone
+ * as well, but only if this is a reload, not an initial zone load: in
+ * the former case, zone_postload() will not be run for the secure
+ * zone; in the latter case, it will be. Check which case we are
+ * dealing with by consulting the DNS_ZONEFLG_LOADED flag for the
+ * secure zone: if it is set, this must be a reload.
+ */
+ if (inline_raw(zone) && DNS_ZONE_FLAG(zone->secure, DNS_ZONEFLG_LOADED))
+ {
+ DNS_ZONE_CLRFLAG(zone->secure, DNS_ZONEFLG_LOADPENDING);
+ /*
+ * Re-start zone maintenance if it had been stalled
+ * due to DNS_ZONEFLG_LOADPENDING being set when
+ * zone_maintenance was called.
+ */
+ if (zone->secure->task != NULL) {
+ zone_settimer(zone->secure, &now);
+ }
+ }
+
+ zone_debuglog(zone, "zone_postload", 99, "done");
+
+ return (result);
+}
+
+static bool
+exit_check(dns_zone_t *zone) {
+ REQUIRE(LOCKED_ZONE(zone));
+
+ if (DNS_ZONE_FLAG(zone, DNS_ZONEFLG_SHUTDOWN) &&
+ isc_refcount_current(&zone->irefs) == 0)
+ {
+ /*
+ * DNS_ZONEFLG_SHUTDOWN can only be set if erefs == 0.
+ */
+ INSIST(isc_refcount_current(&zone->erefs) == 0);
+ return (true);
+ }
+ return (false);
+}
+
+static bool
+zone_check_ns(dns_zone_t *zone, dns_db_t *db, dns_dbversion_t *version,
+ dns_name_t *name, bool logit) {
+ isc_result_t result;
+ char namebuf[DNS_NAME_FORMATSIZE];
+ char altbuf[DNS_NAME_FORMATSIZE];
+ dns_fixedname_t fixed;
+ dns_name_t *foundname;
+ int level;
+
+ if (DNS_ZONE_OPTION(zone, DNS_ZONEOPT_NOCHECKNS)) {
+ return (true);
+ }
+
+ if (zone->type == dns_zone_primary) {
+ level = ISC_LOG_ERROR;
+ } else {
+ level = ISC_LOG_WARNING;
+ }
+
+ foundname = dns_fixedname_initname(&fixed);
+
+ result = dns_db_find(db, name, version, dns_rdatatype_a, 0, 0, NULL,
+ foundname, NULL, NULL);
+ if (result == ISC_R_SUCCESS) {
+ return (true);
+ }
+
+ if (result == DNS_R_NXRRSET) {
+ result = dns_db_find(db, name, version, dns_rdatatype_aaaa, 0,
+ 0, NULL, foundname, NULL, NULL);
+ if (result == ISC_R_SUCCESS) {
+ return (true);
+ }
+ }
+
+ if (result == DNS_R_NXRRSET || result == DNS_R_NXDOMAIN ||
+ result == DNS_R_EMPTYNAME)
+ {
+ if (logit) {
+ dns_name_format(name, namebuf, sizeof namebuf);
+ dns_zone_log(zone, level,
+ "NS '%s' has no address "
+ "records (A or AAAA)",
+ namebuf);
+ }
+ return (false);
+ }
+
+ if (result == DNS_R_CNAME) {
+ if (logit) {
+ dns_name_format(name, namebuf, sizeof namebuf);
+ dns_zone_log(zone, level,
+ "NS '%s' is a CNAME "
+ "(illegal)",
+ namebuf);
+ }
+ return (false);
+ }
+
+ if (result == DNS_R_DNAME) {
+ if (logit) {
+ dns_name_format(name, namebuf, sizeof namebuf);
+ dns_name_format(foundname, altbuf, sizeof altbuf);
+ dns_zone_log(zone, level,
+ "NS '%s' is below a DNAME "
+ "'%s' (illegal)",
+ namebuf, altbuf);
+ }
+ return (false);
+ }
+
+ return (true);
+}
+
+static isc_result_t
+zone_count_ns_rr(dns_zone_t *zone, dns_db_t *db, dns_dbnode_t *node,
+ dns_dbversion_t *version, unsigned int *nscount,
+ unsigned int *errors, bool logit) {
+ isc_result_t result;
+ unsigned int count = 0;
+ unsigned int ecount = 0;
+ dns_rdataset_t rdataset;
+ dns_rdata_t rdata;
+ dns_rdata_ns_t ns;
+
+ dns_rdataset_init(&rdataset);
+ result = dns_db_findrdataset(db, node, version, dns_rdatatype_ns,
+ dns_rdatatype_none, 0, &rdataset, NULL);
+ if (result == ISC_R_NOTFOUND) {
+ INSIST(!dns_rdataset_isassociated(&rdataset));
+ goto success;
+ }
+ if (result != ISC_R_SUCCESS) {
+ INSIST(!dns_rdataset_isassociated(&rdataset));
+ goto invalidate_rdataset;
+ }
+
+ result = dns_rdataset_first(&rdataset);
+ while (result == ISC_R_SUCCESS) {
+ if (errors != NULL && zone->rdclass == dns_rdataclass_in &&
+ (zone->type == dns_zone_primary ||
+ zone->type == dns_zone_secondary ||
+ zone->type == dns_zone_mirror))
+ {
+ dns_rdata_init(&rdata);
+ dns_rdataset_current(&rdataset, &rdata);
+ result = dns_rdata_tostruct(&rdata, &ns, NULL);
+ RUNTIME_CHECK(result == ISC_R_SUCCESS);
+ if (dns_name_issubdomain(&ns.name, &zone->origin) &&
+ !zone_check_ns(zone, db, version, &ns.name, logit))
+ {
+ ecount++;
+ }
+ }
+ count++;
+ result = dns_rdataset_next(&rdataset);
+ }
+ dns_rdataset_disassociate(&rdataset);
+
+success:
+ if (nscount != NULL) {
+ *nscount = count;
+ }
+ if (errors != NULL) {
+ *errors = ecount;
+ }
+
+ result = ISC_R_SUCCESS;
+
+invalidate_rdataset:
+ dns_rdataset_invalidate(&rdataset);
+
+ return (result);
+}
+
+#define SET_IF_NOT_NULL(obj, val) \
+ if (obj != NULL) { \
+ *obj = val; \
+ }
+
+#define SET_SOA_VALUES(soattl_v, serial_v, refresh_v, retry_v, expire_v, \
+ minimum_v) \
+ { \
+ SET_IF_NOT_NULL(soattl, soattl_v); \
+ SET_IF_NOT_NULL(serial, serial_v); \
+ SET_IF_NOT_NULL(refresh, refresh_v); \
+ SET_IF_NOT_NULL(retry, retry_v); \
+ SET_IF_NOT_NULL(expire, expire_v); \
+ SET_IF_NOT_NULL(minimum, minimum_v); \
+ }
+
+#define CLR_SOA_VALUES() \
+ { \
+ SET_SOA_VALUES(0, 0, 0, 0, 0, 0); \
+ }
+
+static isc_result_t
+zone_load_soa_rr(dns_db_t *db, dns_dbnode_t *node, dns_dbversion_t *version,
+ unsigned int *soacount, uint32_t *soattl, uint32_t *serial,
+ uint32_t *refresh, uint32_t *retry, uint32_t *expire,
+ uint32_t *minimum) {
+ isc_result_t result;
+ unsigned int count = 0;
+ dns_rdataset_t rdataset;
+ dns_rdata_t rdata = DNS_RDATA_INIT;
+
+ dns_rdataset_init(&rdataset);
+ result = dns_db_findrdataset(db, node, version, dns_rdatatype_soa,
+ dns_rdatatype_none, 0, &rdataset, NULL);
+ if (result == ISC_R_NOTFOUND) {
+ INSIST(!dns_rdataset_isassociated(&rdataset));
+ result = ISC_R_SUCCESS;
+ goto invalidate_rdataset;
+ }
+ if (result != ISC_R_SUCCESS) {
+ INSIST(!dns_rdataset_isassociated(&rdataset));
+ goto invalidate_rdataset;
+ }
+
+ result = dns_rdataset_first(&rdataset);
+ while (result == ISC_R_SUCCESS) {
+ dns_rdata_init(&rdata);
+ dns_rdataset_current(&rdataset, &rdata);
+ count++;
+ if (count == 1) {
+ dns_rdata_soa_t soa;
+ result = dns_rdata_tostruct(&rdata, &soa, NULL);
+ SET_SOA_VALUES(rdataset.ttl, soa.serial, soa.refresh,
+ soa.retry, soa.expire, soa.minimum);
+ RUNTIME_CHECK(result == ISC_R_SUCCESS);
+ }
+
+ result = dns_rdataset_next(&rdataset);
+ dns_rdata_reset(&rdata);
+ }
+ dns_rdataset_disassociate(&rdataset);
+
+ result = ISC_R_SUCCESS;
+
+invalidate_rdataset:
+ SET_IF_NOT_NULL(soacount, count);
+ if (count == 0) {
+ CLR_SOA_VALUES();
+ }
+
+ dns_rdataset_invalidate(&rdataset);
+
+ return (result);
+}
+
+/*
+ * zone must be locked.
+ */
+static isc_result_t
+zone_get_from_db(dns_zone_t *zone, dns_db_t *db, unsigned int *nscount,
+ unsigned int *soacount, uint32_t *soattl, uint32_t *serial,
+ uint32_t *refresh, uint32_t *retry, uint32_t *expire,
+ uint32_t *minimum, unsigned int *errors) {
+ isc_result_t result;
+ isc_result_t answer = ISC_R_SUCCESS;
+ dns_dbversion_t *version = NULL;
+ dns_dbnode_t *node;
+
+ REQUIRE(db != NULL);
+ REQUIRE(zone != NULL);
+
+ dns_db_currentversion(db, &version);
+
+ SET_IF_NOT_NULL(nscount, 0);
+ SET_IF_NOT_NULL(soacount, 0);
+ SET_IF_NOT_NULL(errors, 0);
+ CLR_SOA_VALUES();
+
+ node = NULL;
+ result = dns_db_findnode(db, &zone->origin, false, &node);
+ if (result != ISC_R_SUCCESS) {
+ answer = result;
+ goto closeversion;
+ }
+
+ if (nscount != NULL || errors != NULL) {
+ result = zone_count_ns_rr(zone, db, node, version, nscount,
+ errors, true);
+ if (result != ISC_R_SUCCESS) {
+ answer = result;
+ }
+ }
+
+ if (soacount != NULL || soattl != NULL || serial != NULL ||
+ refresh != NULL || retry != NULL || expire != NULL ||
+ minimum != NULL)
+ {
+ result = zone_load_soa_rr(db, node, version, soacount, soattl,
+ serial, refresh, retry, expire,
+ minimum);
+ if (result != ISC_R_SUCCESS) {
+ answer = result;
+ }
+ }
+
+ dns_db_detachnode(db, &node);
+closeversion:
+ dns_db_closeversion(db, &version, false);
+
+ return (answer);
+}
+
+void
+dns_zone_attach(dns_zone_t *source, dns_zone_t **target) {
+ REQUIRE(DNS_ZONE_VALID(source));
+ REQUIRE(target != NULL && *target == NULL);
+ isc_refcount_increment(&source->erefs);
+ *target = source;
+}
+
+void
+dns_zone_detach(dns_zone_t **zonep) {
+ REQUIRE(zonep != NULL && DNS_ZONE_VALID(*zonep));
+
+ dns_zone_t *zone = *zonep;
+ *zonep = NULL;
+
+ if (isc_refcount_decrement(&zone->erefs) == 1) {
+ isc_event_t *ev = &zone->ctlevent;
+
+ isc_refcount_destroy(&zone->erefs);
+
+ /*
+ * Stop things being restarted after we cancel them below.
+ */
+ DNS_ZONE_SETFLAG(zone, DNS_ZONEFLG_EXITING);
+ dns_zone_log(zone, ISC_LOG_DEBUG(1),
+ "final reference detached");
+ if (zone->task != NULL) {
+ /*
+ * This zone has a task; it can clean
+ * itself up asynchronously.
+ */
+ isc_task_send(zone->task, &ev);
+ return;
+ }
+
+ /*
+ * This zone is unmanaged; we're probably running in
+ * named-checkzone or a unit test. There's no task,
+ * so we need to free it immediately.
+ *
+ * Unmanaged zones must not have null views; we have no way
+ * of detaching from the view here without causing deadlock
+ * because this code is called with the view already
+ * locked.
+ */
+ INSIST(zone->view == NULL);
+
+ zone_shutdown(zone->task, ev);
+ ev = NULL;
+ }
+}
+
+void
+dns_zone_iattach(dns_zone_t *source, dns_zone_t **target) {
+ REQUIRE(DNS_ZONE_VALID(source));
+
+ LOCK_ZONE(source);
+ zone_iattach(source, target);
+ UNLOCK_ZONE(source);
+}
+
+static void
+zone_iattach(dns_zone_t *source, dns_zone_t **target) {
+ REQUIRE(DNS_ZONE_VALID(source));
+ REQUIRE(LOCKED_ZONE(source));
+ REQUIRE(target != NULL && *target == NULL);
+ INSIST(isc_refcount_increment0(&source->irefs) +
+ isc_refcount_current(&source->erefs) >
+ 0);
+ *target = source;
+}
+
+static void
+zone_idetach(dns_zone_t **zonep) {
+ dns_zone_t *zone;
+
+ /*
+ * 'zone' locked by caller.
+ */
+ REQUIRE(zonep != NULL && DNS_ZONE_VALID(*zonep));
+ REQUIRE(LOCKED_ZONE(*zonep));
+
+ zone = *zonep;
+ *zonep = NULL;
+
+ INSIST(isc_refcount_decrement(&zone->irefs) - 1 +
+ isc_refcount_current(&zone->erefs) >
+ 0);
+}
+
+void
+dns_zone_idetach(dns_zone_t **zonep) {
+ dns_zone_t *zone;
+
+ REQUIRE(zonep != NULL && DNS_ZONE_VALID(*zonep));
+
+ zone = *zonep;
+ *zonep = NULL;
+
+ if (isc_refcount_decrement(&zone->irefs) == 1) {
+ bool free_needed;
+ LOCK_ZONE(zone);
+ free_needed = exit_check(zone);
+ UNLOCK_ZONE(zone);
+ if (free_needed) {
+ zone_free(zone);
+ }
+ }
+}
+
+isc_mem_t *
+dns_zone_getmctx(dns_zone_t *zone) {
+ REQUIRE(DNS_ZONE_VALID(zone));
+
+ return (zone->mctx);
+}
+
+dns_zonemgr_t *
+dns_zone_getmgr(dns_zone_t *zone) {
+ REQUIRE(DNS_ZONE_VALID(zone));
+
+ return (zone->zmgr);
+}
+
+void
+dns_zone_setkasp(dns_zone_t *zone, dns_kasp_t *kasp) {
+ REQUIRE(DNS_ZONE_VALID(zone));
+
+ LOCK_ZONE(zone);
+ if (zone->kasp != NULL) {
+ dns_kasp_detach(&zone->kasp);
+ }
+ if (kasp != NULL) {
+ dns_kasp_attach(kasp, &zone->kasp);
+ }
+ UNLOCK_ZONE(zone);
+}
+
+dns_kasp_t *
+dns_zone_getkasp(dns_zone_t *zone) {
+ REQUIRE(DNS_ZONE_VALID(zone));
+
+ return (zone->kasp);
+}
+
+void
+dns_zone_setoption(dns_zone_t *zone, dns_zoneopt_t option, bool value) {
+ REQUIRE(DNS_ZONE_VALID(zone));
+
+ if (value) {
+ DNS_ZONE_SETOPTION(zone, option);
+ } else {
+ DNS_ZONE_CLROPTION(zone, option);
+ }
+}
+
+dns_zoneopt_t
+dns_zone_getoptions(dns_zone_t *zone) {
+ REQUIRE(DNS_ZONE_VALID(zone));
+
+ return (atomic_load_relaxed(&zone->options));
+}
+
+void
+dns_zone_setkeyopt(dns_zone_t *zone, unsigned int keyopt, bool value) {
+ REQUIRE(DNS_ZONE_VALID(zone));
+
+ if (value) {
+ DNS_ZONEKEY_SETOPTION(zone, keyopt);
+ } else {
+ DNS_ZONEKEY_CLROPTION(zone, keyopt);
+ }
+}
+
+unsigned int
+dns_zone_getkeyopts(dns_zone_t *zone) {
+ REQUIRE(DNS_ZONE_VALID(zone));
+
+ return (atomic_load_relaxed(&zone->keyopts));
+}
+
+isc_result_t
+dns_zone_setxfrsource4(dns_zone_t *zone, const isc_sockaddr_t *xfrsource) {
+ REQUIRE(DNS_ZONE_VALID(zone));
+
+ LOCK_ZONE(zone);
+ zone->xfrsource4 = *xfrsource;
+ UNLOCK_ZONE(zone);
+
+ return (ISC_R_SUCCESS);
+}
+
+isc_sockaddr_t *
+dns_zone_getxfrsource4(dns_zone_t *zone) {
+ REQUIRE(DNS_ZONE_VALID(zone));
+ return (&zone->xfrsource4);
+}
+
+isc_result_t
+dns_zone_setxfrsource6(dns_zone_t *zone, const isc_sockaddr_t *xfrsource) {
+ REQUIRE(DNS_ZONE_VALID(zone));
+
+ LOCK_ZONE(zone);
+ zone->xfrsource6 = *xfrsource;
+ UNLOCK_ZONE(zone);
+
+ return (ISC_R_SUCCESS);
+}
+
+isc_sockaddr_t *
+dns_zone_getxfrsource6(dns_zone_t *zone) {
+ REQUIRE(DNS_ZONE_VALID(zone));
+ return (&zone->xfrsource6);
+}
+
+isc_result_t
+dns_zone_setaltxfrsource4(dns_zone_t *zone,
+ const isc_sockaddr_t *altxfrsource) {
+ REQUIRE(DNS_ZONE_VALID(zone));
+
+ LOCK_ZONE(zone);
+ zone->altxfrsource4 = *altxfrsource;
+ UNLOCK_ZONE(zone);
+
+ return (ISC_R_SUCCESS);
+}
+
+isc_sockaddr_t *
+dns_zone_getaltxfrsource4(dns_zone_t *zone) {
+ REQUIRE(DNS_ZONE_VALID(zone));
+ return (&zone->altxfrsource4);
+}
+
+isc_result_t
+dns_zone_setaltxfrsource6(dns_zone_t *zone,
+ const isc_sockaddr_t *altxfrsource) {
+ REQUIRE(DNS_ZONE_VALID(zone));
+
+ LOCK_ZONE(zone);
+ zone->altxfrsource6 = *altxfrsource;
+ UNLOCK_ZONE(zone);
+
+ return (ISC_R_SUCCESS);
+}
+
+isc_sockaddr_t *
+dns_zone_getaltxfrsource6(dns_zone_t *zone) {
+ REQUIRE(DNS_ZONE_VALID(zone));
+ return (&zone->altxfrsource6);
+}
+
+isc_result_t
+dns_zone_setparentalsrc4(dns_zone_t *zone, const isc_sockaddr_t *parentalsrc) {
+ REQUIRE(DNS_ZONE_VALID(zone));
+
+ LOCK_ZONE(zone);
+ zone->parentalsrc4 = *parentalsrc;
+ UNLOCK_ZONE(zone);
+
+ return (ISC_R_SUCCESS);
+}
+
+isc_sockaddr_t *
+dns_zone_getparentalsrc4(dns_zone_t *zone) {
+ REQUIRE(DNS_ZONE_VALID(zone));
+ return (&zone->parentalsrc4);
+}
+
+isc_result_t
+dns_zone_setparentalsrc6(dns_zone_t *zone, const isc_sockaddr_t *parentalsrc) {
+ REQUIRE(DNS_ZONE_VALID(zone));
+
+ LOCK_ZONE(zone);
+ zone->parentalsrc6 = *parentalsrc;
+ UNLOCK_ZONE(zone);
+
+ return (ISC_R_SUCCESS);
+}
+
+isc_sockaddr_t *
+dns_zone_getparentalsrc6(dns_zone_t *zone) {
+ REQUIRE(DNS_ZONE_VALID(zone));
+ return (&zone->parentalsrc6);
+}
+
+isc_result_t
+dns_zone_setnotifysrc4(dns_zone_t *zone, const isc_sockaddr_t *notifysrc) {
+ REQUIRE(DNS_ZONE_VALID(zone));
+
+ LOCK_ZONE(zone);
+ zone->notifysrc4 = *notifysrc;
+ UNLOCK_ZONE(zone);
+
+ return (ISC_R_SUCCESS);
+}
+
+isc_sockaddr_t *
+dns_zone_getnotifysrc4(dns_zone_t *zone) {
+ REQUIRE(DNS_ZONE_VALID(zone));
+ return (&zone->notifysrc4);
+}
+
+isc_result_t
+dns_zone_setnotifysrc6(dns_zone_t *zone, const isc_sockaddr_t *notifysrc) {
+ REQUIRE(DNS_ZONE_VALID(zone));
+
+ LOCK_ZONE(zone);
+ zone->notifysrc6 = *notifysrc;
+ UNLOCK_ZONE(zone);
+
+ return (ISC_R_SUCCESS);
+}
+
+isc_sockaddr_t *
+dns_zone_getnotifysrc6(dns_zone_t *zone) {
+ REQUIRE(DNS_ZONE_VALID(zone));
+ return (&zone->notifysrc6);
+}
+
+static bool
+same_addrs(isc_sockaddr_t const *oldlist, isc_sockaddr_t const *newlist,
+ uint32_t count) {
+ unsigned int i;
+
+ for (i = 0; i < count; i++) {
+ if (!isc_sockaddr_equal(&oldlist[i], &newlist[i])) {
+ return (false);
+ }
+ }
+ return (true);
+}
+
+static bool
+same_names(dns_name_t *const *oldlist, dns_name_t *const *newlist,
+ uint32_t count) {
+ unsigned int i;
+
+ if (oldlist == NULL && newlist == NULL) {
+ return (true);
+ }
+ if (oldlist == NULL || newlist == NULL) {
+ return (false);
+ }
+
+ for (i = 0; i < count; i++) {
+ if (oldlist[i] == NULL && newlist[i] == NULL) {
+ continue;
+ }
+ if (oldlist[i] == NULL || newlist[i] == NULL ||
+ !dns_name_equal(oldlist[i], newlist[i]))
+ {
+ return (false);
+ }
+ }
+ return (true);
+}
+
+static void
+clear_serverslist(isc_sockaddr_t **addrsp, dns_name_t ***keynamesp,
+ dns_name_t ***tlsnamesp, unsigned int *countp,
+ isc_mem_t *mctx) {
+ unsigned int count;
+ isc_sockaddr_t *addrs;
+ dns_name_t **keynames;
+ dns_name_t **tlsnames;
+
+ REQUIRE(countp != NULL);
+ REQUIRE(addrsp != NULL);
+ REQUIRE(keynamesp != NULL);
+ REQUIRE(tlsnamesp != NULL);
+
+ count = *countp;
+ *countp = 0;
+ addrs = *addrsp;
+ *addrsp = NULL;
+ keynames = *keynamesp;
+ *keynamesp = NULL;
+ tlsnames = *tlsnamesp;
+ *tlsnamesp = NULL;
+
+ if (addrs != NULL) {
+ isc_mem_put(mctx, addrs, count * sizeof(isc_sockaddr_t));
+ }
+
+ if (keynames != NULL) {
+ unsigned int i;
+ for (i = 0; i < count; i++) {
+ if (keynames[i] != NULL) {
+ dns_name_free(keynames[i], mctx);
+ isc_mem_put(mctx, keynames[i],
+ sizeof(dns_name_t));
+ keynames[i] = NULL;
+ }
+ }
+ isc_mem_put(mctx, keynames, count * sizeof(dns_name_t *));
+ }
+
+ if (tlsnames != NULL) {
+ unsigned int i;
+ for (i = 0; i < count; i++) {
+ if (tlsnames[i] != NULL) {
+ dns_name_free(tlsnames[i], mctx);
+ isc_mem_put(mctx, tlsnames[i],
+ sizeof(dns_name_t));
+ tlsnames[i] = NULL;
+ }
+ }
+ isc_mem_put(mctx, tlsnames, count * sizeof(dns_name_t *));
+ }
+}
+
+static void
+set_serverslist(unsigned int count, const isc_sockaddr_t *addrs,
+ isc_sockaddr_t **newaddrsp, dns_name_t **keynames,
+ dns_name_t ***newkeynamesp, dns_name_t **tlsnames,
+ dns_name_t ***newtlsnamesp, isc_mem_t *mctx) {
+ isc_sockaddr_t *newaddrs = NULL;
+ dns_name_t **newkeynames = NULL;
+ dns_name_t **newtlsnames = NULL;
+ unsigned int i;
+
+ REQUIRE(newaddrsp != NULL && *newaddrsp == NULL);
+ REQUIRE(newkeynamesp != NULL && *newkeynamesp == NULL);
+ REQUIRE(newtlsnamesp != NULL && *newtlsnamesp == NULL);
+
+ newaddrs = isc_mem_get(mctx, count * sizeof(*newaddrs));
+ memmove(newaddrs, addrs, count * sizeof(*newaddrs));
+
+ if (keynames != NULL) {
+ newkeynames = isc_mem_get(mctx, count * sizeof(*newkeynames));
+ for (i = 0; i < count; i++) {
+ newkeynames[i] = NULL;
+ }
+ for (i = 0; i < count; i++) {
+ if (keynames[i] != NULL) {
+ newkeynames[i] =
+ isc_mem_get(mctx, sizeof(dns_name_t));
+ dns_name_init(newkeynames[i], NULL);
+ dns_name_dup(keynames[i], mctx, newkeynames[i]);
+ }
+ }
+ }
+
+ if (tlsnames != NULL) {
+ newtlsnames = isc_mem_get(mctx, count * sizeof(*newtlsnames));
+ for (i = 0; i < count; i++) {
+ newtlsnames[i] = NULL;
+ }
+ for (i = 0; i < count; i++) {
+ if (tlsnames[i] != NULL) {
+ newtlsnames[i] =
+ isc_mem_get(mctx, sizeof(dns_name_t));
+ dns_name_init(newtlsnames[i], NULL);
+ dns_name_dup(tlsnames[i], mctx, newtlsnames[i]);
+ }
+ }
+ }
+
+ *newaddrsp = newaddrs;
+ *newkeynamesp = newkeynames;
+ *newtlsnamesp = newtlsnames;
+}
+
+void
+dns_zone_setalsonotify(dns_zone_t *zone, const isc_sockaddr_t *notify,
+ dns_name_t **keynames, dns_name_t **tlsnames,
+ uint32_t count) {
+ isc_sockaddr_t *newaddrs = NULL;
+ dns_name_t **newkeynames = NULL;
+ dns_name_t **newtlsnames = NULL;
+
+ REQUIRE(DNS_ZONE_VALID(zone));
+ REQUIRE(count == 0 || notify != NULL);
+ if (keynames != NULL) {
+ REQUIRE(count != 0);
+ }
+
+ LOCK_ZONE(zone);
+
+ if (count == zone->notifycnt &&
+ same_addrs(zone->notify, notify, count) &&
+ same_names(zone->notifykeynames, keynames, count) &&
+ same_names(zone->notifytlsnames, tlsnames, count))
+ {
+ goto unlock;
+ }
+
+ clear_serverslist(&zone->notify, &zone->notifykeynames,
+ &zone->notifytlsnames, &zone->notifycnt, zone->mctx);
+
+ if (count == 0) {
+ goto unlock;
+ }
+
+ /*
+ * Set up the notify and notifykey lists
+ */
+ set_serverslist(count, notify, &newaddrs, keynames, &newkeynames,
+ tlsnames, &newtlsnames, zone->mctx);
+
+ /*
+ * Everything is ok so attach to the zone.
+ */
+ zone->notify = newaddrs;
+ zone->notifykeynames = newkeynames;
+ zone->notifytlsnames = newtlsnames;
+ zone->notifycnt = count;
+unlock:
+ UNLOCK_ZONE(zone);
+}
+
+void
+dns_zone_setprimaries(dns_zone_t *zone, const isc_sockaddr_t *primaries,
+ dns_name_t **keynames, dns_name_t **tlsnames,
+ uint32_t count) {
+ isc_sockaddr_t *newaddrs = NULL;
+ dns_name_t **newkeynames = NULL;
+ dns_name_t **newtlsnames = NULL;
+ bool *newok;
+ unsigned int i;
+
+ REQUIRE(DNS_ZONE_VALID(zone));
+ REQUIRE(count == 0 || primaries != NULL);
+ if (keynames != NULL || tlsnames != NULL) {
+ REQUIRE(count != 0);
+ }
+
+ LOCK_ZONE(zone);
+ /*
+ * The refresh code assumes that 'primaries' wouldn't change under it.
+ * If it will change then kill off any current refresh in progress
+ * and update the primaries info. If it won't change then we can just
+ * unlock and exit.
+ */
+ if (count != zone->primariescnt ||
+ !same_addrs(zone->primaries, primaries, count) ||
+ !same_names(zone->primarykeynames, keynames, count) ||
+ !same_names(zone->primarytlsnames, tlsnames, count))
+ {
+ if (zone->request != NULL) {
+ dns_request_cancel(zone->request);
+ }
+ } else {
+ goto unlock;
+ }
+
+ /*
+ * This needs to happen before clear_addresskeylist() sets
+ * zone->primariescnt to 0:
+ */
+ if (zone->primariesok != NULL) {
+ isc_mem_put(zone->mctx, zone->primariesok,
+ zone->primariescnt * sizeof(bool));
+ zone->primariesok = NULL;
+ }
+ clear_serverslist(&zone->primaries, &zone->primarykeynames,
+ &zone->primarytlsnames, &zone->primariescnt,
+ zone->mctx);
+ /*
+ * If count == 0, don't allocate any space for primaries, primariesok or
+ * keynames so internally, those pointers are NULL if count == 0
+ */
+ if (count == 0) {
+ goto unlock;
+ }
+
+ /*
+ * primariesok must contain count elements
+ */
+ newok = isc_mem_get(zone->mctx, count * sizeof(*newok));
+ for (i = 0; i < count; i++) {
+ newok[i] = false;
+ }
+
+ /*
+ * Now set up the primaries and primary key lists
+ */
+ set_serverslist(count, primaries, &newaddrs, keynames, &newkeynames,
+ tlsnames, &newtlsnames, zone->mctx);
+
+ /*
+ * Everything is ok so attach to the zone.
+ */
+ zone->curprimary = 0;
+ zone->primariesok = newok;
+ zone->primaries = newaddrs;
+ zone->primarykeynames = newkeynames;
+ zone->primarytlsnames = newtlsnames;
+ zone->primariescnt = count;
+ DNS_ZONE_CLRFLAG(zone, DNS_ZONEFLG_NOPRIMARIES);
+
+unlock:
+ UNLOCK_ZONE(zone);
+}
+
+void
+dns_zone_setparentals(dns_zone_t *zone, const isc_sockaddr_t *parentals,
+ dns_name_t **keynames, dns_name_t **tlsnames,
+ uint32_t count) {
+ isc_sockaddr_t *newaddrs = NULL;
+ dns_name_t **newkeynames = NULL;
+ dns_name_t **newtlsnames = NULL;
+
+ REQUIRE(DNS_ZONE_VALID(zone));
+ REQUIRE(count == 0 || parentals != NULL);
+ if (keynames != NULL || tlsnames != NULL) {
+ REQUIRE(count != 0);
+ }
+
+ LOCK_ZONE(zone);
+
+ clear_serverslist(&zone->parentals, &zone->parentalkeynames,
+ &zone->parentaltlsnames, &zone->parentalscnt,
+ zone->mctx);
+ /*
+ * If count == 0, don't allocate any space for parentals, or keynames
+ * so internally, those pointers are NULL if count == 0
+ */
+ if (count == 0) {
+ goto unlock;
+ }
+
+ /*
+ * Now set up the parentals and parental key lists
+ */
+ set_serverslist(count, parentals, &newaddrs, keynames, &newkeynames,
+ tlsnames, &newtlsnames, zone->mctx);
+
+ /*
+ * Everything is ok so attach to the zone.
+ */
+ zone->parentals = newaddrs;
+ zone->parentalkeynames = newkeynames;
+ zone->parentaltlsnames = newtlsnames;
+ zone->parentalscnt = count;
+
+ dns_zone_log(zone, ISC_LOG_NOTICE, "checkds: set %u parentals", count);
+
+unlock:
+ UNLOCK_ZONE(zone);
+}
+
+isc_result_t
+dns_zone_getdb(dns_zone_t *zone, dns_db_t **dpb) {
+ isc_result_t result = ISC_R_SUCCESS;
+
+ REQUIRE(DNS_ZONE_VALID(zone));
+
+ ZONEDB_LOCK(&zone->dblock, isc_rwlocktype_read);
+ if (zone->db == NULL) {
+ result = DNS_R_NOTLOADED;
+ } else {
+ dns_db_attach(zone->db, dpb);
+ }
+ ZONEDB_UNLOCK(&zone->dblock, isc_rwlocktype_read);
+
+ return (result);
+}
+
+void
+dns_zone_setdb(dns_zone_t *zone, dns_db_t *db) {
+ REQUIRE(DNS_ZONE_VALID(zone));
+ REQUIRE(zone->type == dns_zone_staticstub);
+
+ ZONEDB_LOCK(&zone->dblock, isc_rwlocktype_write);
+ REQUIRE(zone->db == NULL);
+ dns_db_attach(db, &zone->db);
+ ZONEDB_UNLOCK(&zone->dblock, isc_rwlocktype_write);
+}
+
+/*
+ * Coordinates the starting of routine jobs.
+ */
+void
+dns_zone_maintenance(dns_zone_t *zone) {
+ const char me[] = "dns_zone_maintenance";
+ isc_time_t now;
+
+ REQUIRE(DNS_ZONE_VALID(zone));
+ ENTER;
+
+ LOCK_ZONE(zone);
+ TIME_NOW(&now);
+ zone_settimer(zone, &now);
+ UNLOCK_ZONE(zone);
+}
+
+static bool
+was_dumping(dns_zone_t *zone) {
+ REQUIRE(LOCKED_ZONE(zone));
+
+ if (DNS_ZONE_FLAG(zone, DNS_ZONEFLG_DUMPING)) {
+ return (true);
+ }
+
+ DNS_ZONE_SETFLAG(zone, DNS_ZONEFLG_DUMPING);
+ DNS_ZONE_CLRFLAG(zone, DNS_ZONEFLG_NEEDDUMP);
+ isc_time_settoepoch(&zone->dumptime);
+ return (false);
+}
+
+/*%
+ * Find up to 'maxkeys' DNSSEC keys used for signing version 'ver' of database
+ * 'db' for zone 'zone' in its key directory, then load these keys into 'keys'.
+ * Only load the public part of a given key if it is not active at timestamp
+ * 'now'. Store the number of keys found in 'nkeys'.
+ */
+isc_result_t
+dns__zone_findkeys(dns_zone_t *zone, dns_db_t *db, dns_dbversion_t *ver,
+ isc_stdtime_t now, isc_mem_t *mctx, unsigned int maxkeys,
+ dst_key_t **keys, unsigned int *nkeys) {
+ isc_result_t result;
+ dns_dbnode_t *node = NULL;
+ const char *directory = dns_zone_getkeydirectory(zone);
+
+ CHECK(dns_db_findnode(db, dns_db_origin(db), false, &node));
+ memset(keys, 0, sizeof(*keys) * maxkeys);
+
+ dns_zone_lock_keyfiles(zone);
+
+ result = dns_dnssec_findzonekeys(db, ver, node, dns_db_origin(db),
+ directory, now, mctx, maxkeys, keys,
+ nkeys);
+
+ dns_zone_unlock_keyfiles(zone);
+
+ if (result == ISC_R_NOTFOUND) {
+ result = ISC_R_SUCCESS;
+ }
+
+failure:
+
+ if (node != NULL) {
+ dns_db_detachnode(db, &node);
+ }
+ return (result);
+}
+
+/*%
+ * Find DNSSEC keys used for signing zone with dnssec-policy. Load these keys
+ * into 'keys'. Requires KASP to be locked.
+ */
+isc_result_t
+dns_zone_getdnsseckeys(dns_zone_t *zone, dns_db_t *db, dns_dbversion_t *ver,
+ isc_stdtime_t now, dns_dnsseckeylist_t *keys) {
+ isc_result_t result;
+ const char *dir = dns_zone_getkeydirectory(zone);
+ dns_dbnode_t *node = NULL;
+ dns_dnsseckey_t *key, *key_next;
+ dns_dnsseckeylist_t dnskeys;
+ dns_name_t *origin = dns_zone_getorigin(zone);
+ dns_kasp_t *kasp = dns_zone_getkasp(zone);
+ dns_rdataset_t keyset;
+
+ REQUIRE(DNS_ZONE_VALID(zone));
+ REQUIRE(kasp != NULL);
+
+ ISC_LIST_INIT(dnskeys);
+
+ dns_rdataset_init(&keyset);
+
+ CHECK(dns_db_findnode(db, origin, false, &node));
+
+ /* Get keys from private key files. */
+ dns_zone_lock_keyfiles(zone);
+ result = dns_dnssec_findmatchingkeys(origin, dir, now,
+ dns_zone_getmctx(zone), keys);
+ dns_zone_unlock_keyfiles(zone);
+
+ if (result != ISC_R_SUCCESS && result != ISC_R_NOTFOUND) {
+ goto failure;
+ }
+
+ /* Get public keys (dnskeys). */
+ dns_rdataset_init(&keyset);
+ result = dns_db_findrdataset(db, node, ver, dns_rdatatype_dnskey,
+ dns_rdatatype_none, 0, &keyset, NULL);
+ if (result == ISC_R_SUCCESS) {
+ CHECK(dns_dnssec_keylistfromrdataset(
+ origin, dir, dns_zone_getmctx(zone), &keyset, NULL,
+ NULL, false, false, &dnskeys));
+ } else if (result != ISC_R_NOTFOUND) {
+ CHECK(result);
+ }
+
+ /* Add new 'dnskeys' to 'keys'. */
+ for (dns_dnsseckey_t *k1 = ISC_LIST_HEAD(dnskeys); k1 != NULL;
+ k1 = key_next)
+ {
+ dns_dnsseckey_t *k2 = NULL;
+ key_next = ISC_LIST_NEXT(k1, link);
+
+ for (k2 = ISC_LIST_HEAD(*keys); k2 != NULL;
+ k2 = ISC_LIST_NEXT(k2, link))
+ {
+ if (dst_key_compare(k1->key, k2->key)) {
+ break;
+ }
+ }
+ /* No match found, add the new key. */
+ if (k2 == NULL) {
+ ISC_LIST_UNLINK(dnskeys, k1, link);
+ ISC_LIST_APPEND(*keys, k1, link);
+ }
+ }
+
+failure:
+ if (dns_rdataset_isassociated(&keyset)) {
+ dns_rdataset_disassociate(&keyset);
+ }
+ if (node != NULL) {
+ dns_db_detachnode(db, &node);
+ }
+ while (!ISC_LIST_EMPTY(dnskeys)) {
+ key = ISC_LIST_HEAD(dnskeys);
+ ISC_LIST_UNLINK(dnskeys, key, link);
+ dns_dnsseckey_destroy(dns_zone_getmctx(zone), &key);
+ }
+ return (result);
+}
+
+static isc_result_t
+offline(dns_db_t *db, dns_dbversion_t *ver, dns__zonediff_t *zonediff,
+ dns_name_t *name, dns_ttl_t ttl, dns_rdata_t *rdata) {
+ isc_result_t result;
+
+ if ((rdata->flags & DNS_RDATA_OFFLINE) != 0) {
+ return (ISC_R_SUCCESS);
+ }
+ result = update_one_rr(db, ver, zonediff->diff, DNS_DIFFOP_DELRESIGN,
+ name, ttl, rdata);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+ rdata->flags |= DNS_RDATA_OFFLINE;
+ result = update_one_rr(db, ver, zonediff->diff, DNS_DIFFOP_ADDRESIGN,
+ name, ttl, rdata);
+ zonediff->offline = true;
+ return (result);
+}
+
+static void
+set_key_expiry_warning(dns_zone_t *zone, isc_stdtime_t when,
+ isc_stdtime_t now) {
+ unsigned int delta;
+ char timebuf[80];
+
+ LOCK_ZONE(zone);
+ zone->key_expiry = when;
+ if (when <= now) {
+ dns_zone_log(zone, ISC_LOG_ERROR,
+ "DNSKEY RRSIG(s) have expired");
+ isc_time_settoepoch(&zone->keywarntime);
+ } else if (when < now + 7 * 24 * 3600) {
+ isc_time_t t;
+ isc_time_set(&t, when, 0);
+ isc_time_formattimestamp(&t, timebuf, 80);
+ dns_zone_log(zone, ISC_LOG_WARNING,
+ "DNSKEY RRSIG(s) will expire within 7 days: %s",
+ timebuf);
+ delta = when - now;
+ delta--; /* loop prevention */
+ delta /= 24 * 3600; /* to whole days */
+ delta *= 24 * 3600; /* to seconds */
+ isc_time_set(&zone->keywarntime, when - delta, 0);
+ } else {
+ isc_time_set(&zone->keywarntime, when - 7 * 24 * 3600, 0);
+ isc_time_formattimestamp(&zone->keywarntime, timebuf, 80);
+ dns_zone_log(zone, ISC_LOG_NOTICE, "setting keywarntime to %s",
+ timebuf);
+ }
+ UNLOCK_ZONE(zone);
+}
+
+/*
+ * Helper function to del_sigs(). We don't want to delete RRSIGs that
+ * have no new key.
+ */
+static bool
+delsig_ok(dns_rdata_rrsig_t *rrsig_ptr, dst_key_t **keys, unsigned int nkeys,
+ bool kasp, bool *warn) {
+ unsigned int i = 0;
+ isc_result_t ret;
+ bool have_ksk = false, have_zsk = false;
+ bool have_pksk = false, have_pzsk = false;
+
+ for (i = 0; i < nkeys; i++) {
+ bool ksk, zsk;
+
+ if (have_pksk && have_ksk && have_pzsk && have_zsk) {
+ break;
+ }
+
+ if (rrsig_ptr->algorithm != dst_key_alg(keys[i])) {
+ continue;
+ }
+
+ ret = dst_key_getbool(keys[i], DST_BOOL_KSK, &ksk);
+ if (ret != ISC_R_SUCCESS) {
+ ksk = KSK(keys[i]);
+ }
+ ret = dst_key_getbool(keys[i], DST_BOOL_ZSK, &zsk);
+ if (ret != ISC_R_SUCCESS) {
+ zsk = !KSK(keys[i]);
+ }
+
+ if (ksk) {
+ have_ksk = true;
+ if (dst_key_isprivate(keys[i])) {
+ have_pksk = true;
+ }
+ }
+ if (zsk) {
+ have_zsk = true;
+ if (dst_key_isprivate(keys[i])) {
+ have_pzsk = true;
+ }
+ }
+ }
+
+ if (have_zsk && have_ksk && !have_pzsk) {
+ *warn = true;
+ }
+
+ if (have_pksk && have_pzsk) {
+ return (true);
+ }
+
+ /*
+ * Deleting the SOA RRSIG is always okay.
+ */
+ if (rrsig_ptr->covered == dns_rdatatype_soa) {
+ return (true);
+ }
+
+ /*
+ * It's okay to delete a signature if there is an active key with the
+ * same algorithm to replace it, unless that violates the DNSSEC
+ * policy.
+ */
+ if (have_pksk || have_pzsk) {
+ if (kasp && have_pzsk) {
+ return (true);
+ }
+ return (!kasp);
+ }
+
+ /*
+ * Failing that, it is *not* okay to delete a signature
+ * if the associated public key is still in the DNSKEY RRset
+ */
+ for (i = 0; i < nkeys; i++) {
+ if ((rrsig_ptr->algorithm == dst_key_alg(keys[i])) &&
+ (rrsig_ptr->keyid == dst_key_id(keys[i])))
+ {
+ return (false);
+ }
+ }
+
+ /*
+ * But if the key is gone, then go ahead.
+ */
+ return (true);
+}
+
+/*
+ * Delete expired RRsigs and any RRsigs we are about to re-sign.
+ * See also update.c:del_keysigs().
+ */
+static isc_result_t
+del_sigs(dns_zone_t *zone, dns_db_t *db, dns_dbversion_t *ver, dns_name_t *name,
+ dns_rdatatype_t type, dns__zonediff_t *zonediff, dst_key_t **keys,
+ unsigned int nkeys, isc_stdtime_t now, bool incremental) {
+ isc_result_t result;
+ dns_dbnode_t *node = NULL;
+ dns_rdataset_t rdataset;
+ unsigned int i;
+ dns_rdata_rrsig_t rrsig;
+ bool kasp = (dns_zone_getkasp(zone) != NULL);
+ bool found;
+ int64_t timewarn = 0, timemaybe = 0;
+
+ dns_rdataset_init(&rdataset);
+
+ if (type == dns_rdatatype_nsec3) {
+ result = dns_db_findnsec3node(db, name, false, &node);
+ } else {
+ result = dns_db_findnode(db, name, false, &node);
+ }
+ if (result == ISC_R_NOTFOUND) {
+ return (ISC_R_SUCCESS);
+ }
+ if (result != ISC_R_SUCCESS) {
+ goto failure;
+ }
+ result = dns_db_findrdataset(db, node, ver, dns_rdatatype_rrsig, type,
+ (isc_stdtime_t)0, &rdataset, NULL);
+ dns_db_detachnode(db, &node);
+
+ if (result == ISC_R_NOTFOUND) {
+ INSIST(!dns_rdataset_isassociated(&rdataset));
+ return (ISC_R_SUCCESS);
+ }
+ if (result != ISC_R_SUCCESS) {
+ INSIST(!dns_rdataset_isassociated(&rdataset));
+ goto failure;
+ }
+
+ for (result = dns_rdataset_first(&rdataset); result == ISC_R_SUCCESS;
+ result = dns_rdataset_next(&rdataset))
+ {
+ dns_rdata_t rdata = DNS_RDATA_INIT;
+
+ dns_rdataset_current(&rdataset, &rdata);
+ result = dns_rdata_tostruct(&rdata, &rrsig, NULL);
+ RUNTIME_CHECK(result == ISC_R_SUCCESS);
+
+ if (type != dns_rdatatype_dnskey && type != dns_rdatatype_cds &&
+ type != dns_rdatatype_cdnskey)
+ {
+ bool warn = false, deleted = false;
+ if (delsig_ok(&rrsig, keys, nkeys, kasp, &warn)) {
+ result = update_one_rr(db, ver, zonediff->diff,
+ DNS_DIFFOP_DELRESIGN,
+ name, rdataset.ttl,
+ &rdata);
+ if (result != ISC_R_SUCCESS) {
+ break;
+ }
+ deleted = true;
+ }
+ if (warn && !deleted) {
+ /*
+ * At this point, we've got an RRSIG,
+ * which is signed by an inactive key.
+ * An administrator needs to provide a new
+ * key/alg, but until that time, we want to
+ * keep the old RRSIG. Marking the key as
+ * offline will prevent us spinning waiting
+ * for the private part.
+ */
+ if (incremental) {
+ result = offline(db, ver, zonediff,
+ name, rdataset.ttl,
+ &rdata);
+ if (result != ISC_R_SUCCESS) {
+ break;
+ }
+ }
+
+ /*
+ * Log the key id and algorithm of
+ * the inactive key with no replacement
+ */
+ if (zone->log_key_expired_timer <= now) {
+ char origin[DNS_NAME_FORMATSIZE];
+ char algbuf[DNS_NAME_FORMATSIZE];
+ dns_name_format(&zone->origin, origin,
+ sizeof(origin));
+ dns_secalg_format(rrsig.algorithm,
+ algbuf,
+ sizeof(algbuf));
+ dns_zone_log(zone, ISC_LOG_WARNING,
+ "Key %s/%s/%d "
+ "missing or inactive "
+ "and has no replacement: "
+ "retaining signatures.",
+ origin, algbuf,
+ rrsig.keyid);
+ zone->log_key_expired_timer = now +
+ 3600;
+ }
+ }
+ continue;
+ }
+
+ /*
+ * KSK RRSIGs requires special processing.
+ */
+ found = false;
+ for (i = 0; i < nkeys; i++) {
+ if (rrsig.algorithm == dst_key_alg(keys[i]) &&
+ rrsig.keyid == dst_key_id(keys[i]))
+ {
+ found = true;
+ /*
+ * Mark offline DNSKEY.
+ * We want the earliest offline expire time
+ * iff there is a new offline signature.
+ */
+ if (!dst_key_inactive(keys[i]) &&
+ !dst_key_isprivate(keys[i]))
+ {
+ int64_t timeexpire = dns_time64_from32(
+ rrsig.timeexpire);
+ if (timewarn != 0 &&
+ timewarn > timeexpire)
+ {
+ timewarn = timeexpire;
+ }
+ if (rdata.flags & DNS_RDATA_OFFLINE) {
+ if (timemaybe == 0 ||
+ timemaybe > timeexpire)
+ {
+ timemaybe = timeexpire;
+ }
+ break;
+ }
+ if (timewarn == 0) {
+ timewarn = timemaybe;
+ }
+ if (timewarn == 0 ||
+ timewarn > timeexpire)
+ {
+ timewarn = timeexpire;
+ }
+ result = offline(db, ver, zonediff,
+ name, rdataset.ttl,
+ &rdata);
+ break;
+ }
+ result = update_one_rr(db, ver, zonediff->diff,
+ DNS_DIFFOP_DELRESIGN,
+ name, rdataset.ttl,
+ &rdata);
+ break;
+ }
+ }
+
+ /*
+ * If there is not a matching DNSKEY then
+ * delete the RRSIG.
+ */
+ if (!found) {
+ result = update_one_rr(db, ver, zonediff->diff,
+ DNS_DIFFOP_DELRESIGN, name,
+ rdataset.ttl, &rdata);
+ }
+ if (result != ISC_R_SUCCESS) {
+ break;
+ }
+ }
+
+ dns_rdataset_disassociate(&rdataset);
+ if (result == ISC_R_NOMORE) {
+ result = ISC_R_SUCCESS;
+ }
+ if (timewarn > 0) {
+ isc_stdtime_t stdwarn = (isc_stdtime_t)timewarn;
+ if (timewarn == stdwarn) {
+ set_key_expiry_warning(zone, (isc_stdtime_t)timewarn,
+ now);
+ } else {
+ dns_zone_log(zone, ISC_LOG_ERROR,
+ "key expiry warning time out of range");
+ }
+ }
+failure:
+ if (node != NULL) {
+ dns_db_detachnode(db, &node);
+ }
+ return (result);
+}
+
+static isc_result_t
+add_sigs(dns_db_t *db, dns_dbversion_t *ver, dns_name_t *name, dns_zone_t *zone,
+ dns_rdatatype_t type, dns_diff_t *diff, dst_key_t **keys,
+ unsigned int nkeys, isc_mem_t *mctx, isc_stdtime_t inception,
+ isc_stdtime_t expire, bool check_ksk, bool keyset_kskonly) {
+ isc_result_t result;
+ dns_dbnode_t *node = NULL;
+ dns_stats_t *dnssecsignstats;
+ dns_rdataset_t rdataset;
+ dns_rdata_t sig_rdata = DNS_RDATA_INIT;
+ unsigned char data[1024]; /* XXX */
+ isc_buffer_t buffer;
+ unsigned int i, j;
+ bool use_kasp = false;
+
+ if (dns_zone_getkasp(zone) != NULL) {
+ check_ksk = false;
+ keyset_kskonly = true;
+ use_kasp = true;
+ }
+
+ dns_rdataset_init(&rdataset);
+ isc_buffer_init(&buffer, data, sizeof(data));
+
+ if (type == dns_rdatatype_nsec3) {
+ result = dns_db_findnsec3node(db, name, false, &node);
+ } else {
+ result = dns_db_findnode(db, name, false, &node);
+ }
+ if (result == ISC_R_NOTFOUND) {
+ return (ISC_R_SUCCESS);
+ }
+ if (result != ISC_R_SUCCESS) {
+ goto failure;
+ }
+ result = dns_db_findrdataset(db, node, ver, type, 0, (isc_stdtime_t)0,
+ &rdataset, NULL);
+ dns_db_detachnode(db, &node);
+ if (result == ISC_R_NOTFOUND) {
+ INSIST(!dns_rdataset_isassociated(&rdataset));
+ return (ISC_R_SUCCESS);
+ }
+ if (result != ISC_R_SUCCESS) {
+ INSIST(!dns_rdataset_isassociated(&rdataset));
+ goto failure;
+ }
+
+ for (i = 0; i < nkeys; i++) {
+ bool both = false;
+
+ /* Don't add signatures for offline or inactive keys */
+ if (!dst_key_isprivate(keys[i])) {
+ continue;
+ }
+ if (dst_key_inactive(keys[i])) {
+ continue;
+ }
+
+ if (check_ksk && !REVOKE(keys[i])) {
+ bool have_ksk, have_nonksk;
+ if (KSK(keys[i])) {
+ have_ksk = true;
+ have_nonksk = false;
+ } else {
+ have_ksk = false;
+ have_nonksk = true;
+ }
+
+ for (j = 0; j < nkeys; j++) {
+ if (j == i || ALG(keys[i]) != ALG(keys[j])) {
+ continue;
+ }
+
+ /*
+ * Don't consider inactive keys, however
+ * the KSK may be temporary offline, so do
+ * consider keys which private key files are
+ * unavailable.
+ */
+ if (dst_key_inactive(keys[j])) {
+ continue;
+ }
+
+ if (REVOKE(keys[j])) {
+ continue;
+ }
+ if (KSK(keys[j])) {
+ have_ksk = true;
+ } else if (dst_key_isprivate(keys[j])) {
+ have_nonksk = true;
+ }
+ both = have_ksk && have_nonksk;
+ if (both) {
+ break;
+ }
+ }
+ }
+ if (use_kasp) {
+ /*
+ * A dnssec-policy is found. Check what RRsets this
+ * key should sign.
+ */
+ isc_result_t kresult;
+ isc_stdtime_t when;
+ bool ksk = false;
+ bool zsk = false;
+ bool have_ksk = false;
+ bool have_zsk = false;
+
+ kresult = dst_key_getbool(keys[i], DST_BOOL_KSK, &ksk);
+ if (kresult != ISC_R_SUCCESS) {
+ if (KSK(keys[i])) {
+ ksk = true;
+ }
+ }
+ kresult = dst_key_getbool(keys[i], DST_BOOL_ZSK, &zsk);
+ if (kresult != ISC_R_SUCCESS) {
+ if (!KSK(keys[i])) {
+ zsk = true;
+ }
+ }
+
+ have_ksk = ksk;
+ have_zsk = zsk;
+ both = have_ksk && have_zsk;
+
+ for (j = 0; j < nkeys; j++) {
+ if (both) {
+ break;
+ }
+
+ if (j == i || ALG(keys[i]) != ALG(keys[j])) {
+ continue;
+ }
+
+ /*
+ * Don't consider inactive keys or offline keys.
+ */
+ if (!dst_key_isprivate(keys[j])) {
+ continue;
+ }
+ if (dst_key_inactive(keys[j])) {
+ continue;
+ }
+
+ if (REVOKE(keys[j])) {
+ continue;
+ }
+
+ if (!have_ksk) {
+ kresult = dst_key_getbool(keys[j],
+ DST_BOOL_KSK,
+ &have_ksk);
+ if (kresult != ISC_R_SUCCESS) {
+ if (KSK(keys[j])) {
+ have_ksk = true;
+ }
+ }
+ }
+ if (!have_zsk) {
+ kresult = dst_key_getbool(keys[j],
+ DST_BOOL_ZSK,
+ &have_zsk);
+ if (kresult != ISC_R_SUCCESS) {
+ if (!KSK(keys[j])) {
+ have_zsk = true;
+ }
+ }
+ }
+ both = have_ksk && have_zsk;
+ }
+
+ if (type == dns_rdatatype_dnskey ||
+ type == dns_rdatatype_cdnskey ||
+ type == dns_rdatatype_cds)
+ {
+ /*
+ * DNSKEY RRset is signed with KSK.
+ * CDS and CDNSKEY RRsets too (RFC 7344, 4.1).
+ */
+ if (!ksk) {
+ continue;
+ }
+ } else if (!zsk) {
+ /*
+ * Other RRsets are signed with ZSK.
+ */
+ if (type != dns_rdatatype_soa &&
+ type != zone->privatetype)
+ {
+ continue;
+ }
+ if (have_zsk) {
+ continue;
+ }
+ } else if (!dst_key_is_signing(keys[i], DST_BOOL_ZSK,
+ inception, &when))
+ {
+ /*
+ * This key is not active for zone-signing.
+ */
+ continue;
+ }
+
+ /*
+ * If this key is revoked, it may only sign the
+ * DNSKEY RRset.
+ */
+ if (REVOKE(keys[i]) && type != dns_rdatatype_dnskey) {
+ continue;
+ }
+ } else if (both) {
+ /*
+ * CDS and CDNSKEY are signed with KSK (RFC 7344, 4.1).
+ */
+ if (type == dns_rdatatype_dnskey ||
+ type == dns_rdatatype_cdnskey ||
+ type == dns_rdatatype_cds)
+ {
+ if (!KSK(keys[i]) && keyset_kskonly) {
+ continue;
+ }
+ } else if (KSK(keys[i])) {
+ continue;
+ }
+ } else if (REVOKE(keys[i]) && type != dns_rdatatype_dnskey) {
+ continue;
+ }
+
+ /* Calculate the signature, creating a RRSIG RDATA. */
+ isc_buffer_clear(&buffer);
+ CHECK(dns_dnssec_sign(name, &rdataset, keys[i], &inception,
+ &expire, mctx, &buffer, &sig_rdata));
+
+ /* Update the database and journal with the RRSIG. */
+ /* XXX inefficient - will cause dataset merging */
+ CHECK(update_one_rr(db, ver, diff, DNS_DIFFOP_ADDRESIGN, name,
+ rdataset.ttl, &sig_rdata));
+ dns_rdata_reset(&sig_rdata);
+ isc_buffer_init(&buffer, data, sizeof(data));
+
+ /* Update DNSSEC sign statistics. */
+ dnssecsignstats = dns_zone_getdnssecsignstats(zone);
+ if (dnssecsignstats != NULL) {
+ /* Generated a new signature. */
+ dns_dnssecsignstats_increment(dnssecsignstats,
+ ID(keys[i]),
+ (uint8_t)ALG(keys[i]),
+ dns_dnssecsignstats_sign);
+ /* This is a refresh. */
+ dns_dnssecsignstats_increment(
+ dnssecsignstats, ID(keys[i]),
+ (uint8_t)ALG(keys[i]),
+ dns_dnssecsignstats_refresh);
+ }
+ }
+
+failure:
+ if (dns_rdataset_isassociated(&rdataset)) {
+ dns_rdataset_disassociate(&rdataset);
+ }
+ if (node != NULL) {
+ dns_db_detachnode(db, &node);
+ }
+ return (result);
+}
+
+static void
+zone_resigninc(dns_zone_t *zone) {
+ const char *me = "zone_resigninc";
+ dns_db_t *db = NULL;
+ dns_dbversion_t *version = NULL;
+ dns_diff_t _sig_diff;
+ dns__zonediff_t zonediff;
+ dns_fixedname_t fixed;
+ dns_name_t *name;
+ dns_rdataset_t rdataset;
+ dns_rdatatype_t covers;
+ dst_key_t *zone_keys[DNS_MAXZONEKEYS];
+ bool check_ksk, keyset_kskonly = false;
+ isc_result_t result;
+ isc_stdtime_t now, inception, soaexpire, expire, fullexpire, stop;
+ uint32_t sigvalidityinterval, expiryinterval;
+ unsigned int i;
+ unsigned int nkeys = 0;
+ unsigned int resign;
+
+ ENTER;
+
+ dns_rdataset_init(&rdataset);
+ dns_diff_init(zone->mctx, &_sig_diff);
+ zonediff_init(&zonediff, &_sig_diff);
+
+ /*
+ * Zone is frozen or automatic resigning is disabled.
+ * Pause for 5 minutes.
+ */
+ if (zone->update_disabled ||
+ DNS_ZONEKEY_OPTION(zone, DNS_ZONEKEY_NORESIGN))
+ {
+ result = ISC_R_FAILURE;
+ goto failure;
+ }
+
+ ZONEDB_LOCK(&zone->dblock, isc_rwlocktype_read);
+ if (zone->db != NULL) {
+ dns_db_attach(zone->db, &db);
+ }
+ ZONEDB_UNLOCK(&zone->dblock, isc_rwlocktype_read);
+ if (db == NULL) {
+ result = ISC_R_FAILURE;
+ goto failure;
+ }
+
+ result = dns_db_newversion(db, &version);
+ if (result != ISC_R_SUCCESS) {
+ dns_zone_log(zone, ISC_LOG_ERROR,
+ "zone_resigninc:dns_db_newversion -> %s",
+ isc_result_totext(result));
+ goto failure;
+ }
+
+ isc_stdtime_get(&now);
+
+ result = dns__zone_findkeys(zone, db, version, now, zone->mctx,
+ DNS_MAXZONEKEYS, zone_keys, &nkeys);
+ if (result != ISC_R_SUCCESS) {
+ dns_zone_log(zone, ISC_LOG_ERROR,
+ "zone_resigninc:dns__zone_findkeys -> %s",
+ isc_result_totext(result));
+ goto failure;
+ }
+
+ sigvalidityinterval = dns_zone_getsigvalidityinterval(zone);
+ inception = now - 3600; /* Allow for clock skew. */
+ soaexpire = now + sigvalidityinterval;
+ expiryinterval = dns_zone_getsigresigninginterval(zone);
+ if (expiryinterval > sigvalidityinterval) {
+ expiryinterval = sigvalidityinterval;
+ } else {
+ expiryinterval = sigvalidityinterval - expiryinterval;
+ }
+
+ /*
+ * Spread out signatures over time if they happen to be
+ * clumped. We don't do this for each add_sigs() call as
+ * we still want some clustering to occur. In normal operations
+ * the records should be re-signed as they fall due and they should
+ * already be spread out. However if the server is off for a
+ * period we need to ensure that the clusters don't become
+ * synchronised by using the full jitter range.
+ */
+ if (sigvalidityinterval >= 3600U) {
+ uint32_t normaljitter, fulljitter;
+ if (sigvalidityinterval > 7200U) {
+ normaljitter = isc_random_uniform(3600);
+ fulljitter = isc_random_uniform(expiryinterval);
+ } else {
+ normaljitter = fulljitter = isc_random_uniform(1200);
+ }
+ expire = soaexpire - normaljitter - 1;
+ fullexpire = soaexpire - fulljitter - 1;
+ } else {
+ expire = fullexpire = soaexpire - 1;
+ }
+ stop = now + 5;
+
+ check_ksk = DNS_ZONE_OPTION(zone, DNS_ZONEOPT_UPDATECHECKKSK);
+ keyset_kskonly = DNS_ZONE_OPTION(zone, DNS_ZONEOPT_DNSKEYKSKONLY);
+
+ name = dns_fixedname_initname(&fixed);
+ result = dns_db_getsigningtime(db, &rdataset, name);
+ if (result != ISC_R_SUCCESS && result != ISC_R_NOTFOUND) {
+ dns_zone_log(zone, ISC_LOG_ERROR,
+ "zone_resigninc:dns_db_getsigningtime -> %s",
+ isc_result_totext(result));
+ }
+
+ i = 0;
+ while (result == ISC_R_SUCCESS) {
+ resign = rdataset.resign -
+ dns_zone_getsigresigninginterval(zone);
+ covers = rdataset.covers;
+ dns_rdataset_disassociate(&rdataset);
+
+ /*
+ * Stop if we hit the SOA as that means we have walked the
+ * entire zone. The SOA record should always be the most
+ * recent signature.
+ */
+ /* XXXMPA increase number of RRsets signed pre call */
+ if ((covers == dns_rdatatype_soa &&
+ dns_name_equal(name, &zone->origin)) ||
+ i++ > zone->signatures || resign > stop)
+ {
+ break;
+ }
+
+ result = del_sigs(zone, db, version, name, covers, &zonediff,
+ zone_keys, nkeys, now, true);
+ if (result != ISC_R_SUCCESS) {
+ dns_zone_log(zone, ISC_LOG_ERROR,
+ "zone_resigninc:del_sigs -> %s",
+ isc_result_totext(result));
+ break;
+ }
+
+ /*
+ * If re-signing is over 5 minutes late use 'fullexpire'
+ * to redistribute the signature over the complete
+ * re-signing window, otherwise only add a small amount
+ * of jitter.
+ */
+ result = add_sigs(db, version, name, zone, covers,
+ zonediff.diff, zone_keys, nkeys, zone->mctx,
+ inception,
+ resign > (now - 300) ? expire : fullexpire,
+ check_ksk, keyset_kskonly);
+ if (result != ISC_R_SUCCESS) {
+ dns_zone_log(zone, ISC_LOG_ERROR,
+ "zone_resigninc:add_sigs -> %s",
+ isc_result_totext(result));
+ break;
+ }
+ result = dns_db_getsigningtime(db, &rdataset, name);
+ if (nkeys == 0 && result == ISC_R_NOTFOUND) {
+ result = ISC_R_SUCCESS;
+ break;
+ }
+ if (result != ISC_R_SUCCESS) {
+ dns_zone_log(zone, ISC_LOG_ERROR,
+ "zone_resigninc:dns_db_getsigningtime -> "
+ "%s",
+ isc_result_totext(result));
+ }
+ }
+
+ if (result != ISC_R_NOMORE && result != ISC_R_SUCCESS) {
+ goto failure;
+ }
+
+ result = del_sigs(zone, db, version, &zone->origin, dns_rdatatype_soa,
+ &zonediff, zone_keys, nkeys, now, true);
+ if (result != ISC_R_SUCCESS) {
+ dns_zone_log(zone, ISC_LOG_ERROR,
+ "zone_resigninc:del_sigs -> %s",
+ isc_result_totext(result));
+ goto failure;
+ }
+
+ /*
+ * Did we change anything in the zone?
+ */
+ if (ISC_LIST_EMPTY(zonediff.diff->tuples)) {
+ /*
+ * Commit the changes if any key has been marked as offline.
+ */
+ if (zonediff.offline) {
+ dns_db_closeversion(db, &version, true);
+ }
+ goto failure;
+ }
+
+ /* Increment SOA serial if we have made changes */
+ result = update_soa_serial(zone, db, version, zonediff.diff, zone->mctx,
+ zone->updatemethod);
+ if (result != ISC_R_SUCCESS) {
+ dns_zone_log(zone, ISC_LOG_ERROR,
+ "zone_resigninc:update_soa_serial -> %s",
+ isc_result_totext(result));
+ goto failure;
+ }
+
+ /*
+ * Generate maximum life time signatures so that the above loop
+ * termination is sensible.
+ */
+ result = add_sigs(db, version, &zone->origin, zone, dns_rdatatype_soa,
+ zonediff.diff, zone_keys, nkeys, zone->mctx,
+ inception, soaexpire, check_ksk, keyset_kskonly);
+ if (result != ISC_R_SUCCESS) {
+ dns_zone_log(zone, ISC_LOG_ERROR,
+ "zone_resigninc:add_sigs -> %s",
+ isc_result_totext(result));
+ goto failure;
+ }
+
+ /* Write changes to journal file. */
+ CHECK(zone_journal(zone, zonediff.diff, NULL, "zone_resigninc"));
+
+ /* Everything has succeeded. Commit the changes. */
+ dns_db_closeversion(db, &version, true);
+
+failure:
+ dns_diff_clear(&_sig_diff);
+ for (i = 0; i < nkeys; i++) {
+ dst_key_free(&zone_keys[i]);
+ }
+ if (version != NULL) {
+ dns_db_closeversion(db, &version, false);
+ dns_db_detach(&db);
+ } else if (db != NULL) {
+ dns_db_detach(&db);
+ }
+
+ LOCK_ZONE(zone);
+ if (result == ISC_R_SUCCESS) {
+ set_resigntime(zone);
+ zone_needdump(zone, DNS_DUMP_DELAY);
+ DNS_ZONE_SETFLAG(zone, DNS_ZONEFLG_NEEDNOTIFY);
+ } else {
+ /*
+ * Something failed. Retry in 5 minutes.
+ */
+ isc_interval_t ival;
+ isc_interval_set(&ival, 300, 0);
+ isc_time_nowplusinterval(&zone->resigntime, &ival);
+ }
+ UNLOCK_ZONE(zone);
+
+ INSIST(version == NULL);
+}
+
+static isc_result_t
+next_active(dns_db_t *db, dns_dbversion_t *version, dns_name_t *oldname,
+ dns_name_t *newname, bool bottom) {
+ isc_result_t result;
+ dns_dbiterator_t *dbit = NULL;
+ dns_rdatasetiter_t *rdsit = NULL;
+ dns_dbnode_t *node = NULL;
+
+ CHECK(dns_db_createiterator(db, DNS_DB_NONSEC3, &dbit));
+ CHECK(dns_dbiterator_seek(dbit, oldname));
+ do {
+ result = dns_dbiterator_next(dbit);
+ if (result == ISC_R_NOMORE) {
+ CHECK(dns_dbiterator_first(dbit));
+ }
+ CHECK(dns_dbiterator_current(dbit, &node, newname));
+ if (bottom && dns_name_issubdomain(newname, oldname) &&
+ !dns_name_equal(newname, oldname))
+ {
+ dns_db_detachnode(db, &node);
+ continue;
+ }
+ /*
+ * Is this node empty?
+ */
+ CHECK(dns_db_allrdatasets(db, node, version, 0, 0, &rdsit));
+ result = dns_rdatasetiter_first(rdsit);
+ dns_db_detachnode(db, &node);
+ dns_rdatasetiter_destroy(&rdsit);
+ if (result != ISC_R_NOMORE) {
+ break;
+ }
+ } while (1);
+failure:
+ if (node != NULL) {
+ dns_db_detachnode(db, &node);
+ }
+ if (dbit != NULL) {
+ dns_dbiterator_destroy(&dbit);
+ }
+ return (result);
+}
+
+static bool
+signed_with_good_key(dns_zone_t *zone, dns_db_t *db, dns_dbnode_t *node,
+ dns_dbversion_t *version, dns_rdatatype_t type,
+ dst_key_t *key) {
+ isc_result_t result;
+ dns_rdataset_t rdataset;
+ dns_rdata_t rdata = DNS_RDATA_INIT;
+ dns_rdata_rrsig_t rrsig;
+ int count = 0;
+ dns_kasp_t *kasp = dns_zone_getkasp(zone);
+
+ dns_rdataset_init(&rdataset);
+ result = dns_db_findrdataset(db, node, version, dns_rdatatype_rrsig,
+ type, 0, &rdataset, NULL);
+ if (result != ISC_R_SUCCESS) {
+ INSIST(!dns_rdataset_isassociated(&rdataset));
+ return (false);
+ }
+ for (result = dns_rdataset_first(&rdataset); result == ISC_R_SUCCESS;
+ result = dns_rdataset_next(&rdataset))
+ {
+ dns_rdataset_current(&rdataset, &rdata);
+ result = dns_rdata_tostruct(&rdata, &rrsig, NULL);
+ INSIST(result == ISC_R_SUCCESS);
+ if (rrsig.algorithm == dst_key_alg(key) &&
+ rrsig.keyid == dst_key_id(key))
+ {
+ dns_rdataset_disassociate(&rdataset);
+ return (true);
+ }
+ if (rrsig.algorithm == dst_key_alg(key)) {
+ count++;
+ }
+ dns_rdata_reset(&rdata);
+ }
+
+ if (dns_zone_getkasp(zone) != NULL) {
+ dns_kasp_key_t *kkey;
+ int zsk_count = 0;
+ bool approved;
+
+ KASP_LOCK(kasp);
+ for (kkey = ISC_LIST_HEAD(dns_kasp_keys(kasp)); kkey != NULL;
+ kkey = ISC_LIST_NEXT(kkey, link))
+ {
+ if (dns_kasp_key_algorithm(kkey) != dst_key_alg(key)) {
+ continue;
+ }
+ if (dns_kasp_key_zsk(kkey)) {
+ zsk_count++;
+ }
+ }
+ KASP_UNLOCK(kasp);
+
+ if (type == dns_rdatatype_dnskey ||
+ type == dns_rdatatype_cdnskey || type == dns_rdatatype_cds)
+ {
+ /*
+ * CDS and CDNSKEY are signed with KSK like DNSKEY.
+ * (RFC 7344, section 4.1 specifies that they must
+ * be signed with a key in the current DS RRset,
+ * which would only include KSK's.)
+ */
+ approved = false;
+ } else {
+ approved = (zsk_count == count);
+ }
+
+ dns_rdataset_disassociate(&rdataset);
+ return (approved);
+ }
+
+ dns_rdataset_disassociate(&rdataset);
+ return (false);
+}
+
+static isc_result_t
+add_nsec(dns_db_t *db, dns_dbversion_t *version, dns_name_t *name,
+ dns_dbnode_t *node, dns_ttl_t ttl, bool bottom, dns_diff_t *diff) {
+ dns_fixedname_t fixed;
+ dns_name_t *next;
+ dns_rdata_t rdata = DNS_RDATA_INIT;
+ isc_result_t result;
+ unsigned char nsecbuffer[DNS_NSEC_BUFFERSIZE];
+
+ next = dns_fixedname_initname(&fixed);
+
+ CHECK(next_active(db, version, name, next, bottom));
+ CHECK(dns_nsec_buildrdata(db, version, node, next, nsecbuffer, &rdata));
+ CHECK(update_one_rr(db, version, diff, DNS_DIFFOP_ADD, name, ttl,
+ &rdata));
+failure:
+ return (result);
+}
+
+static isc_result_t
+check_if_bottom_of_zone(dns_db_t *db, dns_dbnode_t *node,
+ dns_dbversion_t *version, bool *is_bottom_of_zone) {
+ isc_result_t result;
+ dns_rdatasetiter_t *iterator = NULL;
+ dns_rdataset_t rdataset;
+ bool seen_soa = false, seen_ns = false, seen_dname = false;
+
+ REQUIRE(is_bottom_of_zone != NULL);
+
+ result = dns_db_allrdatasets(db, node, version, 0, 0, &iterator);
+ if (result != ISC_R_SUCCESS) {
+ if (result == ISC_R_NOTFOUND) {
+ result = ISC_R_SUCCESS;
+ }
+ return (result);
+ }
+
+ dns_rdataset_init(&rdataset);
+ for (result = dns_rdatasetiter_first(iterator); result == ISC_R_SUCCESS;
+ result = dns_rdatasetiter_next(iterator))
+ {
+ dns_rdatasetiter_current(iterator, &rdataset);
+ switch (rdataset.type) {
+ case dns_rdatatype_soa:
+ seen_soa = true;
+ break;
+ case dns_rdatatype_ns:
+ seen_ns = true;
+ break;
+ case dns_rdatatype_dname:
+ seen_dname = true;
+ break;
+ }
+ dns_rdataset_disassociate(&rdataset);
+ }
+ if (result != ISC_R_NOMORE) {
+ goto failure;
+ }
+ if ((seen_ns && !seen_soa) || seen_dname) {
+ *is_bottom_of_zone = true;
+ }
+ result = ISC_R_SUCCESS;
+
+failure:
+ dns_rdatasetiter_destroy(&iterator);
+
+ return (result);
+}
+
+static isc_result_t
+sign_a_node(dns_db_t *db, dns_zone_t *zone, dns_name_t *name,
+ dns_dbnode_t *node, dns_dbversion_t *version, bool build_nsec3,
+ bool build_nsec, dst_key_t *key, isc_stdtime_t inception,
+ isc_stdtime_t expire, dns_ttl_t nsecttl, bool is_ksk, bool is_zsk,
+ bool keyset_kskonly, bool is_bottom_of_zone, dns_diff_t *diff,
+ int32_t *signatures, isc_mem_t *mctx) {
+ isc_result_t result;
+ dns_rdatasetiter_t *iterator = NULL;
+ dns_rdataset_t rdataset;
+ dns_rdata_t rdata = DNS_RDATA_INIT;
+ dns_stats_t *dnssecsignstats;
+
+ isc_buffer_t buffer;
+ unsigned char data[1024];
+ bool seen_soa, seen_ns, seen_rr, seen_nsec, seen_nsec3, seen_ds;
+
+ result = dns_db_allrdatasets(db, node, version, 0, 0, &iterator);
+ if (result != ISC_R_SUCCESS) {
+ if (result == ISC_R_NOTFOUND) {
+ result = ISC_R_SUCCESS;
+ }
+ return (result);
+ }
+
+ dns_rdataset_init(&rdataset);
+ isc_buffer_init(&buffer, data, sizeof(data));
+ seen_rr = seen_soa = seen_ns = seen_nsec = seen_nsec3 = seen_ds = false;
+ for (result = dns_rdatasetiter_first(iterator); result == ISC_R_SUCCESS;
+ result = dns_rdatasetiter_next(iterator))
+ {
+ dns_rdatasetiter_current(iterator, &rdataset);
+ if (rdataset.type == dns_rdatatype_soa) {
+ seen_soa = true;
+ } else if (rdataset.type == dns_rdatatype_ns) {
+ seen_ns = true;
+ } else if (rdataset.type == dns_rdatatype_ds) {
+ seen_ds = true;
+ } else if (rdataset.type == dns_rdatatype_nsec) {
+ seen_nsec = true;
+ } else if (rdataset.type == dns_rdatatype_nsec3) {
+ seen_nsec3 = true;
+ }
+ if (rdataset.type != dns_rdatatype_rrsig) {
+ seen_rr = true;
+ }
+ dns_rdataset_disassociate(&rdataset);
+ }
+ if (result != ISC_R_NOMORE) {
+ goto failure;
+ }
+ /*
+ * Going from insecure to NSEC3.
+ * Don't generate NSEC3 records for NSEC3 records.
+ */
+ if (build_nsec3 && !seen_nsec3 && seen_rr) {
+ bool unsecure = !seen_ds && seen_ns && !seen_soa;
+ CHECK(dns_nsec3_addnsec3s(db, version, name, nsecttl, unsecure,
+ diff));
+ (*signatures)--;
+ }
+ /*
+ * Going from insecure to NSEC.
+ * Don't generate NSEC records for NSEC3 records.
+ */
+ if (build_nsec && !seen_nsec3 && !seen_nsec && seen_rr) {
+ /*
+ * Build a NSEC record except at the origin.
+ */
+ if (!dns_name_equal(name, dns_db_origin(db))) {
+ CHECK(add_nsec(db, version, name, node, nsecttl,
+ is_bottom_of_zone, diff));
+ /* Count a NSEC generation as a signature generation. */
+ (*signatures)--;
+ }
+ }
+ result = dns_rdatasetiter_first(iterator);
+ while (result == ISC_R_SUCCESS) {
+ isc_stdtime_t when;
+
+ dns_rdatasetiter_current(iterator, &rdataset);
+ if (rdataset.type == dns_rdatatype_soa ||
+ rdataset.type == dns_rdatatype_rrsig)
+ {
+ goto next_rdataset;
+ }
+ if (rdataset.type == dns_rdatatype_dnskey ||
+ rdataset.type == dns_rdatatype_cdnskey ||
+ rdataset.type == dns_rdatatype_cds)
+ {
+ /*
+ * CDS and CDNSKEY are signed with KSK like DNSKEY.
+ * (RFC 7344, section 4.1 specifies that they must
+ * be signed with a key in the current DS RRset,
+ * which would only include KSK's.)
+ */
+ if (!is_ksk && keyset_kskonly) {
+ goto next_rdataset;
+ }
+ } else if (!is_zsk) {
+ goto next_rdataset;
+ } else if (is_zsk && !dst_key_is_signing(key, DST_BOOL_ZSK,
+ inception, &when))
+ {
+ /* Only applies to dnssec-policy. */
+ if (dns_zone_getkasp(zone) != NULL) {
+ goto next_rdataset;
+ }
+ }
+
+ if (seen_ns && !seen_soa && rdataset.type != dns_rdatatype_ds &&
+ rdataset.type != dns_rdatatype_nsec)
+ {
+ goto next_rdataset;
+ }
+ if (signed_with_good_key(zone, db, node, version, rdataset.type,
+ key))
+ {
+ goto next_rdataset;
+ }
+
+ /* Calculate the signature, creating a RRSIG RDATA. */
+ isc_buffer_clear(&buffer);
+ CHECK(dns_dnssec_sign(name, &rdataset, key, &inception, &expire,
+ mctx, &buffer, &rdata));
+ /* Update the database and journal with the RRSIG. */
+ /* XXX inefficient - will cause dataset merging */
+ CHECK(update_one_rr(db, version, diff, DNS_DIFFOP_ADDRESIGN,
+ name, rdataset.ttl, &rdata));
+ dns_rdata_reset(&rdata);
+
+ /* Update DNSSEC sign statistics. */
+ dnssecsignstats = dns_zone_getdnssecsignstats(zone);
+ if (dnssecsignstats != NULL) {
+ /* Generated a new signature. */
+ dns_dnssecsignstats_increment(dnssecsignstats, ID(key),
+ ALG(key),
+ dns_dnssecsignstats_sign);
+ /* This is a refresh. */
+ dns_dnssecsignstats_increment(
+ dnssecsignstats, ID(key), ALG(key),
+ dns_dnssecsignstats_refresh);
+ }
+
+ (*signatures)--;
+ next_rdataset:
+ dns_rdataset_disassociate(&rdataset);
+ result = dns_rdatasetiter_next(iterator);
+ }
+ if (result == ISC_R_NOMORE) {
+ result = ISC_R_SUCCESS;
+ }
+failure:
+ if (dns_rdataset_isassociated(&rdataset)) {
+ dns_rdataset_disassociate(&rdataset);
+ }
+ if (iterator != NULL) {
+ dns_rdatasetiter_destroy(&iterator);
+ }
+ return (result);
+}
+
+/*
+ * If 'update_only' is set then don't create a NSEC RRset if it doesn't exist.
+ */
+static isc_result_t
+updatesecure(dns_db_t *db, dns_dbversion_t *version, dns_name_t *name,
+ dns_ttl_t nsecttl, bool update_only, dns_diff_t *diff) {
+ isc_result_t result;
+ dns_rdataset_t rdataset;
+ dns_dbnode_t *node = NULL;
+
+ CHECK(dns_db_getoriginnode(db, &node));
+ if (update_only) {
+ dns_rdataset_init(&rdataset);
+ result = dns_db_findrdataset(
+ db, node, version, dns_rdatatype_nsec,
+ dns_rdatatype_none, 0, &rdataset, NULL);
+ if (dns_rdataset_isassociated(&rdataset)) {
+ dns_rdataset_disassociate(&rdataset);
+ }
+ if (result == ISC_R_NOTFOUND) {
+ goto success;
+ }
+ if (result != ISC_R_SUCCESS) {
+ goto failure;
+ }
+ }
+ CHECK(delete_nsec(db, version, node, name, diff));
+ CHECK(add_nsec(db, version, name, node, nsecttl, false, diff));
+success:
+ result = ISC_R_SUCCESS;
+failure:
+ if (node != NULL) {
+ dns_db_detachnode(db, &node);
+ }
+ return (result);
+}
+
+static isc_result_t
+updatesignwithkey(dns_zone_t *zone, dns_signing_t *signing,
+ dns_dbversion_t *version, bool build_nsec3, dns_ttl_t nsecttl,
+ dns_diff_t *diff) {
+ isc_result_t result;
+ dns_dbnode_t *node = NULL;
+ dns_rdataset_t rdataset;
+ dns_rdata_t rdata = DNS_RDATA_INIT;
+ unsigned char data[5];
+ bool seen_done = false;
+ bool have_rr = false;
+
+ dns_rdataset_init(&rdataset);
+ result = dns_db_getoriginnode(signing->db, &node);
+ if (result != ISC_R_SUCCESS) {
+ goto failure;
+ }
+
+ result = dns_db_findrdataset(signing->db, node, version,
+ zone->privatetype, dns_rdatatype_none, 0,
+ &rdataset, NULL);
+ if (result == ISC_R_NOTFOUND) {
+ INSIST(!dns_rdataset_isassociated(&rdataset));
+ result = ISC_R_SUCCESS;
+ goto failure;
+ }
+ if (result != ISC_R_SUCCESS) {
+ INSIST(!dns_rdataset_isassociated(&rdataset));
+ goto failure;
+ }
+ for (result = dns_rdataset_first(&rdataset); result == ISC_R_SUCCESS;
+ result = dns_rdataset_next(&rdataset))
+ {
+ dns_rdataset_current(&rdataset, &rdata);
+ /*
+ * If we don't match the algorithm or keyid skip the record.
+ */
+ if (rdata.length != 5 || rdata.data[0] != signing->algorithm ||
+ rdata.data[1] != ((signing->keyid >> 8) & 0xff) ||
+ rdata.data[2] != (signing->keyid & 0xff))
+ {
+ have_rr = true;
+ dns_rdata_reset(&rdata);
+ continue;
+ }
+ /*
+ * We have a match. If we were signing (!signing->deleteit)
+ * and we already have a record indicating that we have
+ * finished signing (rdata.data[4] != 0) then keep it.
+ * Otherwise it needs to be deleted as we have removed all
+ * the signatures (signing->deleteit), so any record indicating
+ * completion is now out of date, or we have finished signing
+ * with the new record so we no longer need to remember that
+ * we need to sign the zone with the matching key across a
+ * nameserver re-start.
+ */
+ if (!signing->deleteit && rdata.data[4] != 0) {
+ seen_done = true;
+ have_rr = true;
+ } else {
+ CHECK(update_one_rr(signing->db, version, diff,
+ DNS_DIFFOP_DEL, &zone->origin,
+ rdataset.ttl, &rdata));
+ }
+ dns_rdata_reset(&rdata);
+ }
+ if (result == ISC_R_NOMORE) {
+ result = ISC_R_SUCCESS;
+ }
+ if (!signing->deleteit && !seen_done) {
+ /*
+ * If we were signing then we need to indicate that we have
+ * finished signing the zone with this key. If it is already
+ * there we don't need to add it a second time.
+ */
+ data[0] = signing->algorithm;
+ data[1] = (signing->keyid >> 8) & 0xff;
+ data[2] = signing->keyid & 0xff;
+ data[3] = 0;
+ data[4] = 1;
+ rdata.length = sizeof(data);
+ rdata.data = data;
+ rdata.type = zone->privatetype;
+ rdata.rdclass = dns_db_class(signing->db);
+ CHECK(update_one_rr(signing->db, version, diff, DNS_DIFFOP_ADD,
+ &zone->origin, rdataset.ttl, &rdata));
+ } else if (!have_rr) {
+ dns_name_t *origin = dns_db_origin(signing->db);
+ /*
+ * Rebuild the NSEC/NSEC3 record for the origin as we no
+ * longer have any private records.
+ */
+ if (build_nsec3) {
+ CHECK(dns_nsec3_addnsec3s(signing->db, version, origin,
+ nsecttl, false, diff));
+ }
+ CHECK(updatesecure(signing->db, version, origin, nsecttl, true,
+ diff));
+ }
+
+failure:
+ if (dns_rdataset_isassociated(&rdataset)) {
+ dns_rdataset_disassociate(&rdataset);
+ }
+ if (node != NULL) {
+ dns_db_detachnode(signing->db, &node);
+ }
+ return (result);
+}
+
+/*
+ * Called from zone_nsec3chain() in order to update zone records indicating
+ * processing status of given NSEC3 chain:
+ *
+ * - If the supplied dns_nsec3chain_t structure has been fully processed
+ * (which is indicated by "active" being set to false):
+ *
+ * - remove all NSEC3PARAM records matching the relevant NSEC3 chain,
+ *
+ * - remove all private-type records containing NSEC3PARAM RDATA matching
+ * the relevant NSEC3 chain.
+ *
+ * - If the supplied dns_nsec3chain_t structure has not been fully processed
+ * (which is indicated by "active" being set to true), only remove the
+ * NSEC3PARAM record which matches the relevant NSEC3 chain and has the
+ * "flags" field set to 0.
+ *
+ * - If given NSEC3 chain is being added, add an NSEC3PARAM record contained
+ * in the relevant private-type record, but with the "flags" field set to
+ * 0, indicating that this NSEC3 chain is now complete for this zone.
+ *
+ * Note that this function is called at different processing stages for NSEC3
+ * chain additions vs. removals and needs to handle all cases properly.
+ */
+static isc_result_t
+fixup_nsec3param(dns_db_t *db, dns_dbversion_t *ver, dns_nsec3chain_t *chain,
+ bool active, dns_rdatatype_t privatetype, dns_diff_t *diff) {
+ dns_dbnode_t *node = NULL;
+ dns_name_t *name = dns_db_origin(db);
+ dns_rdata_t rdata = DNS_RDATA_INIT;
+ dns_rdataset_t rdataset;
+ dns_rdata_nsec3param_t nsec3param;
+ isc_result_t result;
+ isc_buffer_t buffer;
+ unsigned char parambuf[DNS_NSEC3PARAM_BUFFERSIZE];
+ dns_ttl_t ttl = 0;
+ bool nseconly = false, nsec3ok = false;
+
+ dns_rdataset_init(&rdataset);
+
+ result = dns_db_getoriginnode(db, &node);
+ RUNTIME_CHECK(result == ISC_R_SUCCESS);
+ result = dns_db_findrdataset(db, node, ver, dns_rdatatype_nsec3param, 0,
+ 0, &rdataset, NULL);
+ if (result == ISC_R_NOTFOUND) {
+ goto try_private;
+ }
+ if (result != ISC_R_SUCCESS) {
+ goto failure;
+ }
+
+ /*
+ * Preserve the existing ttl.
+ */
+ ttl = rdataset.ttl;
+
+ /*
+ * Delete all NSEC3PARAM records which match that in nsec3chain.
+ */
+ for (result = dns_rdataset_first(&rdataset); result == ISC_R_SUCCESS;
+ result = dns_rdataset_next(&rdataset))
+ {
+ dns_rdataset_current(&rdataset, &rdata);
+ CHECK(dns_rdata_tostruct(&rdata, &nsec3param, NULL));
+
+ if (nsec3param.hash != chain->nsec3param.hash ||
+ (active && nsec3param.flags != 0) ||
+ nsec3param.iterations != chain->nsec3param.iterations ||
+ nsec3param.salt_length != chain->nsec3param.salt_length ||
+ memcmp(nsec3param.salt, chain->nsec3param.salt,
+ nsec3param.salt_length))
+ {
+ dns_rdata_reset(&rdata);
+ continue;
+ }
+
+ CHECK(update_one_rr(db, ver, diff, DNS_DIFFOP_DEL, name,
+ rdataset.ttl, &rdata));
+ dns_rdata_reset(&rdata);
+ }
+ if (result != ISC_R_NOMORE) {
+ goto failure;
+ }
+
+ dns_rdataset_disassociate(&rdataset);
+
+try_private:
+
+ if (active) {
+ goto add;
+ }
+
+ result = dns_nsec_nseconly(db, ver, diff, &nseconly);
+ nsec3ok = (result == ISC_R_SUCCESS && !nseconly);
+
+ /*
+ * Delete all private records which match that in nsec3chain.
+ */
+ result = dns_db_findrdataset(db, node, ver, privatetype, 0, 0,
+ &rdataset, NULL);
+ if (result == ISC_R_NOTFOUND) {
+ goto add;
+ }
+ if (result != ISC_R_SUCCESS) {
+ goto failure;
+ }
+
+ for (result = dns_rdataset_first(&rdataset); result == ISC_R_SUCCESS;
+ result = dns_rdataset_next(&rdataset))
+ {
+ dns_rdata_t private = DNS_RDATA_INIT;
+ unsigned char buf[DNS_NSEC3PARAM_BUFFERSIZE];
+
+ dns_rdataset_current(&rdataset, &private);
+ if (!dns_nsec3param_fromprivate(&private, &rdata, buf,
+ sizeof(buf)))
+ {
+ continue;
+ }
+ CHECK(dns_rdata_tostruct(&rdata, &nsec3param, NULL));
+
+ if ((!nsec3ok &&
+ (nsec3param.flags & DNS_NSEC3FLAG_INITIAL) != 0) ||
+ nsec3param.hash != chain->nsec3param.hash ||
+ nsec3param.iterations != chain->nsec3param.iterations ||
+ nsec3param.salt_length != chain->nsec3param.salt_length ||
+ memcmp(nsec3param.salt, chain->nsec3param.salt,
+ nsec3param.salt_length))
+ {
+ dns_rdata_reset(&rdata);
+ continue;
+ }
+
+ CHECK(update_one_rr(db, ver, diff, DNS_DIFFOP_DEL, name,
+ rdataset.ttl, &private));
+ dns_rdata_reset(&rdata);
+ }
+ if (result != ISC_R_NOMORE) {
+ goto failure;
+ }
+
+add:
+ if ((chain->nsec3param.flags & DNS_NSEC3FLAG_REMOVE) != 0) {
+ result = ISC_R_SUCCESS;
+ goto failure;
+ }
+
+ /*
+ * Add a NSEC3PARAM record which matches that in nsec3chain but
+ * with all flags bits cleared.
+ *
+ * Note: we do not clear chain->nsec3param.flags as this change
+ * may be reversed.
+ */
+ isc_buffer_init(&buffer, &parambuf, sizeof(parambuf));
+ CHECK(dns_rdata_fromstruct(&rdata, dns_db_class(db),
+ dns_rdatatype_nsec3param, &chain->nsec3param,
+ &buffer));
+ rdata.data[1] = 0; /* Clear flag bits. */
+ CHECK(update_one_rr(db, ver, diff, DNS_DIFFOP_ADD, name, ttl, &rdata));
+
+failure:
+ dns_db_detachnode(db, &node);
+ if (dns_rdataset_isassociated(&rdataset)) {
+ dns_rdataset_disassociate(&rdataset);
+ }
+ return (result);
+}
+
+static isc_result_t
+delete_nsec(dns_db_t *db, dns_dbversion_t *ver, dns_dbnode_t *node,
+ dns_name_t *name, dns_diff_t *diff) {
+ dns_rdataset_t rdataset;
+ isc_result_t result;
+
+ dns_rdataset_init(&rdataset);
+
+ result = dns_db_findrdataset(db, node, ver, dns_rdatatype_nsec, 0, 0,
+ &rdataset, NULL);
+ if (result == ISC_R_NOTFOUND) {
+ return (ISC_R_SUCCESS);
+ }
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+ for (result = dns_rdataset_first(&rdataset); result == ISC_R_SUCCESS;
+ result = dns_rdataset_next(&rdataset))
+ {
+ dns_rdata_t rdata = DNS_RDATA_INIT;
+
+ dns_rdataset_current(&rdataset, &rdata);
+ CHECK(update_one_rr(db, ver, diff, DNS_DIFFOP_DEL, name,
+ rdataset.ttl, &rdata));
+ }
+ if (result == ISC_R_NOMORE) {
+ result = ISC_R_SUCCESS;
+ }
+failure:
+ dns_rdataset_disassociate(&rdataset);
+ return (result);
+}
+
+static isc_result_t
+deletematchingnsec3(dns_db_t *db, dns_dbversion_t *ver, dns_dbnode_t *node,
+ dns_name_t *name, const dns_rdata_nsec3param_t *param,
+ dns_diff_t *diff) {
+ dns_rdataset_t rdataset;
+ dns_rdata_nsec3_t nsec3;
+ isc_result_t result;
+
+ dns_rdataset_init(&rdataset);
+ result = dns_db_findrdataset(db, node, ver, dns_rdatatype_nsec3, 0, 0,
+ &rdataset, NULL);
+ if (result == ISC_R_NOTFOUND) {
+ return (ISC_R_SUCCESS);
+ }
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+
+ for (result = dns_rdataset_first(&rdataset); result == ISC_R_SUCCESS;
+ result = dns_rdataset_next(&rdataset))
+ {
+ dns_rdata_t rdata = DNS_RDATA_INIT;
+
+ dns_rdataset_current(&rdataset, &rdata);
+ CHECK(dns_rdata_tostruct(&rdata, &nsec3, NULL));
+ if (nsec3.hash != param->hash ||
+ nsec3.iterations != param->iterations ||
+ nsec3.salt_length != param->salt_length ||
+ memcmp(nsec3.salt, param->salt, nsec3.salt_length))
+ {
+ continue;
+ }
+ CHECK(update_one_rr(db, ver, diff, DNS_DIFFOP_DEL, name,
+ rdataset.ttl, &rdata));
+ }
+ if (result == ISC_R_NOMORE) {
+ result = ISC_R_SUCCESS;
+ }
+failure:
+ dns_rdataset_disassociate(&rdataset);
+ return (result);
+}
+
+static isc_result_t
+need_nsec_chain(dns_db_t *db, dns_dbversion_t *ver,
+ const dns_rdata_nsec3param_t *param, bool *answer) {
+ dns_dbnode_t *node = NULL;
+ dns_rdata_t rdata = DNS_RDATA_INIT;
+ dns_rdata_nsec3param_t myparam;
+ dns_rdataset_t rdataset;
+ isc_result_t result;
+
+ *answer = false;
+
+ result = dns_db_getoriginnode(db, &node);
+ RUNTIME_CHECK(result == ISC_R_SUCCESS);
+
+ dns_rdataset_init(&rdataset);
+
+ result = dns_db_findrdataset(db, node, ver, dns_rdatatype_nsec, 0, 0,
+ &rdataset, NULL);
+ if (result == ISC_R_SUCCESS) {
+ dns_rdataset_disassociate(&rdataset);
+ dns_db_detachnode(db, &node);
+ return (result);
+ }
+ if (result != ISC_R_NOTFOUND) {
+ dns_db_detachnode(db, &node);
+ return (result);
+ }
+
+ result = dns_db_findrdataset(db, node, ver, dns_rdatatype_nsec3param, 0,
+ 0, &rdataset, NULL);
+ if (result == ISC_R_NOTFOUND) {
+ *answer = true;
+ dns_db_detachnode(db, &node);
+ return (ISC_R_SUCCESS);
+ }
+ if (result != ISC_R_SUCCESS) {
+ dns_db_detachnode(db, &node);
+ return (result);
+ }
+
+ for (result = dns_rdataset_first(&rdataset); result == ISC_R_SUCCESS;
+ result = dns_rdataset_next(&rdataset))
+ {
+ dns_rdataset_current(&rdataset, &rdata);
+ CHECK(dns_rdata_tostruct(&rdata, &myparam, NULL));
+ dns_rdata_reset(&rdata);
+ /*
+ * Ignore any NSEC3PARAM removals.
+ */
+ if (NSEC3REMOVE(myparam.flags)) {
+ continue;
+ }
+ /*
+ * Ignore the chain that we are in the process of deleting.
+ */
+ if (myparam.hash == param->hash &&
+ myparam.iterations == param->iterations &&
+ myparam.salt_length == param->salt_length &&
+ !memcmp(myparam.salt, param->salt, myparam.salt_length))
+ {
+ continue;
+ }
+ /*
+ * Found an active NSEC3 chain.
+ */
+ break;
+ }
+ if (result == ISC_R_NOMORE) {
+ *answer = true;
+ result = ISC_R_SUCCESS;
+ }
+
+failure:
+ if (dns_rdataset_isassociated(&rdataset)) {
+ dns_rdataset_disassociate(&rdataset);
+ }
+ dns_db_detachnode(db, &node);
+ return (result);
+}
+
+/*%
+ * Given a tuple which is part of a diff, return a pointer to the next tuple in
+ * that diff which has the same name and type (or NULL if no such tuple is
+ * found).
+ */
+static dns_difftuple_t *
+find_next_matching_tuple(dns_difftuple_t *cur) {
+ dns_difftuple_t *next = cur;
+
+ while ((next = ISC_LIST_NEXT(next, link)) != NULL) {
+ if (cur->rdata.type == next->rdata.type &&
+ dns_name_equal(&cur->name, &next->name))
+ {
+ return (next);
+ }
+ }
+
+ return (NULL);
+}
+
+/*%
+ * Remove all tuples with the same name and type as 'cur' from 'src' and append
+ * them to 'dst'.
+ */
+static void
+move_matching_tuples(dns_difftuple_t *cur, dns_diff_t *src, dns_diff_t *dst) {
+ do {
+ dns_difftuple_t *next = find_next_matching_tuple(cur);
+ ISC_LIST_UNLINK(src->tuples, cur, link);
+ dns_diff_appendminimal(dst, &cur);
+ cur = next;
+ } while (cur != NULL);
+}
+
+/*%
+ * Add/remove DNSSEC signatures for the list of "raw" zone changes supplied in
+ * 'diff'. Gradually remove tuples from 'diff' and append them to 'zonediff'
+ * along with tuples representing relevant signature changes.
+ */
+isc_result_t
+dns__zone_updatesigs(dns_diff_t *diff, dns_db_t *db, dns_dbversion_t *version,
+ dst_key_t *zone_keys[], unsigned int nkeys,
+ dns_zone_t *zone, isc_stdtime_t inception,
+ isc_stdtime_t expire, isc_stdtime_t keyexpire,
+ isc_stdtime_t now, bool check_ksk, bool keyset_kskonly,
+ dns__zonediff_t *zonediff) {
+ dns_difftuple_t *tuple;
+ isc_result_t result;
+
+ while ((tuple = ISC_LIST_HEAD(diff->tuples)) != NULL) {
+ isc_stdtime_t exp = expire;
+
+ if (keyexpire != 0 &&
+ (tuple->rdata.type == dns_rdatatype_dnskey ||
+ tuple->rdata.type == dns_rdatatype_cdnskey ||
+ tuple->rdata.type == dns_rdatatype_cds))
+ {
+ exp = keyexpire;
+ }
+
+ result = del_sigs(zone, db, version, &tuple->name,
+ tuple->rdata.type, zonediff, zone_keys, nkeys,
+ now, false);
+ if (result != ISC_R_SUCCESS) {
+ dns_zone_log(zone, ISC_LOG_ERROR,
+ "dns__zone_updatesigs:del_sigs -> %s",
+ isc_result_totext(result));
+ return (result);
+ }
+ result = add_sigs(db, version, &tuple->name, zone,
+ tuple->rdata.type, zonediff->diff, zone_keys,
+ nkeys, zone->mctx, inception, exp, check_ksk,
+ keyset_kskonly);
+ if (result != ISC_R_SUCCESS) {
+ dns_zone_log(zone, ISC_LOG_ERROR,
+ "dns__zone_updatesigs:add_sigs -> %s",
+ isc_result_totext(result));
+ return (result);
+ }
+
+ /*
+ * Signature changes for all RRs with name tuple->name and type
+ * tuple->rdata.type were appended to zonediff->diff. Now we
+ * remove all the "raw" changes with the same name and type
+ * from diff (so that they are not processed by this loop
+ * again) and append them to zonediff so that they get applied.
+ */
+ move_matching_tuples(tuple, diff, zonediff->diff);
+ }
+ return (ISC_R_SUCCESS);
+}
+
+/*
+ * Incrementally build and sign a new NSEC3 chain using the parameters
+ * requested.
+ */
+static void
+zone_nsec3chain(dns_zone_t *zone) {
+ const char *me = "zone_nsec3chain";
+ dns_db_t *db = NULL;
+ dns_dbnode_t *node = NULL;
+ dns_dbversion_t *version = NULL;
+ dns_diff_t _sig_diff;
+ dns_diff_t nsec_diff;
+ dns_diff_t nsec3_diff;
+ dns_diff_t param_diff;
+ dns__zonediff_t zonediff;
+ dns_fixedname_t fixed;
+ dns_fixedname_t nextfixed;
+ dns_name_t *name, *nextname;
+ dns_rdataset_t rdataset;
+ dns_nsec3chain_t *nsec3chain = NULL, *nextnsec3chain;
+ dns_nsec3chainlist_t cleanup;
+ dst_key_t *zone_keys[DNS_MAXZONEKEYS];
+ int32_t signatures;
+ bool check_ksk, keyset_kskonly;
+ bool delegation;
+ bool first;
+ isc_result_t result;
+ isc_stdtime_t now, inception, soaexpire, expire;
+ uint32_t jitter, sigvalidityinterval, expiryinterval;
+ unsigned int i;
+ unsigned int nkeys = 0;
+ uint32_t nodes;
+ bool unsecure = false;
+ bool seen_soa, seen_ns, seen_dname, seen_ds;
+ bool seen_nsec, seen_nsec3, seen_rr;
+ dns_rdatasetiter_t *iterator = NULL;
+ bool buildnsecchain;
+ bool updatensec = false;
+ dns_rdatatype_t privatetype = zone->privatetype;
+
+ ENTER;
+
+ dns_rdataset_init(&rdataset);
+ name = dns_fixedname_initname(&fixed);
+ nextname = dns_fixedname_initname(&nextfixed);
+ dns_diff_init(zone->mctx, &param_diff);
+ dns_diff_init(zone->mctx, &nsec3_diff);
+ dns_diff_init(zone->mctx, &nsec_diff);
+ dns_diff_init(zone->mctx, &_sig_diff);
+ zonediff_init(&zonediff, &_sig_diff);
+ ISC_LIST_INIT(cleanup);
+
+ /*
+ * Updates are disabled. Pause for 5 minutes.
+ */
+ if (zone->update_disabled) {
+ result = ISC_R_FAILURE;
+ goto failure;
+ }
+
+ ZONEDB_LOCK(&zone->dblock, isc_rwlocktype_read);
+ /*
+ * This function is called when zone timer fires, after the latter gets
+ * set by zone_addnsec3chain(). If the action triggering the call to
+ * zone_addnsec3chain() is closely followed by a zone deletion request,
+ * it might turn out that the timer thread will not be woken up until
+ * after the zone is deleted by rmzone(), which calls dns_db_detach()
+ * for zone->db, causing the latter to become NULL. Return immediately
+ * if that happens.
+ */
+ if (zone->db != NULL) {
+ dns_db_attach(zone->db, &db);
+ }
+ ZONEDB_UNLOCK(&zone->dblock, isc_rwlocktype_read);
+ if (db == NULL) {
+ return;
+ }
+
+ result = dns_db_newversion(db, &version);
+ if (result != ISC_R_SUCCESS) {
+ dnssec_log(zone, ISC_LOG_ERROR,
+ "zone_nsec3chain:dns_db_newversion -> %s",
+ isc_result_totext(result));
+ goto failure;
+ }
+
+ isc_stdtime_get(&now);
+
+ result = dns__zone_findkeys(zone, db, version, now, zone->mctx,
+ DNS_MAXZONEKEYS, zone_keys, &nkeys);
+ if (result != ISC_R_SUCCESS) {
+ dnssec_log(zone, ISC_LOG_ERROR,
+ "zone_nsec3chain:dns__zone_findkeys -> %s",
+ isc_result_totext(result));
+ goto failure;
+ }
+
+ sigvalidityinterval = dns_zone_getsigvalidityinterval(zone);
+ inception = now - 3600; /* Allow for clock skew. */
+ soaexpire = now + sigvalidityinterval;
+ expiryinterval = dns_zone_getsigresigninginterval(zone);
+ if (expiryinterval > sigvalidityinterval) {
+ expiryinterval = sigvalidityinterval;
+ } else {
+ expiryinterval = sigvalidityinterval - expiryinterval;
+ }
+
+ /*
+ * Spread out signatures over time if they happen to be
+ * clumped. We don't do this for each add_sigs() call as
+ * we still want some clustering to occur.
+ */
+ if (sigvalidityinterval >= 3600U) {
+ if (sigvalidityinterval > 7200U) {
+ jitter = isc_random_uniform(expiryinterval);
+ } else {
+ jitter = isc_random_uniform(1200);
+ }
+ expire = soaexpire - jitter - 1;
+ } else {
+ expire = soaexpire - 1;
+ }
+
+ check_ksk = DNS_ZONE_OPTION(zone, DNS_ZONEOPT_UPDATECHECKKSK);
+ keyset_kskonly = DNS_ZONE_OPTION(zone, DNS_ZONEOPT_DNSKEYKSKONLY);
+
+ /*
+ * We keep pulling nodes off each iterator in turn until
+ * we have no more nodes to pull off or we reach the limits
+ * for this quantum.
+ */
+ nodes = zone->nodes;
+ signatures = zone->signatures;
+ LOCK_ZONE(zone);
+ nsec3chain = ISC_LIST_HEAD(zone->nsec3chain);
+ UNLOCK_ZONE(zone);
+ first = true;
+
+ if (nsec3chain != NULL) {
+ nsec3chain->save_delete_nsec = nsec3chain->delete_nsec;
+ }
+ /*
+ * Generate new NSEC3 chains first.
+ *
+ * The following while loop iterates over nodes in the zone database,
+ * updating the NSEC3 chain by calling dns_nsec3_addnsec3() for each of
+ * them. Once all nodes are processed, the "delete_nsec" field is
+ * consulted to check whether we are supposed to remove NSEC records
+ * from the zone database; if so, the database iterator is reset to
+ * point to the first node and the loop traverses all of them again,
+ * this time removing NSEC records. If we hit a node which is obscured
+ * by a delegation or a DNAME, nodes are skipped over until we find one
+ * that is not obscured by the same obscuring name and then normal
+ * processing is resumed.
+ *
+ * The above is repeated until all requested NSEC3 chain changes are
+ * applied or when we reach the limits for this quantum, whichever
+ * happens first.
+ *
+ * Note that the "signatures" variable is only used here to limit the
+ * amount of work performed. Actual DNSSEC signatures are only
+ * generated by dns__zone_updatesigs() calls later in this function.
+ */
+ while (nsec3chain != NULL && nodes-- > 0 && signatures > 0) {
+ dns_dbiterator_pause(nsec3chain->dbiterator);
+
+ LOCK_ZONE(zone);
+ nextnsec3chain = ISC_LIST_NEXT(nsec3chain, link);
+
+ ZONEDB_LOCK(&zone->dblock, isc_rwlocktype_read);
+ if (nsec3chain->done || nsec3chain->db != zone->db) {
+ ISC_LIST_UNLINK(zone->nsec3chain, nsec3chain, link);
+ ISC_LIST_APPEND(cleanup, nsec3chain, link);
+ }
+ ZONEDB_UNLOCK(&zone->dblock, isc_rwlocktype_read);
+ UNLOCK_ZONE(zone);
+ if (ISC_LIST_TAIL(cleanup) == nsec3chain) {
+ goto next_addchain;
+ }
+
+ /*
+ * Possible future db.
+ */
+ if (nsec3chain->db != db) {
+ goto next_addchain;
+ }
+
+ if (NSEC3REMOVE(nsec3chain->nsec3param.flags)) {
+ goto next_addchain;
+ }
+
+ dns_dbiterator_current(nsec3chain->dbiterator, &node, name);
+
+ if (nsec3chain->delete_nsec) {
+ delegation = false;
+ dns_dbiterator_pause(nsec3chain->dbiterator);
+ CHECK(delete_nsec(db, version, node, name, &nsec_diff));
+ goto next_addnode;
+ }
+ /*
+ * On the first pass we need to check if the current node
+ * has not been obscured.
+ */
+ delegation = false;
+ unsecure = false;
+ if (first) {
+ dns_fixedname_t ffound;
+ dns_name_t *found;
+ found = dns_fixedname_initname(&ffound);
+ result = dns_db_find(
+ db, name, version, dns_rdatatype_soa,
+ DNS_DBFIND_NOWILD, 0, NULL, found, NULL, NULL);
+ if ((result == DNS_R_DELEGATION ||
+ result == DNS_R_DNAME) &&
+ !dns_name_equal(name, found))
+ {
+ /*
+ * Remember the obscuring name so that
+ * we skip all obscured names.
+ */
+ dns_name_copy(found, name);
+ delegation = true;
+ goto next_addnode;
+ }
+ }
+
+ /*
+ * Check to see if this is a bottom of zone node.
+ */
+ result = dns_db_allrdatasets(db, node, version, 0, 0,
+ &iterator);
+ if (result == ISC_R_NOTFOUND) {
+ /* Empty node? */
+ goto next_addnode;
+ }
+ if (result != ISC_R_SUCCESS) {
+ goto failure;
+ }
+
+ seen_soa = seen_ns = seen_dname = seen_ds = seen_nsec = false;
+ for (result = dns_rdatasetiter_first(iterator);
+ result == ISC_R_SUCCESS;
+ result = dns_rdatasetiter_next(iterator))
+ {
+ dns_rdatasetiter_current(iterator, &rdataset);
+ INSIST(rdataset.type != dns_rdatatype_nsec3);
+ if (rdataset.type == dns_rdatatype_soa) {
+ seen_soa = true;
+ } else if (rdataset.type == dns_rdatatype_ns) {
+ seen_ns = true;
+ } else if (rdataset.type == dns_rdatatype_dname) {
+ seen_dname = true;
+ } else if (rdataset.type == dns_rdatatype_ds) {
+ seen_ds = true;
+ } else if (rdataset.type == dns_rdatatype_nsec) {
+ seen_nsec = true;
+ }
+ dns_rdataset_disassociate(&rdataset);
+ }
+ dns_rdatasetiter_destroy(&iterator);
+ /*
+ * Is there a NSEC chain than needs to be cleaned up?
+ */
+ if (seen_nsec) {
+ nsec3chain->seen_nsec = true;
+ }
+ if (seen_ns && !seen_soa && !seen_ds) {
+ unsecure = true;
+ }
+ if ((seen_ns && !seen_soa) || seen_dname) {
+ delegation = true;
+ }
+
+ /*
+ * Process one node.
+ */
+ dns_dbiterator_pause(nsec3chain->dbiterator);
+ result = dns_nsec3_addnsec3(
+ db, version, name, &nsec3chain->nsec3param,
+ zone_nsecttl(zone), unsecure, &nsec3_diff);
+ if (result != ISC_R_SUCCESS) {
+ dnssec_log(zone, ISC_LOG_ERROR,
+ "zone_nsec3chain:"
+ "dns_nsec3_addnsec3 -> %s",
+ isc_result_totext(result));
+ goto failure;
+ }
+
+ /*
+ * Treat each call to dns_nsec3_addnsec3() as if it's cost is
+ * two signatures. Additionally there will, in general, be
+ * two signature generated below.
+ *
+ * If we are only changing the optout flag the cost is half
+ * that of the cost of generating a completely new chain.
+ */
+ signatures -= 4;
+
+ /*
+ * Go onto next node.
+ */
+ next_addnode:
+ first = false;
+ dns_db_detachnode(db, &node);
+ do {
+ result = dns_dbiterator_next(nsec3chain->dbiterator);
+
+ if (result == ISC_R_NOMORE && nsec3chain->delete_nsec) {
+ dns_dbiterator_pause(nsec3chain->dbiterator);
+ CHECK(fixup_nsec3param(db, version, nsec3chain,
+ false, privatetype,
+ &param_diff));
+ LOCK_ZONE(zone);
+ ISC_LIST_UNLINK(zone->nsec3chain, nsec3chain,
+ link);
+ UNLOCK_ZONE(zone);
+ ISC_LIST_APPEND(cleanup, nsec3chain, link);
+ goto next_addchain;
+ }
+ if (result == ISC_R_NOMORE) {
+ dns_dbiterator_pause(nsec3chain->dbiterator);
+ if (nsec3chain->seen_nsec) {
+ CHECK(fixup_nsec3param(
+ db, version, nsec3chain, true,
+ privatetype, &param_diff));
+ nsec3chain->delete_nsec = true;
+ goto same_addchain;
+ }
+ CHECK(fixup_nsec3param(db, version, nsec3chain,
+ false, privatetype,
+ &param_diff));
+ LOCK_ZONE(zone);
+ ISC_LIST_UNLINK(zone->nsec3chain, nsec3chain,
+ link);
+ UNLOCK_ZONE(zone);
+ ISC_LIST_APPEND(cleanup, nsec3chain, link);
+ goto next_addchain;
+ } else if (result != ISC_R_SUCCESS) {
+ dnssec_log(zone, ISC_LOG_ERROR,
+ "zone_nsec3chain:"
+ "dns_dbiterator_next -> %s",
+ isc_result_totext(result));
+ goto failure;
+ } else if (delegation) {
+ dns_dbiterator_current(nsec3chain->dbiterator,
+ &node, nextname);
+ dns_db_detachnode(db, &node);
+ if (!dns_name_issubdomain(nextname, name)) {
+ break;
+ }
+ } else {
+ break;
+ }
+ } while (1);
+ continue;
+
+ same_addchain:
+ CHECK(dns_dbiterator_first(nsec3chain->dbiterator));
+ first = true;
+ continue;
+
+ next_addchain:
+ dns_dbiterator_pause(nsec3chain->dbiterator);
+ nsec3chain = nextnsec3chain;
+ first = true;
+ if (nsec3chain != NULL) {
+ nsec3chain->save_delete_nsec = nsec3chain->delete_nsec;
+ }
+ }
+
+ if (nsec3chain != NULL) {
+ goto skip_removals;
+ }
+
+ /*
+ * Process removals.
+ *
+ * This is a counterpart of the above while loop which takes care of
+ * removing an NSEC3 chain. It starts with determining whether the
+ * zone needs to switch from NSEC3 to NSEC; if so, it first builds an
+ * NSEC chain by iterating over all nodes in the zone database and only
+ * then goes on to remove NSEC3 records be iterating over all nodes
+ * again and calling deletematchingnsec3() for each of them; otherwise,
+ * it starts removing NSEC3 records immediately. Rules for processing
+ * obscured nodes and interrupting work are the same as for the while
+ * loop above.
+ */
+ LOCK_ZONE(zone);
+ nsec3chain = ISC_LIST_HEAD(zone->nsec3chain);
+ UNLOCK_ZONE(zone);
+ first = true;
+ buildnsecchain = false;
+ while (nsec3chain != NULL && nodes-- > 0 && signatures > 0) {
+ dns_dbiterator_pause(nsec3chain->dbiterator);
+
+ LOCK_ZONE(zone);
+ nextnsec3chain = ISC_LIST_NEXT(nsec3chain, link);
+ UNLOCK_ZONE(zone);
+
+ if (nsec3chain->db != db) {
+ goto next_removechain;
+ }
+
+ if (!NSEC3REMOVE(nsec3chain->nsec3param.flags)) {
+ goto next_removechain;
+ }
+
+ /*
+ * Work out if we need to build a NSEC chain as a consequence
+ * of removing this NSEC3 chain.
+ */
+ if (first && !updatensec &&
+ (nsec3chain->nsec3param.flags & DNS_NSEC3FLAG_NONSEC) == 0)
+ {
+ result = need_nsec_chain(db, version,
+ &nsec3chain->nsec3param,
+ &buildnsecchain);
+ if (result != ISC_R_SUCCESS) {
+ dnssec_log(zone, ISC_LOG_ERROR,
+ "zone_nsec3chain:"
+ "need_nsec_chain -> %s",
+ isc_result_totext(result));
+ goto failure;
+ }
+ }
+
+ if (first) {
+ dnssec_log(zone, ISC_LOG_DEBUG(3),
+ "zone_nsec3chain:buildnsecchain = %u\n",
+ buildnsecchain);
+ }
+
+ dns_dbiterator_current(nsec3chain->dbiterator, &node, name);
+ dns_dbiterator_pause(nsec3chain->dbiterator);
+ delegation = false;
+
+ if (!buildnsecchain) {
+ /*
+ * Delete the NSEC3PARAM record matching this chain.
+ */
+ if (first) {
+ result = fixup_nsec3param(
+ db, version, nsec3chain, true,
+ privatetype, &param_diff);
+ if (result != ISC_R_SUCCESS) {
+ dnssec_log(zone, ISC_LOG_ERROR,
+ "zone_nsec3chain:"
+ "fixup_nsec3param -> %s",
+ isc_result_totext(result));
+ goto failure;
+ }
+ }
+
+ /*
+ * Delete the NSEC3 records.
+ */
+ result = deletematchingnsec3(db, version, node, name,
+ &nsec3chain->nsec3param,
+ &nsec3_diff);
+ if (result != ISC_R_SUCCESS) {
+ dnssec_log(zone, ISC_LOG_ERROR,
+ "zone_nsec3chain:"
+ "deletematchingnsec3 -> %s",
+ isc_result_totext(result));
+ goto failure;
+ }
+ goto next_removenode;
+ }
+
+ if (first) {
+ dns_fixedname_t ffound;
+ dns_name_t *found;
+ found = dns_fixedname_initname(&ffound);
+ result = dns_db_find(
+ db, name, version, dns_rdatatype_soa,
+ DNS_DBFIND_NOWILD, 0, NULL, found, NULL, NULL);
+ if ((result == DNS_R_DELEGATION ||
+ result == DNS_R_DNAME) &&
+ !dns_name_equal(name, found))
+ {
+ /*
+ * Remember the obscuring name so that
+ * we skip all obscured names.
+ */
+ dns_name_copy(found, name);
+ delegation = true;
+ goto next_removenode;
+ }
+ }
+
+ /*
+ * Check to see if this is a bottom of zone node.
+ */
+ result = dns_db_allrdatasets(db, node, version, 0, 0,
+ &iterator);
+ if (result == ISC_R_NOTFOUND) {
+ /* Empty node? */
+ goto next_removenode;
+ }
+ if (result != ISC_R_SUCCESS) {
+ goto failure;
+ }
+
+ seen_soa = seen_ns = seen_dname = seen_nsec3 = seen_nsec =
+ seen_rr = false;
+ for (result = dns_rdatasetiter_first(iterator);
+ result == ISC_R_SUCCESS;
+ result = dns_rdatasetiter_next(iterator))
+ {
+ dns_rdatasetiter_current(iterator, &rdataset);
+ if (rdataset.type == dns_rdatatype_soa) {
+ seen_soa = true;
+ } else if (rdataset.type == dns_rdatatype_ns) {
+ seen_ns = true;
+ } else if (rdataset.type == dns_rdatatype_dname) {
+ seen_dname = true;
+ } else if (rdataset.type == dns_rdatatype_nsec) {
+ seen_nsec = true;
+ } else if (rdataset.type == dns_rdatatype_nsec3) {
+ seen_nsec3 = true;
+ } else if (rdataset.type != dns_rdatatype_rrsig) {
+ seen_rr = true;
+ }
+ dns_rdataset_disassociate(&rdataset);
+ }
+ dns_rdatasetiter_destroy(&iterator);
+
+ if (!seen_rr || seen_nsec3 || seen_nsec) {
+ goto next_removenode;
+ }
+ if ((seen_ns && !seen_soa) || seen_dname) {
+ delegation = true;
+ }
+
+ /*
+ * Add a NSEC record except at the origin.
+ */
+ if (!dns_name_equal(name, dns_db_origin(db))) {
+ dns_dbiterator_pause(nsec3chain->dbiterator);
+ CHECK(add_nsec(db, version, name, node,
+ zone_nsecttl(zone), delegation,
+ &nsec_diff));
+ signatures--;
+ }
+
+ next_removenode:
+ first = false;
+ dns_db_detachnode(db, &node);
+ do {
+ result = dns_dbiterator_next(nsec3chain->dbiterator);
+ if (result == ISC_R_NOMORE && buildnsecchain) {
+ /*
+ * The NSEC chain should now be built.
+ * We can now remove the NSEC3 chain.
+ */
+ updatensec = true;
+ goto same_removechain;
+ }
+ if (result == ISC_R_NOMORE) {
+ dns_dbiterator_pause(nsec3chain->dbiterator);
+ LOCK_ZONE(zone);
+ ISC_LIST_UNLINK(zone->nsec3chain, nsec3chain,
+ link);
+ UNLOCK_ZONE(zone);
+ ISC_LIST_APPEND(cleanup, nsec3chain, link);
+ result = fixup_nsec3param(
+ db, version, nsec3chain, false,
+ privatetype, &param_diff);
+ if (result != ISC_R_SUCCESS) {
+ dnssec_log(zone, ISC_LOG_ERROR,
+ "zone_nsec3chain:"
+ "fixup_nsec3param -> %s",
+ isc_result_totext(result));
+ goto failure;
+ }
+ goto next_removechain;
+ } else if (result != ISC_R_SUCCESS) {
+ dnssec_log(zone, ISC_LOG_ERROR,
+ "zone_nsec3chain:"
+ "dns_dbiterator_next -> %s",
+ isc_result_totext(result));
+ goto failure;
+ } else if (delegation) {
+ dns_dbiterator_current(nsec3chain->dbiterator,
+ &node, nextname);
+ dns_db_detachnode(db, &node);
+ if (!dns_name_issubdomain(nextname, name)) {
+ break;
+ }
+ } else {
+ break;
+ }
+ } while (1);
+ continue;
+
+ same_removechain:
+ CHECK(dns_dbiterator_first(nsec3chain->dbiterator));
+ buildnsecchain = false;
+ first = true;
+ continue;
+
+ next_removechain:
+ dns_dbiterator_pause(nsec3chain->dbiterator);
+ nsec3chain = nextnsec3chain;
+ first = true;
+ }
+
+skip_removals:
+ /*
+ * We may need to update the NSEC/NSEC3 records for the zone apex.
+ */
+ if (!ISC_LIST_EMPTY(param_diff.tuples)) {
+ bool rebuild_nsec = false, rebuild_nsec3 = false;
+ result = dns_db_getoriginnode(db, &node);
+ RUNTIME_CHECK(result == ISC_R_SUCCESS);
+ result = dns_db_allrdatasets(db, node, version, 0, 0,
+ &iterator);
+ if (result != ISC_R_SUCCESS) {
+ dnssec_log(zone, ISC_LOG_ERROR,
+ "zone_nsec3chain:dns_db_allrdatasets -> %s",
+ isc_result_totext(result));
+ goto failure;
+ }
+ for (result = dns_rdatasetiter_first(iterator);
+ result == ISC_R_SUCCESS;
+ result = dns_rdatasetiter_next(iterator))
+ {
+ dns_rdatasetiter_current(iterator, &rdataset);
+ if (rdataset.type == dns_rdatatype_nsec) {
+ rebuild_nsec = true;
+ } else if (rdataset.type == dns_rdatatype_nsec3param) {
+ rebuild_nsec3 = true;
+ }
+ dns_rdataset_disassociate(&rdataset);
+ }
+ dns_rdatasetiter_destroy(&iterator);
+ dns_db_detachnode(db, &node);
+
+ if (rebuild_nsec) {
+ if (nsec3chain != NULL) {
+ dns_dbiterator_pause(nsec3chain->dbiterator);
+ }
+
+ result = updatesecure(db, version, &zone->origin,
+ zone_nsecttl(zone), true,
+ &nsec_diff);
+ if (result != ISC_R_SUCCESS) {
+ dnssec_log(zone, ISC_LOG_ERROR,
+ "zone_nsec3chain:updatesecure -> %s",
+ isc_result_totext(result));
+ goto failure;
+ }
+ }
+
+ if (rebuild_nsec3) {
+ if (nsec3chain != NULL) {
+ dns_dbiterator_pause(nsec3chain->dbiterator);
+ }
+
+ result = dns_nsec3_addnsec3s(
+ db, version, dns_db_origin(db),
+ zone_nsecttl(zone), false, &nsec3_diff);
+ if (result != ISC_R_SUCCESS) {
+ dnssec_log(zone, ISC_LOG_ERROR,
+ "zone_nsec3chain:"
+ "dns_nsec3_addnsec3s -> %s",
+ isc_result_totext(result));
+ goto failure;
+ }
+ }
+ }
+
+ /*
+ * Add / update signatures for the NSEC3 records.
+ */
+ if (nsec3chain != NULL) {
+ dns_dbiterator_pause(nsec3chain->dbiterator);
+ }
+ result = dns__zone_updatesigs(&nsec3_diff, db, version, zone_keys,
+ nkeys, zone, inception, expire, 0, now,
+ check_ksk, keyset_kskonly, &zonediff);
+ if (result != ISC_R_SUCCESS) {
+ dnssec_log(zone, ISC_LOG_ERROR,
+ "zone_nsec3chain:dns__zone_updatesigs -> %s",
+ isc_result_totext(result));
+ goto failure;
+ }
+
+ /*
+ * We have changed the NSEC3PARAM or private RRsets
+ * above so we need to update the signatures.
+ */
+ result = dns__zone_updatesigs(&param_diff, db, version, zone_keys,
+ nkeys, zone, inception, expire, 0, now,
+ check_ksk, keyset_kskonly, &zonediff);
+ if (result != ISC_R_SUCCESS) {
+ dnssec_log(zone, ISC_LOG_ERROR,
+ "zone_nsec3chain:dns__zone_updatesigs -> %s",
+ isc_result_totext(result));
+ goto failure;
+ }
+
+ if (updatensec) {
+ result = updatesecure(db, version, &zone->origin,
+ zone_nsecttl(zone), false, &nsec_diff);
+ if (result != ISC_R_SUCCESS) {
+ dnssec_log(zone, ISC_LOG_ERROR,
+ "zone_nsec3chain:updatesecure -> %s",
+ isc_result_totext(result));
+ goto failure;
+ }
+ }
+
+ result = dns__zone_updatesigs(&nsec_diff, db, version, zone_keys, nkeys,
+ zone, inception, expire, 0, now,
+ check_ksk, keyset_kskonly, &zonediff);
+ if (result != ISC_R_SUCCESS) {
+ dnssec_log(zone, ISC_LOG_ERROR,
+ "zone_nsec3chain:dns__zone_updatesigs -> %s",
+ isc_result_totext(result));
+ goto failure;
+ }
+
+ /*
+ * If we made no effective changes to the zone then we can just
+ * cleanup otherwise we need to increment the serial.
+ */
+ if (ISC_LIST_EMPTY(zonediff.diff->tuples)) {
+ /*
+ * No need to call dns_db_closeversion() here as it is
+ * called with commit = true below.
+ */
+ goto done;
+ }
+
+ result = del_sigs(zone, db, version, &zone->origin, dns_rdatatype_soa,
+ &zonediff, zone_keys, nkeys, now, false);
+ if (result != ISC_R_SUCCESS) {
+ dnssec_log(zone, ISC_LOG_ERROR,
+ "zone_nsec3chain:del_sigs -> %s",
+ isc_result_totext(result));
+ goto failure;
+ }
+
+ result = update_soa_serial(zone, db, version, zonediff.diff, zone->mctx,
+ zone->updatemethod);
+ if (result != ISC_R_SUCCESS) {
+ dnssec_log(zone, ISC_LOG_ERROR,
+ "zone_nsec3chain:update_soa_serial -> %s",
+ isc_result_totext(result));
+ goto failure;
+ }
+
+ result = add_sigs(db, version, &zone->origin, zone, dns_rdatatype_soa,
+ zonediff.diff, zone_keys, nkeys, zone->mctx,
+ inception, soaexpire, check_ksk, keyset_kskonly);
+ if (result != ISC_R_SUCCESS) {
+ dnssec_log(zone, ISC_LOG_ERROR,
+ "zone_nsec3chain:add_sigs -> %s",
+ isc_result_totext(result));
+ goto failure;
+ }
+
+ /* Write changes to journal file. */
+ CHECK(zone_journal(zone, zonediff.diff, NULL, "zone_nsec3chain"));
+
+ LOCK_ZONE(zone);
+ zone_needdump(zone, DNS_DUMP_DELAY);
+ DNS_ZONE_SETFLAG(zone, DNS_ZONEFLG_NEEDNOTIFY);
+ UNLOCK_ZONE(zone);
+
+done:
+ /*
+ * Pause all iterators so that dns_db_closeversion() can succeed.
+ */
+ LOCK_ZONE(zone);
+ for (nsec3chain = ISC_LIST_HEAD(zone->nsec3chain); nsec3chain != NULL;
+ nsec3chain = ISC_LIST_NEXT(nsec3chain, link))
+ {
+ dns_dbiterator_pause(nsec3chain->dbiterator);
+ }
+ UNLOCK_ZONE(zone);
+
+ /*
+ * Everything has succeeded. Commit the changes.
+ * Unconditionally commit as zonediff.offline not checked above.
+ */
+ dns_db_closeversion(db, &version, true);
+
+ /*
+ * Everything succeeded so we can clean these up now.
+ */
+ nsec3chain = ISC_LIST_HEAD(cleanup);
+ while (nsec3chain != NULL) {
+ ISC_LIST_UNLINK(cleanup, nsec3chain, link);
+ dns_db_detach(&nsec3chain->db);
+ dns_dbiterator_destroy(&nsec3chain->dbiterator);
+ isc_mem_put(zone->mctx, nsec3chain, sizeof *nsec3chain);
+ nsec3chain = ISC_LIST_HEAD(cleanup);
+ }
+
+ LOCK_ZONE(zone);
+ set_resigntime(zone);
+ UNLOCK_ZONE(zone);
+
+failure:
+ if (result != ISC_R_SUCCESS) {
+ dnssec_log(zone, ISC_LOG_ERROR, "zone_nsec3chain: %s",
+ isc_result_totext(result));
+ }
+
+ /*
+ * On error roll back the current nsec3chain.
+ */
+ if (result != ISC_R_SUCCESS && nsec3chain != NULL) {
+ if (nsec3chain->done) {
+ dns_db_detach(&nsec3chain->db);
+ dns_dbiterator_destroy(&nsec3chain->dbiterator);
+ isc_mem_put(zone->mctx, nsec3chain, sizeof *nsec3chain);
+ } else {
+ result = dns_dbiterator_first(nsec3chain->dbiterator);
+ RUNTIME_CHECK(result == ISC_R_SUCCESS);
+ dns_dbiterator_pause(nsec3chain->dbiterator);
+ nsec3chain->delete_nsec = nsec3chain->save_delete_nsec;
+ }
+ }
+
+ /*
+ * Rollback the cleanup list.
+ */
+ nsec3chain = ISC_LIST_TAIL(cleanup);
+ while (nsec3chain != NULL) {
+ ISC_LIST_UNLINK(cleanup, nsec3chain, link);
+ if (nsec3chain->done) {
+ dns_db_detach(&nsec3chain->db);
+ dns_dbiterator_destroy(&nsec3chain->dbiterator);
+ isc_mem_put(zone->mctx, nsec3chain, sizeof *nsec3chain);
+ } else {
+ LOCK_ZONE(zone);
+ ISC_LIST_PREPEND(zone->nsec3chain, nsec3chain, link);
+ UNLOCK_ZONE(zone);
+ result = dns_dbiterator_first(nsec3chain->dbiterator);
+ RUNTIME_CHECK(result == ISC_R_SUCCESS);
+ dns_dbiterator_pause(nsec3chain->dbiterator);
+ nsec3chain->delete_nsec = nsec3chain->save_delete_nsec;
+ }
+ nsec3chain = ISC_LIST_TAIL(cleanup);
+ }
+
+ LOCK_ZONE(zone);
+ for (nsec3chain = ISC_LIST_HEAD(zone->nsec3chain); nsec3chain != NULL;
+ nsec3chain = ISC_LIST_NEXT(nsec3chain, link))
+ {
+ dns_dbiterator_pause(nsec3chain->dbiterator);
+ }
+ UNLOCK_ZONE(zone);
+
+ dns_diff_clear(&param_diff);
+ dns_diff_clear(&nsec3_diff);
+ dns_diff_clear(&nsec_diff);
+ dns_diff_clear(&_sig_diff);
+
+ if (iterator != NULL) {
+ dns_rdatasetiter_destroy(&iterator);
+ }
+
+ for (i = 0; i < nkeys; i++) {
+ dst_key_free(&zone_keys[i]);
+ }
+
+ if (node != NULL) {
+ dns_db_detachnode(db, &node);
+ }
+ if (version != NULL) {
+ dns_db_closeversion(db, &version, false);
+ dns_db_detach(&db);
+ } else if (db != NULL) {
+ dns_db_detach(&db);
+ }
+
+ LOCK_ZONE(zone);
+ if (ISC_LIST_HEAD(zone->nsec3chain) != NULL) {
+ isc_interval_t interval;
+ if (zone->update_disabled || result != ISC_R_SUCCESS) {
+ isc_interval_set(&interval, 60, 0); /* 1 minute */
+ } else {
+ isc_interval_set(&interval, 0, 10000000); /* 10 ms */
+ }
+ isc_time_nowplusinterval(&zone->nsec3chaintime, &interval);
+ } else {
+ isc_time_settoepoch(&zone->nsec3chaintime);
+ }
+ UNLOCK_ZONE(zone);
+
+ INSIST(version == NULL);
+}
+
+/*%
+ * Delete all RRSIG records with the given algorithm and keyid.
+ * Remove the NSEC record and RRSIGs if nkeys is zero.
+ * If all remaining RRsets are signed with the given algorithm
+ * set *has_algp to true.
+ */
+static isc_result_t
+del_sig(dns_db_t *db, dns_dbversion_t *version, dns_name_t *name,
+ dns_dbnode_t *node, unsigned int nkeys, dns_secalg_t algorithm,
+ uint16_t keyid, bool *has_algp, dns_diff_t *diff) {
+ dns_rdata_rrsig_t rrsig;
+ dns_rdataset_t rdataset;
+ dns_rdatasetiter_t *iterator = NULL;
+ isc_result_t result;
+ bool alg_missed = false;
+ bool alg_found = false;
+
+ char namebuf[DNS_NAME_FORMATSIZE];
+ dns_name_format(name, namebuf, sizeof(namebuf));
+
+ result = dns_db_allrdatasets(db, node, version, 0, 0, &iterator);
+ if (result != ISC_R_SUCCESS) {
+ if (result == ISC_R_NOTFOUND) {
+ result = ISC_R_SUCCESS;
+ }
+ return (result);
+ }
+
+ dns_rdataset_init(&rdataset);
+ for (result = dns_rdatasetiter_first(iterator); result == ISC_R_SUCCESS;
+ result = dns_rdatasetiter_next(iterator))
+ {
+ bool has_alg = false;
+ dns_rdatasetiter_current(iterator, &rdataset);
+ if (nkeys == 0 && rdataset.type == dns_rdatatype_nsec) {
+ for (result = dns_rdataset_first(&rdataset);
+ result == ISC_R_SUCCESS;
+ result = dns_rdataset_next(&rdataset))
+ {
+ dns_rdata_t rdata = DNS_RDATA_INIT;
+ dns_rdataset_current(&rdataset, &rdata);
+ CHECK(update_one_rr(db, version, diff,
+ DNS_DIFFOP_DEL, name,
+ rdataset.ttl, &rdata));
+ }
+ if (result != ISC_R_NOMORE) {
+ goto failure;
+ }
+ dns_rdataset_disassociate(&rdataset);
+ continue;
+ }
+ if (rdataset.type != dns_rdatatype_rrsig) {
+ dns_rdataset_disassociate(&rdataset);
+ continue;
+ }
+ for (result = dns_rdataset_first(&rdataset);
+ result == ISC_R_SUCCESS;
+ result = dns_rdataset_next(&rdataset))
+ {
+ dns_rdata_t rdata = DNS_RDATA_INIT;
+ dns_rdataset_current(&rdataset, &rdata);
+ CHECK(dns_rdata_tostruct(&rdata, &rrsig, NULL));
+ if (nkeys != 0 && (rrsig.algorithm != algorithm ||
+ rrsig.keyid != keyid))
+ {
+ if (rrsig.algorithm == algorithm) {
+ has_alg = true;
+ }
+ continue;
+ }
+ CHECK(update_one_rr(db, version, diff,
+ DNS_DIFFOP_DELRESIGN, name,
+ rdataset.ttl, &rdata));
+ }
+ dns_rdataset_disassociate(&rdataset);
+ if (result != ISC_R_NOMORE) {
+ break;
+ }
+
+ /*
+ * After deleting, if there's still a signature for
+ * 'algorithm', set alg_found; if not, set alg_missed.
+ */
+ if (has_alg) {
+ alg_found = true;
+ } else {
+ alg_missed = true;
+ }
+ }
+ if (result == ISC_R_NOMORE) {
+ result = ISC_R_SUCCESS;
+ }
+
+ /*
+ * Set `has_algp` if the algorithm was found in every RRset:
+ * i.e., found in at least one, and not missing from any.
+ */
+ *has_algp = (alg_found && !alg_missed);
+failure:
+ if (dns_rdataset_isassociated(&rdataset)) {
+ dns_rdataset_disassociate(&rdataset);
+ }
+ dns_rdatasetiter_destroy(&iterator);
+ return (result);
+}
+
+/*
+ * Prevent the zone entering a inconsistent state where
+ * NSEC only DNSKEYs are present with NSEC3 chains.
+ */
+bool
+dns_zone_check_dnskey_nsec3(dns_zone_t *zone, dns_db_t *db,
+ dns_dbversion_t *ver, dns_diff_t *diff,
+ dst_key_t **keys, unsigned int numkeys) {
+ uint8_t alg;
+ dns_rdatatype_t privatetype;
+ ;
+ bool nseconly = false, nsec3 = false;
+ isc_result_t result;
+
+ REQUIRE(DNS_ZONE_VALID(zone));
+ REQUIRE(db != NULL);
+
+ privatetype = dns_zone_getprivatetype(zone);
+
+ /* Scan the tuples for an NSEC-only DNSKEY */
+ if (diff != NULL) {
+ for (dns_difftuple_t *tuple = ISC_LIST_HEAD(diff->tuples);
+ tuple != NULL; tuple = ISC_LIST_NEXT(tuple, link))
+ {
+ if (nseconly && nsec3) {
+ break;
+ }
+
+ if (tuple->op != DNS_DIFFOP_ADD) {
+ continue;
+ }
+
+ if (tuple->rdata.type == dns_rdatatype_nsec3param) {
+ nsec3 = true;
+ }
+
+ if (tuple->rdata.type != dns_rdatatype_dnskey) {
+ continue;
+ }
+
+ alg = tuple->rdata.data[3];
+ if (alg == DNS_KEYALG_RSAMD5 || alg == DNS_KEYALG_DH ||
+ alg == DNS_KEYALG_DSA || alg == DNS_KEYALG_RSASHA1)
+ {
+ nseconly = true;
+ }
+ }
+ }
+ /* Scan the zone keys for an NSEC-only DNSKEY */
+ if (keys != NULL && !nseconly) {
+ for (unsigned int i = 0; i < numkeys; i++) {
+ alg = dst_key_alg(keys[i]);
+ if (alg == DNS_KEYALG_RSAMD5 || alg == DNS_KEYALG_DH ||
+ alg == DNS_KEYALG_DSA || alg == DNS_KEYALG_RSASHA1)
+ {
+ nseconly = true;
+ break;
+ }
+ }
+ }
+
+ /* Check DB for NSEC-only DNSKEY */
+ if (!nseconly) {
+ result = dns_nsec_nseconly(db, ver, diff, &nseconly);
+ /*
+ * Adding an NSEC3PARAM record can proceed without a
+ * DNSKEY (it will trigger a delayed change), so we can
+ * ignore ISC_R_NOTFOUND here.
+ */
+ if (result == ISC_R_NOTFOUND) {
+ result = ISC_R_SUCCESS;
+ }
+ CHECK(result);
+ }
+
+ /* Check existing DB for NSEC3 */
+ if (!nsec3) {
+ CHECK(dns_nsec3_activex(db, ver, false, privatetype, &nsec3));
+ }
+
+ /* Check kasp for NSEC3PARAM settings */
+ if (!nsec3) {
+ dns_kasp_t *kasp = dns_zone_getkasp(zone);
+ if (kasp != NULL) {
+ nsec3 = dns_kasp_nsec3(kasp);
+ }
+ }
+
+ /* Refuse to allow NSEC3 with NSEC-only keys */
+ if (nseconly && nsec3) {
+ goto failure;
+ }
+
+ return (true);
+
+failure:
+ return (false);
+}
+
+/*
+ * Incrementally sign the zone using the keys requested.
+ * Builds the NSEC chain if required.
+ */
+static void
+zone_sign(dns_zone_t *zone) {
+ const char *me = "zone_sign";
+ dns_db_t *db = NULL;
+ dns_dbnode_t *node = NULL;
+ dns_dbversion_t *version = NULL;
+ dns_diff_t _sig_diff;
+ dns_diff_t post_diff;
+ dns__zonediff_t zonediff;
+ dns_fixedname_t fixed;
+ dns_fixedname_t nextfixed;
+ dns_kasp_t *kasp;
+ dns_name_t *name, *nextname;
+ dns_rdataset_t rdataset;
+ dns_signing_t *signing, *nextsigning;
+ dns_signinglist_t cleanup;
+ dst_key_t *zone_keys[DNS_MAXZONEKEYS];
+ int32_t signatures;
+ bool check_ksk, keyset_kskonly, is_ksk, is_zsk;
+ bool with_ksk, with_zsk;
+ bool commit = false;
+ bool is_bottom_of_zone;
+ bool build_nsec = false;
+ bool build_nsec3 = false;
+ bool use_kasp = false;
+ bool first;
+ isc_result_t result;
+ isc_stdtime_t now, inception, soaexpire, expire;
+ uint32_t jitter, sigvalidityinterval, expiryinterval;
+ unsigned int i, j;
+ unsigned int nkeys = 0;
+ uint32_t nodes;
+
+ ENTER;
+
+ dns_rdataset_init(&rdataset);
+ name = dns_fixedname_initname(&fixed);
+ nextname = dns_fixedname_initname(&nextfixed);
+ dns_diff_init(zone->mctx, &_sig_diff);
+ dns_diff_init(zone->mctx, &post_diff);
+ zonediff_init(&zonediff, &_sig_diff);
+ ISC_LIST_INIT(cleanup);
+
+ /*
+ * Updates are disabled. Pause for 1 minute.
+ */
+ if (zone->update_disabled) {
+ result = ISC_R_FAILURE;
+ goto cleanup;
+ }
+
+ ZONEDB_LOCK(&zone->dblock, isc_rwlocktype_read);
+ if (zone->db != NULL) {
+ dns_db_attach(zone->db, &db);
+ }
+ ZONEDB_UNLOCK(&zone->dblock, isc_rwlocktype_read);
+ if (db == NULL) {
+ result = ISC_R_FAILURE;
+ goto cleanup;
+ }
+
+ result = dns_db_newversion(db, &version);
+ if (result != ISC_R_SUCCESS) {
+ dnssec_log(zone, ISC_LOG_ERROR,
+ "zone_sign:dns_db_newversion -> %s",
+ isc_result_totext(result));
+ goto cleanup;
+ }
+
+ isc_stdtime_get(&now);
+
+ result = dns__zone_findkeys(zone, db, version, now, zone->mctx,
+ DNS_MAXZONEKEYS, zone_keys, &nkeys);
+ if (result != ISC_R_SUCCESS) {
+ dnssec_log(zone, ISC_LOG_ERROR,
+ "zone_sign:dns__zone_findkeys -> %s",
+ isc_result_totext(result));
+ goto cleanup;
+ }
+
+ kasp = dns_zone_getkasp(zone);
+ sigvalidityinterval = dns_zone_getsigvalidityinterval(zone);
+ inception = now - 3600; /* Allow for clock skew. */
+ soaexpire = now + sigvalidityinterval;
+ expiryinterval = dns_zone_getsigresigninginterval(zone);
+ if (expiryinterval > sigvalidityinterval) {
+ expiryinterval = sigvalidityinterval;
+ } else {
+ expiryinterval = sigvalidityinterval - expiryinterval;
+ }
+
+ /*
+ * Spread out signatures over time if they happen to be
+ * clumped. We don't do this for each add_sigs() call as
+ * we still want some clustering to occur.
+ */
+ if (sigvalidityinterval >= 3600U) {
+ if (sigvalidityinterval > 7200U) {
+ jitter = isc_random_uniform(expiryinterval);
+ } else {
+ jitter = isc_random_uniform(1200);
+ }
+ expire = soaexpire - jitter - 1;
+ } else {
+ expire = soaexpire - 1;
+ }
+
+ /*
+ * We keep pulling nodes off each iterator in turn until
+ * we have no more nodes to pull off or we reach the limits
+ * for this quantum.
+ */
+ nodes = zone->nodes;
+ signatures = zone->signatures;
+ signing = ISC_LIST_HEAD(zone->signing);
+ first = true;
+
+ if (dns_zone_getkasp(zone) != NULL) {
+ check_ksk = false;
+ keyset_kskonly = true;
+ use_kasp = true;
+ } else {
+ check_ksk = DNS_ZONE_OPTION(zone, DNS_ZONEOPT_UPDATECHECKKSK);
+ keyset_kskonly = DNS_ZONE_OPTION(zone,
+ DNS_ZONEOPT_DNSKEYKSKONLY);
+ }
+ dnssec_log(zone, ISC_LOG_DEBUG(3), "zone_sign:use kasp -> %s",
+ use_kasp ? "yes" : "no");
+
+ /* Determine which type of chain to build */
+ CHECK(dns_private_chains(db, version, zone->privatetype, &build_nsec,
+ &build_nsec3));
+ if (!build_nsec && !build_nsec3) {
+ if (use_kasp) {
+ build_nsec3 = dns_kasp_nsec3(kasp);
+ if (!dns_zone_check_dnskey_nsec3(
+ zone, db, version, NULL,
+ (dst_key_t **)&zone_keys, nkeys))
+ {
+ dnssec_log(zone, ISC_LOG_INFO,
+ "wait building NSEC3 chain until "
+ "NSEC only DNSKEYs are removed");
+ build_nsec3 = false;
+ }
+ build_nsec = !build_nsec3;
+ } else {
+ /* If neither chain is found, default to NSEC */
+ build_nsec = true;
+ }
+ }
+
+ while (signing != NULL && nodes-- > 0 && signatures > 0) {
+ bool has_alg = false;
+
+ dns_dbiterator_pause(signing->dbiterator);
+ nextsigning = ISC_LIST_NEXT(signing, link);
+
+ ZONEDB_LOCK(&zone->dblock, isc_rwlocktype_read);
+ if (signing->done || signing->db != zone->db) {
+ /*
+ * The zone has been reloaded. We will have to
+ * created new signings as part of the reload
+ * process so we can destroy this one.
+ */
+ ISC_LIST_UNLINK(zone->signing, signing, link);
+ ISC_LIST_APPEND(cleanup, signing, link);
+ ZONEDB_UNLOCK(&zone->dblock, isc_rwlocktype_read);
+ goto next_signing;
+ }
+ ZONEDB_UNLOCK(&zone->dblock, isc_rwlocktype_read);
+
+ if (signing->db != db) {
+ goto next_signing;
+ }
+
+ is_bottom_of_zone = false;
+
+ if (first && signing->deleteit) {
+ /*
+ * Remove the key we are deleting from consideration.
+ */
+ for (i = 0, j = 0; i < nkeys; i++) {
+ /*
+ * Find the key we want to remove.
+ */
+ if (ALG(zone_keys[i]) == signing->algorithm &&
+ dst_key_id(zone_keys[i]) == signing->keyid)
+ {
+ bool ksk = false;
+ isc_result_t ret = dst_key_getbool(
+ zone_keys[i], DST_BOOL_KSK,
+ &ksk);
+ if (ret != ISC_R_SUCCESS) {
+ ksk = KSK(zone_keys[i]);
+ }
+ if (ksk) {
+ dst_key_free(&zone_keys[i]);
+ }
+ continue;
+ }
+ zone_keys[j] = zone_keys[i];
+ j++;
+ }
+ for (i = j; i < nkeys; i++) {
+ zone_keys[i] = NULL;
+ }
+ nkeys = j;
+ }
+
+ dns_dbiterator_current(signing->dbiterator, &node, name);
+
+ if (signing->deleteit) {
+ dns_dbiterator_pause(signing->dbiterator);
+ CHECK(del_sig(db, version, name, node, nkeys,
+ signing->algorithm, signing->keyid,
+ &has_alg, zonediff.diff));
+ }
+
+ /*
+ * On the first pass we need to check if the current node
+ * has not been obscured.
+ */
+ if (first) {
+ dns_fixedname_t ffound;
+ dns_name_t *found;
+ found = dns_fixedname_initname(&ffound);
+ result = dns_db_find(
+ db, name, version, dns_rdatatype_soa,
+ DNS_DBFIND_NOWILD, 0, NULL, found, NULL, NULL);
+ if ((result == DNS_R_DELEGATION ||
+ result == DNS_R_DNAME) &&
+ !dns_name_equal(name, found))
+ {
+ /*
+ * Remember the obscuring name so that
+ * we skip all obscured names.
+ */
+ dns_name_copy(found, name);
+ is_bottom_of_zone = true;
+ goto next_node;
+ }
+ }
+
+ /*
+ * Process one node.
+ */
+ with_ksk = false;
+ with_zsk = false;
+ dns_dbiterator_pause(signing->dbiterator);
+
+ CHECK(check_if_bottom_of_zone(db, node, version,
+ &is_bottom_of_zone));
+
+ for (i = 0; !has_alg && i < nkeys; i++) {
+ bool both = false;
+
+ /*
+ * Find the keys we want to sign with.
+ */
+ if (!dst_key_isprivate(zone_keys[i])) {
+ continue;
+ }
+ if (dst_key_inactive(zone_keys[i])) {
+ continue;
+ }
+
+ /*
+ * When adding look for the specific key.
+ */
+ if (!signing->deleteit &&
+ (dst_key_alg(zone_keys[i]) != signing->algorithm ||
+ dst_key_id(zone_keys[i]) != signing->keyid))
+ {
+ continue;
+ }
+
+ /*
+ * When deleting make sure we are properly signed
+ * with the algorithm that was being removed.
+ */
+ if (signing->deleteit &&
+ ALG(zone_keys[i]) != signing->algorithm)
+ {
+ continue;
+ }
+
+ /*
+ * Do we do KSK processing?
+ */
+ if (check_ksk && !REVOKE(zone_keys[i])) {
+ bool have_ksk, have_nonksk;
+ if (KSK(zone_keys[i])) {
+ have_ksk = true;
+ have_nonksk = false;
+ } else {
+ have_ksk = false;
+ have_nonksk = true;
+ }
+ for (j = 0; j < nkeys; j++) {
+ if (j == i || (ALG(zone_keys[i]) !=
+ ALG(zone_keys[j])))
+ {
+ continue;
+ }
+ /*
+ * Don't consider inactive keys, however
+ * the key may be temporary offline, so
+ * do consider KSKs which private key
+ * files are unavailable.
+ */
+ if (dst_key_inactive(zone_keys[j])) {
+ continue;
+ }
+ if (REVOKE(zone_keys[j])) {
+ continue;
+ }
+ if (KSK(zone_keys[j])) {
+ have_ksk = true;
+ } else if (dst_key_isprivate(
+ zone_keys[j]))
+ {
+ have_nonksk = true;
+ }
+ both = have_ksk && have_nonksk;
+ if (both) {
+ break;
+ }
+ }
+ }
+ if (use_kasp) {
+ /*
+ * A dnssec-policy is found. Check what
+ * RRsets this key can sign.
+ */
+ isc_result_t kresult;
+ is_ksk = false;
+ kresult = dst_key_getbool(
+ zone_keys[i], DST_BOOL_KSK, &is_ksk);
+ if (kresult != ISC_R_SUCCESS) {
+ if (KSK(zone_keys[i])) {
+ is_ksk = true;
+ }
+ }
+
+ is_zsk = false;
+ kresult = dst_key_getbool(
+ zone_keys[i], DST_BOOL_ZSK, &is_zsk);
+ if (kresult != ISC_R_SUCCESS) {
+ if (!KSK(zone_keys[i])) {
+ is_zsk = true;
+ }
+ }
+ /* Treat as if we have both KSK and ZSK. */
+ both = true;
+ } else if (both || REVOKE(zone_keys[i])) {
+ is_ksk = KSK(zone_keys[i]);
+ is_zsk = !KSK(zone_keys[i]);
+ } else {
+ is_ksk = false;
+ is_zsk = true;
+ }
+
+ /*
+ * If deleting signatures, we need to ensure that
+ * the RRset is still signed at least once by a
+ * KSK and a ZSK.
+ */
+ if (signing->deleteit && is_zsk && with_zsk) {
+ continue;
+ }
+
+ if (signing->deleteit && is_ksk && with_ksk) {
+ continue;
+ }
+
+ CHECK(sign_a_node(
+ db, zone, name, node, version, build_nsec3,
+ build_nsec, zone_keys[i], inception, expire,
+ zone_nsecttl(zone), is_ksk, is_zsk,
+ (both && keyset_kskonly), is_bottom_of_zone,
+ zonediff.diff, &signatures, zone->mctx));
+ /*
+ * If we are adding we are done. Look for other keys
+ * of the same algorithm if deleting.
+ */
+ if (!signing->deleteit) {
+ break;
+ }
+ if (is_zsk) {
+ with_zsk = true;
+ }
+ if (is_ksk) {
+ with_ksk = true;
+ }
+ }
+
+ /*
+ * Go onto next node.
+ */
+ next_node:
+ first = false;
+ dns_db_detachnode(db, &node);
+ do {
+ result = dns_dbiterator_next(signing->dbiterator);
+ if (result == ISC_R_NOMORE) {
+ ISC_LIST_UNLINK(zone->signing, signing, link);
+ ISC_LIST_APPEND(cleanup, signing, link);
+ dns_dbiterator_pause(signing->dbiterator);
+ if (nkeys != 0 && build_nsec) {
+ /*
+ * We have finished regenerating the
+ * zone with a zone signing key.
+ * The NSEC chain is now complete and
+ * there is a full set of signatures
+ * for the zone. We can now clear the
+ * OPT bit from the NSEC record.
+ */
+ result = updatesecure(
+ db, version, &zone->origin,
+ zone_nsecttl(zone), false,
+ &post_diff);
+ if (result != ISC_R_SUCCESS) {
+ dnssec_log(zone, ISC_LOG_ERROR,
+ "updatesecure -> %s",
+ isc_result_totext(
+ result));
+ goto cleanup;
+ }
+ }
+ result = updatesignwithkey(
+ zone, signing, version, build_nsec3,
+ zone_nsecttl(zone), &post_diff);
+ if (result != ISC_R_SUCCESS) {
+ dnssec_log(zone, ISC_LOG_ERROR,
+ "updatesignwithkey -> %s",
+ isc_result_totext(result));
+ goto cleanup;
+ }
+ build_nsec = false;
+ goto next_signing;
+ } else if (result != ISC_R_SUCCESS) {
+ dnssec_log(zone, ISC_LOG_ERROR,
+ "zone_sign:"
+ "dns_dbiterator_next -> %s",
+ isc_result_totext(result));
+ goto cleanup;
+ } else if (is_bottom_of_zone) {
+ dns_dbiterator_current(signing->dbiterator,
+ &node, nextname);
+ dns_db_detachnode(db, &node);
+ if (!dns_name_issubdomain(nextname, name)) {
+ break;
+ }
+ } else {
+ break;
+ }
+ } while (1);
+ continue;
+
+ next_signing:
+ dns_dbiterator_pause(signing->dbiterator);
+ signing = nextsigning;
+ first = true;
+ }
+
+ if (ISC_LIST_HEAD(post_diff.tuples) != NULL) {
+ result = dns__zone_updatesigs(&post_diff, db, version,
+ zone_keys, nkeys, zone, inception,
+ expire, 0, now, check_ksk,
+ keyset_kskonly, &zonediff);
+ if (result != ISC_R_SUCCESS) {
+ dnssec_log(zone, ISC_LOG_ERROR,
+ "zone_sign:dns__zone_updatesigs -> %s",
+ isc_result_totext(result));
+ goto cleanup;
+ }
+ }
+
+ /*
+ * Have we changed anything?
+ */
+ if (ISC_LIST_EMPTY(zonediff.diff->tuples)) {
+ if (zonediff.offline) {
+ commit = true;
+ }
+ result = ISC_R_SUCCESS;
+ goto pauseall;
+ }
+
+ commit = true;
+
+ result = del_sigs(zone, db, version, &zone->origin, dns_rdatatype_soa,
+ &zonediff, zone_keys, nkeys, now, false);
+ if (result != ISC_R_SUCCESS) {
+ dnssec_log(zone, ISC_LOG_ERROR, "zone_sign:del_sigs -> %s",
+ isc_result_totext(result));
+ goto cleanup;
+ }
+
+ result = update_soa_serial(zone, db, version, zonediff.diff, zone->mctx,
+ zone->updatemethod);
+ if (result != ISC_R_SUCCESS) {
+ dnssec_log(zone, ISC_LOG_ERROR,
+ "zone_sign:update_soa_serial -> %s",
+ isc_result_totext(result));
+ goto cleanup;
+ }
+
+ /*
+ * Generate maximum life time signatures so that the above loop
+ * termination is sensible.
+ */
+ result = add_sigs(db, version, &zone->origin, zone, dns_rdatatype_soa,
+ zonediff.diff, zone_keys, nkeys, zone->mctx,
+ inception, soaexpire, check_ksk, keyset_kskonly);
+ if (result != ISC_R_SUCCESS) {
+ dnssec_log(zone, ISC_LOG_ERROR, "zone_sign:add_sigs -> %s",
+ isc_result_totext(result));
+ goto cleanup;
+ }
+
+ /*
+ * Write changes to journal file.
+ */
+ CHECK(zone_journal(zone, zonediff.diff, NULL, "zone_sign"));
+
+pauseall:
+ /*
+ * Pause all iterators so that dns_db_closeversion() can succeed.
+ */
+ for (signing = ISC_LIST_HEAD(zone->signing); signing != NULL;
+ signing = ISC_LIST_NEXT(signing, link))
+ {
+ dns_dbiterator_pause(signing->dbiterator);
+ }
+
+ for (signing = ISC_LIST_HEAD(cleanup); signing != NULL;
+ signing = ISC_LIST_NEXT(signing, link))
+ {
+ dns_dbiterator_pause(signing->dbiterator);
+ }
+
+ /*
+ * Everything has succeeded. Commit the changes.
+ */
+ dns_db_closeversion(db, &version, commit);
+
+ /*
+ * Everything succeeded so we can clean these up now.
+ */
+ signing = ISC_LIST_HEAD(cleanup);
+ while (signing != NULL) {
+ ISC_LIST_UNLINK(cleanup, signing, link);
+ dns_db_detach(&signing->db);
+ dns_dbiterator_destroy(&signing->dbiterator);
+ isc_mem_put(zone->mctx, signing, sizeof *signing);
+ signing = ISC_LIST_HEAD(cleanup);
+ }
+
+ LOCK_ZONE(zone);
+ set_resigntime(zone);
+ if (commit) {
+ DNS_ZONE_SETFLAG(zone, DNS_ZONEFLG_NEEDNOTIFY);
+ zone_needdump(zone, DNS_DUMP_DELAY);
+ }
+ UNLOCK_ZONE(zone);
+
+failure:
+ if (result != ISC_R_SUCCESS) {
+ dnssec_log(zone, ISC_LOG_ERROR, "zone_sign: failed: %s",
+ isc_result_totext(result));
+ }
+
+cleanup:
+ /*
+ * Pause all dbiterators.
+ */
+ for (signing = ISC_LIST_HEAD(zone->signing); signing != NULL;
+ signing = ISC_LIST_NEXT(signing, link))
+ {
+ dns_dbiterator_pause(signing->dbiterator);
+ }
+
+ /*
+ * Rollback the cleanup list.
+ */
+ signing = ISC_LIST_HEAD(cleanup);
+ while (signing != NULL) {
+ ISC_LIST_UNLINK(cleanup, signing, link);
+ ISC_LIST_PREPEND(zone->signing, signing, link);
+ dns_dbiterator_first(signing->dbiterator);
+ dns_dbiterator_pause(signing->dbiterator);
+ signing = ISC_LIST_HEAD(cleanup);
+ }
+
+ dns_diff_clear(&_sig_diff);
+
+ for (i = 0; i < nkeys; i++) {
+ dst_key_free(&zone_keys[i]);
+ }
+
+ if (node != NULL) {
+ dns_db_detachnode(db, &node);
+ }
+
+ if (version != NULL) {
+ dns_db_closeversion(db, &version, false);
+ dns_db_detach(&db);
+ } else if (db != NULL) {
+ dns_db_detach(&db);
+ }
+
+ LOCK_ZONE(zone);
+ if (ISC_LIST_HEAD(zone->signing) != NULL) {
+ isc_interval_t interval;
+ if (zone->update_disabled || result != ISC_R_SUCCESS) {
+ isc_interval_set(&interval, 60, 0); /* 1 minute */
+ } else {
+ isc_interval_set(&interval, 0, 10000000); /* 10 ms */
+ }
+ isc_time_nowplusinterval(&zone->signingtime, &interval);
+ } else {
+ isc_time_settoepoch(&zone->signingtime);
+ }
+ UNLOCK_ZONE(zone);
+
+ INSIST(version == NULL);
+}
+
+static isc_result_t
+normalize_key(dns_rdata_t *rr, dns_rdata_t *target, unsigned char *data,
+ int size) {
+ dns_rdata_dnskey_t dnskey;
+ dns_rdata_keydata_t keydata;
+ isc_buffer_t buf;
+ isc_result_t result;
+
+ dns_rdata_reset(target);
+ isc_buffer_init(&buf, data, size);
+
+ switch (rr->type) {
+ case dns_rdatatype_dnskey:
+ result = dns_rdata_tostruct(rr, &dnskey, NULL);
+ RUNTIME_CHECK(result == ISC_R_SUCCESS);
+ dnskey.flags &= ~DNS_KEYFLAG_REVOKE;
+ dns_rdata_fromstruct(target, rr->rdclass, dns_rdatatype_dnskey,
+ &dnskey, &buf);
+ break;
+ case dns_rdatatype_keydata:
+ result = dns_rdata_tostruct(rr, &keydata, NULL);
+ if (result == ISC_R_UNEXPECTEDEND) {
+ return (result);
+ }
+ RUNTIME_CHECK(result == ISC_R_SUCCESS);
+ dns_keydata_todnskey(&keydata, &dnskey, NULL);
+ dns_rdata_fromstruct(target, rr->rdclass, dns_rdatatype_dnskey,
+ &dnskey, &buf);
+ break;
+ default:
+ UNREACHABLE();
+ }
+ return (ISC_R_SUCCESS);
+}
+
+/*
+ * 'rdset' contains either a DNSKEY rdataset from the zone apex, or
+ * a KEYDATA rdataset from the key zone.
+ *
+ * 'rr' contains either a DNSKEY record, or a KEYDATA record
+ *
+ * After normalizing keys to the same format (DNSKEY, with revoke bit
+ * cleared), return true if a key that matches 'rr' is found in
+ * 'rdset', or false if not.
+ */
+
+static bool
+matchkey(dns_rdataset_t *rdset, dns_rdata_t *rr) {
+ unsigned char data1[4096], data2[4096];
+ dns_rdata_t rdata, rdata1, rdata2;
+ isc_result_t result;
+
+ dns_rdata_init(&rdata);
+ dns_rdata_init(&rdata1);
+ dns_rdata_init(&rdata2);
+
+ result = normalize_key(rr, &rdata1, data1, sizeof(data1));
+ if (result != ISC_R_SUCCESS) {
+ return (false);
+ }
+
+ for (result = dns_rdataset_first(rdset); result == ISC_R_SUCCESS;
+ result = dns_rdataset_next(rdset))
+ {
+ dns_rdata_reset(&rdata);
+ dns_rdataset_current(rdset, &rdata);
+ result = normalize_key(&rdata, &rdata2, data2, sizeof(data2));
+ if (result != ISC_R_SUCCESS) {
+ continue;
+ }
+ if (dns_rdata_compare(&rdata1, &rdata2) == 0) {
+ return (true);
+ }
+ }
+
+ return (false);
+}
+
+/*
+ * Calculate the refresh interval for a keydata zone, per
+ * RFC5011: MAX(1 hr,
+ * MIN(15 days,
+ * 1/2 * OrigTTL,
+ * 1/2 * RRSigExpirationInterval))
+ * or for retries: MAX(1 hr,
+ * MIN(1 day,
+ * 1/10 * OrigTTL,
+ * 1/10 * RRSigExpirationInterval))
+ */
+static isc_stdtime_t
+refresh_time(dns_keyfetch_t *kfetch, bool retry) {
+ isc_result_t result;
+ uint32_t t;
+ dns_rdataset_t *rdset;
+ dns_rdata_t sigrr = DNS_RDATA_INIT;
+ dns_rdata_sig_t sig;
+ isc_stdtime_t now;
+
+ isc_stdtime_get(&now);
+
+ if (dns_rdataset_isassociated(&kfetch->dnskeysigset)) {
+ rdset = &kfetch->dnskeysigset;
+ } else {
+ return (now + dns_zone_mkey_hour);
+ }
+
+ result = dns_rdataset_first(rdset);
+ if (result != ISC_R_SUCCESS) {
+ return (now + dns_zone_mkey_hour);
+ }
+
+ dns_rdataset_current(rdset, &sigrr);
+ result = dns_rdata_tostruct(&sigrr, &sig, NULL);
+ RUNTIME_CHECK(result == ISC_R_SUCCESS);
+
+ if (!retry) {
+ t = sig.originalttl / 2;
+
+ if (isc_serial_gt(sig.timeexpire, now)) {
+ uint32_t exp = (sig.timeexpire - now) / 2;
+ if (t > exp) {
+ t = exp;
+ }
+ }
+
+ if (t > (15 * dns_zone_mkey_day)) {
+ t = (15 * dns_zone_mkey_day);
+ }
+
+ if (t < dns_zone_mkey_hour) {
+ t = dns_zone_mkey_hour;
+ }
+ } else {
+ t = sig.originalttl / 10;
+
+ if (isc_serial_gt(sig.timeexpire, now)) {
+ uint32_t exp = (sig.timeexpire - now) / 10;
+ if (t > exp) {
+ t = exp;
+ }
+ }
+
+ if (t > dns_zone_mkey_day) {
+ t = dns_zone_mkey_day;
+ }
+
+ if (t < dns_zone_mkey_hour) {
+ t = dns_zone_mkey_hour;
+ }
+ }
+
+ return (now + t);
+}
+
+/*
+ * This routine is called when no changes are needed in a KEYDATA
+ * record except to simply update the refresh timer. Caller should
+ * hold zone lock.
+ */
+static isc_result_t
+minimal_update(dns_keyfetch_t *kfetch, dns_dbversion_t *ver, dns_diff_t *diff) {
+ isc_result_t result;
+ isc_buffer_t keyb;
+ unsigned char key_buf[4096];
+ dns_rdata_t rdata = DNS_RDATA_INIT;
+ dns_rdata_keydata_t keydata;
+ dns_name_t *name;
+ dns_zone_t *zone = kfetch->zone;
+ isc_stdtime_t now;
+
+ name = dns_fixedname_name(&kfetch->name);
+ isc_stdtime_get(&now);
+
+ for (result = dns_rdataset_first(&kfetch->keydataset);
+ result == ISC_R_SUCCESS;
+ result = dns_rdataset_next(&kfetch->keydataset))
+ {
+ dns_rdata_reset(&rdata);
+ dns_rdataset_current(&kfetch->keydataset, &rdata);
+
+ /* Delete old version */
+ CHECK(update_one_rr(kfetch->db, ver, diff, DNS_DIFFOP_DEL, name,
+ 0, &rdata));
+
+ /* Update refresh timer */
+ result = dns_rdata_tostruct(&rdata, &keydata, NULL);
+ if (result == ISC_R_UNEXPECTEDEND) {
+ continue;
+ }
+ if (result != ISC_R_SUCCESS) {
+ goto failure;
+ }
+ keydata.refresh = refresh_time(kfetch, true);
+ set_refreshkeytimer(zone, &keydata, now, false);
+
+ dns_rdata_reset(&rdata);
+ isc_buffer_init(&keyb, key_buf, sizeof(key_buf));
+ CHECK(dns_rdata_fromstruct(&rdata, zone->rdclass,
+ dns_rdatatype_keydata, &keydata,
+ &keyb));
+
+ /* Insert updated version */
+ CHECK(update_one_rr(kfetch->db, ver, diff, DNS_DIFFOP_ADD, name,
+ 0, &rdata));
+ }
+ result = ISC_R_SUCCESS;
+failure:
+ return (result);
+}
+
+/*
+ * Verify that DNSKEY set is signed by the key specified in 'keydata'.
+ */
+static bool
+revocable(dns_keyfetch_t *kfetch, dns_rdata_keydata_t *keydata) {
+ isc_result_t result;
+ dns_name_t *keyname;
+ isc_mem_t *mctx;
+ dns_rdata_t sigrr = DNS_RDATA_INIT;
+ dns_rdata_t rr = DNS_RDATA_INIT;
+ dns_rdata_rrsig_t sig;
+ dns_rdata_dnskey_t dnskey;
+ dst_key_t *dstkey = NULL;
+ unsigned char key_buf[4096];
+ isc_buffer_t keyb;
+ bool answer = false;
+
+ REQUIRE(kfetch != NULL && keydata != NULL);
+ REQUIRE(dns_rdataset_isassociated(&kfetch->dnskeysigset));
+
+ keyname = dns_fixedname_name(&kfetch->name);
+ mctx = kfetch->zone->view->mctx;
+
+ /* Generate a key from keydata */
+ isc_buffer_init(&keyb, key_buf, sizeof(key_buf));
+ dns_keydata_todnskey(keydata, &dnskey, NULL);
+ dns_rdata_fromstruct(&rr, keydata->common.rdclass, dns_rdatatype_dnskey,
+ &dnskey, &keyb);
+ result = dns_dnssec_keyfromrdata(keyname, &rr, mctx, &dstkey);
+ if (result != ISC_R_SUCCESS) {
+ return (false);
+ }
+
+ /* See if that key generated any of the signatures */
+ for (result = dns_rdataset_first(&kfetch->dnskeysigset);
+ result == ISC_R_SUCCESS;
+ result = dns_rdataset_next(&kfetch->dnskeysigset))
+ {
+ dns_fixedname_t fixed;
+ dns_fixedname_init(&fixed);
+
+ dns_rdata_reset(&sigrr);
+ dns_rdataset_current(&kfetch->dnskeysigset, &sigrr);
+ result = dns_rdata_tostruct(&sigrr, &sig, NULL);
+ RUNTIME_CHECK(result == ISC_R_SUCCESS);
+
+ if (dst_key_alg(dstkey) == sig.algorithm &&
+ dst_key_rid(dstkey) == sig.keyid)
+ {
+ result = dns_dnssec_verify(
+ keyname, &kfetch->dnskeyset, dstkey, false, 0,
+ mctx, &sigrr, dns_fixedname_name(&fixed));
+
+ dnssec_log(kfetch->zone, ISC_LOG_DEBUG(3),
+ "Confirm revoked DNSKEY is self-signed: %s",
+ isc_result_totext(result));
+
+ if (result == ISC_R_SUCCESS) {
+ answer = true;
+ break;
+ }
+ }
+ }
+
+ dst_key_free(&dstkey);
+ return (answer);
+}
+
+/*
+ * A DNSKEY set has been fetched from the zone apex of a zone whose trust
+ * anchors are being managed; scan the keyset, and update the key zone and the
+ * local trust anchors according to RFC5011.
+ */
+static void
+keyfetch_done(isc_task_t *task, isc_event_t *event) {
+ isc_result_t result, eresult;
+ dns_fetchevent_t *devent;
+ dns_keyfetch_t *kfetch;
+ dns_zone_t *zone;
+ isc_mem_t *mctx = NULL;
+ dns_keytable_t *secroots = NULL;
+ dns_dbversion_t *ver = NULL;
+ dns_diff_t diff;
+ bool alldone = false;
+ bool commit = false;
+ dns_name_t *keyname = NULL;
+ dns_rdata_t sigrr = DNS_RDATA_INIT;
+ dns_rdata_t dnskeyrr = DNS_RDATA_INIT;
+ dns_rdata_t keydatarr = DNS_RDATA_INIT;
+ dns_rdata_rrsig_t sig;
+ dns_rdata_dnskey_t dnskey;
+ dns_rdata_keydata_t keydata;
+ bool initializing;
+ char namebuf[DNS_NAME_FORMATSIZE];
+ unsigned char key_buf[4096];
+ isc_buffer_t keyb;
+ dst_key_t *dstkey = NULL;
+ isc_stdtime_t now;
+ int pending = 0;
+ bool secure = false, initial = false;
+ bool free_needed;
+ dns_keynode_t *keynode = NULL;
+ dns_rdataset_t *dnskeys = NULL, *dnskeysigs = NULL;
+ dns_rdataset_t *keydataset = NULL, dsset;
+
+ UNUSED(task);
+ INSIST(event != NULL && event->ev_type == DNS_EVENT_FETCHDONE);
+ INSIST(event->ev_arg != NULL);
+
+ kfetch = event->ev_arg;
+ zone = kfetch->zone;
+ mctx = kfetch->mctx;
+ keyname = dns_fixedname_name(&kfetch->name);
+ dnskeys = &kfetch->dnskeyset;
+ dnskeysigs = &kfetch->dnskeysigset;
+ keydataset = &kfetch->keydataset;
+
+ devent = (dns_fetchevent_t *)event;
+ eresult = devent->result;
+
+ /* Free resources which are not of interest */
+ if (devent->node != NULL) {
+ dns_db_detachnode(devent->db, &devent->node);
+ }
+ if (devent->db != NULL) {
+ dns_db_detach(&devent->db);
+ }
+ isc_event_free(&event);
+ dns_resolver_destroyfetch(&kfetch->fetch);
+
+ LOCK_ZONE(zone);
+ if (DNS_ZONE_FLAG(zone, DNS_ZONEFLG_EXITING) || zone->view == NULL) {
+ goto cleanup;
+ }
+
+ isc_stdtime_get(&now);
+ dns_name_format(keyname, namebuf, sizeof(namebuf));
+
+ result = dns_view_getsecroots(zone->view, &secroots);
+ INSIST(result == ISC_R_SUCCESS);
+
+ dns_diff_init(mctx, &diff);
+
+ CHECK(dns_db_newversion(kfetch->db, &ver));
+
+ zone->refreshkeycount--;
+ alldone = (zone->refreshkeycount == 0);
+
+ if (alldone) {
+ DNS_ZONE_CLRFLAG(zone, DNS_ZONEFLG_REFRESHING);
+ }
+
+ dnssec_log(zone, ISC_LOG_DEBUG(3),
+ "Returned from key fetch in keyfetch_done() for '%s': %s",
+ namebuf, isc_result_totext(eresult));
+
+ /* Fetch failed */
+ if (eresult != ISC_R_SUCCESS || !dns_rdataset_isassociated(dnskeys)) {
+ dnssec_log(zone, ISC_LOG_WARNING,
+ "Unable to fetch DNSKEY set '%s': %s", namebuf,
+ isc_result_totext(eresult));
+ CHECK(minimal_update(kfetch, ver, &diff));
+ goto done;
+ }
+
+ /* No RRSIGs found */
+ if (!dns_rdataset_isassociated(dnskeysigs)) {
+ dnssec_log(zone, ISC_LOG_WARNING,
+ "No DNSKEY RRSIGs found for '%s': %s", namebuf,
+ isc_result_totext(eresult));
+ CHECK(minimal_update(kfetch, ver, &diff));
+ goto done;
+ }
+
+ /*
+ * Clear any cached trust level, as we need to run validation
+ * over again; trusted keys might have changed.
+ */
+ dnskeys->trust = dnskeysigs->trust = dns_trust_none;
+
+ /* Look up the trust anchor */
+ result = dns_keytable_find(secroots, keyname, &keynode);
+ if (result != ISC_R_SUCCESS) {
+ goto anchors_done;
+ }
+
+ /*
+ * If the keynode has a DS trust anchor, use it for verification.
+ */
+ dns_rdataset_init(&dsset);
+ if (dns_keynode_dsset(keynode, &dsset)) {
+ for (result = dns_rdataset_first(dnskeysigs);
+ result == ISC_R_SUCCESS;
+ result = dns_rdataset_next(dnskeysigs))
+ {
+ isc_result_t tresult;
+ dns_rdata_t keyrdata = DNS_RDATA_INIT;
+
+ dns_rdata_reset(&sigrr);
+ dns_rdataset_current(dnskeysigs, &sigrr);
+ result = dns_rdata_tostruct(&sigrr, &sig, NULL);
+ RUNTIME_CHECK(result == ISC_R_SUCCESS);
+
+ for (tresult = dns_rdataset_first(&dsset);
+ tresult == ISC_R_SUCCESS;
+ tresult = dns_rdataset_next(&dsset))
+ {
+ dns_rdata_t dsrdata = DNS_RDATA_INIT;
+ dns_rdata_ds_t ds;
+
+ dns_rdata_reset(&dsrdata);
+ dns_rdataset_current(&dsset, &dsrdata);
+ tresult = dns_rdata_tostruct(&dsrdata, &ds,
+ NULL);
+ RUNTIME_CHECK(tresult == ISC_R_SUCCESS);
+
+ if (ds.key_tag != sig.keyid ||
+ ds.algorithm != sig.algorithm)
+ {
+ continue;
+ }
+
+ result = dns_dnssec_matchdskey(
+ keyname, &dsrdata, dnskeys, &keyrdata);
+ if (result == ISC_R_SUCCESS) {
+ break;
+ }
+ }
+
+ if (tresult == ISC_R_NOMORE) {
+ continue;
+ }
+
+ result = dns_dnssec_keyfromrdata(keyname, &keyrdata,
+ mctx, &dstkey);
+ if (result != ISC_R_SUCCESS) {
+ continue;
+ }
+
+ result = dns_dnssec_verify(keyname, dnskeys, dstkey,
+ false, 0, mctx, &sigrr,
+ NULL);
+ dst_key_free(&dstkey);
+
+ dnssec_log(zone, ISC_LOG_DEBUG(3),
+ "Verifying DNSKEY set for zone "
+ "'%s' using DS %d/%d: %s",
+ namebuf, sig.keyid, sig.algorithm,
+ isc_result_totext(result));
+
+ if (result == ISC_R_SUCCESS) {
+ dnskeys->trust = dns_trust_secure;
+ dnskeysigs->trust = dns_trust_secure;
+ initial = dns_keynode_initial(keynode);
+ dns_keynode_trust(keynode);
+ secure = true;
+ break;
+ }
+ }
+ dns_rdataset_disassociate(&dsset);
+ }
+
+anchors_done:
+ if (keynode != NULL) {
+ dns_keytable_detachkeynode(secroots, &keynode);
+ }
+
+ /*
+ * If we were not able to verify the answer using the current
+ * trusted keys then all we can do is look at any revoked keys.
+ */
+ if (!secure) {
+ dnssec_log(zone, ISC_LOG_INFO,
+ "DNSKEY set for zone '%s' could not be verified "
+ "with current keys",
+ namebuf);
+ }
+
+ /*
+ * First scan keydataset to find keys that are not in dnskeyset
+ * - Missing keys which are not scheduled for removal,
+ * log a warning
+ * - Missing keys which are scheduled for removal and
+ * the remove hold-down timer has completed should
+ * be removed from the key zone
+ * - Missing keys whose acceptance timers have not yet
+ * completed, log a warning and reset the acceptance
+ * timer to 30 days in the future
+ * - All keys not being removed have their refresh timers
+ * updated
+ */
+ initializing = true;
+ for (result = dns_rdataset_first(keydataset); result == ISC_R_SUCCESS;
+ result = dns_rdataset_next(keydataset))
+ {
+ dns_keytag_t keytag;
+
+ dns_rdata_reset(&keydatarr);
+ dns_rdataset_current(keydataset, &keydatarr);
+ result = dns_rdata_tostruct(&keydatarr, &keydata, NULL);
+ RUNTIME_CHECK(result == ISC_R_SUCCESS);
+
+ dns_keydata_todnskey(&keydata, &dnskey, NULL);
+ result = compute_tag(keyname, &dnskey, mctx, &keytag);
+ if (result != ISC_R_SUCCESS) {
+ /*
+ * Skip if we cannot compute the key tag.
+ * This may happen if the algorithm is unsupported
+ */
+ dns_zone_log(zone, ISC_LOG_ERROR,
+ "Cannot compute tag for key in zone %s: "
+ "%s "
+ "(skipping)",
+ namebuf, isc_result_totext(result));
+ continue;
+ }
+ RUNTIME_CHECK(result == ISC_R_SUCCESS);
+
+ /*
+ * If any keydata record has a nonzero add holddown, then
+ * there was a pre-existing trust anchor for this domain;
+ * that means we are *not* initializing it and shouldn't
+ * automatically trust all the keys we find at the zone apex.
+ */
+ initializing = initializing && (keydata.addhd == 0);
+
+ if (!matchkey(dnskeys, &keydatarr)) {
+ bool deletekey = false;
+
+ if (!secure) {
+ if (keydata.removehd != 0 &&
+ keydata.removehd <= now)
+ {
+ deletekey = true;
+ }
+ } else if (keydata.addhd == 0) {
+ deletekey = true;
+ } else if (keydata.addhd > now) {
+ dnssec_log(zone, ISC_LOG_INFO,
+ "Pending key %d for zone %s "
+ "unexpectedly missing from DNSKEY "
+ "RRset: restarting 30-day "
+ "acceptance timer",
+ keytag, namebuf);
+ if (keydata.addhd < now + dns_zone_mkey_month) {
+ keydata.addhd = now +
+ dns_zone_mkey_month;
+ }
+ keydata.refresh = refresh_time(kfetch, false);
+ } else if (keydata.removehd == 0) {
+ dnssec_log(zone, ISC_LOG_INFO,
+ "Active key %d for zone %s "
+ "unexpectedly missing from DNSKEY "
+ "RRset",
+ keytag, namebuf);
+ keydata.refresh = now + dns_zone_mkey_hour;
+ } else if (keydata.removehd <= now) {
+ deletekey = true;
+ dnssec_log(
+ zone, ISC_LOG_INFO,
+ "Revoked key %d for zone %s no longer "
+ "present in DNSKEY RRset: deleting "
+ "from managed keys database",
+ keytag, namebuf);
+ } else {
+ keydata.refresh = refresh_time(kfetch, false);
+ }
+
+ if (secure || deletekey) {
+ /* Delete old version */
+ CHECK(update_one_rr(kfetch->db, ver, &diff,
+ DNS_DIFFOP_DEL, keyname, 0,
+ &keydatarr));
+ }
+
+ if (!secure || deletekey) {
+ continue;
+ }
+
+ dns_rdata_reset(&keydatarr);
+ isc_buffer_init(&keyb, key_buf, sizeof(key_buf));
+ dns_rdata_fromstruct(&keydatarr, zone->rdclass,
+ dns_rdatatype_keydata, &keydata,
+ &keyb);
+
+ /* Insert updated version */
+ CHECK(update_one_rr(kfetch->db, ver, &diff,
+ DNS_DIFFOP_ADD, keyname, 0,
+ &keydatarr));
+
+ set_refreshkeytimer(zone, &keydata, now, false);
+ }
+ }
+
+ /*
+ * Next scan dnskeyset:
+ * - If new keys are found (i.e., lacking a match in keydataset)
+ * add them to the key zone and set the acceptance timer
+ * to 30 days in the future (or to immediately if we've
+ * determined that we're initializing the zone for the
+ * first time)
+ * - Previously-known keys that have been revoked
+ * must be scheduled for removal from the key zone (or,
+ * if they hadn't been accepted as trust anchors yet
+ * anyway, removed at once)
+ * - Previously-known unrevoked keys whose acceptance timers
+ * have completed are promoted to trust anchors
+ * - All keys not being removed have their refresh
+ * timers updated
+ */
+ for (result = dns_rdataset_first(dnskeys); result == ISC_R_SUCCESS;
+ result = dns_rdataset_next(dnskeys))
+ {
+ bool revoked = false;
+ bool newkey = false;
+ bool updatekey = false;
+ bool deletekey = false;
+ bool trustkey = false;
+ dns_keytag_t keytag;
+
+ dns_rdata_reset(&dnskeyrr);
+ dns_rdataset_current(dnskeys, &dnskeyrr);
+ result = dns_rdata_tostruct(&dnskeyrr, &dnskey, NULL);
+ RUNTIME_CHECK(result == ISC_R_SUCCESS);
+
+ /* Skip ZSK's */
+ if ((dnskey.flags & DNS_KEYFLAG_KSK) == 0) {
+ continue;
+ }
+
+ result = compute_tag(keyname, &dnskey, mctx, &keytag);
+ if (result != ISC_R_SUCCESS) {
+ /*
+ * Skip if we cannot compute the key tag.
+ * This may happen if the algorithm is unsupported
+ */
+ dns_zone_log(zone, ISC_LOG_ERROR,
+ "Cannot compute tag for key in zone %s: "
+ "%s "
+ "(skipping)",
+ namebuf, isc_result_totext(result));
+ continue;
+ }
+ RUNTIME_CHECK(result == ISC_R_SUCCESS);
+
+ revoked = ((dnskey.flags & DNS_KEYFLAG_REVOKE) != 0);
+
+ if (matchkey(keydataset, &dnskeyrr)) {
+ dns_rdata_reset(&keydatarr);
+ dns_rdataset_current(keydataset, &keydatarr);
+ result = dns_rdata_tostruct(&keydatarr, &keydata, NULL);
+ RUNTIME_CHECK(result == ISC_R_SUCCESS);
+
+ if (revoked && revocable(kfetch, &keydata)) {
+ if (keydata.addhd > now) {
+ /*
+ * Key wasn't trusted yet, and now
+ * it's been revoked? Just remove it
+ */
+ deletekey = true;
+ dnssec_log(zone, ISC_LOG_INFO,
+ "Pending key %d for "
+ "zone %s is now revoked: "
+ "deleting from the "
+ "managed keys database",
+ keytag, namebuf);
+ } else if (keydata.removehd == 0) {
+ /*
+ * Remove key from secroots.
+ */
+ dns_view_untrust(zone->view, keyname,
+ &dnskey);
+
+ /* If initializing, delete now */
+ if (keydata.addhd == 0) {
+ deletekey = true;
+ } else {
+ keydata.removehd =
+ now +
+ dns_zone_mkey_month;
+ keydata.flags |=
+ DNS_KEYFLAG_REVOKE;
+ }
+
+ dnssec_log(zone, ISC_LOG_INFO,
+ "Trusted key %d for "
+ "zone %s is now revoked",
+ keytag, namebuf);
+ } else if (keydata.removehd < now) {
+ /* Scheduled for removal */
+ deletekey = true;
+
+ dnssec_log(zone, ISC_LOG_INFO,
+ "Revoked key %d for "
+ "zone %s removal timer "
+ "complete: deleting from "
+ "the managed keys database",
+ keytag, namebuf);
+ }
+ } else if (revoked && keydata.removehd == 0) {
+ dnssec_log(zone, ISC_LOG_WARNING,
+ "Active key %d for zone "
+ "%s is revoked but "
+ "did not self-sign; "
+ "ignoring",
+ keytag, namebuf);
+ continue;
+ } else if (secure) {
+ if (keydata.removehd != 0) {
+ /*
+ * Key isn't revoked--but it
+ * seems it used to be.
+ * Remove it now and add it
+ * back as if it were a fresh key,
+ * with a 30-day acceptance timer.
+ */
+ deletekey = true;
+ newkey = true;
+ keydata.removehd = 0;
+ keydata.addhd = now +
+ dns_zone_mkey_month;
+
+ dnssec_log(zone, ISC_LOG_INFO,
+ "Revoked key %d for "
+ "zone %s has returned: "
+ "starting 30-day "
+ "acceptance timer",
+ keytag, namebuf);
+ } else if (keydata.addhd > now) {
+ pending++;
+ } else if (keydata.addhd == 0) {
+ keydata.addhd = now;
+ }
+
+ if (keydata.addhd <= now) {
+ trustkey = true;
+ dnssec_log(zone, ISC_LOG_INFO,
+ "Key %d for zone %s "
+ "is now trusted (%s)",
+ keytag, namebuf,
+ initial ? "initializing key "
+ "verified"
+ : "acceptance timer "
+ "complete");
+ }
+ } else if (keydata.addhd > now) {
+ /*
+ * Not secure, and key is pending:
+ * reset the acceptance timer
+ */
+ pending++;
+ keydata.addhd = now + dns_zone_mkey_month;
+ dnssec_log(zone, ISC_LOG_INFO,
+ "Pending key %d "
+ "for zone %s was "
+ "not validated: restarting "
+ "30-day acceptance timer",
+ keytag, namebuf);
+ }
+
+ if (!deletekey && !newkey) {
+ updatekey = true;
+ }
+ } else if (secure) {
+ /*
+ * Key wasn't in the key zone but it's
+ * revoked now anyway, so just skip it
+ */
+ if (revoked) {
+ continue;
+ }
+
+ /* Key wasn't in the key zone: add it */
+ newkey = true;
+
+ if (initializing) {
+ dnssec_log(zone, ISC_LOG_WARNING,
+ "Initializing automatic trust "
+ "anchor management for zone '%s'; "
+ "DNSKEY ID %d is now trusted, "
+ "waiving the normal 30-day "
+ "waiting period.",
+ namebuf, keytag);
+ trustkey = true;
+ } else {
+ dnssec_log(zone, ISC_LOG_INFO,
+ "New key %d observed "
+ "for zone '%s': "
+ "starting 30-day "
+ "acceptance timer",
+ keytag, namebuf);
+ }
+ } else {
+ /*
+ * No previously known key, and the key is not
+ * secure, so skip it.
+ */
+ continue;
+ }
+
+ /* Delete old version */
+ if (deletekey || !newkey) {
+ CHECK(update_one_rr(kfetch->db, ver, &diff,
+ DNS_DIFFOP_DEL, keyname, 0,
+ &keydatarr));
+ }
+
+ if (updatekey) {
+ /* Set refresh timer */
+ keydata.refresh = refresh_time(kfetch, false);
+ dns_rdata_reset(&keydatarr);
+ isc_buffer_init(&keyb, key_buf, sizeof(key_buf));
+ dns_rdata_fromstruct(&keydatarr, zone->rdclass,
+ dns_rdatatype_keydata, &keydata,
+ &keyb);
+
+ /* Insert updated version */
+ CHECK(update_one_rr(kfetch->db, ver, &diff,
+ DNS_DIFFOP_ADD, keyname, 0,
+ &keydatarr));
+ } else if (newkey) {
+ /* Convert DNSKEY to KEYDATA */
+ result = dns_rdata_tostruct(&dnskeyrr, &dnskey, NULL);
+ RUNTIME_CHECK(result == ISC_R_SUCCESS);
+ dns_keydata_fromdnskey(&keydata, &dnskey, 0, 0, 0,
+ NULL);
+ keydata.addhd = initializing
+ ? now
+ : now + dns_zone_mkey_month;
+ keydata.refresh = refresh_time(kfetch, false);
+ dns_rdata_reset(&keydatarr);
+ isc_buffer_init(&keyb, key_buf, sizeof(key_buf));
+ dns_rdata_fromstruct(&keydatarr, zone->rdclass,
+ dns_rdatatype_keydata, &keydata,
+ &keyb);
+
+ /* Insert into key zone */
+ CHECK(update_one_rr(kfetch->db, ver, &diff,
+ DNS_DIFFOP_ADD, keyname, 0,
+ &keydatarr));
+ }
+
+ if (trustkey) {
+ /* Trust this key. */
+ result = dns_rdata_tostruct(&dnskeyrr, &dnskey, NULL);
+ RUNTIME_CHECK(result == ISC_R_SUCCESS);
+ trust_key(zone, keyname, &dnskey, false);
+ }
+
+ if (secure && !deletekey) {
+ INSIST(newkey || updatekey);
+ set_refreshkeytimer(zone, &keydata, now, false);
+ }
+ }
+
+ /*
+ * RFC5011 says, "A trust point that has all of its trust anchors
+ * revoked is considered deleted and is treated as if the trust
+ * point was never configured." But if someone revoked their
+ * active key before the standby was trusted, that would mean the
+ * zone would suddenly be nonsecured. We avoid this by checking to
+ * see if there's pending keydata. If so, we put a null key in
+ * the security roots; then all queries to the zone will fail.
+ */
+ if (pending != 0) {
+ fail_secure(zone, keyname);
+ }
+
+done:
+ if (!ISC_LIST_EMPTY(diff.tuples)) {
+ /* Write changes to journal file. */
+ CHECK(update_soa_serial(zone, kfetch->db, ver, &diff, mctx,
+ zone->updatemethod));
+ CHECK(zone_journal(zone, &diff, NULL, "keyfetch_done"));
+ commit = true;
+
+ DNS_ZONE_SETFLAG(zone, DNS_ZONEFLG_LOADED);
+ zone_needdump(zone, 30);
+ } else if (result == ISC_R_NOMORE) {
+ /*
+ * If "updatekey" was true for all keys found in the DNSKEY
+ * response and the previous update of those keys happened
+ * during the same second (only possible if a key refresh was
+ * externally triggered), it may happen that all relevant
+ * update_one_rr() calls will return ISC_R_SUCCESS, but
+ * diff.tuples will remain empty. Reset result to
+ * ISC_R_SUCCESS to prevent a bogus warning from being logged.
+ */
+ result = ISC_R_SUCCESS;
+ }
+
+failure:
+ if (result != ISC_R_SUCCESS) {
+ dnssec_log(zone, ISC_LOG_ERROR,
+ "error during managed-keys processing (%s): "
+ "DNSSEC validation may be at risk",
+ isc_result_totext(result));
+ }
+ dns_diff_clear(&diff);
+ if (ver != NULL) {
+ dns_db_closeversion(kfetch->db, &ver, commit);
+ }
+
+cleanup:
+ dns_db_detach(&kfetch->db);
+
+ /* The zone must be managed */
+ INSIST(kfetch->zone->task != NULL);
+ isc_refcount_decrement(&zone->irefs);
+
+ if (dns_rdataset_isassociated(keydataset)) {
+ dns_rdataset_disassociate(keydataset);
+ }
+ if (dns_rdataset_isassociated(dnskeys)) {
+ dns_rdataset_disassociate(dnskeys);
+ }
+ if (dns_rdataset_isassociated(dnskeysigs)) {
+ dns_rdataset_disassociate(dnskeysigs);
+ }
+
+ dns_name_free(keyname, mctx);
+ isc_mem_putanddetach(&kfetch->mctx, kfetch, sizeof(dns_keyfetch_t));
+
+ if (secroots != NULL) {
+ dns_keytable_detach(&secroots);
+ }
+
+ free_needed = exit_check(zone);
+ UNLOCK_ZONE(zone);
+
+ if (free_needed) {
+ zone_free(zone);
+ }
+
+ INSIST(ver == NULL);
+}
+
+static void
+retry_keyfetch(dns_keyfetch_t *kfetch, dns_name_t *kname) {
+ isc_time_t timenow, timethen;
+ dns_zone_t *zone = kfetch->zone;
+ bool free_needed;
+ char namebuf[DNS_NAME_FORMATSIZE];
+
+ dns_name_format(kname, namebuf, sizeof(namebuf));
+ dnssec_log(zone, ISC_LOG_WARNING,
+ "Failed to create fetch for %s DNSKEY update", namebuf);
+
+ /*
+ * Error during a key fetch; cancel and retry in an hour.
+ */
+ LOCK_ZONE(zone);
+ zone->refreshkeycount--;
+ isc_refcount_decrement(&zone->irefs);
+ dns_db_detach(&kfetch->db);
+ dns_rdataset_disassociate(&kfetch->keydataset);
+ dns_name_free(kname, zone->mctx);
+ isc_mem_putanddetach(&kfetch->mctx, kfetch, sizeof(*kfetch));
+
+ if (!DNS_ZONE_FLAG(zone, DNS_ZONEFLG_EXITING)) {
+ /* Don't really retry if we are exiting */
+ char timebuf[80];
+
+ TIME_NOW(&timenow);
+ DNS_ZONE_TIME_ADD(&timenow, dns_zone_mkey_hour, &timethen);
+ zone->refreshkeytime = timethen;
+ zone_settimer(zone, &timenow);
+
+ isc_time_formattimestamp(&zone->refreshkeytime, timebuf, 80);
+ dnssec_log(zone, ISC_LOG_DEBUG(1), "retry key refresh: %s",
+ timebuf);
+ }
+
+ free_needed = exit_check(zone);
+ UNLOCK_ZONE(zone);
+
+ if (free_needed) {
+ zone_free(zone);
+ }
+}
+
+static void
+do_keyfetch(isc_task_t *task, isc_event_t *event) {
+ isc_result_t result;
+ dns_keyfetch_t *kfetch = (dns_keyfetch_t *)event->ev_arg;
+ dns_name_t *kname = dns_fixedname_name(&kfetch->name);
+ dns_zone_t *zone = kfetch->zone;
+
+ UNUSED(task);
+
+ isc_event_free(&event);
+
+ if (DNS_ZONE_FLAG(zone, DNS_ZONEFLG_EXITING)) {
+ retry_keyfetch(kfetch, kname);
+ return;
+ }
+
+ /*
+ * Use of DNS_FETCHOPT_NOCACHED is essential here. If it is not
+ * set and the cache still holds a non-expired, validated version
+ * of the RRset being queried for by the time the response is
+ * received, the cached RRset will be passed to keyfetch_done()
+ * instead of the one received in the response as the latter will
+ * have a lower trust level due to not being validated until
+ * keyfetch_done() is called.
+ */
+ result = dns_resolver_createfetch(
+ zone->view->resolver, kname, dns_rdatatype_dnskey, NULL, NULL,
+ NULL, NULL, 0,
+ DNS_FETCHOPT_NOVALIDATE | DNS_FETCHOPT_UNSHARED |
+ DNS_FETCHOPT_NOCACHED,
+ 0, NULL, zone->task, keyfetch_done, kfetch, &kfetch->dnskeyset,
+ &kfetch->dnskeysigset, &kfetch->fetch);
+
+ if (result != ISC_R_SUCCESS) {
+ retry_keyfetch(kfetch, kname);
+ }
+}
+
+/*
+ * Refresh the data in the key zone. Initiate a fetch to look up
+ * DNSKEY records at the trust anchor name.
+ */
+static void
+zone_refreshkeys(dns_zone_t *zone) {
+ const char me[] = "zone_refreshkeys";
+ isc_result_t result;
+ dns_rriterator_t rrit;
+ dns_db_t *db = NULL;
+ dns_dbversion_t *ver = NULL;
+ dns_diff_t diff;
+ dns_rdata_t rdata = DNS_RDATA_INIT;
+ dns_rdata_keydata_t kd;
+ isc_stdtime_t now;
+ bool commit = false;
+ bool fetching = false;
+ bool timerset = false;
+
+ ENTER;
+ REQUIRE(zone->db != NULL);
+
+ isc_stdtime_get(&now);
+
+ LOCK_ZONE(zone);
+ if (DNS_ZONE_FLAG(zone, DNS_ZONEFLG_EXITING)) {
+ isc_time_settoepoch(&zone->refreshkeytime);
+ UNLOCK_ZONE(zone);
+ return;
+ }
+
+ ZONEDB_LOCK(&zone->dblock, isc_rwlocktype_read);
+ dns_db_attach(zone->db, &db);
+ ZONEDB_UNLOCK(&zone->dblock, isc_rwlocktype_read);
+
+ dns_diff_init(zone->mctx, &diff);
+
+ CHECK(dns_db_newversion(db, &ver));
+
+ DNS_ZONE_SETFLAG(zone, DNS_ZONEFLG_REFRESHING);
+
+ dns_rriterator_init(&rrit, db, ver, 0);
+ for (result = dns_rriterator_first(&rrit); result == ISC_R_SUCCESS;
+ result = dns_rriterator_nextrrset(&rrit))
+ {
+ isc_stdtime_t timer = 0xffffffff;
+ dns_name_t *name = NULL, *kname = NULL;
+ dns_rdataset_t *kdset = NULL;
+ uint32_t ttl;
+
+ dns_rriterator_current(&rrit, &name, &ttl, &kdset, NULL);
+ if (kdset == NULL || kdset->type != dns_rdatatype_keydata ||
+ !dns_rdataset_isassociated(kdset))
+ {
+ continue;
+ }
+
+ /*
+ * Scan the stored keys looking for ones that need
+ * removal or refreshing
+ */
+ for (result = dns_rdataset_first(kdset);
+ result == ISC_R_SUCCESS; result = dns_rdataset_next(kdset))
+ {
+ dns_rdata_reset(&rdata);
+ dns_rdataset_current(kdset, &rdata);
+ result = dns_rdata_tostruct(&rdata, &kd, NULL);
+ RUNTIME_CHECK(result == ISC_R_SUCCESS);
+
+ /* Removal timer expired? */
+ if (kd.removehd != 0 && kd.removehd < now) {
+ dns_rriterator_pause(&rrit);
+ CHECK(update_one_rr(db, ver, &diff,
+ DNS_DIFFOP_DEL, name, ttl,
+ &rdata));
+ continue;
+ }
+
+ /* Acceptance timer expired? */
+ if (kd.addhd <= now) {
+ timer = kd.addhd;
+ }
+
+ /* Or do we just need to refresh the keyset? */
+ if (timer > kd.refresh) {
+ timer = kd.refresh;
+ }
+
+ dns_rriterator_pause(&rrit);
+ set_refreshkeytimer(zone, &kd, now, false);
+ timerset = true;
+ }
+
+ if (timer > now) {
+ continue;
+ }
+
+ dns_rriterator_pause(&rrit);
+
+#ifdef ENABLE_AFL
+ if (!dns_fuzzing_resolver) {
+#endif /* ifdef ENABLE_AFL */
+ dns_keyfetch_t *kfetch = NULL;
+ isc_event_t *e;
+
+ kfetch = isc_mem_get(zone->mctx,
+ sizeof(dns_keyfetch_t));
+ *kfetch = (dns_keyfetch_t){ .zone = zone };
+ isc_mem_attach(zone->mctx, &kfetch->mctx);
+
+ zone->refreshkeycount++;
+ isc_refcount_increment0(&zone->irefs);
+ kname = dns_fixedname_initname(&kfetch->name);
+ dns_name_dup(name, zone->mctx, kname);
+ dns_rdataset_init(&kfetch->dnskeyset);
+ dns_rdataset_init(&kfetch->dnskeysigset);
+ dns_rdataset_init(&kfetch->keydataset);
+ dns_rdataset_clone(kdset, &kfetch->keydataset);
+ dns_db_attach(db, &kfetch->db);
+
+ if (isc_log_wouldlog(dns_lctx, ISC_LOG_DEBUG(3))) {
+ char namebuf[DNS_NAME_FORMATSIZE];
+ dns_name_format(kname, namebuf,
+ sizeof(namebuf));
+ dnssec_log(zone, ISC_LOG_DEBUG(3),
+ "Creating key fetch in "
+ "zone_refreshkeys() for '%s'",
+ namebuf);
+ }
+
+ e = isc_event_allocate(zone->mctx, NULL, DNS_EVENT_ZONE,
+ do_keyfetch, kfetch,
+ sizeof(isc_event_t));
+ isc_task_send(zone->task, &e);
+ fetching = true;
+#ifdef ENABLE_AFL
+ }
+#endif /* ifdef ENABLE_AFL */
+ }
+ if (!ISC_LIST_EMPTY(diff.tuples)) {
+ CHECK(update_soa_serial(zone, db, ver, &diff, zone->mctx,
+ zone->updatemethod));
+ CHECK(zone_journal(zone, &diff, NULL, "zone_refreshkeys"));
+ commit = true;
+ DNS_ZONE_SETFLAG(zone, DNS_ZONEFLG_LOADED);
+ zone_needdump(zone, 30);
+ }
+
+failure:
+ if (!timerset) {
+ isc_time_settoepoch(&zone->refreshkeytime);
+ }
+
+ if (!fetching) {
+ DNS_ZONE_CLRFLAG(zone, DNS_ZONEFLG_REFRESHING);
+ }
+
+ dns_diff_clear(&diff);
+ if (ver != NULL) {
+ dns_rriterator_destroy(&rrit);
+ dns_db_closeversion(db, &ver, commit);
+ }
+ dns_db_detach(&db);
+
+ UNLOCK_ZONE(zone);
+
+ INSIST(ver == NULL);
+}
+
+static void
+zone_maintenance(dns_zone_t *zone) {
+ const char me[] = "zone_maintenance";
+ isc_time_t now;
+ isc_result_t result;
+ bool load_pending, exiting, dumping, viewok, notify;
+ bool refreshkeys, sign, resign, rekey, chain, warn_expire;
+
+ REQUIRE(DNS_ZONE_VALID(zone));
+ ENTER;
+
+ /*
+ * Are we pending load/reload, exiting, or unconfigured
+ * (e.g. because of a syntax failure in the config file)?
+ * If so, don't attempt maintenance.
+ */
+ LOCK_ZONE(zone);
+ load_pending = DNS_ZONE_FLAG(zone, DNS_ZONEFLG_LOADPENDING);
+ exiting = DNS_ZONE_FLAG(zone, DNS_ZONEFLG_EXITING);
+ viewok = (zone->view != NULL && zone->view->adb != NULL);
+ UNLOCK_ZONE(zone);
+
+ if (load_pending || exiting || !viewok) {
+ return;
+ }
+
+ TIME_NOW(&now);
+
+ /*
+ * Expire check.
+ */
+ switch (zone->type) {
+ case dns_zone_redirect:
+ if (zone->primaries == NULL) {
+ break;
+ }
+ FALLTHROUGH;
+ case dns_zone_secondary:
+ case dns_zone_mirror:
+ case dns_zone_stub:
+ LOCK_ZONE(zone);
+ if (isc_time_compare(&now, &zone->expiretime) >= 0 &&
+ DNS_ZONE_FLAG(zone, DNS_ZONEFLG_LOADED))
+ {
+ zone_expire(zone);
+ zone->refreshtime = now;
+ }
+ UNLOCK_ZONE(zone);
+ break;
+ default:
+ break;
+ }
+
+ /*
+ * Up to date check.
+ */
+ switch (zone->type) {
+ case dns_zone_redirect:
+ if (zone->primaries == NULL) {
+ break;
+ }
+ FALLTHROUGH;
+ case dns_zone_secondary:
+ case dns_zone_mirror:
+ case dns_zone_stub:
+ LOCK_ZONE(zone);
+ if (!DNS_ZONE_FLAG(zone, DNS_ZONEFLG_DIALREFRESH) &&
+ isc_time_compare(&now, &zone->refreshtime) >= 0)
+ {
+ zone_refresh(zone);
+ }
+ UNLOCK_ZONE(zone);
+ break;
+ default:
+ break;
+ }
+
+ /*
+ * Secondaries send notifies before backing up to disk,
+ * primaries after.
+ */
+ LOCK_ZONE(zone);
+ notify = (zone->type == dns_zone_secondary ||
+ zone->type == dns_zone_mirror) &&
+ (DNS_ZONE_FLAG(zone, DNS_ZONEFLG_NEEDNOTIFY) ||
+ DNS_ZONE_FLAG(zone, DNS_ZONEFLG_NEEDSTARTUPNOTIFY)) &&
+ isc_time_compare(&now, &zone->notifytime) >= 0;
+ UNLOCK_ZONE(zone);
+
+ if (notify) {
+ zone_notify(zone, &now);
+ }
+
+ /*
+ * Do we need to consolidate the backing store?
+ */
+ switch (zone->type) {
+ case dns_zone_primary:
+ case dns_zone_secondary:
+ case dns_zone_mirror:
+ case dns_zone_key:
+ case dns_zone_redirect:
+ case dns_zone_stub:
+ LOCK_ZONE(zone);
+ if (zone->masterfile != NULL &&
+ isc_time_compare(&now, &zone->dumptime) >= 0 &&
+ DNS_ZONE_FLAG(zone, DNS_ZONEFLG_LOADED) &&
+ DNS_ZONE_FLAG(zone, DNS_ZONEFLG_NEEDDUMP))
+ {
+ dumping = was_dumping(zone);
+ } else {
+ dumping = true;
+ }
+ UNLOCK_ZONE(zone);
+ if (!dumping) {
+ result = zone_dump(zone, true); /* task locked */
+ if (result != ISC_R_SUCCESS) {
+ dns_zone_log(zone, ISC_LOG_WARNING,
+ "dump failed: %s",
+ isc_result_totext(result));
+ }
+ }
+ break;
+ default:
+ break;
+ }
+
+ /*
+ * Primary/redirect zones send notifies now, if needed
+ */
+ switch (zone->type) {
+ case dns_zone_primary:
+ case dns_zone_redirect:
+ LOCK_ZONE(zone);
+ notify = (DNS_ZONE_FLAG(zone, DNS_ZONEFLG_NEEDNOTIFY) ||
+ DNS_ZONE_FLAG(zone, DNS_ZONEFLG_NEEDSTARTUPNOTIFY)) &&
+ isc_time_compare(&now, &zone->notifytime) >= 0;
+ UNLOCK_ZONE(zone);
+ if (notify) {
+ zone_notify(zone, &now);
+ }
+ default:
+ break;
+ }
+
+ /*
+ * Do we need to refresh keys?
+ */
+ switch (zone->type) {
+ case dns_zone_key:
+ LOCK_ZONE(zone);
+ refreshkeys = isc_time_compare(&now, &zone->refreshkeytime) >=
+ 0 &&
+ DNS_ZONE_FLAG(zone, DNS_ZONEFLG_LOADED) &&
+ !DNS_ZONE_FLAG(zone, DNS_ZONEFLG_REFRESHING);
+ UNLOCK_ZONE(zone);
+ if (refreshkeys) {
+ zone_refreshkeys(zone);
+ }
+ break;
+ case dns_zone_primary:
+ LOCK_ZONE(zone);
+ rekey = (!isc_time_isepoch(&zone->refreshkeytime) &&
+ isc_time_compare(&now, &zone->refreshkeytime) >= 0 &&
+ zone->rss_event == NULL);
+ UNLOCK_ZONE(zone);
+ if (rekey) {
+ zone_rekey(zone);
+ }
+ default:
+ break;
+ }
+
+ switch (zone->type) {
+ case dns_zone_primary:
+ case dns_zone_redirect:
+ case dns_zone_secondary:
+ /*
+ * Do we need to sign/resign some RRsets?
+ */
+ LOCK_ZONE(zone);
+ if (zone->rss_event != NULL) {
+ UNLOCK_ZONE(zone);
+ break;
+ }
+ sign = !isc_time_isepoch(&zone->signingtime) &&
+ isc_time_compare(&now, &zone->signingtime) >= 0;
+ resign = !isc_time_isepoch(&zone->resigntime) &&
+ isc_time_compare(&now, &zone->resigntime) >= 0;
+ chain = !isc_time_isepoch(&zone->nsec3chaintime) &&
+ isc_time_compare(&now, &zone->nsec3chaintime) >= 0;
+ warn_expire = !isc_time_isepoch(&zone->keywarntime) &&
+ isc_time_compare(&now, &zone->keywarntime) >= 0;
+ UNLOCK_ZONE(zone);
+
+ if (sign) {
+ zone_sign(zone);
+ } else if (resign) {
+ zone_resigninc(zone);
+ } else if (chain) {
+ zone_nsec3chain(zone);
+ }
+
+ /*
+ * Do we need to issue a key expiry warning?
+ */
+ if (warn_expire) {
+ set_key_expiry_warning(zone, zone->key_expiry,
+ isc_time_seconds(&now));
+ }
+ break;
+
+ default:
+ break;
+ }
+ LOCK_ZONE(zone);
+ zone_settimer(zone, &now);
+ UNLOCK_ZONE(zone);
+}
+
+void
+dns_zone_markdirty(dns_zone_t *zone) {
+ uint32_t serial;
+ isc_result_t result = ISC_R_SUCCESS;
+ dns_zone_t *secure = NULL;
+
+ /*
+ * Obtaining a lock on the zone->secure (see zone_send_secureserial)
+ * could result in a deadlock due to a LOR so we will spin if we
+ * can't obtain the both locks.
+ */
+again:
+ LOCK_ZONE(zone);
+ if (zone->type == dns_zone_primary) {
+ if (inline_raw(zone)) {
+ unsigned int soacount;
+ secure = zone->secure;
+ INSIST(secure != zone);
+ TRYLOCK_ZONE(result, secure);
+ if (result != ISC_R_SUCCESS) {
+ UNLOCK_ZONE(zone);
+ secure = NULL;
+ isc_thread_yield();
+ goto again;
+ }
+
+ ZONEDB_LOCK(&zone->dblock, isc_rwlocktype_read);
+ if (zone->db != NULL) {
+ result = zone_get_from_db(
+ zone, zone->db, NULL, &soacount, NULL,
+ &serial, NULL, NULL, NULL, NULL, NULL);
+ } else {
+ result = DNS_R_NOTLOADED;
+ }
+ ZONEDB_UNLOCK(&zone->dblock, isc_rwlocktype_read);
+ if (result == ISC_R_SUCCESS && soacount > 0U) {
+ zone_send_secureserial(zone, serial);
+ }
+ }
+
+ /* XXXMPA make separate call back */
+ if (result == ISC_R_SUCCESS) {
+ set_resigntime(zone);
+ if (zone->task != NULL) {
+ isc_time_t now;
+ TIME_NOW(&now);
+ zone_settimer(zone, &now);
+ }
+ }
+ }
+ if (secure != NULL) {
+ UNLOCK_ZONE(secure);
+ }
+ zone_needdump(zone, DNS_DUMP_DELAY);
+ UNLOCK_ZONE(zone);
+}
+
+void
+dns_zone_expire(dns_zone_t *zone) {
+ REQUIRE(DNS_ZONE_VALID(zone));
+
+ LOCK_ZONE(zone);
+ zone_expire(zone);
+ UNLOCK_ZONE(zone);
+}
+
+static void
+zone_expire(dns_zone_t *zone) {
+ dns_db_t *db = NULL;
+
+ /*
+ * 'zone' locked by caller.
+ */
+
+ REQUIRE(LOCKED_ZONE(zone));
+
+ dns_zone_log(zone, ISC_LOG_WARNING, "expired");
+
+ DNS_ZONE_SETFLAG(zone, DNS_ZONEFLG_EXPIRED);
+ zone->refresh = DNS_ZONE_DEFAULTREFRESH;
+ zone->retry = DNS_ZONE_DEFAULTRETRY;
+ DNS_ZONE_CLRFLAG(zone, DNS_ZONEFLG_HAVETIMERS);
+
+ /*
+ * An RPZ zone has expired; before unloading it, we must
+ * first remove it from the RPZ summary database. The
+ * easiest way to do this is "update" it with an empty
+ * database so that the update callback synchronizes
+ * the diff automatically.
+ */
+ if (zone->rpzs != NULL && zone->rpz_num != DNS_RPZ_INVALID_NUM) {
+ isc_result_t result;
+ dns_rpz_zone_t *rpz = zone->rpzs->zones[zone->rpz_num];
+
+ CHECK(dns_db_create(zone->mctx, "rbt", &zone->origin,
+ dns_dbtype_zone, zone->rdclass, 0, NULL,
+ &db));
+ CHECK(dns_rpz_dbupdate_callback(db, rpz));
+ dns_zone_log(zone, ISC_LOG_WARNING,
+ "response-policy zone expired; "
+ "policies unloaded");
+ }
+
+failure:
+ if (db != NULL) {
+ dns_db_detach(&db);
+ }
+
+ zone_unload(zone);
+}
+
+static void
+zone_refresh(dns_zone_t *zone) {
+ isc_interval_t i;
+ uint32_t oldflags;
+ unsigned int j;
+ isc_result_t result;
+
+ REQUIRE(DNS_ZONE_VALID(zone));
+ REQUIRE(LOCKED_ZONE(zone));
+
+ if (DNS_ZONE_FLAG(zone, DNS_ZONEFLG_EXITING)) {
+ return;
+ }
+
+ /*
+ * Set DNS_ZONEFLG_REFRESH so that there is only one refresh operation
+ * in progress at a time.
+ */
+
+ oldflags = atomic_load(&zone->flags);
+ if (zone->primariescnt == 0) {
+ DNS_ZONE_SETFLAG(zone, DNS_ZONEFLG_NOPRIMARIES);
+ if ((oldflags & DNS_ZONEFLG_NOPRIMARIES) == 0) {
+ dns_zone_log(zone, ISC_LOG_ERROR,
+ "cannot refresh: no primaries");
+ }
+ return;
+ }
+ DNS_ZONE_SETFLAG(zone, DNS_ZONEFLG_REFRESH);
+ DNS_ZONE_CLRFLAG(zone, DNS_ZONEFLG_NOEDNS);
+ DNS_ZONE_CLRFLAG(zone, DNS_ZONEFLG_USEALTXFRSRC);
+ if ((oldflags & (DNS_ZONEFLG_REFRESH | DNS_ZONEFLG_LOADING)) != 0) {
+ return;
+ }
+
+ /*
+ * Set the next refresh time as if refresh check has failed.
+ * Setting this to the retry time will do that. XXXMLG
+ * If we are successful it will be reset using zone->refresh.
+ */
+ isc_interval_set(&i, zone->retry - isc_random_uniform(zone->retry / 4),
+ 0);
+ result = isc_time_nowplusinterval(&zone->refreshtime, &i);
+ if (result != ISC_R_SUCCESS) {
+ dns_zone_log(zone, ISC_LOG_WARNING,
+ "isc_time_nowplusinterval() failed: %s",
+ isc_result_totext(result));
+ }
+
+ /*
+ * When lacking user-specified timer values from the SOA,
+ * do exponential backoff of the retry time up to a
+ * maximum of six hours.
+ */
+ if (!DNS_ZONE_FLAG(zone, DNS_ZONEFLG_HAVETIMERS)) {
+ zone->retry = ISC_MIN(zone->retry * 2, 6 * 3600);
+ }
+
+ zone->curprimary = 0;
+ for (j = 0; j < zone->primariescnt; j++) {
+ zone->primariesok[j] = false;
+ }
+ /* initiate soa query */
+ queue_soa_query(zone);
+}
+
+void
+dns_zone_refresh(dns_zone_t *zone) {
+ LOCK_ZONE(zone);
+ zone_refresh(zone);
+ UNLOCK_ZONE(zone);
+}
+
+static isc_result_t
+zone_journal_rollforward(dns_zone_t *zone, dns_db_t *db, bool *needdump,
+ bool *fixjournal) {
+ dns_journal_t *journal = NULL;
+ unsigned int options;
+ isc_result_t result;
+
+ if (zone->type == dns_zone_primary &&
+ (inline_secure(zone) ||
+ (zone->update_acl != NULL || zone->ssutable != NULL)))
+ {
+ options = DNS_JOURNALOPT_RESIGN;
+ } else {
+ options = 0;
+ }
+
+ result = dns_journal_open(zone->mctx, zone->journal, DNS_JOURNAL_READ,
+ &journal);
+ if (result == ISC_R_NOTFOUND) {
+ dns_zone_logc(zone, DNS_LOGCATEGORY_ZONELOAD, ISC_LOG_DEBUG(3),
+ "no journal file, but that's OK ");
+ return (ISC_R_SUCCESS);
+ } else if (result != ISC_R_SUCCESS) {
+ dns_zone_logc(zone, DNS_LOGCATEGORY_ZONELOAD, ISC_LOG_ERROR,
+ "journal open failed: %s",
+ isc_result_totext(result));
+ return (result);
+ }
+
+ if (dns_journal_empty(journal)) {
+ dns_zone_logc(zone, DNS_LOGCATEGORY_ZONELOAD, ISC_LOG_DEBUG(1),
+ "journal empty");
+ dns_journal_destroy(&journal);
+ return (ISC_R_SUCCESS);
+ }
+
+ result = dns_journal_rollforward(journal, db, options);
+ switch (result) {
+ case ISC_R_SUCCESS:
+ *needdump = true;
+ FALLTHROUGH;
+ case DNS_R_UPTODATE:
+ if (dns_journal_recovered(journal)) {
+ *fixjournal = true;
+ dns_zone_logc(
+ zone, DNS_LOGCATEGORY_ZONELOAD,
+ ISC_LOG_DEBUG(1),
+ "journal rollforward completed successfully "
+ "using old journal format: %s",
+ isc_result_totext(result));
+ } else {
+ dns_zone_logc(zone, DNS_LOGCATEGORY_ZONELOAD,
+ ISC_LOG_DEBUG(1),
+ "journal rollforward completed "
+ "successfully: %s",
+ isc_result_totext(result));
+ }
+
+ dns_journal_destroy(&journal);
+ return (ISC_R_SUCCESS);
+ case ISC_R_NOTFOUND:
+ case ISC_R_RANGE:
+ dns_zone_logc(zone, DNS_LOGCATEGORY_ZONELOAD, ISC_LOG_ERROR,
+ "journal rollforward failed: journal out of sync "
+ "with zone");
+ dns_journal_destroy(&journal);
+ return (result);
+ default:
+ dns_zone_logc(zone, DNS_LOGCATEGORY_ZONELOAD, ISC_LOG_ERROR,
+ "journal rollforward failed: %s",
+ isc_result_totext(result));
+ dns_journal_destroy(&journal);
+ return (result);
+ }
+}
+
+static void
+zone_journal_compact(dns_zone_t *zone, dns_db_t *db, uint32_t serial) {
+ isc_result_t result;
+ int32_t journalsize;
+ dns_dbversion_t *ver = NULL;
+ uint64_t dbsize;
+ uint32_t options = 0;
+
+ INSIST(LOCKED_ZONE(zone));
+ if (inline_raw(zone)) {
+ INSIST(LOCKED_ZONE(zone->secure));
+ }
+
+ journalsize = zone->journalsize;
+ if (journalsize == -1) {
+ journalsize = DNS_JOURNAL_SIZE_MAX;
+ dns_db_currentversion(db, &ver);
+ result = dns_db_getsize(db, ver, NULL, &dbsize);
+ dns_db_closeversion(db, &ver, false);
+ if (result != ISC_R_SUCCESS) {
+ dns_zone_log(zone, ISC_LOG_ERROR,
+ "zone_journal_compact: "
+ "could not get zone size: %s",
+ isc_result_totext(result));
+ } else if (dbsize < DNS_JOURNAL_SIZE_MAX / 2) {
+ journalsize = (int32_t)dbsize * 2;
+ }
+ }
+ if (DNS_ZONE_FLAG(zone, DNS_ZONEFLG_FIXJOURNAL)) {
+ options |= DNS_JOURNAL_COMPACTALL;
+ DNS_ZONE_CLRFLAG(zone, DNS_ZONEFLG_FIXJOURNAL);
+ zone_debuglog(zone, "zone_journal_compact", 1,
+ "repair full journal");
+ } else {
+ zone_debuglog(zone, "zone_journal_compact", 1,
+ "target journal size %d", journalsize);
+ }
+ result = dns_journal_compact(zone->mctx, zone->journal, serial, options,
+ journalsize);
+ switch (result) {
+ case ISC_R_SUCCESS:
+ case ISC_R_NOSPACE:
+ case ISC_R_NOTFOUND:
+ dns_zone_log(zone, ISC_LOG_DEBUG(3), "dns_journal_compact: %s",
+ isc_result_totext(result));
+ break;
+ default:
+ dns_zone_log(zone, ISC_LOG_ERROR,
+ "dns_journal_compact failed: %s",
+ isc_result_totext(result));
+ break;
+ }
+}
+
+isc_result_t
+dns_zone_flush(dns_zone_t *zone) {
+ isc_result_t result = ISC_R_SUCCESS;
+ bool dumping;
+
+ REQUIRE(DNS_ZONE_VALID(zone));
+
+ LOCK_ZONE(zone);
+ DNS_ZONE_SETFLAG(zone, DNS_ZONEFLG_FLUSH);
+ if (DNS_ZONE_FLAG(zone, DNS_ZONEFLG_NEEDDUMP) &&
+ zone->masterfile != NULL)
+ {
+ DNS_ZONE_SETFLAG(zone, DNS_ZONEFLG_NEEDCOMPACT);
+ result = ISC_R_ALREADYRUNNING;
+ dumping = was_dumping(zone);
+ } else {
+ dumping = true;
+ }
+ UNLOCK_ZONE(zone);
+ if (!dumping) {
+ result = zone_dump(zone, true); /* Unknown task. */
+ }
+ return (result);
+}
+
+isc_result_t
+dns_zone_dump(dns_zone_t *zone) {
+ isc_result_t result = ISC_R_ALREADYRUNNING;
+ bool dumping;
+
+ REQUIRE(DNS_ZONE_VALID(zone));
+
+ LOCK_ZONE(zone);
+ dumping = was_dumping(zone);
+ UNLOCK_ZONE(zone);
+ if (!dumping) {
+ result = zone_dump(zone, false); /* Unknown task. */
+ }
+ return (result);
+}
+
+static void
+zone_needdump(dns_zone_t *zone, unsigned int delay) {
+ const char me[] = "zone_needdump";
+ isc_time_t dumptime;
+ isc_time_t now;
+
+ /*
+ * 'zone' locked by caller
+ */
+
+ REQUIRE(DNS_ZONE_VALID(zone));
+ REQUIRE(LOCKED_ZONE(zone));
+ ENTER;
+
+ /*
+ * Do we have a place to dump to and are we loaded?
+ */
+ if (zone->masterfile == NULL ||
+ DNS_ZONE_FLAG(zone, DNS_ZONEFLG_LOADED) == 0)
+ {
+ return;
+ }
+
+ TIME_NOW(&now);
+ /* add some noise */
+ DNS_ZONE_JITTER_ADD(&now, delay, &dumptime);
+
+ DNS_ZONE_SETFLAG(zone, DNS_ZONEFLG_NEEDDUMP);
+ if (isc_time_isepoch(&zone->dumptime) ||
+ isc_time_compare(&zone->dumptime, &dumptime) > 0)
+ {
+ zone->dumptime = dumptime;
+ }
+ if (zone->task != NULL) {
+ zone_settimer(zone, &now);
+ }
+}
+
+static void
+dump_done(void *arg, isc_result_t result) {
+ const char me[] = "dump_done";
+ dns_zone_t *zone = arg;
+ dns_zone_t *secure = NULL;
+ dns_db_t *db;
+ dns_dbversion_t *version;
+ bool again = false;
+ bool compact = false;
+ uint32_t serial;
+ isc_result_t tresult;
+
+ REQUIRE(DNS_ZONE_VALID(zone));
+
+ ENTER;
+
+ if (result == ISC_R_SUCCESS && zone->journal != NULL) {
+ /*
+ * We don't own these, zone->dctx must stay valid.
+ */
+ db = dns_dumpctx_db(zone->dctx);
+ version = dns_dumpctx_version(zone->dctx);
+ tresult = dns_db_getsoaserial(db, version, &serial);
+
+ /*
+ * Handle lock order inversion.
+ */
+ again:
+ LOCK_ZONE(zone);
+ if (inline_raw(zone)) {
+ secure = zone->secure;
+ INSIST(secure != zone);
+ TRYLOCK_ZONE(result, secure);
+ if (result != ISC_R_SUCCESS) {
+ UNLOCK_ZONE(zone);
+ secure = NULL;
+ isc_thread_yield();
+ goto again;
+ }
+ }
+
+ /*
+ * If there is a secure version of this zone
+ * use its serial if it is less than ours.
+ */
+ if (tresult == ISC_R_SUCCESS && secure != NULL) {
+ uint32_t sserial;
+ isc_result_t mresult;
+
+ ZONEDB_LOCK(&secure->dblock, isc_rwlocktype_read);
+ if (secure->db != NULL) {
+ mresult = dns_db_getsoaserial(zone->secure->db,
+ NULL, &sserial);
+ if (mresult == ISC_R_SUCCESS &&
+ isc_serial_lt(sserial, serial))
+ {
+ serial = sserial;
+ }
+ }
+ ZONEDB_UNLOCK(&secure->dblock, isc_rwlocktype_read);
+ }
+ if (tresult == ISC_R_SUCCESS && zone->xfr == NULL) {
+ dns_db_t *zdb = NULL;
+ if (dns_zone_getdb(zone, &zdb) == ISC_R_SUCCESS) {
+ zone_journal_compact(zone, zdb, serial);
+ dns_db_detach(&zdb);
+ }
+ } else if (tresult == ISC_R_SUCCESS) {
+ compact = true;
+ zone->compact_serial = serial;
+ }
+ if (secure != NULL) {
+ UNLOCK_ZONE(secure);
+ }
+ UNLOCK_ZONE(zone);
+ }
+
+ LOCK_ZONE(zone);
+ DNS_ZONE_CLRFLAG(zone, DNS_ZONEFLG_DUMPING);
+ if (compact) {
+ DNS_ZONE_SETFLAG(zone, DNS_ZONEFLG_NEEDCOMPACT);
+ }
+ if (DNS_ZONE_FLAG(zone, DNS_ZONEFLG_SHUTDOWN)) {
+ /*
+ * If DNS_ZONEFLG_SHUTDOWN is set, all external references to
+ * the zone are gone, which means it is in the process of being
+ * cleaned up, so do not reschedule dumping.
+ *
+ * Detach from the raw version of the zone in case this
+ * operation has been deferred in zone_shutdown().
+ */
+ if (zone->raw != NULL) {
+ dns_zone_detach(&zone->raw);
+ }
+ if (result == ISC_R_SUCCESS) {
+ DNS_ZONE_CLRFLAG(zone, DNS_ZONEFLG_FLUSH);
+ }
+ } else if (result != ISC_R_SUCCESS && result != ISC_R_CANCELED) {
+ /*
+ * Try again in a short while.
+ */
+ zone_needdump(zone, DNS_DUMP_DELAY);
+ } else if (result == ISC_R_SUCCESS &&
+ DNS_ZONE_FLAG(zone, DNS_ZONEFLG_FLUSH) &&
+ DNS_ZONE_FLAG(zone, DNS_ZONEFLG_NEEDDUMP) &&
+ DNS_ZONE_FLAG(zone, DNS_ZONEFLG_LOADED))
+ {
+ DNS_ZONE_CLRFLAG(zone, DNS_ZONEFLG_NEEDDUMP);
+ DNS_ZONE_SETFLAG(zone, DNS_ZONEFLG_DUMPING);
+ isc_time_settoepoch(&zone->dumptime);
+ again = true;
+ } else if (result == ISC_R_SUCCESS) {
+ DNS_ZONE_CLRFLAG(zone, DNS_ZONEFLG_FLUSH);
+ }
+
+ if (zone->dctx != NULL) {
+ dns_dumpctx_detach(&zone->dctx);
+ }
+ zonemgr_putio(&zone->writeio);
+ UNLOCK_ZONE(zone);
+ if (again) {
+ (void)zone_dump(zone, false);
+ }
+ dns_zone_idetach(&zone);
+}
+
+static isc_result_t
+zone_dump(dns_zone_t *zone, bool compact) {
+ const char me[] = "zone_dump";
+ isc_result_t result;
+ dns_dbversion_t *version = NULL;
+ bool again;
+ dns_db_t *db = NULL;
+ char *masterfile = NULL;
+ dns_masterformat_t masterformat = dns_masterformat_none;
+
+ /*
+ * 'compact' MUST only be set if we are task locked.
+ */
+
+ REQUIRE(DNS_ZONE_VALID(zone));
+ ENTER;
+
+redo:
+ ZONEDB_LOCK(&zone->dblock, isc_rwlocktype_read);
+ if (zone->db != NULL) {
+ dns_db_attach(zone->db, &db);
+ }
+ ZONEDB_UNLOCK(&zone->dblock, isc_rwlocktype_read);
+ LOCK_ZONE(zone);
+ if (zone->masterfile != NULL) {
+ masterfile = isc_mem_strdup(zone->mctx, zone->masterfile);
+ masterformat = zone->masterformat;
+ }
+ UNLOCK_ZONE(zone);
+ if (db == NULL) {
+ result = DNS_R_NOTLOADED;
+ goto fail;
+ }
+ if (masterfile == NULL) {
+ result = DNS_R_NOMASTERFILE;
+ goto fail;
+ }
+
+ if (compact && zone->type != dns_zone_stub) {
+ dns_zone_t *dummy = NULL;
+ LOCK_ZONE(zone);
+ zone_iattach(zone, &dummy);
+ result = zonemgr_getio(zone->zmgr, false, zone->task,
+ zone_gotwritehandle, zone,
+ &zone->writeio);
+ if (result != ISC_R_SUCCESS) {
+ zone_idetach(&dummy);
+ } else {
+ result = DNS_R_CONTINUE;
+ }
+ UNLOCK_ZONE(zone);
+ } else {
+ const dns_master_style_t *output_style;
+
+ dns_masterrawheader_t rawdata;
+ dns_db_currentversion(db, &version);
+ dns_master_initrawheader(&rawdata);
+ if (inline_secure(zone)) {
+ get_raw_serial(zone->raw, &rawdata);
+ }
+ if (zone->type == dns_zone_key) {
+ output_style = &dns_master_style_keyzone;
+ } else {
+ output_style = &dns_master_style_default;
+ }
+ result = dns_master_dump(zone->mctx, db, version, output_style,
+ masterfile, masterformat, &rawdata);
+ dns_db_closeversion(db, &version, false);
+ }
+fail:
+ if (db != NULL) {
+ dns_db_detach(&db);
+ }
+ if (masterfile != NULL) {
+ isc_mem_free(zone->mctx, masterfile);
+ }
+ masterfile = NULL;
+
+ if (result == DNS_R_CONTINUE) {
+ return (ISC_R_SUCCESS); /* XXXMPA */
+ }
+
+ again = false;
+ LOCK_ZONE(zone);
+ DNS_ZONE_CLRFLAG(zone, DNS_ZONEFLG_DUMPING);
+ if (result != ISC_R_SUCCESS) {
+ /*
+ * Try again in a short while.
+ */
+ zone_needdump(zone, DNS_DUMP_DELAY);
+ } else if (DNS_ZONE_FLAG(zone, DNS_ZONEFLG_FLUSH) &&
+ DNS_ZONE_FLAG(zone, DNS_ZONEFLG_NEEDDUMP) &&
+ DNS_ZONE_FLAG(zone, DNS_ZONEFLG_LOADED))
+ {
+ DNS_ZONE_CLRFLAG(zone, DNS_ZONEFLG_NEEDDUMP);
+ DNS_ZONE_SETFLAG(zone, DNS_ZONEFLG_DUMPING);
+ isc_time_settoepoch(&zone->dumptime);
+ again = true;
+ } else {
+ DNS_ZONE_CLRFLAG(zone, DNS_ZONEFLG_FLUSH);
+ }
+ UNLOCK_ZONE(zone);
+ if (again) {
+ goto redo;
+ }
+
+ return (result);
+}
+
+static isc_result_t
+dumptostream(dns_zone_t *zone, FILE *fd, const dns_master_style_t *style,
+ dns_masterformat_t format, const uint32_t rawversion) {
+ isc_result_t result;
+ dns_dbversion_t *version = NULL;
+ dns_db_t *db = NULL;
+ dns_masterrawheader_t rawdata;
+
+ REQUIRE(DNS_ZONE_VALID(zone));
+
+ ZONEDB_LOCK(&zone->dblock, isc_rwlocktype_read);
+ if (zone->db != NULL) {
+ dns_db_attach(zone->db, &db);
+ }
+ ZONEDB_UNLOCK(&zone->dblock, isc_rwlocktype_read);
+ if (db == NULL) {
+ return (DNS_R_NOTLOADED);
+ }
+
+ dns_db_currentversion(db, &version);
+ dns_master_initrawheader(&rawdata);
+ if (rawversion == 0) {
+ rawdata.flags |= DNS_MASTERRAW_COMPAT;
+ } else if (inline_secure(zone)) {
+ get_raw_serial(zone->raw, &rawdata);
+ } else if (zone->sourceserialset) {
+ rawdata.flags = DNS_MASTERRAW_SOURCESERIALSET;
+ rawdata.sourceserial = zone->sourceserial;
+ }
+ result = dns_master_dumptostream(zone->mctx, db, version, style, format,
+ &rawdata, fd);
+ dns_db_closeversion(db, &version, false);
+ dns_db_detach(&db);
+ return (result);
+}
+
+isc_result_t
+dns_zone_dumptostream(dns_zone_t *zone, FILE *fd, dns_masterformat_t format,
+ const dns_master_style_t *style,
+ const uint32_t rawversion) {
+ return (dumptostream(zone, fd, style, format, rawversion));
+}
+
+void
+dns_zone_unload(dns_zone_t *zone) {
+ REQUIRE(DNS_ZONE_VALID(zone));
+
+ LOCK_ZONE(zone);
+ zone_unload(zone);
+ UNLOCK_ZONE(zone);
+}
+
+static void
+notify_cancel(dns_zone_t *zone) {
+ dns_notify_t *notify;
+
+ /*
+ * 'zone' locked by caller.
+ */
+
+ REQUIRE(LOCKED_ZONE(zone));
+
+ for (notify = ISC_LIST_HEAD(zone->notifies); notify != NULL;
+ notify = ISC_LIST_NEXT(notify, link))
+ {
+ if (notify->find != NULL) {
+ dns_adb_cancelfind(notify->find);
+ }
+ if (notify->request != NULL) {
+ dns_request_cancel(notify->request);
+ }
+ }
+}
+
+static void
+checkds_cancel(dns_zone_t *zone) {
+ dns_checkds_t *checkds;
+
+ /*
+ * 'zone' locked by caller.
+ */
+
+ REQUIRE(LOCKED_ZONE(zone));
+
+ for (checkds = ISC_LIST_HEAD(zone->checkds_requests); checkds != NULL;
+ checkds = ISC_LIST_NEXT(checkds, link))
+ {
+ if (checkds->request != NULL) {
+ dns_request_cancel(checkds->request);
+ }
+ }
+}
+
+static void
+forward_cancel(dns_zone_t *zone) {
+ dns_forward_t *forward;
+
+ /*
+ * 'zone' locked by caller.
+ */
+
+ REQUIRE(LOCKED_ZONE(zone));
+
+ for (forward = ISC_LIST_HEAD(zone->forwards); forward != NULL;
+ forward = ISC_LIST_NEXT(forward, link))
+ {
+ if (forward->request != NULL) {
+ dns_request_cancel(forward->request);
+ }
+ }
+}
+
+static void
+zone_unload(dns_zone_t *zone) {
+ /*
+ * 'zone' locked by caller.
+ */
+
+ REQUIRE(LOCKED_ZONE(zone));
+
+ if (!DNS_ZONE_FLAG(zone, DNS_ZONEFLG_FLUSH) ||
+ !DNS_ZONE_FLAG(zone, DNS_ZONEFLG_DUMPING))
+ {
+ if (zone->writeio != NULL) {
+ zonemgr_cancelio(zone->writeio);
+ }
+
+ if (zone->dctx != NULL) {
+ dns_dumpctx_cancel(zone->dctx);
+ }
+ }
+ ZONEDB_LOCK(&zone->dblock, isc_rwlocktype_write);
+ zone_detachdb(zone);
+ ZONEDB_UNLOCK(&zone->dblock, isc_rwlocktype_write);
+ DNS_ZONE_CLRFLAG(zone, DNS_ZONEFLG_LOADED);
+ DNS_ZONE_CLRFLAG(zone, DNS_ZONEFLG_NEEDDUMP);
+
+ if (zone->type == dns_zone_mirror) {
+ dns_zone_log(zone, ISC_LOG_INFO,
+ "mirror zone is no longer in use; "
+ "reverting to normal recursion");
+ }
+}
+
+void
+dns_zone_setminrefreshtime(dns_zone_t *zone, uint32_t val) {
+ REQUIRE(DNS_ZONE_VALID(zone));
+ REQUIRE(val > 0);
+
+ zone->minrefresh = val;
+}
+
+void
+dns_zone_setmaxrefreshtime(dns_zone_t *zone, uint32_t val) {
+ REQUIRE(DNS_ZONE_VALID(zone));
+ REQUIRE(val > 0);
+
+ zone->maxrefresh = val;
+}
+
+void
+dns_zone_setminretrytime(dns_zone_t *zone, uint32_t val) {
+ REQUIRE(DNS_ZONE_VALID(zone));
+ REQUIRE(val > 0);
+
+ zone->minretry = val;
+}
+
+void
+dns_zone_setmaxretrytime(dns_zone_t *zone, uint32_t val) {
+ REQUIRE(DNS_ZONE_VALID(zone));
+ REQUIRE(val > 0);
+
+ zone->maxretry = val;
+}
+
+uint32_t
+dns_zone_getmaxrecords(dns_zone_t *zone) {
+ REQUIRE(DNS_ZONE_VALID(zone));
+
+ return (zone->maxrecords);
+}
+
+void
+dns_zone_setmaxrecords(dns_zone_t *zone, uint32_t val) {
+ REQUIRE(DNS_ZONE_VALID(zone));
+
+ zone->maxrecords = val;
+}
+
+static bool
+notify_isqueued(dns_zone_t *zone, unsigned int flags, dns_name_t *name,
+ isc_sockaddr_t *addr, dns_tsigkey_t *key,
+ dns_transport_t *transport) {
+ dns_notify_t *notify;
+ dns_zonemgr_t *zmgr;
+ isc_result_t result;
+
+ for (notify = ISC_LIST_HEAD(zone->notifies); notify != NULL;
+ notify = ISC_LIST_NEXT(notify, link))
+ {
+ if (notify->request != NULL) {
+ continue;
+ }
+ if (name != NULL && dns_name_dynamic(&notify->ns) &&
+ dns_name_equal(name, &notify->ns))
+ {
+ goto requeue;
+ }
+ if (addr != NULL && isc_sockaddr_equal(addr, &notify->dst) &&
+ notify->key == key && notify->transport == transport)
+ {
+ goto requeue;
+ }
+ }
+ return (false);
+
+requeue:
+ /*
+ * If we are enqueued on the startup ratelimiter and this is
+ * not a startup notify, re-enqueue on the normal notify
+ * ratelimiter.
+ */
+ if (notify->event != NULL && (flags & DNS_NOTIFY_STARTUP) == 0 &&
+ (notify->flags & DNS_NOTIFY_STARTUP) != 0)
+ {
+ zmgr = notify->zone->zmgr;
+ result = isc_ratelimiter_dequeue(zmgr->startupnotifyrl,
+ notify->event);
+ if (result != ISC_R_SUCCESS) {
+ return (true);
+ }
+
+ notify->flags &= ~DNS_NOTIFY_STARTUP;
+ result = isc_ratelimiter_enqueue(notify->zone->zmgr->notifyrl,
+ notify->zone->task,
+ &notify->event);
+ if (result != ISC_R_SUCCESS) {
+ isc_event_free(&notify->event);
+ return (false);
+ }
+ }
+
+ return (true);
+}
+
+static bool
+notify_isself(dns_zone_t *zone, isc_sockaddr_t *dst) {
+ dns_tsigkey_t *key = NULL;
+ isc_sockaddr_t src;
+ isc_sockaddr_t any;
+ bool isself;
+ isc_netaddr_t dstaddr;
+ isc_result_t result;
+
+ if (zone->view == NULL || zone->isself == NULL) {
+ return (false);
+ }
+
+ switch (isc_sockaddr_pf(dst)) {
+ case PF_INET:
+ src = zone->notifysrc4;
+ isc_sockaddr_any(&any);
+ break;
+ case PF_INET6:
+ src = zone->notifysrc6;
+ isc_sockaddr_any6(&any);
+ break;
+ default:
+ return (false);
+ }
+
+ /*
+ * When sending from any the kernel will assign a source address
+ * that matches the destination address.
+ */
+ if (isc_sockaddr_eqaddr(&any, &src)) {
+ src = *dst;
+ }
+
+ isc_netaddr_fromsockaddr(&dstaddr, dst);
+ result = dns_view_getpeertsig(zone->view, &dstaddr, &key);
+ if (result != ISC_R_SUCCESS && result != ISC_R_NOTFOUND) {
+ return (false);
+ }
+ isself = (zone->isself)(zone->view, key, &src, dst, zone->rdclass,
+ zone->isselfarg);
+ if (key != NULL) {
+ dns_tsigkey_detach(&key);
+ }
+ return (isself);
+}
+
+static void
+notify_destroy(dns_notify_t *notify, bool locked) {
+ isc_mem_t *mctx;
+
+ REQUIRE(DNS_NOTIFY_VALID(notify));
+
+ if (notify->zone != NULL) {
+ if (!locked) {
+ LOCK_ZONE(notify->zone);
+ }
+ REQUIRE(LOCKED_ZONE(notify->zone));
+ if (ISC_LINK_LINKED(notify, link)) {
+ ISC_LIST_UNLINK(notify->zone->notifies, notify, link);
+ }
+ if (!locked) {
+ UNLOCK_ZONE(notify->zone);
+ }
+ if (locked) {
+ zone_idetach(&notify->zone);
+ } else {
+ dns_zone_idetach(&notify->zone);
+ }
+ }
+ if (notify->find != NULL) {
+ dns_adb_destroyfind(&notify->find);
+ }
+ if (notify->request != NULL) {
+ dns_request_destroy(&notify->request);
+ }
+ if (dns_name_dynamic(&notify->ns)) {
+ dns_name_free(&notify->ns, notify->mctx);
+ }
+ if (notify->key != NULL) {
+ dns_tsigkey_detach(&notify->key);
+ }
+ if (notify->transport != NULL) {
+ dns_transport_detach(&notify->transport);
+ }
+ mctx = notify->mctx;
+ isc_mem_put(notify->mctx, notify, sizeof(*notify));
+ isc_mem_detach(&mctx);
+}
+
+static isc_result_t
+notify_create(isc_mem_t *mctx, unsigned int flags, dns_notify_t **notifyp) {
+ dns_notify_t *notify;
+
+ REQUIRE(notifyp != NULL && *notifyp == NULL);
+
+ notify = isc_mem_get(mctx, sizeof(*notify));
+ *notify = (dns_notify_t){
+ .flags = flags,
+ };
+
+ isc_mem_attach(mctx, &notify->mctx);
+ isc_sockaddr_any(&notify->dst);
+ dns_name_init(&notify->ns, NULL);
+ ISC_LINK_INIT(notify, link);
+ notify->magic = NOTIFY_MAGIC;
+ *notifyp = notify;
+ return (ISC_R_SUCCESS);
+}
+
+/*
+ * XXXAG should check for DNS_ZONEFLG_EXITING
+ */
+static void
+process_adb_event(isc_task_t *task, isc_event_t *ev) {
+ dns_notify_t *notify;
+ isc_eventtype_t result;
+
+ UNUSED(task);
+
+ notify = ev->ev_arg;
+ REQUIRE(DNS_NOTIFY_VALID(notify));
+ INSIST(task == notify->zone->task);
+ result = ev->ev_type;
+ isc_event_free(&ev);
+ if (result == DNS_EVENT_ADBMOREADDRESSES) {
+ dns_adb_destroyfind(&notify->find);
+ notify_find_address(notify);
+ return;
+ }
+ if (result == DNS_EVENT_ADBNOMOREADDRESSES) {
+ LOCK_ZONE(notify->zone);
+ notify_send(notify);
+ UNLOCK_ZONE(notify->zone);
+ }
+ notify_destroy(notify, false);
+}
+
+static void
+notify_find_address(dns_notify_t *notify) {
+ isc_result_t result;
+ unsigned int options;
+
+ REQUIRE(DNS_NOTIFY_VALID(notify));
+ options = DNS_ADBFIND_WANTEVENT | DNS_ADBFIND_INET | DNS_ADBFIND_INET6 |
+ DNS_ADBFIND_RETURNLAME;
+
+ if (notify->zone->view->adb == NULL) {
+ goto destroy;
+ }
+
+ result = dns_adb_createfind(
+ notify->zone->view->adb, notify->zone->task, process_adb_event,
+ notify, &notify->ns, dns_rootname, 0, options, 0, NULL,
+ notify->zone->view->dstport, 0, NULL, &notify->find);
+
+ /* Something failed? */
+ if (result != ISC_R_SUCCESS) {
+ goto destroy;
+ }
+
+ /* More addresses pending? */
+ if ((notify->find->options & DNS_ADBFIND_WANTEVENT) != 0) {
+ return;
+ }
+
+ /* We have as many addresses as we can get. */
+ LOCK_ZONE(notify->zone);
+ notify_send(notify);
+ UNLOCK_ZONE(notify->zone);
+
+destroy:
+ notify_destroy(notify, false);
+}
+
+static isc_result_t
+notify_send_queue(dns_notify_t *notify, bool startup) {
+ isc_event_t *e;
+ isc_result_t result;
+
+ INSIST(notify->event == NULL);
+ e = isc_event_allocate(notify->mctx, NULL, DNS_EVENT_NOTIFYSENDTOADDR,
+ notify_send_toaddr, notify, sizeof(isc_event_t));
+ if (startup) {
+ notify->event = e;
+ }
+ e->ev_arg = notify;
+ e->ev_sender = NULL;
+ result = isc_ratelimiter_enqueue(
+ startup ? notify->zone->zmgr->startupnotifyrl
+ : notify->zone->zmgr->notifyrl,
+ notify->zone->task, &e);
+ if (result != ISC_R_SUCCESS) {
+ isc_event_free(&e);
+ notify->event = NULL;
+ }
+ return (result);
+}
+
+static void
+notify_send_toaddr(isc_task_t *task, isc_event_t *event) {
+ dns_notify_t *notify;
+ isc_result_t result;
+ dns_message_t *message = NULL;
+ isc_netaddr_t dstip;
+ dns_tsigkey_t *key = NULL;
+ char addrbuf[ISC_SOCKADDR_FORMATSIZE];
+ isc_sockaddr_t src;
+ unsigned int options, timeout;
+ bool have_notifysource = false;
+
+ notify = event->ev_arg;
+ REQUIRE(DNS_NOTIFY_VALID(notify));
+
+ UNUSED(task);
+
+ LOCK_ZONE(notify->zone);
+
+ notify->event = NULL;
+
+ if (DNS_ZONE_FLAG(notify->zone, DNS_ZONEFLG_LOADED) == 0) {
+ result = ISC_R_CANCELED;
+ goto cleanup;
+ }
+
+ if ((event->ev_attributes & ISC_EVENTATTR_CANCELED) != 0 ||
+ DNS_ZONE_FLAG(notify->zone, DNS_ZONEFLG_EXITING) ||
+ notify->zone->view->requestmgr == NULL || notify->zone->db == NULL)
+ {
+ result = ISC_R_CANCELED;
+ goto cleanup;
+ }
+
+ /*
+ * The raw IPv4 address should also exist. Don't send to the
+ * mapped form.
+ */
+ if (isc_sockaddr_pf(&notify->dst) == PF_INET6 &&
+ IN6_IS_ADDR_V4MAPPED(&notify->dst.type.sin6.sin6_addr))
+ {
+ isc_sockaddr_format(&notify->dst, addrbuf, sizeof(addrbuf));
+ notify_log(notify->zone, ISC_LOG_DEBUG(3),
+ "notify: ignoring IPv6 mapped IPV4 address: %s",
+ addrbuf);
+ result = ISC_R_CANCELED;
+ goto cleanup;
+ }
+
+ result = notify_createmessage(notify->zone, notify->flags, &message);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+
+ isc_sockaddr_format(&notify->dst, addrbuf, sizeof(addrbuf));
+ if (notify->key != NULL) {
+ /* Transfer ownership of key */
+ key = notify->key;
+ notify->key = NULL;
+ } else {
+ isc_netaddr_fromsockaddr(&dstip, &notify->dst);
+ result = dns_view_getpeertsig(notify->zone->view, &dstip, &key);
+ if (result != ISC_R_SUCCESS && result != ISC_R_NOTFOUND) {
+ notify_log(notify->zone, ISC_LOG_ERROR,
+ "NOTIFY to %s not sent. "
+ "Peer TSIG key lookup failure.",
+ addrbuf);
+ goto cleanup_message;
+ }
+ }
+
+ if (key != NULL) {
+ char namebuf[DNS_NAME_FORMATSIZE];
+
+ dns_name_format(&key->name, namebuf, sizeof(namebuf));
+ notify_log(notify->zone, ISC_LOG_DEBUG(3),
+ "sending notify to %s : TSIG (%s)", addrbuf,
+ namebuf);
+ } else {
+ notify_log(notify->zone, ISC_LOG_DEBUG(3),
+ "sending notify to %s", addrbuf);
+ }
+ options = 0;
+ if (notify->zone->view->peers != NULL) {
+ dns_peer_t *peer = NULL;
+ bool usetcp = false;
+ result = dns_peerlist_peerbyaddr(notify->zone->view->peers,
+ &dstip, &peer);
+ if (result == ISC_R_SUCCESS) {
+ result = dns_peer_getnotifysource(peer, &src);
+ if (result == ISC_R_SUCCESS) {
+ have_notifysource = true;
+ }
+ result = dns_peer_getforcetcp(peer, &usetcp);
+ if (result == ISC_R_SUCCESS && usetcp) {
+ options |= DNS_FETCHOPT_TCP;
+ }
+ }
+ }
+ switch (isc_sockaddr_pf(&notify->dst)) {
+ case PF_INET:
+ if (!have_notifysource) {
+ src = notify->zone->notifysrc4;
+ }
+ break;
+ case PF_INET6:
+ if (!have_notifysource) {
+ src = notify->zone->notifysrc6;
+ }
+ break;
+ default:
+ result = ISC_R_NOTIMPLEMENTED;
+ goto cleanup_key;
+ }
+ timeout = 15;
+ if (DNS_ZONE_FLAG(notify->zone, DNS_ZONEFLG_DIALNOTIFY)) {
+ timeout = 30;
+ }
+ result = dns_request_create(notify->zone->view->requestmgr, message,
+ &src, &notify->dst, options, key,
+ timeout * 3, timeout, 2, notify->zone->task,
+ notify_done, notify, &notify->request);
+ if (result == ISC_R_SUCCESS) {
+ if (isc_sockaddr_pf(&notify->dst) == AF_INET) {
+ inc_stats(notify->zone,
+ dns_zonestatscounter_notifyoutv4);
+ } else {
+ inc_stats(notify->zone,
+ dns_zonestatscounter_notifyoutv6);
+ }
+ }
+
+cleanup_key:
+ if (key != NULL) {
+ dns_tsigkey_detach(&key);
+ }
+cleanup_message:
+ dns_message_detach(&message);
+cleanup:
+ UNLOCK_ZONE(notify->zone);
+ isc_event_free(&event);
+ if (result != ISC_R_SUCCESS) {
+ notify_destroy(notify, false);
+ }
+}
+
+static void
+notify_send(dns_notify_t *notify) {
+ dns_adbaddrinfo_t *ai;
+ isc_sockaddr_t dst;
+ isc_result_t result;
+ dns_notify_t *newnotify = NULL;
+ unsigned int flags;
+ bool startup;
+
+ /*
+ * Zone lock held by caller.
+ */
+ REQUIRE(DNS_NOTIFY_VALID(notify));
+ REQUIRE(LOCKED_ZONE(notify->zone));
+
+ if (DNS_ZONE_FLAG(notify->zone, DNS_ZONEFLG_EXITING)) {
+ return;
+ }
+
+ for (ai = ISC_LIST_HEAD(notify->find->list); ai != NULL;
+ ai = ISC_LIST_NEXT(ai, publink))
+ {
+ dst = ai->sockaddr;
+ if (notify_isqueued(notify->zone, notify->flags, NULL, &dst,
+ NULL, NULL))
+ {
+ continue;
+ }
+ if (notify_isself(notify->zone, &dst)) {
+ continue;
+ }
+ newnotify = NULL;
+ flags = notify->flags & DNS_NOTIFY_NOSOA;
+ result = notify_create(notify->mctx, flags, &newnotify);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+ zone_iattach(notify->zone, &newnotify->zone);
+ ISC_LIST_APPEND(newnotify->zone->notifies, newnotify, link);
+ newnotify->dst = dst;
+ startup = ((notify->flags & DNS_NOTIFY_STARTUP) != 0);
+ result = notify_send_queue(newnotify, startup);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+ newnotify = NULL;
+ }
+
+cleanup:
+ if (newnotify != NULL) {
+ notify_destroy(newnotify, true);
+ }
+}
+
+void
+dns_zone_notify(dns_zone_t *zone) {
+ isc_time_t now;
+
+ REQUIRE(DNS_ZONE_VALID(zone));
+
+ LOCK_ZONE(zone);
+ DNS_ZONE_SETFLAG(zone, DNS_ZONEFLG_NEEDNOTIFY);
+
+ TIME_NOW(&now);
+ zone_settimer(zone, &now);
+ UNLOCK_ZONE(zone);
+}
+
+static void
+zone_notify(dns_zone_t *zone, isc_time_t *now) {
+ dns_dbnode_t *node = NULL;
+ dns_db_t *zonedb = NULL;
+ dns_dbversion_t *version = NULL;
+ dns_name_t *origin = NULL;
+ dns_name_t primary;
+ dns_rdata_ns_t ns;
+ dns_rdata_soa_t soa;
+ uint32_t serial;
+ dns_rdata_t rdata = DNS_RDATA_INIT;
+ dns_rdataset_t nsrdset;
+ dns_rdataset_t soardset;
+ isc_result_t result;
+ unsigned int i;
+ isc_sockaddr_t dst;
+ bool isqueued;
+ dns_notifytype_t notifytype;
+ unsigned int flags = 0;
+ bool loggednotify = false;
+ bool startup;
+
+ REQUIRE(DNS_ZONE_VALID(zone));
+
+ LOCK_ZONE(zone);
+ startup = !DNS_ZONE_FLAG(zone, DNS_ZONEFLG_NEEDNOTIFY);
+ DNS_ZONE_CLRFLAG(zone, DNS_ZONEFLG_NEEDNOTIFY);
+ DNS_ZONE_CLRFLAG(zone, DNS_ZONEFLG_NEEDSTARTUPNOTIFY);
+ notifytype = zone->notifytype;
+ DNS_ZONE_TIME_ADD(now, zone->notifydelay, &zone->notifytime);
+ UNLOCK_ZONE(zone);
+
+ if (DNS_ZONE_FLAG(zone, DNS_ZONEFLG_EXITING) ||
+ !DNS_ZONE_FLAG(zone, DNS_ZONEFLG_LOADED))
+ {
+ return;
+ }
+
+ if (notifytype == dns_notifytype_no) {
+ return;
+ }
+
+ if (notifytype == dns_notifytype_masteronly &&
+ zone->type != dns_zone_primary)
+ {
+ return;
+ }
+
+ origin = &zone->origin;
+
+ /*
+ * If the zone is dialup we are done as we don't want to send
+ * the current soa so as to force a refresh query.
+ */
+ if (DNS_ZONE_FLAG(zone, DNS_ZONEFLG_DIALNOTIFY)) {
+ flags |= DNS_NOTIFY_NOSOA;
+ }
+
+ /*
+ * Record that this was a notify due to starting up.
+ */
+ if (startup) {
+ flags |= DNS_NOTIFY_STARTUP;
+ }
+
+ /*
+ * Get SOA RRset.
+ */
+ ZONEDB_LOCK(&zone->dblock, isc_rwlocktype_read);
+ if (zone->db != NULL) {
+ dns_db_attach(zone->db, &zonedb);
+ }
+ ZONEDB_UNLOCK(&zone->dblock, isc_rwlocktype_read);
+ if (zonedb == NULL) {
+ return;
+ }
+ dns_db_currentversion(zonedb, &version);
+ result = dns_db_findnode(zonedb, origin, false, &node);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup1;
+ }
+
+ dns_rdataset_init(&soardset);
+ result = dns_db_findrdataset(zonedb, node, version, dns_rdatatype_soa,
+ dns_rdatatype_none, 0, &soardset, NULL);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup2;
+ }
+
+ /*
+ * Find serial and primary server's name.
+ */
+ dns_name_init(&primary, NULL);
+ result = dns_rdataset_first(&soardset);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup3;
+ }
+ dns_rdataset_current(&soardset, &rdata);
+ result = dns_rdata_tostruct(&rdata, &soa, NULL);
+ RUNTIME_CHECK(result == ISC_R_SUCCESS);
+ dns_rdata_reset(&rdata);
+ dns_name_dup(&soa.origin, zone->mctx, &primary);
+ serial = soa.serial;
+ dns_rdataset_disassociate(&soardset);
+
+ /*
+ * Enqueue notify requests for 'also-notify' servers.
+ */
+ LOCK_ZONE(zone);
+ for (i = 0; i < zone->notifycnt; i++) {
+ dns_tsigkey_t *key = NULL;
+ dns_transport_t *transport = NULL;
+ dns_notify_t *notify = NULL;
+ dns_view_t *view = dns_zone_getview(zone);
+
+ if ((zone->notifykeynames != NULL) &&
+ (zone->notifykeynames[i] != NULL))
+ {
+ dns_name_t *keyname = zone->notifykeynames[i];
+ (void)dns_view_gettsig(view, keyname, &key);
+ }
+
+ if ((zone->notifytlsnames != NULL) &&
+ (zone->notifytlsnames[i] != NULL))
+ {
+ dns_name_t *tlsname = zone->notifytlsnames[i];
+ (void)dns_view_gettransport(view, DNS_TRANSPORT_TLS,
+ tlsname, &transport);
+
+ dns_zone_logc(
+ zone, DNS_LOGCATEGORY_XFER_IN, ISC_LOG_INFO,
+ "got TLS configuration for zone transfer");
+ }
+
+ /* TODO: glue the transport to the notify */
+
+ dst = zone->notify[i];
+ if (notify_isqueued(zone, flags, NULL, &dst, key, transport)) {
+ if (key != NULL) {
+ dns_tsigkey_detach(&key);
+ }
+ if (transport != NULL) {
+ dns_transport_detach(&transport);
+ }
+ continue;
+ }
+
+ result = notify_create(zone->mctx, flags, &notify);
+ if (result != ISC_R_SUCCESS) {
+ if (key != NULL) {
+ dns_tsigkey_detach(&key);
+ }
+ if (transport != NULL) {
+ dns_transport_detach(&transport);
+ }
+ continue;
+ }
+
+ zone_iattach(zone, &notify->zone);
+ notify->dst = dst;
+
+ INSIST(notify->key == NULL);
+
+ if (key != NULL) {
+ notify->key = key;
+ key = NULL;
+ }
+
+ INSIST(notify->transport == NULL);
+ if (transport != NULL) {
+ notify->transport = transport;
+ transport = NULL;
+ }
+
+ ISC_LIST_APPEND(zone->notifies, notify, link);
+ result = notify_send_queue(notify, startup);
+ if (result != ISC_R_SUCCESS) {
+ notify_destroy(notify, true);
+ }
+ if (!loggednotify) {
+ notify_log(zone, ISC_LOG_INFO,
+ "sending notifies (serial %u)", serial);
+ loggednotify = true;
+ }
+ }
+ UNLOCK_ZONE(zone);
+
+ if (notifytype == dns_notifytype_explicit) {
+ goto cleanup3;
+ }
+
+ /*
+ * Process NS RRset to generate notifies.
+ */
+
+ dns_rdataset_init(&nsrdset);
+ result = dns_db_findrdataset(zonedb, node, version, dns_rdatatype_ns,
+ dns_rdatatype_none, 0, &nsrdset, NULL);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup3;
+ }
+
+ result = dns_rdataset_first(&nsrdset);
+ while (result == ISC_R_SUCCESS) {
+ dns_notify_t *notify = NULL;
+
+ dns_rdataset_current(&nsrdset, &rdata);
+ result = dns_rdata_tostruct(&rdata, &ns, NULL);
+ RUNTIME_CHECK(result == ISC_R_SUCCESS);
+ dns_rdata_reset(&rdata);
+ /*
+ * Don't notify the primary server unless explicitly
+ * configured to do so.
+ */
+ if (!DNS_ZONE_OPTION(zone, DNS_ZONEOPT_NOTIFYTOSOA) &&
+ dns_name_compare(&primary, &ns.name) == 0)
+ {
+ result = dns_rdataset_next(&nsrdset);
+ continue;
+ }
+
+ if (!loggednotify) {
+ notify_log(zone, ISC_LOG_INFO,
+ "sending notifies (serial %u)", serial);
+ loggednotify = true;
+ }
+
+ LOCK_ZONE(zone);
+ isqueued = notify_isqueued(zone, flags, &ns.name, NULL, NULL,
+ NULL);
+ UNLOCK_ZONE(zone);
+ if (isqueued) {
+ result = dns_rdataset_next(&nsrdset);
+ continue;
+ }
+ result = notify_create(zone->mctx, flags, &notify);
+ if (result != ISC_R_SUCCESS) {
+ continue;
+ }
+ dns_zone_iattach(zone, &notify->zone);
+ dns_name_dup(&ns.name, zone->mctx, &notify->ns);
+ LOCK_ZONE(zone);
+ ISC_LIST_APPEND(zone->notifies, notify, link);
+ UNLOCK_ZONE(zone);
+ notify_find_address(notify);
+ result = dns_rdataset_next(&nsrdset);
+ }
+ dns_rdataset_disassociate(&nsrdset);
+
+cleanup3:
+ if (dns_name_dynamic(&primary)) {
+ dns_name_free(&primary, zone->mctx);
+ }
+cleanup2:
+ dns_db_detachnode(zonedb, &node);
+cleanup1:
+ dns_db_closeversion(zonedb, &version, false);
+ dns_db_detach(&zonedb);
+}
+
+/***
+ *** Private
+ ***/
+static isc_result_t
+create_query(dns_zone_t *zone, dns_rdatatype_t rdtype, dns_name_t *name,
+ dns_message_t **messagep) {
+ dns_message_t *message = NULL;
+ dns_name_t *qname = NULL;
+ dns_rdataset_t *qrdataset = NULL;
+ isc_result_t result;
+
+ dns_message_create(zone->mctx, DNS_MESSAGE_INTENTRENDER, &message);
+
+ message->opcode = dns_opcode_query;
+ message->rdclass = zone->rdclass;
+
+ result = dns_message_gettempname(message, &qname);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+
+ result = dns_message_gettemprdataset(message, &qrdataset);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+
+ /*
+ * Make question.
+ */
+ dns_name_clone(name, qname);
+ dns_rdataset_makequestion(qrdataset, zone->rdclass, rdtype);
+ ISC_LIST_APPEND(qname->list, qrdataset, link);
+ dns_message_addname(message, qname, DNS_SECTION_QUESTION);
+
+ *messagep = message;
+ return (ISC_R_SUCCESS);
+
+cleanup:
+ if (qname != NULL) {
+ dns_message_puttempname(message, &qname);
+ }
+ if (qrdataset != NULL) {
+ dns_message_puttemprdataset(message, &qrdataset);
+ }
+ dns_message_detach(&message);
+ return (result);
+}
+
+static isc_result_t
+add_opt(dns_message_t *message, uint16_t udpsize, bool reqnsid,
+ bool reqexpire) {
+ isc_result_t result;
+ dns_rdataset_t *rdataset = NULL;
+ dns_ednsopt_t ednsopts[DNS_EDNSOPTIONS];
+ int count = 0;
+
+ /* Set EDNS options if applicable. */
+ if (reqnsid) {
+ INSIST(count < DNS_EDNSOPTIONS);
+ ednsopts[count].code = DNS_OPT_NSID;
+ ednsopts[count].length = 0;
+ ednsopts[count].value = NULL;
+ count++;
+ }
+ if (reqexpire) {
+ INSIST(count < DNS_EDNSOPTIONS);
+ ednsopts[count].code = DNS_OPT_EXPIRE;
+ ednsopts[count].length = 0;
+ ednsopts[count].value = NULL;
+ count++;
+ }
+ result = dns_message_buildopt(message, &rdataset, 0, udpsize, 0,
+ ednsopts, count);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+
+ return (dns_message_setopt(message, rdataset));
+}
+
+/*
+ * Called when stub zone update is finished.
+ * Update zone refresh, retry, expire values accordingly with
+ * SOA received from primary, sync database to file, restart
+ * zone management timer.
+ */
+static void
+stub_finish_zone_update(dns_stub_t *stub, isc_time_t now) {
+ uint32_t refresh, retry, expire;
+ isc_result_t result;
+ isc_interval_t i;
+ unsigned int soacount;
+ dns_zone_t *zone = stub->zone;
+
+ /*
+ * Tidy up.
+ */
+ dns_db_closeversion(stub->db, &stub->version, true);
+ ZONEDB_LOCK(&zone->dblock, isc_rwlocktype_write);
+ if (zone->db == NULL) {
+ zone_attachdb(zone, stub->db);
+ }
+ result = zone_get_from_db(zone, zone->db, NULL, &soacount, NULL, NULL,
+ &refresh, &retry, &expire, NULL, NULL);
+ if (result == ISC_R_SUCCESS && soacount > 0U) {
+ zone->refresh = RANGE(refresh, zone->minrefresh,
+ zone->maxrefresh);
+ zone->retry = RANGE(retry, zone->minretry, zone->maxretry);
+ zone->expire = RANGE(expire, zone->refresh + zone->retry,
+ DNS_MAX_EXPIRE);
+ DNS_ZONE_SETFLAG(zone, DNS_ZONEFLG_HAVETIMERS);
+ }
+ ZONEDB_UNLOCK(&zone->dblock, isc_rwlocktype_write);
+ dns_db_detach(&stub->db);
+
+ DNS_ZONE_CLRFLAG(zone, DNS_ZONEFLG_REFRESH);
+ DNS_ZONE_SETFLAG(zone, DNS_ZONEFLG_LOADED);
+ DNS_ZONE_JITTER_ADD(&now, zone->refresh, &zone->refreshtime);
+ isc_interval_set(&i, zone->expire, 0);
+ DNS_ZONE_TIME_ADD(&now, zone->expire, &zone->expiretime);
+
+ if (zone->masterfile != NULL) {
+ zone_needdump(zone, 0);
+ }
+
+ zone_settimer(zone, &now);
+}
+
+/*
+ * Process answers for A and AAAA queries when
+ * resolving nameserver addresses for which glue
+ * was missing in a previous answer for a NS query.
+ */
+static void
+stub_glue_response_cb(isc_task_t *task, isc_event_t *event) {
+ const char me[] = "stub_glue_response_cb";
+ dns_requestevent_t *revent = (dns_requestevent_t *)event;
+ dns_stub_t *stub = NULL;
+ dns_message_t *msg = NULL;
+ dns_zone_t *zone = NULL;
+ char primary[ISC_SOCKADDR_FORMATSIZE];
+ char source[ISC_SOCKADDR_FORMATSIZE];
+ uint32_t addr_count, cnamecnt;
+ isc_result_t result;
+ isc_time_t now;
+ struct stub_glue_request *request;
+ struct stub_cb_args *cb_args;
+ dns_rdataset_t *addr_rdataset = NULL;
+ dns_dbnode_t *node = NULL;
+
+ UNUSED(task);
+
+ request = revent->ev_arg;
+ cb_args = request->args;
+ stub = cb_args->stub;
+ INSIST(DNS_STUB_VALID(stub));
+
+ zone = stub->zone;
+
+ ENTER;
+
+ TIME_NOW(&now);
+
+ LOCK_ZONE(zone);
+
+ if (DNS_ZONE_FLAG(zone, DNS_ZONEFLG_EXITING)) {
+ zone_debuglog(zone, me, 1, "exiting");
+ goto cleanup;
+ }
+
+ isc_sockaddr_format(&zone->primaryaddr, primary, sizeof(primary));
+ isc_sockaddr_format(&zone->sourceaddr, source, sizeof(source));
+
+ if (revent->result != ISC_R_SUCCESS) {
+ dns_zonemgr_unreachableadd(zone->zmgr, &zone->primaryaddr,
+ &zone->sourceaddr, &now);
+ dns_zone_log(zone, ISC_LOG_INFO,
+ "could not refresh stub from primary %s"
+ " (source %s): %s",
+ primary, source,
+ isc_result_totext(revent->result));
+ goto cleanup;
+ }
+
+ dns_message_create(zone->mctx, DNS_MESSAGE_INTENTPARSE, &msg);
+ result = dns_request_getresponse(revent->request, msg, 0);
+ if (result != ISC_R_SUCCESS) {
+ dns_zone_log(zone, ISC_LOG_INFO,
+ "refreshing stub: unable to parse response (%s)",
+ isc_result_totext(result));
+ goto cleanup;
+ }
+
+ /*
+ * Unexpected opcode.
+ */
+ if (msg->opcode != dns_opcode_query) {
+ char opcode[128];
+ isc_buffer_t rb;
+
+ isc_buffer_init(&rb, opcode, sizeof(opcode));
+ (void)dns_opcode_totext(msg->opcode, &rb);
+
+ dns_zone_log(zone, ISC_LOG_INFO,
+ "refreshing stub: "
+ "unexpected opcode (%.*s) from %s (source %s)",
+ (int)rb.used, opcode, primary, source);
+ goto cleanup;
+ }
+
+ /*
+ * Unexpected rcode.
+ */
+ if (msg->rcode != dns_rcode_noerror) {
+ char rcode[128];
+ isc_buffer_t rb;
+
+ isc_buffer_init(&rb, rcode, sizeof(rcode));
+ (void)dns_rcode_totext(msg->rcode, &rb);
+
+ dns_zone_log(zone, ISC_LOG_INFO,
+ "refreshing stub: "
+ "unexpected rcode (%.*s) from %s (source %s)",
+ (int)rb.used, rcode, primary, source);
+ goto cleanup;
+ }
+
+ /*
+ * We need complete messages.
+ */
+ if ((msg->flags & DNS_MESSAGEFLAG_TC) != 0) {
+ if (dns_request_usedtcp(revent->request)) {
+ dns_zone_log(zone, ISC_LOG_INFO,
+ "refreshing stub: truncated TCP "
+ "response from primary %s (source %s)",
+ primary, source);
+ }
+ goto cleanup;
+ }
+
+ /*
+ * If non-auth log.
+ */
+ if ((msg->flags & DNS_MESSAGEFLAG_AA) == 0) {
+ dns_zone_log(zone, ISC_LOG_INFO,
+ "refreshing stub: "
+ "non-authoritative answer from "
+ "primary %s (source %s)",
+ primary, source);
+ goto cleanup;
+ }
+
+ /*
+ * Sanity checks.
+ */
+ cnamecnt = message_count(msg, DNS_SECTION_ANSWER, dns_rdatatype_cname);
+ addr_count = message_count(msg, DNS_SECTION_ANSWER,
+ request->ipv4 ? dns_rdatatype_a
+ : dns_rdatatype_aaaa);
+
+ if (cnamecnt != 0) {
+ dns_zone_log(zone, ISC_LOG_INFO,
+ "refreshing stub: unexpected CNAME response "
+ "from primary %s (source %s)",
+ primary, source);
+ goto cleanup;
+ }
+
+ if (addr_count == 0) {
+ dns_zone_log(zone, ISC_LOG_INFO,
+ "refreshing stub: no %s records in response "
+ "from primary %s (source %s)",
+ request->ipv4 ? "A" : "AAAA", primary, source);
+ goto cleanup;
+ }
+ /*
+ * Extract A or AAAA RRset from message.
+ */
+ result = dns_message_findname(msg, DNS_SECTION_ANSWER, &request->name,
+ request->ipv4 ? dns_rdatatype_a
+ : dns_rdatatype_aaaa,
+ dns_rdatatype_none, NULL, &addr_rdataset);
+ if (result != ISC_R_SUCCESS) {
+ if (result != DNS_R_NXDOMAIN && result != DNS_R_NXRRSET) {
+ char namebuf[DNS_NAME_FORMATSIZE];
+ dns_name_format(&request->name, namebuf,
+ sizeof(namebuf));
+ dns_zone_log(
+ zone, ISC_LOG_INFO,
+ "refreshing stub: dns_message_findname(%s/%s) "
+ "failed (%s)",
+ namebuf, request->ipv4 ? "A" : "AAAA",
+ isc_result_totext(result));
+ }
+ goto cleanup;
+ }
+
+ result = dns_db_findnode(stub->db, &request->name, true, &node);
+ if (result != ISC_R_SUCCESS) {
+ dns_zone_log(zone, ISC_LOG_INFO,
+ "refreshing stub: "
+ "dns_db_findnode() failed: %s",
+ isc_result_totext(result));
+ goto cleanup;
+ }
+
+ result = dns_db_addrdataset(stub->db, node, stub->version, 0,
+ addr_rdataset, 0, NULL);
+ if (result != ISC_R_SUCCESS) {
+ dns_zone_log(zone, ISC_LOG_INFO,
+ "refreshing stub: "
+ "dns_db_addrdataset() failed: %s",
+ isc_result_totext(result));
+ }
+ dns_db_detachnode(stub->db, &node);
+
+cleanup:
+ if (msg != NULL) {
+ dns_message_detach(&msg);
+ }
+ isc_event_free(&event);
+ dns_name_free(&request->name, zone->mctx);
+ dns_request_destroy(&request->request);
+ isc_mem_put(zone->mctx, request, sizeof(*request));
+
+ /* If last request, release all related resources */
+ if (atomic_fetch_sub_release(&stub->pending_requests, 1) == 1) {
+ isc_mem_put(zone->mctx, cb_args, sizeof(*cb_args));
+ stub_finish_zone_update(stub, now);
+ UNLOCK_ZONE(zone);
+ stub->magic = 0;
+ dns_zone_idetach(&stub->zone);
+ INSIST(stub->db == NULL);
+ INSIST(stub->version == NULL);
+ isc_mem_put(stub->mctx, stub, sizeof(*stub));
+ } else {
+ UNLOCK_ZONE(zone);
+ }
+}
+
+/*
+ * Create and send an A or AAAA query to the primary
+ * server of the stub zone given.
+ */
+static isc_result_t
+stub_request_nameserver_address(struct stub_cb_args *args, bool ipv4,
+ const dns_name_t *name) {
+ dns_message_t *message = NULL;
+ dns_zone_t *zone;
+ isc_result_t result;
+ struct stub_glue_request *request;
+
+ zone = args->stub->zone;
+ request = isc_mem_get(zone->mctx, sizeof(*request));
+ request->request = NULL;
+ request->args = args;
+ request->name = (dns_name_t)DNS_NAME_INITEMPTY;
+ request->ipv4 = ipv4;
+ dns_name_dup(name, zone->mctx, &request->name);
+
+ result = create_query(zone, ipv4 ? dns_rdatatype_a : dns_rdatatype_aaaa,
+ &request->name, &message);
+ INSIST(result == ISC_R_SUCCESS);
+
+ if (!DNS_ZONE_FLAG(zone, DNS_ZONEFLG_NOEDNS)) {
+ result = add_opt(message, args->udpsize, args->reqnsid, false);
+ if (result != ISC_R_SUCCESS) {
+ zone_debuglog(zone, "stub_send_query", 1,
+ "unable to add opt record: %s",
+ isc_result_totext(result));
+ goto fail;
+ }
+ }
+
+ atomic_fetch_add_release(&args->stub->pending_requests, 1);
+
+ result = dns_request_create(
+ zone->view->requestmgr, message, &zone->sourceaddr,
+ &zone->primaryaddr, DNS_REQUESTOPT_TCP, args->tsig_key,
+ args->timeout * 3, args->timeout, 2, zone->task,
+ stub_glue_response_cb, request, &request->request);
+
+ if (result != ISC_R_SUCCESS) {
+ uint_fast32_t pr;
+ pr = atomic_fetch_sub_release(&args->stub->pending_requests, 1);
+ INSIST(pr > 1);
+ zone_debuglog(zone, "stub_send_query", 1,
+ "dns_request_create() failed: %s",
+ isc_result_totext(result));
+ goto fail;
+ }
+
+ dns_message_detach(&message);
+
+ return (ISC_R_SUCCESS);
+
+fail:
+ dns_name_free(&request->name, zone->mctx);
+ isc_mem_put(zone->mctx, request, sizeof(*request));
+
+ if (message != NULL) {
+ dns_message_detach(&message);
+ }
+
+ return (result);
+}
+
+static isc_result_t
+save_nsrrset(dns_message_t *message, dns_name_t *name,
+ struct stub_cb_args *cb_args, dns_db_t *db,
+ dns_dbversion_t *version) {
+ dns_rdataset_t *nsrdataset = NULL;
+ dns_rdataset_t *rdataset = NULL;
+ dns_dbnode_t *node = NULL;
+ dns_rdata_ns_t ns;
+ isc_result_t result;
+ dns_rdata_t rdata = DNS_RDATA_INIT;
+ bool has_glue = false;
+ dns_name_t *ns_name;
+ /*
+ * List of NS entries in answer, keep names that will be used
+ * to resolve missing A/AAAA glue for each entry.
+ */
+ dns_namelist_t ns_list;
+ ISC_LIST_INIT(ns_list);
+
+ /*
+ * Extract NS RRset from message.
+ */
+ result = dns_message_findname(message, DNS_SECTION_ANSWER, name,
+ dns_rdatatype_ns, dns_rdatatype_none,
+ NULL, &nsrdataset);
+ if (result != ISC_R_SUCCESS) {
+ goto done;
+ }
+
+ /*
+ * Add NS rdataset.
+ */
+ result = dns_db_findnode(db, name, true, &node);
+ if (result != ISC_R_SUCCESS) {
+ goto done;
+ }
+ result = dns_db_addrdataset(db, node, version, 0, nsrdataset, 0, NULL);
+ dns_db_detachnode(db, &node);
+ if (result != ISC_R_SUCCESS) {
+ goto done;
+ }
+ /*
+ * Add glue rdatasets.
+ */
+ for (result = dns_rdataset_first(nsrdataset); result == ISC_R_SUCCESS;
+ result = dns_rdataset_next(nsrdataset))
+ {
+ dns_rdataset_current(nsrdataset, &rdata);
+ result = dns_rdata_tostruct(&rdata, &ns, NULL);
+ RUNTIME_CHECK(result == ISC_R_SUCCESS);
+ dns_rdata_reset(&rdata);
+
+ if (!dns_name_issubdomain(&ns.name, name)) {
+ continue;
+ }
+ rdataset = NULL;
+ result = dns_message_findname(message, DNS_SECTION_ADDITIONAL,
+ &ns.name, dns_rdatatype_aaaa,
+ dns_rdatatype_none, NULL,
+ &rdataset);
+ if (result == ISC_R_SUCCESS) {
+ has_glue = true;
+ result = dns_db_findnode(db, &ns.name, true, &node);
+ if (result != ISC_R_SUCCESS) {
+ goto done;
+ }
+ result = dns_db_addrdataset(db, node, version, 0,
+ rdataset, 0, NULL);
+ dns_db_detachnode(db, &node);
+ if (result != ISC_R_SUCCESS) {
+ goto done;
+ }
+ }
+
+ rdataset = NULL;
+ result = dns_message_findname(
+ message, DNS_SECTION_ADDITIONAL, &ns.name,
+ dns_rdatatype_a, dns_rdatatype_none, NULL, &rdataset);
+ if (result == ISC_R_SUCCESS) {
+ has_glue = true;
+ result = dns_db_findnode(db, &ns.name, true, &node);
+ if (result != ISC_R_SUCCESS) {
+ goto done;
+ }
+ result = dns_db_addrdataset(db, node, version, 0,
+ rdataset, 0, NULL);
+ dns_db_detachnode(db, &node);
+ if (result != ISC_R_SUCCESS) {
+ goto done;
+ }
+ }
+
+ /*
+ * If no glue is found so far, we add the name to the list to
+ * resolve the A/AAAA glue later. If any glue is found in any
+ * iteration step, this list will be discarded and only the glue
+ * provided in this message will be used.
+ */
+ if (!has_glue && dns_name_issubdomain(&ns.name, name)) {
+ dns_name_t *tmp_name;
+ tmp_name = isc_mem_get(cb_args->stub->mctx,
+ sizeof(*tmp_name));
+ dns_name_init(tmp_name, NULL);
+ dns_name_dup(&ns.name, cb_args->stub->mctx, tmp_name);
+ ISC_LIST_APPEND(ns_list, tmp_name, link);
+ }
+ }
+
+ if (result != ISC_R_NOMORE) {
+ goto done;
+ }
+
+ /*
+ * If no glue records were found, we attempt to resolve A/AAAA
+ * for each NS entry found in the answer.
+ */
+ if (!has_glue) {
+ for (ns_name = ISC_LIST_HEAD(ns_list); ns_name != NULL;
+ ns_name = ISC_LIST_NEXT(ns_name, link))
+ {
+ /*
+ * Resolve NS IPv4 address/A.
+ */
+ result = stub_request_nameserver_address(cb_args, true,
+ ns_name);
+ if (result != ISC_R_SUCCESS) {
+ goto done;
+ }
+ /*
+ * Resolve NS IPv6 address/AAAA.
+ */
+ result = stub_request_nameserver_address(cb_args, false,
+ ns_name);
+ if (result != ISC_R_SUCCESS) {
+ goto done;
+ }
+ }
+ }
+
+ result = ISC_R_SUCCESS;
+
+done:
+ while ((ns_name = ISC_LIST_HEAD(ns_list)) != NULL) {
+ ISC_LIST_UNLINK(ns_list, ns_name, link);
+ dns_name_free(ns_name, cb_args->stub->mctx);
+ isc_mem_put(cb_args->stub->mctx, ns_name, sizeof(*ns_name));
+ }
+ return (result);
+}
+
+static void
+stub_callback(isc_task_t *task, isc_event_t *event) {
+ const char me[] = "stub_callback";
+ dns_requestevent_t *revent = (dns_requestevent_t *)event;
+ dns_stub_t *stub = NULL;
+ dns_message_t *msg = NULL;
+ dns_zone_t *zone = NULL;
+ char primary[ISC_SOCKADDR_FORMATSIZE];
+ char source[ISC_SOCKADDR_FORMATSIZE];
+ uint32_t nscnt, cnamecnt;
+ isc_result_t result;
+ isc_time_t now;
+ bool exiting = false;
+ unsigned int j;
+ struct stub_cb_args *cb_args;
+
+ cb_args = revent->ev_arg;
+ stub = cb_args->stub;
+ INSIST(DNS_STUB_VALID(stub));
+
+ UNUSED(task);
+
+ zone = stub->zone;
+
+ ENTER;
+
+ TIME_NOW(&now);
+
+ LOCK_ZONE(zone);
+
+ if (DNS_ZONE_FLAG(zone, DNS_ZONEFLG_EXITING)) {
+ goto exiting;
+ }
+
+ isc_sockaddr_format(&zone->primaryaddr, primary, sizeof(primary));
+ isc_sockaddr_format(&zone->sourceaddr, source, sizeof(source));
+
+ switch (revent->result) {
+ case ISC_R_SUCCESS:
+ break;
+ case ISC_R_SHUTTINGDOWN:
+ goto exiting;
+ case ISC_R_TIMEDOUT:
+ if (!DNS_ZONE_FLAG(zone, DNS_ZONEFLG_NOEDNS)) {
+ DNS_ZONE_SETFLAG(zone, DNS_ZONEFLG_NOEDNS);
+ dns_zone_log(zone, ISC_LOG_DEBUG(1),
+ "refreshing stub: timeout retrying "
+ "without EDNS primary %s (source %s)",
+ primary, source);
+ goto same_primary;
+ }
+ FALLTHROUGH;
+ default:
+ dns_zonemgr_unreachableadd(zone->zmgr, &zone->primaryaddr,
+ &zone->sourceaddr, &now);
+ dns_zone_log(zone, ISC_LOG_INFO,
+ "could not refresh stub from primary "
+ "%s (source %s): %s",
+ primary, source,
+ isc_result_totext(revent->result));
+ goto next_primary;
+ }
+
+ dns_message_create(zone->mctx, DNS_MESSAGE_INTENTPARSE, &msg);
+
+ result = dns_request_getresponse(revent->request, msg, 0);
+ if (result != ISC_R_SUCCESS) {
+ goto next_primary;
+ }
+
+ /*
+ * Unexpected opcode.
+ */
+ if (msg->opcode != dns_opcode_query) {
+ char opcode[128];
+ isc_buffer_t rb;
+
+ isc_buffer_init(&rb, opcode, sizeof(opcode));
+ (void)dns_opcode_totext(msg->opcode, &rb);
+
+ dns_zone_log(zone, ISC_LOG_INFO,
+ "refreshing stub: "
+ "unexpected opcode (%.*s) from %s (source %s)",
+ (int)rb.used, opcode, primary, source);
+ goto next_primary;
+ }
+
+ /*
+ * Unexpected rcode.
+ */
+ if (msg->rcode != dns_rcode_noerror) {
+ char rcode[128];
+ isc_buffer_t rb;
+
+ isc_buffer_init(&rb, rcode, sizeof(rcode));
+ (void)dns_rcode_totext(msg->rcode, &rb);
+
+ if (!DNS_ZONE_FLAG(zone, DNS_ZONEFLG_NOEDNS) &&
+ (msg->rcode == dns_rcode_servfail ||
+ msg->rcode == dns_rcode_notimp ||
+ (msg->rcode == dns_rcode_formerr && msg->opt == NULL)))
+ {
+ dns_zone_log(zone, ISC_LOG_DEBUG(1),
+ "refreshing stub: rcode (%.*s) retrying "
+ "without EDNS primary %s (source %s)",
+ (int)rb.used, rcode, primary, source);
+ DNS_ZONE_SETFLAG(zone, DNS_ZONEFLG_NOEDNS);
+ goto same_primary;
+ }
+
+ dns_zone_log(zone, ISC_LOG_INFO,
+ "refreshing stub: "
+ "unexpected rcode (%.*s) from %s (source %s)",
+ (int)rb.used, rcode, primary, source);
+ goto next_primary;
+ }
+
+ /*
+ * We need complete messages.
+ */
+ if ((msg->flags & DNS_MESSAGEFLAG_TC) != 0) {
+ if (dns_request_usedtcp(revent->request)) {
+ dns_zone_log(zone, ISC_LOG_INFO,
+ "refreshing stub: truncated TCP "
+ "response from primary %s (source %s)",
+ primary, source);
+ goto next_primary;
+ }
+ DNS_ZONE_SETFLAG(zone, DNS_ZONEFLG_USEVC);
+ goto same_primary;
+ }
+
+ /*
+ * If non-auth log and next primary.
+ */
+ if ((msg->flags & DNS_MESSAGEFLAG_AA) == 0) {
+ dns_zone_log(zone, ISC_LOG_INFO,
+ "refreshing stub: "
+ "non-authoritative answer from "
+ "primary %s (source %s)",
+ primary, source);
+ goto next_primary;
+ }
+
+ /*
+ * Sanity checks.
+ */
+ cnamecnt = message_count(msg, DNS_SECTION_ANSWER, dns_rdatatype_cname);
+ nscnt = message_count(msg, DNS_SECTION_ANSWER, dns_rdatatype_ns);
+
+ if (cnamecnt != 0) {
+ dns_zone_log(zone, ISC_LOG_INFO,
+ "refreshing stub: unexpected CNAME response "
+ "from primary %s (source %s)",
+ primary, source);
+ goto next_primary;
+ }
+
+ if (nscnt == 0) {
+ dns_zone_log(zone, ISC_LOG_INFO,
+ "refreshing stub: no NS records in response "
+ "from primary %s (source %s)",
+ primary, source);
+ goto next_primary;
+ }
+
+ atomic_fetch_add(&stub->pending_requests, 1);
+
+ /*
+ * Save answer.
+ */
+ result = save_nsrrset(msg, &zone->origin, cb_args, stub->db,
+ stub->version);
+ if (result != ISC_R_SUCCESS) {
+ dns_zone_log(zone, ISC_LOG_INFO,
+ "refreshing stub: unable to save NS records "
+ "from primary %s (source %s)",
+ primary, source);
+ goto next_primary;
+ }
+
+ dns_message_detach(&msg);
+ isc_event_free(&event);
+ dns_request_destroy(&zone->request);
+
+ /*
+ * Check to see if there are no outstanding requests and
+ * finish off if that is so.
+ */
+ if (atomic_fetch_sub(&stub->pending_requests, 1) == 1) {
+ isc_mem_put(zone->mctx, cb_args, sizeof(*cb_args));
+ stub_finish_zone_update(stub, now);
+ goto free_stub;
+ }
+
+ UNLOCK_ZONE(zone);
+ return;
+
+exiting:
+ zone_debuglog(zone, me, 1, "exiting");
+ exiting = true;
+
+next_primary:
+ isc_mem_put(zone->mctx, cb_args, sizeof(*cb_args));
+ if (stub->version != NULL) {
+ dns_db_closeversion(stub->db, &stub->version, false);
+ }
+ if (stub->db != NULL) {
+ dns_db_detach(&stub->db);
+ }
+ if (msg != NULL) {
+ dns_message_detach(&msg);
+ }
+ isc_event_free(&event);
+ dns_request_destroy(&zone->request);
+ /*
+ * Skip to next failed / untried primary.
+ */
+ do {
+ zone->curprimary++;
+ } while (zone->curprimary < zone->primariescnt &&
+ zone->primariesok[zone->curprimary]);
+ DNS_ZONE_CLRFLAG(zone, DNS_ZONEFLG_NOEDNS);
+ if (exiting || zone->curprimary >= zone->primariescnt) {
+ bool done = true;
+ if (!exiting &&
+ DNS_ZONE_OPTION(zone, DNS_ZONEOPT_USEALTXFRSRC) &&
+ !DNS_ZONE_FLAG(zone, DNS_ZONEFLG_USEALTXFRSRC))
+ {
+ /*
+ * Did we get a good answer from all the primaries?
+ */
+ for (j = 0; j < zone->primariescnt; j++) {
+ if (!zone->primariesok[j]) {
+ {
+ done = false;
+ break;
+ }
+ }
+ }
+ } else {
+ done = true;
+ }
+ if (!done) {
+ zone->curprimary = 0;
+ /*
+ * Find the next failed primary.
+ */
+ while (zone->curprimary < zone->primariescnt &&
+ zone->primariesok[zone->curprimary])
+ {
+ zone->curprimary++;
+ }
+ DNS_ZONE_SETFLAG(zone, DNS_ZONEFLG_USEALTXFRSRC);
+ } else {
+ DNS_ZONE_CLRFLAG(zone, DNS_ZONEFLG_REFRESH);
+
+ zone_settimer(zone, &now);
+ goto free_stub;
+ }
+ }
+ queue_soa_query(zone);
+ goto free_stub;
+
+same_primary:
+ isc_mem_put(zone->mctx, cb_args, sizeof(*cb_args));
+ if (msg != NULL) {
+ dns_message_detach(&msg);
+ }
+ isc_event_free(&event);
+ dns_request_destroy(&zone->request);
+ ns_query(zone, NULL, stub);
+ UNLOCK_ZONE(zone);
+ goto done;
+
+free_stub:
+ UNLOCK_ZONE(zone);
+ stub->magic = 0;
+ dns_zone_idetach(&stub->zone);
+ INSIST(stub->db == NULL);
+ INSIST(stub->version == NULL);
+ isc_mem_put(stub->mctx, stub, sizeof(*stub));
+
+done:
+ INSIST(event == NULL);
+ return;
+}
+
+/*
+ * Get the EDNS EXPIRE option from the response and if it exists trim
+ * expire to be not more than it.
+ */
+static void
+get_edns_expire(dns_zone_t *zone, dns_message_t *message, uint32_t *expirep) {
+ isc_result_t result;
+ uint32_t expire;
+ dns_rdata_t rdata = DNS_RDATA_INIT;
+ isc_buffer_t optbuf;
+ uint16_t optcode;
+ uint16_t optlen;
+
+ REQUIRE(expirep != NULL);
+ REQUIRE(message != NULL);
+
+ if (message->opt == NULL) {
+ return;
+ }
+
+ result = dns_rdataset_first(message->opt);
+ if (result == ISC_R_SUCCESS) {
+ dns_rdataset_current(message->opt, &rdata);
+ isc_buffer_init(&optbuf, rdata.data, rdata.length);
+ isc_buffer_add(&optbuf, rdata.length);
+ while (isc_buffer_remaininglength(&optbuf) >= 4) {
+ optcode = isc_buffer_getuint16(&optbuf);
+ optlen = isc_buffer_getuint16(&optbuf);
+ /*
+ * A EDNS EXPIRE response has a length of 4.
+ */
+ if (optcode != DNS_OPT_EXPIRE || optlen != 4) {
+ isc_buffer_forward(&optbuf, optlen);
+ continue;
+ }
+ expire = isc_buffer_getuint32(&optbuf);
+ dns_zone_log(zone, ISC_LOG_DEBUG(1),
+ "got EDNS EXPIRE of %u", expire);
+ /*
+ * Trim *expirep?
+ */
+ if (expire < *expirep) {
+ *expirep = expire;
+ }
+ break;
+ }
+ }
+}
+
+/*
+ * Set the file modification time zone->expire seconds before expiretime.
+ */
+static void
+setmodtime(dns_zone_t *zone, isc_time_t *expiretime) {
+ isc_result_t result;
+ isc_time_t when;
+ isc_interval_t i;
+
+ isc_interval_set(&i, zone->expire, 0);
+ result = isc_time_subtract(expiretime, &i, &when);
+ if (result != ISC_R_SUCCESS) {
+ return;
+ }
+
+ result = ISC_R_FAILURE;
+ if (zone->journal != NULL) {
+ result = isc_file_settime(zone->journal, &when);
+ }
+ if (result == ISC_R_SUCCESS &&
+ !DNS_ZONE_FLAG(zone, DNS_ZONEFLG_NEEDDUMP) &&
+ !DNS_ZONE_FLAG(zone, DNS_ZONEFLG_DUMPING))
+ {
+ result = isc_file_settime(zone->masterfile, &when);
+ } else if (result != ISC_R_SUCCESS) {
+ result = isc_file_settime(zone->masterfile, &when);
+ }
+
+ /*
+ * Someone removed the file from underneath us!
+ */
+ if (result == ISC_R_FILENOTFOUND) {
+ zone_needdump(zone, DNS_DUMP_DELAY);
+ } else if (result != ISC_R_SUCCESS) {
+ dns_zone_log(zone, ISC_LOG_ERROR,
+ "refresh: could not set "
+ "file modification time of '%s': %s",
+ zone->masterfile, isc_result_totext(result));
+ }
+}
+
+/*
+ * An SOA query has finished (successfully or not).
+ */
+static void
+refresh_callback(isc_task_t *task, isc_event_t *event) {
+ const char me[] = "refresh_callback";
+ dns_requestevent_t *revent = (dns_requestevent_t *)event;
+ dns_zone_t *zone;
+ dns_message_t *msg = NULL;
+ uint32_t soacnt, cnamecnt, soacount, nscount;
+ isc_time_t now;
+ char primary[ISC_SOCKADDR_FORMATSIZE];
+ char source[ISC_SOCKADDR_FORMATSIZE];
+ dns_rdataset_t *rdataset = NULL;
+ dns_rdata_t rdata = DNS_RDATA_INIT;
+ dns_rdata_soa_t soa;
+ isc_result_t result;
+ uint32_t serial, oldserial = 0;
+ unsigned int j;
+ bool do_queue_xfrin = false;
+
+ zone = revent->ev_arg;
+ INSIST(DNS_ZONE_VALID(zone));
+
+ UNUSED(task);
+
+ ENTER;
+
+ TIME_NOW(&now);
+
+ LOCK_ZONE(zone);
+
+ if (DNS_ZONE_FLAG(zone, DNS_ZONEFLG_EXITING)) {
+ goto exiting;
+ }
+
+ /*
+ * If timeout, log and try the next primary
+ */
+ isc_sockaddr_format(&zone->primaryaddr, primary, sizeof(primary));
+ isc_sockaddr_format(&zone->sourceaddr, source, sizeof(source));
+
+ switch (revent->result) {
+ case ISC_R_SUCCESS:
+ break;
+ case ISC_R_SHUTTINGDOWN:
+ goto exiting;
+ case ISC_R_TIMEDOUT:
+ if (!DNS_ZONE_FLAG(zone, DNS_ZONEFLG_NOEDNS)) {
+ DNS_ZONE_SETFLAG(zone, DNS_ZONEFLG_NOEDNS);
+ dns_zone_log(zone, ISC_LOG_DEBUG(1),
+ "refresh: timeout retrying without EDNS "
+ "primary %s (source %s)",
+ primary, source);
+ goto same_primary;
+ } else if (!dns_request_usedtcp(revent->request)) {
+ dns_zone_log(zone, ISC_LOG_INFO,
+ "refresh: retry limit for "
+ "primary %s exceeded (source %s)",
+ primary, source);
+ /* Try with secondary with TCP. */
+ if ((zone->type == dns_zone_secondary ||
+ zone->type == dns_zone_mirror ||
+ zone->type == dns_zone_redirect) &&
+ DNS_ZONE_OPTION(zone, DNS_ZONEOPT_TRYTCPREFRESH))
+ {
+ if (!dns_zonemgr_unreachable(
+ zone->zmgr, &zone->primaryaddr,
+ &zone->sourceaddr, &now))
+ {
+ DNS_ZONE_SETFLAG(
+ zone,
+ DNS_ZONEFLG_SOABEFOREAXFR);
+ goto tcp_transfer;
+ }
+ dns_zone_log(zone, ISC_LOG_DEBUG(1),
+ "refresh: skipped tcp fallback "
+ "as primary %s (source %s) is "
+ "unreachable (cached)",
+ primary, source);
+ }
+ goto next_primary;
+ }
+ FALLTHROUGH;
+ default:
+ dns_zone_log(zone, ISC_LOG_INFO,
+ "refresh: failure trying primary "
+ "%s (source %s): %s",
+ primary, source,
+ isc_result_totext(revent->result));
+ goto next_primary;
+ }
+
+ dns_message_create(zone->mctx, DNS_MESSAGE_INTENTPARSE, &msg);
+ result = dns_request_getresponse(revent->request, msg, 0);
+ if (result != ISC_R_SUCCESS) {
+ dns_zone_log(zone, ISC_LOG_INFO,
+ "refresh: failure trying primary "
+ "%s (source %s): %s",
+ primary, source, isc_result_totext(result));
+ goto next_primary;
+ }
+
+ /*
+ * Unexpected opcode.
+ */
+ if (msg->opcode != dns_opcode_query) {
+ char opcode[128];
+ isc_buffer_t rb;
+
+ isc_buffer_init(&rb, opcode, sizeof(opcode));
+ (void)dns_opcode_totext(msg->opcode, &rb);
+
+ dns_zone_log(zone, ISC_LOG_INFO,
+ "refresh: "
+ "unexpected opcode (%.*s) from %s (source %s)",
+ (int)rb.used, opcode, primary, source);
+ goto next_primary;
+ }
+
+ /*
+ * Unexpected rcode.
+ */
+ if (msg->rcode != dns_rcode_noerror) {
+ char rcode[128];
+ isc_buffer_t rb;
+
+ isc_buffer_init(&rb, rcode, sizeof(rcode));
+ (void)dns_rcode_totext(msg->rcode, &rb);
+
+ if (!DNS_ZONE_FLAG(zone, DNS_ZONEFLG_NOEDNS) &&
+ (msg->rcode == dns_rcode_servfail ||
+ msg->rcode == dns_rcode_notimp ||
+ (msg->rcode == dns_rcode_formerr && msg->opt == NULL)))
+ {
+ dns_zone_log(zone, ISC_LOG_DEBUG(1),
+ "refresh: rcode (%.*s) retrying without "
+ "EDNS primary %s (source %s)",
+ (int)rb.used, rcode, primary, source);
+ DNS_ZONE_SETFLAG(zone, DNS_ZONEFLG_NOEDNS);
+ goto same_primary;
+ }
+ if (!DNS_ZONE_FLAG(zone, DNS_ZONEFLG_NOEDNS) &&
+ msg->rcode == dns_rcode_badvers)
+ {
+ dns_zone_log(zone, ISC_LOG_DEBUG(1),
+ "refresh: rcode (%.*s) retrying without "
+ "EDNS EXPIRE OPTION primary %s "
+ "(source %s)",
+ (int)rb.used, rcode, primary, source);
+ DNS_ZONE_SETFLAG(zone, DNS_ZONEFLG_NOEDNS);
+ goto same_primary;
+ }
+ dns_zone_log(zone, ISC_LOG_INFO,
+ "refresh: unexpected rcode (%.*s) from "
+ "primary %s (source %s)",
+ (int)rb.used, rcode, primary, source);
+ /*
+ * Perhaps AXFR/IXFR is allowed even if SOA queries aren't.
+ */
+ if (msg->rcode == dns_rcode_refused &&
+ (zone->type == dns_zone_secondary ||
+ zone->type == dns_zone_mirror ||
+ zone->type == dns_zone_redirect))
+ {
+ goto tcp_transfer;
+ }
+ goto next_primary;
+ }
+
+ /*
+ * If truncated punt to zone transfer which will query again.
+ */
+ if ((msg->flags & DNS_MESSAGEFLAG_TC) != 0) {
+ if (zone->type == dns_zone_secondary ||
+ zone->type == dns_zone_mirror ||
+ zone->type == dns_zone_redirect)
+ {
+ dns_zone_log(zone, ISC_LOG_INFO,
+ "refresh: truncated UDP answer, "
+ "initiating TCP zone xfer "
+ "for primary %s (source %s)",
+ primary, source);
+ DNS_ZONE_SETFLAG(zone, DNS_ZONEFLG_SOABEFOREAXFR);
+ goto tcp_transfer;
+ } else {
+ INSIST(zone->type == dns_zone_stub);
+ if (dns_request_usedtcp(revent->request)) {
+ dns_zone_log(zone, ISC_LOG_INFO,
+ "refresh: truncated TCP response "
+ "from primary %s (source %s)",
+ primary, source);
+ goto next_primary;
+ }
+ DNS_ZONE_SETFLAG(zone, DNS_ZONEFLG_USEVC);
+ goto same_primary;
+ }
+ }
+
+ /*
+ * If non-auth, log and try the next primary
+ */
+ if ((msg->flags & DNS_MESSAGEFLAG_AA) == 0) {
+ dns_zone_log(zone, ISC_LOG_INFO,
+ "refresh: non-authoritative answer from "
+ "primary %s (source %s)",
+ primary, source);
+ goto next_primary;
+ }
+
+ cnamecnt = message_count(msg, DNS_SECTION_ANSWER, dns_rdatatype_cname);
+ soacnt = message_count(msg, DNS_SECTION_ANSWER, dns_rdatatype_soa);
+ nscount = message_count(msg, DNS_SECTION_AUTHORITY, dns_rdatatype_ns);
+ soacount = message_count(msg, DNS_SECTION_AUTHORITY, dns_rdatatype_soa);
+
+ /*
+ * There should not be a CNAME record at top of zone.
+ */
+ if (cnamecnt != 0) {
+ dns_zone_log(zone, ISC_LOG_INFO,
+ "refresh: CNAME at top of zone "
+ "in primary %s (source %s)",
+ primary, source);
+ goto next_primary;
+ }
+
+ /*
+ * If referral, log and try the next primary;
+ */
+ if (soacnt == 0 && soacount == 0 && nscount != 0) {
+ dns_zone_log(zone, ISC_LOG_INFO,
+ "refresh: referral response "
+ "from primary %s (source %s)",
+ primary, source);
+ goto next_primary;
+ }
+
+ /*
+ * If nodata, log and try the next primary;
+ */
+ if (soacnt == 0 && (nscount == 0 || soacount != 0)) {
+ dns_zone_log(zone, ISC_LOG_INFO,
+ "refresh: NODATA response "
+ "from primary %s (source %s)",
+ primary, source);
+ goto next_primary;
+ }
+
+ /*
+ * Only one soa at top of zone.
+ */
+ if (soacnt != 1) {
+ dns_zone_log(zone, ISC_LOG_INFO,
+ "refresh: answer SOA count (%d) != 1 "
+ "from primary %s (source %s)",
+ soacnt, primary, source);
+ goto next_primary;
+ }
+
+ /*
+ * Extract serial
+ */
+ rdataset = NULL;
+ result = dns_message_findname(msg, DNS_SECTION_ANSWER, &zone->origin,
+ dns_rdatatype_soa, dns_rdatatype_none,
+ NULL, &rdataset);
+ if (result != ISC_R_SUCCESS) {
+ dns_zone_log(zone, ISC_LOG_INFO,
+ "refresh: unable to get SOA record "
+ "from primary %s (source %s)",
+ primary, source);
+ goto next_primary;
+ }
+
+ result = dns_rdataset_first(rdataset);
+ if (result != ISC_R_SUCCESS) {
+ dns_zone_log(zone, ISC_LOG_INFO,
+ "refresh: dns_rdataset_first() failed");
+ goto next_primary;
+ }
+
+ dns_rdataset_current(rdataset, &rdata);
+ result = dns_rdata_tostruct(&rdata, &soa, NULL);
+ RUNTIME_CHECK(result == ISC_R_SUCCESS);
+
+ serial = soa.serial;
+ if (DNS_ZONE_FLAG(zone, DNS_ZONEFLG_LOADED)) {
+ unsigned int dbsoacount;
+ result = zone_get_from_db(zone, zone->db, NULL, &dbsoacount,
+ NULL, &oldserial, NULL, NULL, NULL,
+ NULL, NULL);
+ RUNTIME_CHECK(result == ISC_R_SUCCESS);
+ RUNTIME_CHECK(dbsoacount > 0U);
+ zone_debuglog(zone, me, 1, "serial: new %u, old %u", serial,
+ oldserial);
+ } else {
+ zone_debuglog(zone, me, 1, "serial: new %u, old not loaded",
+ serial);
+ }
+
+ if (!DNS_ZONE_FLAG(zone, DNS_ZONEFLG_LOADED) ||
+ DNS_ZONE_FLAG(zone, DNS_ZONEFLG_FORCEXFER) ||
+ isc_serial_gt(serial, oldserial))
+ {
+ if (dns_zonemgr_unreachable(zone->zmgr, &zone->primaryaddr,
+ &zone->sourceaddr, &now))
+ {
+ dns_zone_log(zone, ISC_LOG_INFO,
+ "refresh: skipping %s as primary %s "
+ "(source %s) is unreachable (cached)",
+ (zone->type == dns_zone_secondary ||
+ zone->type == dns_zone_mirror ||
+ zone->type == dns_zone_redirect)
+ ? "zone transfer"
+ : "NS query",
+ primary, source);
+ goto next_primary;
+ }
+ tcp_transfer:
+ isc_event_free(&event);
+ dns_request_destroy(&zone->request);
+ if (zone->type == dns_zone_secondary ||
+ zone->type == dns_zone_mirror ||
+ zone->type == dns_zone_redirect)
+ {
+ do_queue_xfrin = true;
+ } else {
+ INSIST(zone->type == dns_zone_stub);
+ ns_query(zone, rdataset, NULL);
+ }
+ if (msg != NULL) {
+ dns_message_detach(&msg);
+ }
+ } else if (isc_serial_eq(soa.serial, oldserial)) {
+ isc_time_t expiretime;
+ uint32_t expire;
+
+ /*
+ * Compute the new expire time based on this response.
+ */
+ expire = zone->expire;
+ get_edns_expire(zone, msg, &expire);
+ DNS_ZONE_TIME_ADD(&now, expire, &expiretime);
+
+ /*
+ * Has the expire time improved?
+ */
+ if (isc_time_compare(&expiretime, &zone->expiretime) > 0) {
+ zone->expiretime = expiretime;
+ if (zone->masterfile != NULL) {
+ setmodtime(zone, &expiretime);
+ }
+ }
+
+ DNS_ZONE_JITTER_ADD(&now, zone->refresh, &zone->refreshtime);
+ zone->primariesok[zone->curprimary] = true;
+ goto next_primary;
+ } else {
+ if (!DNS_ZONE_OPTION(zone, DNS_ZONEOPT_MULTIMASTER)) {
+ dns_zone_log(zone, ISC_LOG_INFO,
+ "serial number (%u) "
+ "received from primary %s < ours (%u)",
+ soa.serial, primary, oldserial);
+ } else {
+ zone_debuglog(zone, me, 1, "ahead");
+ }
+ zone->primariesok[zone->curprimary] = true;
+ goto next_primary;
+ }
+ if (msg != NULL) {
+ dns_message_detach(&msg);
+ }
+ goto detach;
+
+next_primary:
+ if (msg != NULL) {
+ dns_message_detach(&msg);
+ }
+ isc_event_free(&event);
+ dns_request_destroy(&zone->request);
+ /*
+ * Skip to next failed / untried primary.
+ */
+ do {
+ zone->curprimary++;
+ } while (zone->curprimary < zone->primariescnt &&
+ zone->primariesok[zone->curprimary]);
+ DNS_ZONE_CLRFLAG(zone, DNS_ZONEFLG_NOEDNS);
+ if (zone->curprimary >= zone->primariescnt) {
+ bool done = true;
+ if (DNS_ZONE_OPTION(zone, DNS_ZONEOPT_USEALTXFRSRC) &&
+ !DNS_ZONE_FLAG(zone, DNS_ZONEFLG_USEALTXFRSRC))
+ {
+ /*
+ * Did we get a good answer from all the primaries?
+ */
+ for (j = 0; j < zone->primariescnt; j++) {
+ if (!zone->primariesok[j]) {
+ {
+ done = false;
+ break;
+ }
+ }
+ }
+ } else {
+ done = true;
+ }
+ if (!done) {
+ DNS_ZONE_SETFLAG(zone, DNS_ZONEFLG_USEALTXFRSRC);
+ zone->curprimary = 0;
+ /*
+ * Find the next failed primary.
+ */
+ while (zone->curprimary < zone->primariescnt &&
+ zone->primariesok[zone->curprimary])
+ {
+ zone->curprimary++;
+ }
+ goto requeue;
+ }
+ DNS_ZONE_CLRFLAG(zone, DNS_ZONEFLG_REFRESH);
+ if (DNS_ZONE_FLAG(zone, DNS_ZONEFLG_NEEDREFRESH)) {
+ DNS_ZONE_CLRFLAG(zone, DNS_ZONEFLG_NEEDREFRESH);
+ zone->refreshtime = now;
+ }
+ DNS_ZONE_CLRFLAG(zone, DNS_ZONEFLG_USEALTXFRSRC);
+ zone_settimer(zone, &now);
+ goto detach;
+ }
+
+requeue:
+ queue_soa_query(zone);
+ goto detach;
+
+exiting:
+ isc_event_free(&event);
+ dns_request_destroy(&zone->request);
+ goto detach;
+
+same_primary:
+ if (msg != NULL) {
+ dns_message_detach(&msg);
+ }
+ isc_event_free(&event);
+ dns_request_destroy(&zone->request);
+ queue_soa_query(zone);
+
+detach:
+ UNLOCK_ZONE(zone);
+ if (do_queue_xfrin) {
+ queue_xfrin(zone);
+ }
+ dns_zone_idetach(&zone);
+ return;
+}
+
+static void
+queue_soa_query(dns_zone_t *zone) {
+ const char me[] = "queue_soa_query";
+ isc_event_t *e;
+ dns_zone_t *dummy = NULL;
+ isc_result_t result;
+
+ ENTER;
+ /*
+ * Locked by caller
+ */
+ REQUIRE(LOCKED_ZONE(zone));
+
+ if (DNS_ZONE_FLAG(zone, DNS_ZONEFLG_EXITING)) {
+ cancel_refresh(zone);
+ return;
+ }
+
+ e = isc_event_allocate(zone->mctx, NULL, DNS_EVENT_ZONE, soa_query,
+ zone, sizeof(isc_event_t));
+
+ /*
+ * Attach so that we won't clean up
+ * until the event is delivered.
+ */
+ zone_iattach(zone, &dummy);
+
+ e->ev_arg = zone;
+ e->ev_sender = NULL;
+ result = isc_ratelimiter_enqueue(zone->zmgr->refreshrl, zone->task, &e);
+ if (result != ISC_R_SUCCESS) {
+ zone_idetach(&dummy);
+ isc_event_free(&e);
+ cancel_refresh(zone);
+ }
+}
+
+static void
+soa_query(isc_task_t *task, isc_event_t *event) {
+ const char me[] = "soa_query";
+ isc_result_t result = ISC_R_FAILURE;
+ dns_message_t *message = NULL;
+ dns_zone_t *zone = event->ev_arg;
+ dns_zone_t *dummy = NULL;
+ isc_netaddr_t primaryip;
+ dns_tsigkey_t *key = NULL;
+ dns_transport_t *transport = NULL;
+ uint32_t options;
+ bool cancel = true;
+ int timeout;
+ bool have_xfrsource = false, reqnsid, reqexpire;
+ uint16_t udpsize = SEND_BUFFER_SIZE;
+ bool do_queue_xfrin = false;
+
+ REQUIRE(DNS_ZONE_VALID(zone));
+
+ UNUSED(task);
+
+ ENTER;
+
+ LOCK_ZONE(zone);
+ if (((event->ev_attributes & ISC_EVENTATTR_CANCELED) != 0) ||
+ DNS_ZONE_FLAG(zone, DNS_ZONEFLG_EXITING) ||
+ zone->view->requestmgr == NULL)
+ {
+ if (DNS_ZONE_FLAG(zone, DNS_ZONEFLG_EXITING)) {
+ cancel = false;
+ }
+ goto cleanup;
+ }
+
+again:
+ INSIST(zone->primariescnt > 0);
+ INSIST(zone->curprimary < zone->primariescnt);
+
+ zone->primaryaddr = zone->primaries[zone->curprimary];
+
+ isc_netaddr_fromsockaddr(&primaryip, &zone->primaryaddr);
+ /*
+ * First, look for a tsig key in the primaries statement, then
+ * try for a server key.
+ */
+ if ((zone->primarykeynames != NULL) &&
+ (zone->primarykeynames[zone->curprimary] != NULL))
+ {
+ dns_view_t *view = dns_zone_getview(zone);
+ dns_name_t *keyname = zone->primarykeynames[zone->curprimary];
+ result = dns_view_gettsig(view, keyname, &key);
+ if (result != ISC_R_SUCCESS) {
+ char namebuf[DNS_NAME_FORMATSIZE];
+ dns_name_format(keyname, namebuf, sizeof(namebuf));
+ dns_zone_log(zone, ISC_LOG_ERROR,
+ "unable to find key: %s", namebuf);
+ goto skip_primary;
+ }
+ }
+ if (key == NULL) {
+ result = dns_view_getpeertsig(zone->view, &primaryip, &key);
+ if (result != ISC_R_SUCCESS && result != ISC_R_NOTFOUND) {
+ char addrbuf[ISC_NETADDR_FORMATSIZE];
+ isc_netaddr_format(&primaryip, addrbuf,
+ sizeof(addrbuf));
+ dns_zone_log(zone, ISC_LOG_ERROR,
+ "unable to find TSIG key for %s", addrbuf);
+ goto skip_primary;
+ }
+ }
+
+ if ((zone->primarytlsnames != NULL) &&
+ (zone->primarytlsnames[zone->curprimary] != NULL))
+ {
+ dns_view_t *view = dns_zone_getview(zone);
+ dns_name_t *tlsname = zone->primarytlsnames[zone->curprimary];
+ result = dns_view_gettransport(view, DNS_TRANSPORT_TLS, tlsname,
+ &transport);
+ if (result != ISC_R_SUCCESS) {
+ char namebuf[DNS_NAME_FORMATSIZE];
+ dns_name_format(tlsname, namebuf, sizeof(namebuf));
+ dns_zone_log(zone, ISC_LOG_ERROR,
+ "unable to find TLS configuration: %s",
+ namebuf);
+ goto skip_primary;
+ }
+ }
+
+ options = DNS_ZONE_FLAG(zone, DNS_ZONEFLG_USEVC) ? DNS_REQUESTOPT_TCP
+ : 0;
+ reqnsid = zone->view->requestnsid;
+ reqexpire = zone->requestexpire;
+ if (zone->view->peers != NULL) {
+ dns_peer_t *peer = NULL;
+ bool edns, usetcp;
+ result = dns_peerlist_peerbyaddr(zone->view->peers, &primaryip,
+ &peer);
+ if (result == ISC_R_SUCCESS) {
+ result = dns_peer_getsupportedns(peer, &edns);
+ if (result == ISC_R_SUCCESS && !edns) {
+ DNS_ZONE_SETFLAG(zone, DNS_ZONEFLG_NOEDNS);
+ }
+ result = dns_peer_gettransfersource(peer,
+ &zone->sourceaddr);
+ if (result == ISC_R_SUCCESS) {
+ have_xfrsource = true;
+ }
+ if (zone->view->resolver != NULL) {
+ udpsize = dns_resolver_getudpsize(
+ zone->view->resolver);
+ }
+ (void)dns_peer_getudpsize(peer, &udpsize);
+ (void)dns_peer_getrequestnsid(peer, &reqnsid);
+ (void)dns_peer_getrequestexpire(peer, &reqexpire);
+ result = dns_peer_getforcetcp(peer, &usetcp);
+ if (result == ISC_R_SUCCESS && usetcp) {
+ options |= DNS_REQUESTOPT_TCP;
+ }
+ }
+ }
+
+ switch (isc_sockaddr_pf(&zone->primaryaddr)) {
+ case PF_INET:
+ if (DNS_ZONE_FLAG(zone, DNS_ZONEFLG_USEALTXFRSRC)) {
+ if (isc_sockaddr_equal(&zone->altxfrsource4,
+ &zone->xfrsource4))
+ {
+ goto skip_primary;
+ }
+ zone->sourceaddr = zone->altxfrsource4;
+ } else if (!have_xfrsource) {
+ zone->sourceaddr = zone->xfrsource4;
+ }
+ break;
+ case PF_INET6:
+ if (DNS_ZONE_FLAG(zone, DNS_ZONEFLG_USEALTXFRSRC)) {
+ if (isc_sockaddr_equal(&zone->altxfrsource6,
+ &zone->xfrsource6))
+ {
+ goto skip_primary;
+ }
+ zone->sourceaddr = zone->altxfrsource6;
+ } else if (!have_xfrsource) {
+ zone->sourceaddr = zone->xfrsource6;
+ }
+ break;
+ default:
+ result = ISC_R_NOTIMPLEMENTED;
+ goto cleanup;
+ }
+
+ /*
+ * FIXME(OS): This is a bit hackish, but it enforces the SOA query to go
+ * through the XFR channel instead of doing dns_request that doesn't
+ * have DoT support yet.
+ */
+ if (transport != NULL) {
+ DNS_ZONE_SETFLAG(zone, DNS_ZONEFLG_SOABEFOREAXFR);
+ do_queue_xfrin = true;
+ cancel = false;
+ result = ISC_R_SUCCESS;
+ goto cleanup;
+ }
+
+ result = create_query(zone, dns_rdatatype_soa, &zone->origin, &message);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+
+ if (!DNS_ZONE_FLAG(zone, DNS_ZONEFLG_NOEDNS)) {
+ result = add_opt(message, udpsize, reqnsid, reqexpire);
+ if (result != ISC_R_SUCCESS) {
+ zone_debuglog(zone, me, 1,
+ "unable to add opt record: %s",
+ isc_result_totext(result));
+ }
+ }
+
+ zone_iattach(zone, &dummy);
+ timeout = 15;
+ if (DNS_ZONE_FLAG(zone, DNS_ZONEFLG_DIALREFRESH)) {
+ timeout = 30;
+ }
+ result = dns_request_create(
+ zone->view->requestmgr, message, &zone->sourceaddr,
+ &zone->primaryaddr, options, key, timeout * 3, timeout, 2,
+ zone->task, refresh_callback, zone, &zone->request);
+ if (result != ISC_R_SUCCESS) {
+ zone_idetach(&dummy);
+ zone_debuglog(zone, me, 1, "dns_request_create() failed: %s",
+ isc_result_totext(result));
+ goto skip_primary;
+ } else {
+ if (isc_sockaddr_pf(&zone->primaryaddr) == PF_INET) {
+ inc_stats(zone, dns_zonestatscounter_soaoutv4);
+ } else {
+ inc_stats(zone, dns_zonestatscounter_soaoutv6);
+ }
+ }
+ cancel = false;
+cleanup:
+ if (transport != NULL) {
+ dns_transport_detach(&transport);
+ }
+ if (key != NULL) {
+ dns_tsigkey_detach(&key);
+ }
+ if (result != ISC_R_SUCCESS) {
+ DNS_ZONE_CLRFLAG(zone, DNS_ZONEFLG_REFRESH);
+ }
+ if (message != NULL) {
+ dns_message_detach(&message);
+ }
+ if (cancel) {
+ cancel_refresh(zone);
+ }
+ isc_event_free(&event);
+ UNLOCK_ZONE(zone);
+ if (do_queue_xfrin) {
+ queue_xfrin(zone);
+ }
+ dns_zone_idetach(&zone);
+ return;
+
+skip_primary:
+ if (transport != NULL) {
+ dns_transport_detach(&transport);
+ }
+ if (key != NULL) {
+ dns_tsigkey_detach(&key);
+ }
+ if (message != NULL) {
+ dns_message_detach(&message);
+ }
+ /*
+ * Skip to next failed / untried primary.
+ */
+ do {
+ zone->curprimary++;
+ } while (zone->curprimary < zone->primariescnt &&
+ zone->primariesok[zone->curprimary]);
+ if (zone->curprimary < zone->primariescnt) {
+ goto again;
+ }
+ zone->curprimary = 0;
+ goto cleanup;
+}
+
+static void
+ns_query(dns_zone_t *zone, dns_rdataset_t *soardataset, dns_stub_t *stub) {
+ const char me[] = "ns_query";
+ isc_result_t result;
+ dns_message_t *message = NULL;
+ isc_netaddr_t primaryip;
+ dns_tsigkey_t *key = NULL;
+ dns_dbnode_t *node = NULL;
+ int timeout;
+ bool have_xfrsource = false;
+ bool reqnsid;
+ uint16_t udpsize = SEND_BUFFER_SIZE;
+ struct stub_cb_args *cb_args;
+
+ REQUIRE(DNS_ZONE_VALID(zone));
+ REQUIRE(LOCKED_ZONE(zone));
+ REQUIRE((soardataset != NULL && stub == NULL) ||
+ (soardataset == NULL && stub != NULL));
+ REQUIRE(stub == NULL || DNS_STUB_VALID(stub));
+
+ ENTER;
+
+ if (stub == NULL) {
+ stub = isc_mem_get(zone->mctx, sizeof(*stub));
+ stub->magic = STUB_MAGIC;
+ stub->mctx = zone->mctx;
+ stub->zone = NULL;
+ stub->db = NULL;
+ stub->version = NULL;
+ atomic_init(&stub->pending_requests, 0);
+
+ /*
+ * Attach so that the zone won't disappear from under us.
+ */
+ zone_iattach(zone, &stub->zone);
+
+ /*
+ * If a db exists we will update it, otherwise we create a
+ * new one and attach it to the zone once we have the NS
+ * RRset and glue.
+ */
+ ZONEDB_LOCK(&zone->dblock, isc_rwlocktype_read);
+ if (zone->db != NULL) {
+ dns_db_attach(zone->db, &stub->db);
+ ZONEDB_UNLOCK(&zone->dblock, isc_rwlocktype_read);
+ } else {
+ ZONEDB_UNLOCK(&zone->dblock, isc_rwlocktype_read);
+
+ INSIST(zone->db_argc >= 1);
+ result = dns_db_create(zone->mctx, zone->db_argv[0],
+ &zone->origin, dns_dbtype_stub,
+ zone->rdclass, zone->db_argc - 1,
+ zone->db_argv + 1, &stub->db);
+ if (result != ISC_R_SUCCESS) {
+ dns_zone_log(zone, ISC_LOG_ERROR,
+ "refreshing stub: "
+ "could not create "
+ "database: %s",
+ isc_result_totext(result));
+ goto cleanup;
+ }
+ dns_db_settask(stub->db, zone->task);
+ }
+
+ result = dns_db_newversion(stub->db, &stub->version);
+ if (result != ISC_R_SUCCESS) {
+ dns_zone_log(zone, ISC_LOG_INFO,
+ "refreshing stub: "
+ "dns_db_newversion() failed: %s",
+ isc_result_totext(result));
+ goto cleanup;
+ }
+
+ /*
+ * Update SOA record.
+ */
+ result = dns_db_findnode(stub->db, &zone->origin, true, &node);
+ if (result != ISC_R_SUCCESS) {
+ dns_zone_log(zone, ISC_LOG_INFO,
+ "refreshing stub: "
+ "dns_db_findnode() failed: %s",
+ isc_result_totext(result));
+ goto cleanup;
+ }
+
+ result = dns_db_addrdataset(stub->db, node, stub->version, 0,
+ soardataset, 0, NULL);
+ dns_db_detachnode(stub->db, &node);
+ if (result != ISC_R_SUCCESS) {
+ dns_zone_log(zone, ISC_LOG_INFO,
+ "refreshing stub: "
+ "dns_db_addrdataset() failed: %s",
+ isc_result_totext(result));
+ goto cleanup;
+ }
+ }
+
+ /*
+ * XXX Optimisation: Create message when zone is setup and reuse.
+ */
+ result = create_query(zone, dns_rdatatype_ns, &zone->origin, &message);
+ INSIST(result == ISC_R_SUCCESS);
+
+ INSIST(zone->primariescnt > 0);
+ INSIST(zone->curprimary < zone->primariescnt);
+ zone->primaryaddr = zone->primaries[zone->curprimary];
+
+ isc_netaddr_fromsockaddr(&primaryip, &zone->primaryaddr);
+ /*
+ * First, look for a tsig key in the primaries statement, then
+ * try for a server key.
+ */
+ if ((zone->primarykeynames != NULL) &&
+ (zone->primarykeynames[zone->curprimary] != NULL))
+ {
+ dns_view_t *view = dns_zone_getview(zone);
+ dns_name_t *keyname = zone->primarykeynames[zone->curprimary];
+ result = dns_view_gettsig(view, keyname, &key);
+ if (result != ISC_R_SUCCESS) {
+ char namebuf[DNS_NAME_FORMATSIZE];
+ dns_name_format(keyname, namebuf, sizeof(namebuf));
+ dns_zone_log(zone, ISC_LOG_ERROR,
+ "unable to find key: %s", namebuf);
+ }
+ }
+ if (key == NULL) {
+ (void)dns_view_getpeertsig(zone->view, &primaryip, &key);
+ }
+
+ /* FIXME(OS): Do we need the transport here too? Most probably yes */
+
+ reqnsid = zone->view->requestnsid;
+ if (zone->view->peers != NULL) {
+ dns_peer_t *peer = NULL;
+ bool edns;
+ result = dns_peerlist_peerbyaddr(zone->view->peers, &primaryip,
+ &peer);
+ if (result == ISC_R_SUCCESS) {
+ result = dns_peer_getsupportedns(peer, &edns);
+ if (result == ISC_R_SUCCESS && !edns) {
+ DNS_ZONE_SETFLAG(zone, DNS_ZONEFLG_NOEDNS);
+ }
+ result = dns_peer_gettransfersource(peer,
+ &zone->sourceaddr);
+ if (result == ISC_R_SUCCESS) {
+ have_xfrsource = true;
+ }
+ if (zone->view->resolver != NULL) {
+ udpsize = dns_resolver_getudpsize(
+ zone->view->resolver);
+ }
+ (void)dns_peer_getudpsize(peer, &udpsize);
+ (void)dns_peer_getrequestnsid(peer, &reqnsid);
+ }
+ }
+ if (!DNS_ZONE_FLAG(zone, DNS_ZONEFLG_NOEDNS)) {
+ result = add_opt(message, udpsize, reqnsid, false);
+ if (result != ISC_R_SUCCESS) {
+ zone_debuglog(zone, me, 1,
+ "unable to add opt record: %s",
+ isc_result_totext(result));
+ }
+ }
+
+ /*
+ * Always use TCP so that we shouldn't truncate in additional section.
+ */
+ switch (isc_sockaddr_pf(&zone->primaryaddr)) {
+ case PF_INET:
+ if (DNS_ZONE_FLAG(zone, DNS_ZONEFLG_USEALTXFRSRC)) {
+ zone->sourceaddr = zone->altxfrsource4;
+ } else if (!have_xfrsource) {
+ zone->sourceaddr = zone->xfrsource4;
+ }
+ break;
+ case PF_INET6:
+ if (DNS_ZONE_FLAG(zone, DNS_ZONEFLG_USEALTXFRSRC)) {
+ zone->sourceaddr = zone->altxfrsource6;
+ } else if (!have_xfrsource) {
+ zone->sourceaddr = zone->xfrsource6;
+ }
+ break;
+ default:
+ result = ISC_R_NOTIMPLEMENTED;
+ POST(result);
+ goto cleanup;
+ }
+ timeout = 15;
+ if (DNS_ZONE_FLAG(zone, DNS_ZONEFLG_DIALREFRESH)) {
+ timeout = 30;
+ }
+
+ /*
+ * Save request parameters so we can reuse them later on
+ * for resolving missing glue A/AAAA records.
+ */
+ cb_args = isc_mem_get(zone->mctx, sizeof(*cb_args));
+ cb_args->stub = stub;
+ cb_args->tsig_key = key;
+ cb_args->udpsize = udpsize;
+ cb_args->timeout = timeout;
+ cb_args->reqnsid = reqnsid;
+
+ result = dns_request_create(
+ zone->view->requestmgr, message, &zone->sourceaddr,
+ &zone->primaryaddr, DNS_REQUESTOPT_TCP, key, timeout * 3,
+ timeout, 2, zone->task, stub_callback, cb_args, &zone->request);
+ if (result != ISC_R_SUCCESS) {
+ zone_debuglog(zone, me, 1, "dns_request_create() failed: %s",
+ isc_result_totext(result));
+ goto cleanup;
+ }
+ dns_message_detach(&message);
+ goto unlock;
+
+cleanup:
+ cancel_refresh(zone);
+ stub->magic = 0;
+ if (stub->version != NULL) {
+ dns_db_closeversion(stub->db, &stub->version, false);
+ }
+ if (stub->db != NULL) {
+ dns_db_detach(&stub->db);
+ }
+ if (stub->zone != NULL) {
+ zone_idetach(&stub->zone);
+ }
+ isc_mem_put(stub->mctx, stub, sizeof(*stub));
+ if (message != NULL) {
+ dns_message_detach(&message);
+ }
+unlock:
+ if (key != NULL) {
+ dns_tsigkey_detach(&key);
+ }
+ return;
+}
+
+/*
+ * Shut the zone down.
+ */
+static void
+zone_shutdown(isc_task_t *task, isc_event_t *event) {
+ dns_zone_t *zone = (dns_zone_t *)event->ev_arg;
+ bool free_needed, linked = false;
+ dns_zone_t *raw = NULL, *secure = NULL;
+ dns_view_t *view = NULL, *prev_view = NULL;
+
+ UNUSED(task);
+ REQUIRE(DNS_ZONE_VALID(zone));
+ INSIST(event->ev_type == DNS_EVENT_ZONECONTROL);
+ INSIST(isc_refcount_current(&zone->erefs) == 0);
+
+ zone_debuglog(zone, "zone_shutdown", 3, "shutting down");
+
+ /*
+ * If we were waiting for xfrin quota, step out of
+ * the queue.
+ * If there's no zone manager, we can't be waiting for the
+ * xfrin quota
+ */
+ if (zone->zmgr != NULL) {
+ RWLOCK(&zone->zmgr->rwlock, isc_rwlocktype_write);
+ if (zone->statelist == &zone->zmgr->waiting_for_xfrin) {
+ ISC_LIST_UNLINK(zone->zmgr->waiting_for_xfrin, zone,
+ statelink);
+ linked = true;
+ zone->statelist = NULL;
+ }
+ if (zone->statelist == &zone->zmgr->xfrin_in_progress) {
+ ISC_LIST_UNLINK(zone->zmgr->xfrin_in_progress, zone,
+ statelink);
+ zone->statelist = NULL;
+ zmgr_resume_xfrs(zone->zmgr, false);
+ }
+ RWUNLOCK(&zone->zmgr->rwlock, isc_rwlocktype_write);
+ }
+
+ /*
+ * In task context, no locking required. See zone_xfrdone().
+ */
+ if (zone->xfr != NULL) {
+ /* The final detach will happen in zone_xfrdone() */
+ dns_xfrin_shutdown(zone->xfr);
+ }
+
+ /* Safe to release the zone now */
+ if (zone->zmgr != NULL) {
+ dns_zonemgr_releasezone(zone->zmgr, zone);
+ }
+
+ LOCK_ZONE(zone);
+ INSIST(zone != zone->raw);
+
+ /*
+ * Detach the views early, we don't need them anymore. However, we need
+ * to detach them outside of the zone lock to break the lock loop
+ * between view, adb and zone locks.
+ */
+ view = zone->view;
+ zone->view = NULL;
+ prev_view = zone->prev_view;
+ zone->prev_view = NULL;
+
+ if (linked) {
+ isc_refcount_decrement(&zone->irefs);
+ }
+ if (zone->request != NULL) {
+ dns_request_cancel(zone->request);
+ }
+
+ if (zone->readio != NULL) {
+ zonemgr_cancelio(zone->readio);
+ }
+
+ if (zone->lctx != NULL) {
+ dns_loadctx_cancel(zone->lctx);
+ }
+
+ if (!DNS_ZONE_FLAG(zone, DNS_ZONEFLG_FLUSH) ||
+ !DNS_ZONE_FLAG(zone, DNS_ZONEFLG_DUMPING))
+ {
+ if (zone->writeio != NULL) {
+ zonemgr_cancelio(zone->writeio);
+ }
+
+ if (zone->dctx != NULL) {
+ dns_dumpctx_cancel(zone->dctx);
+ }
+ }
+
+ checkds_cancel(zone);
+
+ notify_cancel(zone);
+
+ forward_cancel(zone);
+
+ if (zone->timer != NULL) {
+ isc_timer_destroy(&zone->timer);
+ isc_refcount_decrement(&zone->irefs);
+ }
+
+ /*
+ * We have now canceled everything set the flag to allow exit_check()
+ * to succeed. We must not unlock between setting this flag and
+ * calling exit_check().
+ */
+ DNS_ZONE_SETFLAG(zone, DNS_ZONEFLG_SHUTDOWN);
+ free_needed = exit_check(zone);
+ /*
+ * If a dump is in progress for the secure zone, defer detaching from
+ * the raw zone as it may prevent the unsigned serial number from being
+ * stored in the raw-format dump of the secure zone. In this scenario,
+ * dump_done() takes care of cleaning up the zone->raw reference.
+ */
+ if (inline_secure(zone) && !DNS_ZONE_FLAG(zone, DNS_ZONEFLG_DUMPING)) {
+ raw = zone->raw;
+ zone->raw = NULL;
+ }
+ if (inline_raw(zone)) {
+ secure = zone->secure;
+ zone->secure = NULL;
+ }
+ UNLOCK_ZONE(zone);
+
+ if (view != NULL) {
+ dns_view_weakdetach(&view);
+ }
+ if (prev_view != NULL) {
+ dns_view_weakdetach(&prev_view);
+ }
+
+ if (raw != NULL) {
+ dns_zone_detach(&raw);
+ }
+ if (secure != NULL) {
+ dns_zone_idetach(&secure);
+ }
+ if (free_needed) {
+ zone_free(zone);
+ }
+}
+
+static void
+zone_timer(isc_task_t *task, isc_event_t *event) {
+ const char me[] = "zone_timer";
+ dns_zone_t *zone = (dns_zone_t *)event->ev_arg;
+
+ UNUSED(task);
+ REQUIRE(DNS_ZONE_VALID(zone));
+
+ ENTER;
+
+ zone_maintenance(zone);
+
+ isc_event_free(&event);
+}
+
+static void
+zone_settimer(dns_zone_t *zone, isc_time_t *now) {
+ const char me[] = "zone_settimer";
+ isc_time_t next;
+ isc_result_t result;
+
+ REQUIRE(DNS_ZONE_VALID(zone));
+ REQUIRE(LOCKED_ZONE(zone));
+ ENTER;
+
+ if (DNS_ZONE_FLAG(zone, DNS_ZONEFLG_EXITING)) {
+ return;
+ }
+
+ isc_time_settoepoch(&next);
+
+ switch (zone->type) {
+ case dns_zone_redirect:
+ if (zone->primaries != NULL) {
+ goto treat_as_secondary;
+ }
+ FALLTHROUGH;
+ case dns_zone_primary:
+ if (DNS_ZONE_FLAG(zone, DNS_ZONEFLG_NEEDNOTIFY) ||
+ DNS_ZONE_FLAG(zone, DNS_ZONEFLG_NEEDSTARTUPNOTIFY))
+ {
+ next = zone->notifytime;
+ }
+ if (DNS_ZONE_FLAG(zone, DNS_ZONEFLG_NEEDDUMP) &&
+ !DNS_ZONE_FLAG(zone, DNS_ZONEFLG_DUMPING))
+ {
+ INSIST(!isc_time_isepoch(&zone->dumptime));
+ if (isc_time_isepoch(&next) ||
+ isc_time_compare(&zone->dumptime, &next) < 0)
+ {
+ next = zone->dumptime;
+ }
+ }
+ if (zone->type == dns_zone_redirect) {
+ break;
+ }
+ if (!DNS_ZONE_FLAG(zone, DNS_ZONEFLG_REFRESHING) &&
+ !isc_time_isepoch(&zone->refreshkeytime))
+ {
+ if (isc_time_isepoch(&next) ||
+ isc_time_compare(&zone->refreshkeytime, &next) < 0)
+ {
+ next = zone->refreshkeytime;
+ }
+ }
+ if (!isc_time_isepoch(&zone->resigntime)) {
+ if (isc_time_isepoch(&next) ||
+ isc_time_compare(&zone->resigntime, &next) < 0)
+ {
+ next = zone->resigntime;
+ }
+ }
+ if (!isc_time_isepoch(&zone->keywarntime)) {
+ if (isc_time_isepoch(&next) ||
+ isc_time_compare(&zone->keywarntime, &next) < 0)
+ {
+ next = zone->keywarntime;
+ }
+ }
+ if (!isc_time_isepoch(&zone->signingtime)) {
+ if (isc_time_isepoch(&next) ||
+ isc_time_compare(&zone->signingtime, &next) < 0)
+ {
+ next = zone->signingtime;
+ }
+ }
+ if (!isc_time_isepoch(&zone->nsec3chaintime)) {
+ if (isc_time_isepoch(&next) ||
+ isc_time_compare(&zone->nsec3chaintime, &next) < 0)
+ {
+ next = zone->nsec3chaintime;
+ }
+ }
+ break;
+
+ case dns_zone_secondary:
+ case dns_zone_mirror:
+ treat_as_secondary:
+ if (DNS_ZONE_FLAG(zone, DNS_ZONEFLG_NEEDNOTIFY) ||
+ DNS_ZONE_FLAG(zone, DNS_ZONEFLG_NEEDSTARTUPNOTIFY))
+ {
+ next = zone->notifytime;
+ }
+ FALLTHROUGH;
+ case dns_zone_stub:
+ if (!DNS_ZONE_FLAG(zone, DNS_ZONEFLG_REFRESH) &&
+ !DNS_ZONE_FLAG(zone, DNS_ZONEFLG_NOPRIMARIES) &&
+ !DNS_ZONE_FLAG(zone, DNS_ZONEFLG_NOREFRESH) &&
+ !DNS_ZONE_FLAG(zone, DNS_ZONEFLG_LOADING) &&
+ !DNS_ZONE_FLAG(zone, DNS_ZONEFLG_LOADPENDING) &&
+ !isc_time_isepoch(&zone->refreshtime) &&
+ (isc_time_isepoch(&next) ||
+ isc_time_compare(&zone->refreshtime, &next) < 0))
+ {
+ next = zone->refreshtime;
+ }
+ if (DNS_ZONE_FLAG(zone, DNS_ZONEFLG_LOADED) &&
+ !isc_time_isepoch(&zone->expiretime))
+ {
+ if (isc_time_isepoch(&next) ||
+ isc_time_compare(&zone->expiretime, &next) < 0)
+ {
+ next = zone->expiretime;
+ }
+ }
+ if (DNS_ZONE_FLAG(zone, DNS_ZONEFLG_NEEDDUMP) &&
+ !DNS_ZONE_FLAG(zone, DNS_ZONEFLG_DUMPING))
+ {
+ INSIST(!isc_time_isepoch(&zone->dumptime));
+ if (isc_time_isepoch(&next) ||
+ isc_time_compare(&zone->dumptime, &next) < 0)
+ {
+ next = zone->dumptime;
+ }
+ }
+ break;
+
+ case dns_zone_key:
+ if (DNS_ZONE_FLAG(zone, DNS_ZONEFLG_NEEDDUMP) &&
+ !DNS_ZONE_FLAG(zone, DNS_ZONEFLG_DUMPING))
+ {
+ INSIST(!isc_time_isepoch(&zone->dumptime));
+ if (isc_time_isepoch(&next) ||
+ isc_time_compare(&zone->dumptime, &next) < 0)
+ {
+ next = zone->dumptime;
+ }
+ }
+ if (!DNS_ZONE_FLAG(zone, DNS_ZONEFLG_REFRESHING)) {
+ if (isc_time_isepoch(&next) ||
+ (!isc_time_isepoch(&zone->refreshkeytime) &&
+ isc_time_compare(&zone->refreshkeytime, &next) <
+ 0))
+ {
+ next = zone->refreshkeytime;
+ }
+ }
+ break;
+
+ default:
+ break;
+ }
+
+ if (isc_time_isepoch(&next)) {
+ zone_debuglog(zone, me, 10, "settimer inactive");
+ result = isc_timer_reset(zone->timer, isc_timertype_inactive,
+ NULL, NULL, true);
+ if (result != ISC_R_SUCCESS) {
+ dns_zone_log(zone, ISC_LOG_ERROR,
+ "could not deactivate zone timer: %s",
+ isc_result_totext(result));
+ }
+ } else {
+ if (isc_time_compare(&next, now) <= 0) {
+ next = *now;
+ }
+ result = isc_timer_reset(zone->timer, isc_timertype_once, &next,
+ NULL, true);
+ if (result != ISC_R_SUCCESS) {
+ dns_zone_log(zone, ISC_LOG_ERROR,
+ "could not reset zone timer: %s",
+ isc_result_totext(result));
+ }
+ }
+}
+
+static void
+cancel_refresh(dns_zone_t *zone) {
+ const char me[] = "cancel_refresh";
+ isc_time_t now;
+
+ /*
+ * 'zone' locked by caller.
+ */
+
+ REQUIRE(DNS_ZONE_VALID(zone));
+ REQUIRE(LOCKED_ZONE(zone));
+
+ ENTER;
+
+ DNS_ZONE_CLRFLAG(zone, DNS_ZONEFLG_REFRESH);
+ TIME_NOW(&now);
+ zone_settimer(zone, &now);
+}
+
+static isc_result_t
+notify_createmessage(dns_zone_t *zone, unsigned int flags,
+ dns_message_t **messagep) {
+ dns_db_t *zonedb = NULL;
+ dns_dbnode_t *node = NULL;
+ dns_dbversion_t *version = NULL;
+ dns_message_t *message = NULL;
+ dns_rdataset_t rdataset;
+ dns_rdata_t rdata = DNS_RDATA_INIT;
+
+ dns_name_t *tempname = NULL;
+ dns_rdata_t *temprdata = NULL;
+ dns_rdatalist_t *temprdatalist = NULL;
+ dns_rdataset_t *temprdataset = NULL;
+
+ isc_result_t result;
+ isc_region_t r;
+ isc_buffer_t *b = NULL;
+
+ REQUIRE(DNS_ZONE_VALID(zone));
+ REQUIRE(messagep != NULL && *messagep == NULL);
+
+ dns_message_create(zone->mctx, DNS_MESSAGE_INTENTRENDER, &message);
+
+ message->opcode = dns_opcode_notify;
+ message->flags |= DNS_MESSAGEFLAG_AA;
+ message->rdclass = zone->rdclass;
+
+ result = dns_message_gettempname(message, &tempname);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+
+ result = dns_message_gettemprdataset(message, &temprdataset);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+
+ /*
+ * Make question.
+ */
+ dns_name_clone(&zone->origin, tempname);
+ dns_rdataset_makequestion(temprdataset, zone->rdclass,
+ dns_rdatatype_soa);
+ ISC_LIST_APPEND(tempname->list, temprdataset, link);
+ dns_message_addname(message, tempname, DNS_SECTION_QUESTION);
+ tempname = NULL;
+ temprdataset = NULL;
+
+ if ((flags & DNS_NOTIFY_NOSOA) != 0) {
+ goto done;
+ }
+
+ result = dns_message_gettempname(message, &tempname);
+ if (result != ISC_R_SUCCESS) {
+ goto soa_cleanup;
+ }
+ result = dns_message_gettemprdata(message, &temprdata);
+ if (result != ISC_R_SUCCESS) {
+ goto soa_cleanup;
+ }
+ result = dns_message_gettemprdataset(message, &temprdataset);
+ if (result != ISC_R_SUCCESS) {
+ goto soa_cleanup;
+ }
+ result = dns_message_gettemprdatalist(message, &temprdatalist);
+ if (result != ISC_R_SUCCESS) {
+ goto soa_cleanup;
+ }
+
+ ZONEDB_LOCK(&zone->dblock, isc_rwlocktype_read);
+ INSIST(zone->db != NULL); /* XXXJT: is this assumption correct? */
+ dns_db_attach(zone->db, &zonedb);
+ ZONEDB_UNLOCK(&zone->dblock, isc_rwlocktype_read);
+
+ dns_name_clone(&zone->origin, tempname);
+ dns_db_currentversion(zonedb, &version);
+ result = dns_db_findnode(zonedb, tempname, false, &node);
+ if (result != ISC_R_SUCCESS) {
+ goto soa_cleanup;
+ }
+
+ dns_rdataset_init(&rdataset);
+ result = dns_db_findrdataset(zonedb, node, version, dns_rdatatype_soa,
+ dns_rdatatype_none, 0, &rdataset, NULL);
+ if (result != ISC_R_SUCCESS) {
+ goto soa_cleanup;
+ }
+ result = dns_rdataset_first(&rdataset);
+ if (result != ISC_R_SUCCESS) {
+ goto soa_cleanup;
+ }
+ dns_rdataset_current(&rdataset, &rdata);
+ dns_rdata_toregion(&rdata, &r);
+ isc_buffer_allocate(zone->mctx, &b, r.length);
+ isc_buffer_putmem(b, r.base, r.length);
+ isc_buffer_usedregion(b, &r);
+ dns_rdata_init(temprdata);
+ dns_rdata_fromregion(temprdata, rdata.rdclass, rdata.type, &r);
+ dns_message_takebuffer(message, &b);
+ result = dns_rdataset_next(&rdataset);
+ dns_rdataset_disassociate(&rdataset);
+ if (result != ISC_R_NOMORE) {
+ goto soa_cleanup;
+ }
+ temprdatalist->rdclass = rdata.rdclass;
+ temprdatalist->type = rdata.type;
+ temprdatalist->ttl = rdataset.ttl;
+ ISC_LIST_APPEND(temprdatalist->rdata, temprdata, link);
+
+ result = dns_rdatalist_tordataset(temprdatalist, temprdataset);
+ if (result != ISC_R_SUCCESS) {
+ goto soa_cleanup;
+ }
+
+ ISC_LIST_APPEND(tempname->list, temprdataset, link);
+ dns_message_addname(message, tempname, DNS_SECTION_ANSWER);
+ temprdatalist = NULL;
+ temprdataset = NULL;
+ temprdata = NULL;
+ tempname = NULL;
+
+soa_cleanup:
+ if (node != NULL) {
+ dns_db_detachnode(zonedb, &node);
+ }
+ if (version != NULL) {
+ dns_db_closeversion(zonedb, &version, false);
+ }
+ if (zonedb != NULL) {
+ dns_db_detach(&zonedb);
+ }
+ if (tempname != NULL) {
+ dns_message_puttempname(message, &tempname);
+ }
+ if (temprdata != NULL) {
+ dns_message_puttemprdata(message, &temprdata);
+ }
+ if (temprdataset != NULL) {
+ dns_message_puttemprdataset(message, &temprdataset);
+ }
+ if (temprdatalist != NULL) {
+ dns_message_puttemprdatalist(message, &temprdatalist);
+ }
+
+done:
+ *messagep = message;
+ return (ISC_R_SUCCESS);
+
+cleanup:
+ if (tempname != NULL) {
+ dns_message_puttempname(message, &tempname);
+ }
+ if (temprdataset != NULL) {
+ dns_message_puttemprdataset(message, &temprdataset);
+ }
+ dns_message_detach(&message);
+ return (result);
+}
+
+isc_result_t
+dns_zone_notifyreceive(dns_zone_t *zone, isc_sockaddr_t *from,
+ isc_sockaddr_t *to, dns_message_t *msg) {
+ unsigned int i;
+ dns_rdata_soa_t soa;
+ dns_rdataset_t *rdataset = NULL;
+ dns_rdata_t rdata = DNS_RDATA_INIT;
+ isc_result_t result;
+ char fromtext[ISC_SOCKADDR_FORMATSIZE];
+ int match = 0;
+ isc_netaddr_t netaddr;
+ uint32_t serial = 0;
+ bool have_serial = false;
+ dns_tsigkey_t *tsigkey;
+ const dns_name_t *tsig;
+
+ REQUIRE(DNS_ZONE_VALID(zone));
+
+ /*
+ * If type != T_SOA return DNS_R_NOTIMP. We don't yet support
+ * ROLLOVER.
+ *
+ * SOA: RFC1996
+ * Check that 'from' is a valid notify source, (zone->primaries).
+ * Return DNS_R_REFUSED if not.
+ *
+ * If the notify message contains a serial number check it
+ * against the zones serial and return if <= current serial
+ *
+ * If a refresh check is progress, if so just record the
+ * fact we received a NOTIFY and from where and return.
+ * We will perform a new refresh check when the current one
+ * completes. Return ISC_R_SUCCESS.
+ *
+ * Otherwise initiate a refresh check using 'from' as the
+ * first address to check. Return ISC_R_SUCCESS.
+ */
+
+ isc_sockaddr_format(from, fromtext, sizeof(fromtext));
+
+ /*
+ * Notify messages are processed by the raw zone.
+ */
+ LOCK_ZONE(zone);
+ INSIST(zone != zone->raw);
+ if (inline_secure(zone)) {
+ result = dns_zone_notifyreceive(zone->raw, from, to, msg);
+ UNLOCK_ZONE(zone);
+ return (result);
+ }
+ /*
+ * We only handle NOTIFY (SOA) at the present.
+ */
+ if (isc_sockaddr_pf(from) == PF_INET) {
+ inc_stats(zone, dns_zonestatscounter_notifyinv4);
+ } else {
+ inc_stats(zone, dns_zonestatscounter_notifyinv6);
+ }
+ if (msg->counts[DNS_SECTION_QUESTION] == 0 ||
+ dns_message_findname(msg, DNS_SECTION_QUESTION, &zone->origin,
+ dns_rdatatype_soa, dns_rdatatype_none, NULL,
+ NULL) != ISC_R_SUCCESS)
+ {
+ UNLOCK_ZONE(zone);
+ if (msg->counts[DNS_SECTION_QUESTION] == 0) {
+ dns_zone_log(zone, ISC_LOG_NOTICE,
+ "NOTIFY with no "
+ "question section from: %s",
+ fromtext);
+ return (DNS_R_FORMERR);
+ }
+ dns_zone_log(zone, ISC_LOG_NOTICE,
+ "NOTIFY zone does not match");
+ return (DNS_R_NOTIMP);
+ }
+
+ /*
+ * If we are a primary zone just succeed.
+ */
+ if (zone->type == dns_zone_primary) {
+ UNLOCK_ZONE(zone);
+ return (ISC_R_SUCCESS);
+ }
+
+ isc_netaddr_fromsockaddr(&netaddr, from);
+ for (i = 0; i < zone->primariescnt; i++) {
+ if (isc_sockaddr_eqaddr(from, &zone->primaries[i])) {
+ break;
+ }
+ if (zone->view->aclenv->match_mapped &&
+ IN6_IS_ADDR_V4MAPPED(&from->type.sin6.sin6_addr) &&
+ isc_sockaddr_pf(&zone->primaries[i]) == AF_INET)
+ {
+ isc_netaddr_t na1, na2;
+ isc_netaddr_fromv4mapped(&na1, &netaddr);
+ isc_netaddr_fromsockaddr(&na2, &zone->primaries[i]);
+ if (isc_netaddr_equal(&na1, &na2)) {
+ break;
+ }
+ }
+ }
+
+ /*
+ * Accept notify requests from non primaries if they are on
+ * 'zone->notify_acl'.
+ */
+ tsigkey = dns_message_gettsigkey(msg);
+ tsig = dns_tsigkey_identity(tsigkey);
+ if (i >= zone->primariescnt && zone->notify_acl != NULL &&
+ (dns_acl_match(&netaddr, tsig, zone->notify_acl, zone->view->aclenv,
+ &match, NULL) == ISC_R_SUCCESS) &&
+ match > 0)
+ {
+ /* Accept notify. */
+ } else if (i >= zone->primariescnt) {
+ UNLOCK_ZONE(zone);
+ dns_zone_log(zone, ISC_LOG_INFO,
+ "refused notify from non-primary: %s", fromtext);
+ inc_stats(zone, dns_zonestatscounter_notifyrej);
+ return (DNS_R_REFUSED);
+ }
+
+ /*
+ * If the zone is loaded and there are answers check the serial
+ * to see if we need to do a refresh. Do not worry about this
+ * check if we are a dialup zone as we use the notify request
+ * to trigger a refresh check.
+ */
+ if (msg->counts[DNS_SECTION_ANSWER] > 0 &&
+ DNS_ZONE_FLAG(zone, DNS_ZONEFLG_LOADED) &&
+ !DNS_ZONE_FLAG(zone, DNS_ZONEFLG_NOREFRESH))
+ {
+ result = dns_message_findname(
+ msg, DNS_SECTION_ANSWER, &zone->origin,
+ dns_rdatatype_soa, dns_rdatatype_none, NULL, &rdataset);
+ if (result == ISC_R_SUCCESS) {
+ result = dns_rdataset_first(rdataset);
+ }
+ if (result == ISC_R_SUCCESS) {
+ uint32_t oldserial;
+ unsigned int soacount;
+
+ dns_rdataset_current(rdataset, &rdata);
+ result = dns_rdata_tostruct(&rdata, &soa, NULL);
+ RUNTIME_CHECK(result == ISC_R_SUCCESS);
+ serial = soa.serial;
+ have_serial = true;
+ /*
+ * The following should safely be performed without DB
+ * lock and succeed in this context.
+ */
+ result = zone_get_from_db(zone, zone->db, NULL,
+ &soacount, NULL, &oldserial,
+ NULL, NULL, NULL, NULL, NULL);
+ RUNTIME_CHECK(result == ISC_R_SUCCESS);
+ RUNTIME_CHECK(soacount > 0U);
+ if (isc_serial_le(serial, oldserial)) {
+ dns_zone_log(zone, ISC_LOG_INFO,
+ "notify from %s: "
+ "zone is up to date",
+ fromtext);
+ UNLOCK_ZONE(zone);
+ return (ISC_R_SUCCESS);
+ }
+ }
+ }
+
+ /*
+ * If we got this far and there was a refresh in progress just
+ * let it complete. Record where we got the notify from so we
+ * can perform a refresh check when the current one completes
+ */
+ if (DNS_ZONE_FLAG(zone, DNS_ZONEFLG_REFRESH)) {
+ DNS_ZONE_SETFLAG(zone, DNS_ZONEFLG_NEEDREFRESH);
+ zone->notifyfrom = *from;
+ UNLOCK_ZONE(zone);
+ if (have_serial) {
+ dns_zone_log(zone, ISC_LOG_INFO,
+ "notify from %s: serial %u: refresh in "
+ "progress, refresh check queued",
+ fromtext, serial);
+ } else {
+ dns_zone_log(zone, ISC_LOG_INFO,
+ "notify from %s: refresh in progress, "
+ "refresh check queued",
+ fromtext);
+ }
+ return (ISC_R_SUCCESS);
+ }
+ if (have_serial) {
+ dns_zone_log(zone, ISC_LOG_INFO, "notify from %s: serial %u",
+ fromtext, serial);
+ } else {
+ dns_zone_log(zone, ISC_LOG_INFO, "notify from %s: no serial",
+ fromtext);
+ }
+ zone->notifyfrom = *from;
+ UNLOCK_ZONE(zone);
+
+ if (to != NULL) {
+ dns_zonemgr_unreachabledel(zone->zmgr, from, to);
+ }
+ dns_zone_refresh(zone);
+ return (ISC_R_SUCCESS);
+}
+
+void
+dns_zone_setnotifyacl(dns_zone_t *zone, dns_acl_t *acl) {
+ REQUIRE(DNS_ZONE_VALID(zone));
+
+ LOCK_ZONE(zone);
+ if (zone->notify_acl != NULL) {
+ dns_acl_detach(&zone->notify_acl);
+ }
+ dns_acl_attach(acl, &zone->notify_acl);
+ UNLOCK_ZONE(zone);
+}
+
+void
+dns_zone_setqueryacl(dns_zone_t *zone, dns_acl_t *acl) {
+ REQUIRE(DNS_ZONE_VALID(zone));
+
+ LOCK_ZONE(zone);
+ if (zone->query_acl != NULL) {
+ dns_acl_detach(&zone->query_acl);
+ }
+ dns_acl_attach(acl, &zone->query_acl);
+ UNLOCK_ZONE(zone);
+}
+
+void
+dns_zone_setqueryonacl(dns_zone_t *zone, dns_acl_t *acl) {
+ REQUIRE(DNS_ZONE_VALID(zone));
+
+ LOCK_ZONE(zone);
+ if (zone->queryon_acl != NULL) {
+ dns_acl_detach(&zone->queryon_acl);
+ }
+ dns_acl_attach(acl, &zone->queryon_acl);
+ UNLOCK_ZONE(zone);
+}
+
+void
+dns_zone_setupdateacl(dns_zone_t *zone, dns_acl_t *acl) {
+ REQUIRE(DNS_ZONE_VALID(zone));
+
+ LOCK_ZONE(zone);
+ if (zone->update_acl != NULL) {
+ dns_acl_detach(&zone->update_acl);
+ }
+ dns_acl_attach(acl, &zone->update_acl);
+ UNLOCK_ZONE(zone);
+}
+
+void
+dns_zone_setforwardacl(dns_zone_t *zone, dns_acl_t *acl) {
+ REQUIRE(DNS_ZONE_VALID(zone));
+
+ LOCK_ZONE(zone);
+ if (zone->forward_acl != NULL) {
+ dns_acl_detach(&zone->forward_acl);
+ }
+ dns_acl_attach(acl, &zone->forward_acl);
+ UNLOCK_ZONE(zone);
+}
+
+void
+dns_zone_setxfracl(dns_zone_t *zone, dns_acl_t *acl) {
+ REQUIRE(DNS_ZONE_VALID(zone));
+
+ LOCK_ZONE(zone);
+ if (zone->xfr_acl != NULL) {
+ dns_acl_detach(&zone->xfr_acl);
+ }
+ dns_acl_attach(acl, &zone->xfr_acl);
+ UNLOCK_ZONE(zone);
+}
+
+dns_acl_t *
+dns_zone_getnotifyacl(dns_zone_t *zone) {
+ REQUIRE(DNS_ZONE_VALID(zone));
+
+ return (zone->notify_acl);
+}
+
+dns_acl_t *
+dns_zone_getqueryacl(dns_zone_t *zone) {
+ REQUIRE(DNS_ZONE_VALID(zone));
+
+ return (zone->query_acl);
+}
+
+dns_acl_t *
+dns_zone_getqueryonacl(dns_zone_t *zone) {
+ REQUIRE(DNS_ZONE_VALID(zone));
+
+ return (zone->queryon_acl);
+}
+
+dns_acl_t *
+dns_zone_getupdateacl(dns_zone_t *zone) {
+ REQUIRE(DNS_ZONE_VALID(zone));
+
+ return (zone->update_acl);
+}
+
+dns_acl_t *
+dns_zone_getforwardacl(dns_zone_t *zone) {
+ REQUIRE(DNS_ZONE_VALID(zone));
+
+ return (zone->forward_acl);
+}
+
+dns_acl_t *
+dns_zone_getxfracl(dns_zone_t *zone) {
+ REQUIRE(DNS_ZONE_VALID(zone));
+
+ return (zone->xfr_acl);
+}
+
+void
+dns_zone_clearupdateacl(dns_zone_t *zone) {
+ REQUIRE(DNS_ZONE_VALID(zone));
+
+ LOCK_ZONE(zone);
+ if (zone->update_acl != NULL) {
+ dns_acl_detach(&zone->update_acl);
+ }
+ UNLOCK_ZONE(zone);
+}
+
+void
+dns_zone_clearforwardacl(dns_zone_t *zone) {
+ REQUIRE(DNS_ZONE_VALID(zone));
+
+ LOCK_ZONE(zone);
+ if (zone->forward_acl != NULL) {
+ dns_acl_detach(&zone->forward_acl);
+ }
+ UNLOCK_ZONE(zone);
+}
+
+void
+dns_zone_clearnotifyacl(dns_zone_t *zone) {
+ REQUIRE(DNS_ZONE_VALID(zone));
+
+ LOCK_ZONE(zone);
+ if (zone->notify_acl != NULL) {
+ dns_acl_detach(&zone->notify_acl);
+ }
+ UNLOCK_ZONE(zone);
+}
+
+void
+dns_zone_clearqueryacl(dns_zone_t *zone) {
+ REQUIRE(DNS_ZONE_VALID(zone));
+
+ LOCK_ZONE(zone);
+ if (zone->query_acl != NULL) {
+ dns_acl_detach(&zone->query_acl);
+ }
+ UNLOCK_ZONE(zone);
+}
+
+void
+dns_zone_clearqueryonacl(dns_zone_t *zone) {
+ REQUIRE(DNS_ZONE_VALID(zone));
+
+ LOCK_ZONE(zone);
+ if (zone->queryon_acl != NULL) {
+ dns_acl_detach(&zone->queryon_acl);
+ }
+ UNLOCK_ZONE(zone);
+}
+
+void
+dns_zone_clearxfracl(dns_zone_t *zone) {
+ REQUIRE(DNS_ZONE_VALID(zone));
+
+ LOCK_ZONE(zone);
+ if (zone->xfr_acl != NULL) {
+ dns_acl_detach(&zone->xfr_acl);
+ }
+ UNLOCK_ZONE(zone);
+}
+
+bool
+dns_zone_getupdatedisabled(dns_zone_t *zone) {
+ REQUIRE(DNS_ZONE_VALID(zone));
+ return (zone->update_disabled);
+}
+
+void
+dns_zone_setupdatedisabled(dns_zone_t *zone, bool state) {
+ REQUIRE(DNS_ZONE_VALID(zone));
+ zone->update_disabled = state;
+}
+
+bool
+dns_zone_getzeronosoattl(dns_zone_t *zone) {
+ REQUIRE(DNS_ZONE_VALID(zone));
+ return (zone->zero_no_soa_ttl);
+}
+
+void
+dns_zone_setzeronosoattl(dns_zone_t *zone, bool state) {
+ REQUIRE(DNS_ZONE_VALID(zone));
+ zone->zero_no_soa_ttl = state;
+}
+
+void
+dns_zone_setchecknames(dns_zone_t *zone, dns_severity_t severity) {
+ REQUIRE(DNS_ZONE_VALID(zone));
+
+ zone->check_names = severity;
+}
+
+dns_severity_t
+dns_zone_getchecknames(dns_zone_t *zone) {
+ REQUIRE(DNS_ZONE_VALID(zone));
+
+ return (zone->check_names);
+}
+
+void
+dns_zone_setjournalsize(dns_zone_t *zone, int32_t size) {
+ REQUIRE(DNS_ZONE_VALID(zone));
+
+ zone->journalsize = size;
+}
+
+int32_t
+dns_zone_getjournalsize(dns_zone_t *zone) {
+ REQUIRE(DNS_ZONE_VALID(zone));
+
+ return (zone->journalsize);
+}
+
+static void
+zone_namerd_tostr(dns_zone_t *zone, char *buf, size_t length) {
+ isc_result_t result = ISC_R_FAILURE;
+ isc_buffer_t buffer;
+
+ REQUIRE(buf != NULL);
+ REQUIRE(length > 1U);
+
+ /*
+ * Leave space for terminating '\0'.
+ */
+ isc_buffer_init(&buffer, buf, (unsigned int)length - 1);
+ if (zone->type != dns_zone_redirect && zone->type != dns_zone_key) {
+ if (dns_name_dynamic(&zone->origin)) {
+ result = dns_name_totext(&zone->origin, true, &buffer);
+ }
+ if (result != ISC_R_SUCCESS &&
+ isc_buffer_availablelength(&buffer) >=
+ (sizeof("<UNKNOWN>") - 1))
+ {
+ isc_buffer_putstr(&buffer, "<UNKNOWN>");
+ }
+
+ if (isc_buffer_availablelength(&buffer) > 0) {
+ isc_buffer_putstr(&buffer, "/");
+ }
+ (void)dns_rdataclass_totext(zone->rdclass, &buffer);
+ }
+
+ if (zone->view != NULL && strcmp(zone->view->name, "_bind") != 0 &&
+ strcmp(zone->view->name, "_default") != 0 &&
+ strlen(zone->view->name) < isc_buffer_availablelength(&buffer))
+ {
+ isc_buffer_putstr(&buffer, "/");
+ isc_buffer_putstr(&buffer, zone->view->name);
+ }
+ if (inline_secure(zone) && 9U < isc_buffer_availablelength(&buffer)) {
+ isc_buffer_putstr(&buffer, " (signed)");
+ }
+ if (inline_raw(zone) && 11U < isc_buffer_availablelength(&buffer)) {
+ isc_buffer_putstr(&buffer, " (unsigned)");
+ }
+
+ buf[isc_buffer_usedlength(&buffer)] = '\0';
+}
+
+static void
+zone_name_tostr(dns_zone_t *zone, char *buf, size_t length) {
+ isc_result_t result = ISC_R_FAILURE;
+ isc_buffer_t buffer;
+
+ REQUIRE(buf != NULL);
+ REQUIRE(length > 1U);
+
+ /*
+ * Leave space for terminating '\0'.
+ */
+ isc_buffer_init(&buffer, buf, (unsigned int)length - 1);
+ if (dns_name_dynamic(&zone->origin)) {
+ result = dns_name_totext(&zone->origin, true, &buffer);
+ }
+ if (result != ISC_R_SUCCESS &&
+ isc_buffer_availablelength(&buffer) >= (sizeof("<UNKNOWN>") - 1))
+ {
+ isc_buffer_putstr(&buffer, "<UNKNOWN>");
+ }
+
+ buf[isc_buffer_usedlength(&buffer)] = '\0';
+}
+
+static void
+zone_rdclass_tostr(dns_zone_t *zone, char *buf, size_t length) {
+ isc_buffer_t buffer;
+
+ REQUIRE(buf != NULL);
+ REQUIRE(length > 1U);
+
+ /*
+ * Leave space for terminating '\0'.
+ */
+ isc_buffer_init(&buffer, buf, (unsigned int)length - 1);
+ (void)dns_rdataclass_totext(zone->rdclass, &buffer);
+
+ buf[isc_buffer_usedlength(&buffer)] = '\0';
+}
+
+static void
+zone_viewname_tostr(dns_zone_t *zone, char *buf, size_t length) {
+ isc_buffer_t buffer;
+
+ REQUIRE(buf != NULL);
+ REQUIRE(length > 1U);
+
+ /*
+ * Leave space for terminating '\0'.
+ */
+ isc_buffer_init(&buffer, buf, (unsigned int)length - 1);
+
+ if (zone->view == NULL) {
+ isc_buffer_putstr(&buffer, "_none");
+ } else if (strlen(zone->view->name) <
+ isc_buffer_availablelength(&buffer))
+ {
+ isc_buffer_putstr(&buffer, zone->view->name);
+ } else {
+ isc_buffer_putstr(&buffer, "_toolong");
+ }
+
+ buf[isc_buffer_usedlength(&buffer)] = '\0';
+}
+
+void
+dns_zone_name(dns_zone_t *zone, char *buf, size_t length) {
+ REQUIRE(DNS_ZONE_VALID(zone));
+ REQUIRE(buf != NULL);
+
+ LOCK_ZONE(zone);
+ zone_namerd_tostr(zone, buf, length);
+ UNLOCK_ZONE(zone);
+}
+
+void
+dns_zone_nameonly(dns_zone_t *zone, char *buf, size_t length) {
+ REQUIRE(DNS_ZONE_VALID(zone));
+ REQUIRE(buf != NULL);
+ zone_name_tostr(zone, buf, length);
+}
+
+void
+dns_zone_logv(dns_zone_t *zone, isc_logcategory_t *category, int level,
+ const char *prefix, const char *fmt, va_list ap) {
+ char message[4096];
+ const char *zstr;
+
+ REQUIRE(DNS_ZONE_VALID(zone));
+
+ if (!isc_log_wouldlog(dns_lctx, level)) {
+ return;
+ }
+
+ vsnprintf(message, sizeof(message), fmt, ap);
+
+ switch (zone->type) {
+ case dns_zone_key:
+ zstr = "managed-keys-zone";
+ break;
+ case dns_zone_redirect:
+ zstr = "redirect-zone";
+ break;
+ default:
+ zstr = "zone ";
+ }
+
+ isc_log_write(dns_lctx, category, DNS_LOGMODULE_ZONE, level,
+ "%s%s%s%s: %s", (prefix != NULL ? prefix : ""),
+ (prefix != NULL ? ": " : ""), zstr, zone->strnamerd,
+ message);
+}
+
+static void
+notify_log(dns_zone_t *zone, int level, const char *fmt, ...) {
+ va_list ap;
+
+ va_start(ap, fmt);
+ dns_zone_logv(zone, DNS_LOGCATEGORY_NOTIFY, level, NULL, fmt, ap);
+ va_end(ap);
+}
+
+void
+dns_zone_logc(dns_zone_t *zone, isc_logcategory_t *category, int level,
+ const char *fmt, ...) {
+ va_list ap;
+
+ va_start(ap, fmt);
+ dns_zone_logv(zone, category, level, NULL, fmt, ap);
+ va_end(ap);
+}
+
+void
+dns_zone_log(dns_zone_t *zone, int level, const char *fmt, ...) {
+ va_list ap;
+
+ va_start(ap, fmt);
+ dns_zone_logv(zone, DNS_LOGCATEGORY_GENERAL, level, NULL, fmt, ap);
+ va_end(ap);
+}
+
+static void
+zone_debuglog(dns_zone_t *zone, const char *me, int debuglevel, const char *fmt,
+ ...) {
+ int level = ISC_LOG_DEBUG(debuglevel);
+ va_list ap;
+
+ va_start(ap, fmt);
+ dns_zone_logv(zone, DNS_LOGCATEGORY_GENERAL, level, me, fmt, ap);
+ va_end(ap);
+}
+
+static void
+dnssec_log(dns_zone_t *zone, int level, const char *fmt, ...) {
+ va_list ap;
+
+ va_start(ap, fmt);
+ dns_zone_logv(zone, DNS_LOGCATEGORY_DNSSEC, level, NULL, fmt, ap);
+ va_end(ap);
+}
+
+static int
+message_count(dns_message_t *msg, dns_section_t section, dns_rdatatype_t type) {
+ isc_result_t result;
+ dns_name_t *name;
+ dns_rdataset_t *curr;
+ int count = 0;
+
+ result = dns_message_firstname(msg, section);
+ while (result == ISC_R_SUCCESS) {
+ name = NULL;
+ dns_message_currentname(msg, section, &name);
+
+ for (curr = ISC_LIST_TAIL(name->list); curr != NULL;
+ curr = ISC_LIST_PREV(curr, link))
+ {
+ if (curr->type == type) {
+ count++;
+ }
+ }
+ result = dns_message_nextname(msg, section);
+ }
+
+ return (count);
+}
+
+void
+dns_zone_setmaxxfrin(dns_zone_t *zone, uint32_t maxxfrin) {
+ REQUIRE(DNS_ZONE_VALID(zone));
+
+ zone->maxxfrin = maxxfrin;
+}
+
+uint32_t
+dns_zone_getmaxxfrin(dns_zone_t *zone) {
+ REQUIRE(DNS_ZONE_VALID(zone));
+
+ return (zone->maxxfrin);
+}
+
+void
+dns_zone_setmaxxfrout(dns_zone_t *zone, uint32_t maxxfrout) {
+ REQUIRE(DNS_ZONE_VALID(zone));
+ zone->maxxfrout = maxxfrout;
+}
+
+uint32_t
+dns_zone_getmaxxfrout(dns_zone_t *zone) {
+ REQUIRE(DNS_ZONE_VALID(zone));
+
+ return (zone->maxxfrout);
+}
+
+dns_zonetype_t
+dns_zone_gettype(dns_zone_t *zone) {
+ REQUIRE(DNS_ZONE_VALID(zone));
+
+ return (zone->type);
+}
+
+const char *
+dns_zonetype_name(dns_zonetype_t type) {
+ switch (type) {
+ case dns_zone_none:
+ return ("none");
+ case dns_zone_primary:
+ return ("primary");
+ case dns_zone_secondary:
+ return ("secondary");
+ case dns_zone_mirror:
+ return ("mirror");
+ case dns_zone_stub:
+ return ("stub");
+ case dns_zone_staticstub:
+ return ("static-stub");
+ case dns_zone_key:
+ return ("key");
+ case dns_zone_dlz:
+ return ("dlz");
+ case dns_zone_redirect:
+ return ("redirect");
+ default:
+ return ("unknown");
+ }
+}
+
+dns_zonetype_t
+dns_zone_getredirecttype(dns_zone_t *zone) {
+ REQUIRE(DNS_ZONE_VALID(zone));
+ REQUIRE(zone->type == dns_zone_redirect);
+
+ return (zone->primaries == NULL ? dns_zone_primary
+ : dns_zone_secondary);
+}
+
+dns_name_t *
+dns_zone_getorigin(dns_zone_t *zone) {
+ REQUIRE(DNS_ZONE_VALID(zone));
+
+ return (&zone->origin);
+}
+
+void
+dns_zone_settask(dns_zone_t *zone, isc_task_t *task) {
+ REQUIRE(DNS_ZONE_VALID(zone));
+
+ LOCK_ZONE(zone);
+ if (zone->task != NULL) {
+ isc_task_detach(&zone->task);
+ }
+ isc_task_attach(task, &zone->task);
+ ZONEDB_LOCK(&zone->dblock, isc_rwlocktype_read);
+ if (zone->db != NULL) {
+ dns_db_settask(zone->db, zone->task);
+ }
+ ZONEDB_UNLOCK(&zone->dblock, isc_rwlocktype_read);
+ UNLOCK_ZONE(zone);
+}
+
+void
+dns_zone_gettask(dns_zone_t *zone, isc_task_t **target) {
+ REQUIRE(DNS_ZONE_VALID(zone));
+ isc_task_attach(zone->task, target);
+}
+
+void
+dns_zone_setidlein(dns_zone_t *zone, uint32_t idlein) {
+ REQUIRE(DNS_ZONE_VALID(zone));
+
+ if (idlein == 0) {
+ idlein = DNS_DEFAULT_IDLEIN;
+ }
+ zone->idlein = idlein;
+}
+
+uint32_t
+dns_zone_getidlein(dns_zone_t *zone) {
+ REQUIRE(DNS_ZONE_VALID(zone));
+
+ return (zone->idlein);
+}
+
+void
+dns_zone_setidleout(dns_zone_t *zone, uint32_t idleout) {
+ REQUIRE(DNS_ZONE_VALID(zone));
+
+ zone->idleout = idleout;
+}
+
+uint32_t
+dns_zone_getidleout(dns_zone_t *zone) {
+ REQUIRE(DNS_ZONE_VALID(zone));
+
+ return (zone->idleout);
+}
+
+static void
+notify_done(isc_task_t *task, isc_event_t *event) {
+ dns_requestevent_t *revent = (dns_requestevent_t *)event;
+ dns_notify_t *notify;
+ isc_result_t result;
+ dns_message_t *message = NULL;
+ isc_buffer_t buf;
+ char rcode[128];
+ char addrbuf[ISC_SOCKADDR_FORMATSIZE];
+
+ UNUSED(task);
+
+ notify = event->ev_arg;
+ REQUIRE(DNS_NOTIFY_VALID(notify));
+ INSIST(task == notify->zone->task);
+
+ isc_buffer_init(&buf, rcode, sizeof(rcode));
+ isc_sockaddr_format(&notify->dst, addrbuf, sizeof(addrbuf));
+ dns_message_create(notify->zone->mctx, DNS_MESSAGE_INTENTPARSE,
+ &message);
+
+ if (revent->result != ISC_R_SUCCESS) {
+ result = revent->result;
+ goto fail;
+ }
+
+ result = dns_request_getresponse(revent->request, message,
+ DNS_MESSAGEPARSE_PRESERVEORDER);
+ if (result != ISC_R_SUCCESS) {
+ goto fail;
+ }
+
+ result = dns_rcode_totext(message->rcode, &buf);
+ if (result == ISC_R_SUCCESS) {
+ notify_log(notify->zone, ISC_LOG_DEBUG(3),
+ "notify response from %s: %.*s", addrbuf,
+ (int)buf.used, rcode);
+ }
+
+ goto done;
+
+fail:
+ notify_log(notify->zone, ISC_LOG_DEBUG(2), "notify to %s failed: %s",
+ addrbuf, isc_result_totext(result));
+ if (result == ISC_R_TIMEDOUT) {
+ notify_log(notify->zone, ISC_LOG_DEBUG(1),
+ "notify to %s: retries exceeded", addrbuf);
+ }
+done:
+ notify_destroy(notify, false);
+ isc_event_free(&event);
+ dns_message_detach(&message);
+}
+
+struct secure_event {
+ isc_event_t e;
+ dns_db_t *db;
+ uint32_t serial;
+};
+
+static void
+update_log_cb(void *arg, dns_zone_t *zone, int level, const char *message) {
+ UNUSED(arg);
+ dns_zone_log(zone, level, "%s", message);
+}
+
+static isc_result_t
+sync_secure_journal(dns_zone_t *zone, dns_zone_t *raw, dns_journal_t *journal,
+ uint32_t start, uint32_t end, dns_difftuple_t **soatuplep,
+ dns_diff_t *diff) {
+ isc_result_t result;
+ dns_difftuple_t *tuple = NULL;
+ dns_diffop_t op = DNS_DIFFOP_ADD;
+ int n_soa = 0;
+
+ REQUIRE(soatuplep != NULL);
+
+ if (start == end) {
+ return (DNS_R_UNCHANGED);
+ }
+
+ CHECK(dns_journal_iter_init(journal, start, end, NULL));
+ for (result = dns_journal_first_rr(journal); result == ISC_R_SUCCESS;
+ result = dns_journal_next_rr(journal))
+ {
+ dns_name_t *name = NULL;
+ uint32_t ttl;
+ dns_rdata_t *rdata = NULL;
+ dns_journal_current_rr(journal, &name, &ttl, &rdata);
+
+ if (rdata->type == dns_rdatatype_soa) {
+ n_soa++;
+ if (n_soa == 2) {
+ /*
+ * Save the latest raw SOA record.
+ */
+ if (*soatuplep != NULL) {
+ dns_difftuple_free(soatuplep);
+ }
+ CHECK(dns_difftuple_create(
+ diff->mctx, DNS_DIFFOP_ADD, name, ttl,
+ rdata, soatuplep));
+ }
+ if (n_soa == 3) {
+ n_soa = 1;
+ }
+ continue;
+ }
+
+ /* Sanity. */
+ if (n_soa == 0) {
+ dns_zone_log(raw, ISC_LOG_ERROR,
+ "corrupt journal file: '%s'\n",
+ raw->journal);
+ return (ISC_R_FAILURE);
+ }
+
+ if (zone->privatetype != 0 && rdata->type == zone->privatetype)
+ {
+ continue;
+ }
+
+ if (rdata->type == dns_rdatatype_nsec ||
+ rdata->type == dns_rdatatype_rrsig ||
+ rdata->type == dns_rdatatype_nsec3 ||
+ rdata->type == dns_rdatatype_dnskey ||
+ rdata->type == dns_rdatatype_nsec3param)
+ {
+ continue;
+ }
+
+ op = (n_soa == 1) ? DNS_DIFFOP_DEL : DNS_DIFFOP_ADD;
+
+ CHECK(dns_difftuple_create(diff->mctx, op, name, ttl, rdata,
+ &tuple));
+ dns_diff_appendminimal(diff, &tuple);
+ }
+ if (result == ISC_R_NOMORE) {
+ result = ISC_R_SUCCESS;
+ }
+
+failure:
+ return (result);
+}
+
+static isc_result_t
+sync_secure_db(dns_zone_t *seczone, dns_zone_t *raw, dns_db_t *secdb,
+ dns_dbversion_t *secver, dns_difftuple_t **soatuple,
+ dns_diff_t *diff) {
+ isc_result_t result;
+ dns_db_t *rawdb = NULL;
+ dns_dbversion_t *rawver = NULL;
+ dns_difftuple_t *tuple = NULL, *next;
+ dns_difftuple_t *oldtuple = NULL, *newtuple = NULL;
+ dns_rdata_soa_t oldsoa, newsoa;
+
+ REQUIRE(DNS_ZONE_VALID(seczone));
+ REQUIRE(soatuple != NULL && *soatuple == NULL);
+
+ if (!seczone->sourceserialset) {
+ return (DNS_R_UNCHANGED);
+ }
+
+ dns_db_attach(raw->db, &rawdb);
+ dns_db_currentversion(rawdb, &rawver);
+ result = dns_db_diffx(diff, rawdb, rawver, secdb, secver, NULL);
+ dns_db_closeversion(rawdb, &rawver, false);
+ dns_db_detach(&rawdb);
+
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+
+ for (tuple = ISC_LIST_HEAD(diff->tuples); tuple != NULL; tuple = next) {
+ next = ISC_LIST_NEXT(tuple, link);
+ if (tuple->rdata.type == dns_rdatatype_nsec ||
+ tuple->rdata.type == dns_rdatatype_rrsig ||
+ tuple->rdata.type == dns_rdatatype_dnskey ||
+ tuple->rdata.type == dns_rdatatype_nsec3 ||
+ tuple->rdata.type == dns_rdatatype_nsec3param)
+ {
+ ISC_LIST_UNLINK(diff->tuples, tuple, link);
+ dns_difftuple_free(&tuple);
+ continue;
+ }
+ if (tuple->rdata.type == dns_rdatatype_soa) {
+ if (tuple->op == DNS_DIFFOP_DEL) {
+ INSIST(oldtuple == NULL);
+ oldtuple = tuple;
+ }
+ if (tuple->op == DNS_DIFFOP_ADD) {
+ INSIST(newtuple == NULL);
+ newtuple = tuple;
+ }
+ }
+ }
+
+ if (oldtuple != NULL && newtuple != NULL) {
+ result = dns_rdata_tostruct(&oldtuple->rdata, &oldsoa, NULL);
+ RUNTIME_CHECK(result == ISC_R_SUCCESS);
+
+ result = dns_rdata_tostruct(&newtuple->rdata, &newsoa, NULL);
+ RUNTIME_CHECK(result == ISC_R_SUCCESS);
+
+ /*
+ * If the SOA records are the same except for the serial
+ * remove them from the diff.
+ */
+ if (oldtuple->ttl == newtuple->ttl &&
+ oldsoa.refresh == newsoa.refresh &&
+ oldsoa.retry == newsoa.retry &&
+ oldsoa.minimum == newsoa.minimum &&
+ oldsoa.expire == newsoa.expire &&
+ dns_name_equal(&oldsoa.origin, &newsoa.origin) &&
+ dns_name_equal(&oldsoa.contact, &newsoa.contact))
+ {
+ ISC_LIST_UNLINK(diff->tuples, oldtuple, link);
+ dns_difftuple_free(&oldtuple);
+ ISC_LIST_UNLINK(diff->tuples, newtuple, link);
+ dns_difftuple_free(&newtuple);
+ }
+ }
+
+ if (ISC_LIST_EMPTY(diff->tuples)) {
+ return (DNS_R_UNCHANGED);
+ }
+
+ /*
+ * If there are still SOA records in the diff they can now be removed
+ * saving the new SOA record.
+ */
+ if (oldtuple != NULL) {
+ ISC_LIST_UNLINK(diff->tuples, oldtuple, link);
+ dns_difftuple_free(&oldtuple);
+ }
+
+ if (newtuple != NULL) {
+ ISC_LIST_UNLINK(diff->tuples, newtuple, link);
+ *soatuple = newtuple;
+ }
+
+ return (ISC_R_SUCCESS);
+}
+
+static void
+receive_secure_serial(isc_task_t *task, isc_event_t *event) {
+ static char me[] = "receive_secure_serial";
+ isc_result_t result = ISC_R_SUCCESS;
+ dns_journal_t *rjournal = NULL;
+ dns_journal_t *sjournal = NULL;
+ uint32_t start, end;
+ dns_zone_t *zone;
+ dns_difftuple_t *tuple = NULL, *soatuple = NULL;
+ dns_update_log_t log = { update_log_cb, NULL };
+ uint32_t newserial = 0, desired = 0;
+ isc_time_t timenow;
+ int level = ISC_LOG_ERROR;
+
+ UNUSED(task);
+
+ zone = event->ev_arg;
+ end = ((struct secure_event *)event)->serial;
+
+ ENTER;
+
+ LOCK_ZONE(zone);
+
+ /*
+ * If we are already processing a receive secure serial event
+ * for the zone, just queue the new one and exit.
+ */
+ if (zone->rss_event != NULL && zone->rss_event != event) {
+ ISC_LIST_APPEND(zone->rss_events, event, ev_link);
+ UNLOCK_ZONE(zone);
+ return;
+ }
+
+nextevent:
+ if (zone->rss_event != NULL) {
+ INSIST(zone->rss_event == event);
+ UNLOCK_ZONE(zone);
+ } else {
+ zone->rss_event = event;
+ dns_diff_init(zone->mctx, &zone->rss_diff);
+
+ /*
+ * zone->db may be NULL, if the load from disk failed.
+ */
+ result = ISC_R_SUCCESS;
+ ZONEDB_LOCK(&zone->dblock, isc_rwlocktype_read);
+ if (zone->db != NULL) {
+ dns_db_attach(zone->db, &zone->rss_db);
+ } else {
+ result = ISC_R_FAILURE;
+ }
+ ZONEDB_UNLOCK(&zone->dblock, isc_rwlocktype_read);
+
+ if (result == ISC_R_SUCCESS && zone->raw != NULL) {
+ dns_zone_attach(zone->raw, &zone->rss_raw);
+ } else {
+ result = ISC_R_FAILURE;
+ }
+
+ UNLOCK_ZONE(zone);
+
+ CHECK(result);
+
+ /*
+ * We first attempt to sync the raw zone to the secure zone
+ * by using the raw zone's journal, applying all the deltas
+ * from the latest source-serial of the secure zone up to
+ * the current serial number of the raw zone.
+ *
+ * If that fails, then we'll fall back to a direct comparison
+ * between raw and secure zones.
+ */
+ CHECK(dns_journal_open(zone->rss_raw->mctx,
+ zone->rss_raw->journal,
+ DNS_JOURNAL_WRITE, &rjournal));
+
+ result = dns_journal_open(zone->mctx, zone->journal,
+ DNS_JOURNAL_READ, &sjournal);
+ if (result != ISC_R_SUCCESS && result != ISC_R_NOTFOUND) {
+ goto failure;
+ }
+
+ if (!dns_journal_get_sourceserial(rjournal, &start)) {
+ start = dns_journal_first_serial(rjournal);
+ dns_journal_set_sourceserial(rjournal, start);
+ }
+ if (sjournal != NULL) {
+ uint32_t serial;
+ /*
+ * We read the secure journal first, if that
+ * exists use its value provided it is greater
+ * that from the raw journal.
+ */
+ if (dns_journal_get_sourceserial(sjournal, &serial)) {
+ if (isc_serial_gt(serial, start)) {
+ start = serial;
+ }
+ }
+ dns_journal_destroy(&sjournal);
+ }
+
+ dns_db_currentversion(zone->rss_db, &zone->rss_oldver);
+ CHECK(dns_db_newversion(zone->rss_db, &zone->rss_newver));
+
+ /*
+ * Try to apply diffs from the raw zone's journal to the secure
+ * zone. If that fails, we recover by syncing up the databases
+ * directly.
+ */
+ result = sync_secure_journal(zone, zone->rss_raw, rjournal,
+ start, end, &soatuple,
+ &zone->rss_diff);
+ if (result == DNS_R_UNCHANGED) {
+ goto failure;
+ } else if (result != ISC_R_SUCCESS) {
+ CHECK(sync_secure_db(zone, zone->rss_raw, zone->rss_db,
+ zone->rss_oldver, &soatuple,
+ &zone->rss_diff));
+ }
+
+ CHECK(dns_diff_apply(&zone->rss_diff, zone->rss_db,
+ zone->rss_newver));
+
+ if (soatuple != NULL) {
+ uint32_t oldserial;
+
+ CHECK(dns_db_createsoatuple(
+ zone->rss_db, zone->rss_oldver,
+ zone->rss_diff.mctx, DNS_DIFFOP_DEL, &tuple));
+ oldserial = dns_soa_getserial(&tuple->rdata);
+ newserial = desired =
+ dns_soa_getserial(&soatuple->rdata);
+ if (!isc_serial_gt(newserial, oldserial)) {
+ newserial = oldserial + 1;
+ if (newserial == 0) {
+ newserial++;
+ }
+ dns_soa_setserial(newserial, &soatuple->rdata);
+ }
+ CHECK(do_one_tuple(&tuple, zone->rss_db,
+ zone->rss_newver, &zone->rss_diff));
+ CHECK(do_one_tuple(&soatuple, zone->rss_db,
+ zone->rss_newver, &zone->rss_diff));
+ } else {
+ CHECK(update_soa_serial(zone, zone->rss_db,
+ zone->rss_newver,
+ &zone->rss_diff, zone->mctx,
+ zone->updatemethod));
+ }
+ }
+ result = dns_update_signaturesinc(
+ &log, zone, zone->rss_db, zone->rss_oldver, zone->rss_newver,
+ &zone->rss_diff, zone->sigvalidityinterval, &zone->rss_state);
+ if (result == DNS_R_CONTINUE) {
+ if (rjournal != NULL) {
+ dns_journal_destroy(&rjournal);
+ }
+ isc_task_send(task, &event);
+ return;
+ }
+ /*
+ * If something went wrong while trying to update the secure zone and
+ * the latter was already signed before, do not apply raw zone deltas
+ * to it as that would break existing DNSSEC signatures. However, if
+ * the secure zone was not yet signed (e.g. because no signing keys
+ * were created for it), commence applying raw zone deltas to it so
+ * that contents of the raw zone and the secure zone are kept in sync.
+ */
+ if (result != ISC_R_SUCCESS && dns_db_issecure(zone->rss_db)) {
+ goto failure;
+ }
+
+ if (rjournal == NULL) {
+ CHECK(dns_journal_open(zone->rss_raw->mctx,
+ zone->rss_raw->journal,
+ DNS_JOURNAL_WRITE, &rjournal));
+ }
+ CHECK(zone_journal(zone, &zone->rss_diff, &end,
+ "receive_secure_serial"));
+
+ dns_journal_set_sourceserial(rjournal, end);
+ dns_journal_commit(rjournal);
+
+ LOCK_ZONE(zone);
+ DNS_ZONE_SETFLAG(zone, DNS_ZONEFLG_NEEDNOTIFY);
+
+ zone->sourceserial = end;
+ zone->sourceserialset = true;
+ zone_needdump(zone, DNS_DUMP_DELAY);
+
+ /*
+ * Set resign time to make sure it is set to the earliest
+ * signature expiration.
+ */
+ set_resigntime(zone);
+ TIME_NOW(&timenow);
+ zone_settimer(zone, &timenow);
+ UNLOCK_ZONE(zone);
+
+ dns_db_closeversion(zone->rss_db, &zone->rss_oldver, false);
+ dns_db_closeversion(zone->rss_db, &zone->rss_newver, true);
+
+ if (newserial != 0) {
+ dns_zone_log(zone, ISC_LOG_INFO, "serial %u (unsigned %u)",
+ newserial, desired);
+ }
+
+failure:
+ isc_event_free(&zone->rss_event);
+ event = ISC_LIST_HEAD(zone->rss_events);
+
+ if (zone->rss_raw != NULL) {
+ dns_zone_detach(&zone->rss_raw);
+ }
+ if (result != ISC_R_SUCCESS) {
+ LOCK_ZONE(zone);
+ set_resigntime(zone);
+ TIME_NOW(&timenow);
+ zone_settimer(zone, &timenow);
+ UNLOCK_ZONE(zone);
+ if (result == DNS_R_UNCHANGED) {
+ level = ISC_LOG_INFO;
+ }
+ dns_zone_log(zone, level, "receive_secure_serial: %s",
+ isc_result_totext(result));
+ }
+ if (tuple != NULL) {
+ dns_difftuple_free(&tuple);
+ }
+ if (soatuple != NULL) {
+ dns_difftuple_free(&soatuple);
+ }
+ if (zone->rss_db != NULL) {
+ if (zone->rss_oldver != NULL) {
+ dns_db_closeversion(zone->rss_db, &zone->rss_oldver,
+ false);
+ }
+ if (zone->rss_newver != NULL) {
+ dns_db_closeversion(zone->rss_db, &zone->rss_newver,
+ false);
+ }
+ dns_db_detach(&zone->rss_db);
+ }
+ INSIST(zone->rss_oldver == NULL);
+ INSIST(zone->rss_newver == NULL);
+ if (rjournal != NULL) {
+ dns_journal_destroy(&rjournal);
+ }
+ dns_diff_clear(&zone->rss_diff);
+
+ if (event != NULL) {
+ LOCK_ZONE(zone);
+ isc_refcount_decrement(&zone->irefs);
+ ISC_LIST_UNLINK(zone->rss_events, event, ev_link);
+ goto nextevent;
+ }
+
+ event = ISC_LIST_HEAD(zone->rss_post);
+ while (event != NULL) {
+ ISC_LIST_UNLINK(zone->rss_post, event, ev_link);
+ rss_post(zone, event);
+ event = ISC_LIST_HEAD(zone->rss_post);
+ }
+
+ dns_zone_idetach(&zone);
+}
+
+static isc_result_t
+zone_send_secureserial(dns_zone_t *zone, uint32_t serial) {
+ isc_event_t *e;
+ dns_zone_t *dummy = NULL;
+
+ e = isc_event_allocate(zone->secure->mctx, zone,
+ DNS_EVENT_ZONESECURESERIAL,
+ receive_secure_serial, zone->secure,
+ sizeof(struct secure_event));
+ ((struct secure_event *)e)->serial = serial;
+ INSIST(LOCKED_ZONE(zone->secure));
+ zone_iattach(zone->secure, &dummy);
+ isc_task_send(zone->secure->task, &e);
+
+ DNS_ZONE_CLRFLAG(zone, DNS_ZONEFLG_SENDSECURE);
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+checkandaddsoa(dns_db_t *db, dns_dbnode_t *node, dns_dbversion_t *version,
+ dns_rdataset_t *rdataset, uint32_t oldserial) {
+ dns_rdata_soa_t soa;
+ dns_rdata_t rdata = DNS_RDATA_INIT;
+ dns_rdatalist_t temprdatalist;
+ dns_rdataset_t temprdataset;
+ isc_buffer_t b;
+ isc_result_t result;
+ unsigned char buf[DNS_SOA_BUFFERSIZE];
+ dns_fixedname_t fixed;
+ dns_name_t *name;
+
+ result = dns_rdataset_first(rdataset);
+ RUNTIME_CHECK(result == ISC_R_SUCCESS);
+ dns_rdataset_current(rdataset, &rdata);
+ result = dns_rdata_tostruct(&rdata, &soa, NULL);
+ RUNTIME_CHECK(result == ISC_R_SUCCESS);
+
+ if (isc_serial_gt(soa.serial, oldserial)) {
+ return (dns_db_addrdataset(db, node, version, 0, rdataset, 0,
+ NULL));
+ }
+ /*
+ * Always bump the serial.
+ */
+ oldserial++;
+ if (oldserial == 0) {
+ oldserial++;
+ }
+ soa.serial = oldserial;
+
+ /*
+ * Construct a replacement rdataset.
+ */
+ dns_rdata_reset(&rdata);
+ isc_buffer_init(&b, buf, sizeof(buf));
+ result = dns_rdata_fromstruct(&rdata, rdataset->rdclass,
+ dns_rdatatype_soa, &soa, &b);
+ RUNTIME_CHECK(result == ISC_R_SUCCESS);
+ dns_rdatalist_init(&temprdatalist);
+ temprdatalist.rdclass = rdata.rdclass;
+ temprdatalist.type = rdata.type;
+ temprdatalist.ttl = rdataset->ttl;
+ ISC_LIST_APPEND(temprdatalist.rdata, &rdata, link);
+
+ dns_rdataset_init(&temprdataset);
+ result = dns_rdatalist_tordataset(&temprdatalist, &temprdataset);
+ RUNTIME_CHECK(result == ISC_R_SUCCESS);
+
+ name = dns_fixedname_initname(&fixed);
+ result = dns_db_nodefullname(db, node, name);
+ RUNTIME_CHECK(result == ISC_R_SUCCESS);
+ dns_rdataset_getownercase(rdataset, name);
+ dns_rdataset_setownercase(&temprdataset, name);
+ return (dns_db_addrdataset(db, node, version, 0, &temprdataset, 0,
+ NULL));
+}
+
+/*
+ * This function should populate an nsec3paramlist_t with the
+ * nsecparam_t data from a zone.
+ */
+static isc_result_t
+save_nsec3param(dns_zone_t *zone, nsec3paramlist_t *nsec3list) {
+ isc_result_t result;
+ dns_dbnode_t *node = NULL;
+ dns_rdataset_t rdataset, prdataset;
+ dns_dbversion_t *version = NULL;
+ nsec3param_t *nsec3param = NULL;
+ nsec3param_t *nsec3p = NULL;
+ nsec3param_t *next;
+ dns_db_t *db = NULL;
+ unsigned char buf[DNS_NSEC3PARAM_BUFFERSIZE];
+
+ REQUIRE(DNS_ZONE_VALID(zone));
+ REQUIRE(nsec3list != NULL);
+ REQUIRE(ISC_LIST_EMPTY(*nsec3list));
+
+ dns_rdataset_init(&rdataset);
+ dns_rdataset_init(&prdataset);
+
+ dns_db_attach(zone->db, &db);
+ CHECK(dns_db_getoriginnode(db, &node));
+
+ dns_db_currentversion(db, &version);
+ result = dns_db_findrdataset(db, node, version,
+ dns_rdatatype_nsec3param,
+ dns_rdatatype_none, 0, &rdataset, NULL);
+
+ if (result != ISC_R_SUCCESS) {
+ goto getprivate;
+ }
+
+ /*
+ * Walk nsec3param rdataset making a list of parameters (note that
+ * multiple simultaneous nsec3 chains are annoyingly legal -- this
+ * is why we use an nsec3list, even though we will usually only
+ * have one).
+ */
+ for (result = dns_rdataset_first(&rdataset); result == ISC_R_SUCCESS;
+ result = dns_rdataset_next(&rdataset))
+ {
+ dns_rdata_t rdata = DNS_RDATA_INIT;
+ dns_rdata_t private = DNS_RDATA_INIT;
+
+ dns_rdataset_current(&rdataset, &rdata);
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL,
+ DNS_LOGMODULE_ZONE, ISC_LOG_DEBUG(3),
+ "looping through nsec3param data");
+ nsec3param = isc_mem_get(zone->mctx, sizeof(nsec3param_t));
+ ISC_LINK_INIT(nsec3param, link);
+
+ /*
+ * now transfer the data from the rdata to
+ * the nsec3param
+ */
+ dns_nsec3param_toprivate(&rdata, &private, zone->privatetype,
+ nsec3param->data,
+ sizeof(nsec3param->data));
+ nsec3param->length = private.length;
+ ISC_LIST_APPEND(*nsec3list, nsec3param, link);
+ }
+
+getprivate:
+ result = dns_db_findrdataset(db, node, version, zone->privatetype,
+ dns_rdatatype_none, 0, &prdataset, NULL);
+ if (result != ISC_R_SUCCESS) {
+ goto done;
+ }
+
+ /*
+ * walk private type records, converting them to nsec3 parameters
+ * using dns_nsec3param_fromprivate(), do the right thing based on
+ * CREATE and REMOVE flags
+ */
+ for (result = dns_rdataset_first(&prdataset); result == ISC_R_SUCCESS;
+ result = dns_rdataset_next(&prdataset))
+ {
+ dns_rdata_t rdata = DNS_RDATA_INIT;
+ dns_rdata_t private = DNS_RDATA_INIT;
+
+ dns_rdataset_current(&prdataset, &private);
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL,
+ DNS_LOGMODULE_ZONE, ISC_LOG_DEBUG(3),
+ "looping through nsec3param private data");
+
+ /*
+ * Do we have a valid private record?
+ */
+ if (!dns_nsec3param_fromprivate(&private, &rdata, buf,
+ sizeof(buf)))
+ {
+ continue;
+ }
+
+ /*
+ * Remove any NSEC3PARAM records scheduled to be removed.
+ */
+ if (NSEC3REMOVE(rdata.data[1])) {
+ /*
+ * Zero out the flags.
+ */
+ rdata.data[1] = 0;
+
+ for (nsec3p = ISC_LIST_HEAD(*nsec3list); nsec3p != NULL;
+ nsec3p = next)
+ {
+ next = ISC_LIST_NEXT(nsec3p, link);
+
+ if (nsec3p->length == rdata.length + 1 &&
+ memcmp(rdata.data, nsec3p->data + 1,
+ nsec3p->length - 1) == 0)
+ {
+ ISC_LIST_UNLINK(*nsec3list, nsec3p,
+ link);
+ isc_mem_put(zone->mctx, nsec3p,
+ sizeof(nsec3param_t));
+ }
+ }
+ continue;
+ }
+
+ nsec3param = isc_mem_get(zone->mctx, sizeof(nsec3param_t));
+ ISC_LINK_INIT(nsec3param, link);
+
+ /*
+ * Copy the remaining private records so the nsec/nsec3
+ * chain gets created.
+ */
+ INSIST(private.length <= sizeof(nsec3param->data));
+ memmove(nsec3param->data, private.data, private.length);
+ nsec3param->length = private.length;
+ ISC_LIST_APPEND(*nsec3list, nsec3param, link);
+ }
+
+done:
+ if (result == ISC_R_NOMORE || result == ISC_R_NOTFOUND) {
+ result = ISC_R_SUCCESS;
+ }
+
+failure:
+ if (node != NULL) {
+ dns_db_detachnode(db, &node);
+ }
+ if (version != NULL) {
+ dns_db_closeversion(db, &version, false);
+ }
+ if (db != NULL) {
+ dns_db_detach(&db);
+ }
+ if (dns_rdataset_isassociated(&rdataset)) {
+ dns_rdataset_disassociate(&rdataset);
+ }
+ if (dns_rdataset_isassociated(&prdataset)) {
+ dns_rdataset_disassociate(&prdataset);
+ }
+ return (result);
+}
+
+/*
+ * Populate new zone db with private type records found by save_nsec3param().
+ */
+static isc_result_t
+restore_nsec3param(dns_zone_t *zone, dns_db_t *db, dns_dbversion_t *version,
+ nsec3paramlist_t *nsec3list) {
+ isc_result_t result = ISC_R_SUCCESS;
+ dns_diff_t diff;
+ dns_rdata_t rdata;
+ nsec3param_t *nsec3p = NULL;
+ nsec3param_t *next;
+
+ REQUIRE(DNS_ZONE_VALID(zone));
+ REQUIRE(!ISC_LIST_EMPTY(*nsec3list));
+
+ dns_diff_init(zone->mctx, &diff);
+
+ /*
+ * Loop through the list of private-type records, set the INITIAL
+ * and CREATE flags, and the add the record to the apex of the tree
+ * in db.
+ */
+ for (nsec3p = ISC_LIST_HEAD(*nsec3list); nsec3p != NULL; nsec3p = next)
+ {
+ next = ISC_LIST_NEXT(nsec3p, link);
+ dns_rdata_init(&rdata);
+ nsec3p->data[2] = DNS_NSEC3FLAG_CREATE | DNS_NSEC3FLAG_INITIAL;
+ rdata.length = nsec3p->length;
+ rdata.data = nsec3p->data;
+ rdata.type = zone->privatetype;
+ rdata.rdclass = zone->rdclass;
+ result = update_one_rr(db, version, &diff, DNS_DIFFOP_ADD,
+ &zone->origin, 0, &rdata);
+ if (result != ISC_R_SUCCESS) {
+ break;
+ }
+ }
+
+ dns_diff_clear(&diff);
+ return (result);
+}
+
+static isc_result_t
+copy_non_dnssec_records(dns_db_t *db, dns_db_t *version, dns_db_t *rawdb,
+ dns_dbiterator_t *dbiterator, unsigned int *oldserial) {
+ dns_dbnode_t *rawnode = NULL, *node = NULL;
+ dns_fixedname_t fixed;
+ dns_name_t *name = dns_fixedname_initname(&fixed);
+ dns_rdataset_t rdataset;
+ dns_rdatasetiter_t *rdsit = NULL;
+ isc_result_t result;
+
+ result = dns_dbiterator_current(dbiterator, &rawnode, name);
+ if (result != ISC_R_SUCCESS) {
+ return (ISC_R_SUCCESS);
+ }
+
+ dns_dbiterator_pause(dbiterator);
+
+ result = dns_db_findnode(db, name, true, &node);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+
+ result = dns_db_allrdatasets(rawdb, rawnode, NULL, 0, 0, &rdsit);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+
+ dns_rdataset_init(&rdataset);
+
+ for (result = dns_rdatasetiter_first(rdsit); result == ISC_R_SUCCESS;
+ result = dns_rdatasetiter_next(rdsit))
+ {
+ dns_rdatasetiter_current(rdsit, &rdataset);
+ if (rdataset.type == dns_rdatatype_nsec ||
+ rdataset.type == dns_rdatatype_rrsig ||
+ rdataset.type == dns_rdatatype_nsec3 ||
+ rdataset.type == dns_rdatatype_dnskey ||
+ rdataset.type == dns_rdatatype_nsec3param)
+ {
+ dns_rdataset_disassociate(&rdataset);
+ continue;
+ }
+ if (rdataset.type == dns_rdatatype_soa && oldserial != NULL) {
+ result = checkandaddsoa(db, node, version, &rdataset,
+ *oldserial);
+ } else {
+ result = dns_db_addrdataset(db, node, version, 0,
+ &rdataset, 0, NULL);
+ }
+ dns_rdataset_disassociate(&rdataset);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+ }
+ if (result == ISC_R_NOMORE) {
+ result = ISC_R_SUCCESS;
+ }
+
+cleanup:
+ if (rdsit != NULL) {
+ dns_rdatasetiter_destroy(&rdsit);
+ }
+ if (rawnode) {
+ dns_db_detachnode(rawdb, &rawnode);
+ }
+ if (node) {
+ dns_db_detachnode(db, &node);
+ }
+ return (result);
+}
+
+static void
+receive_secure_db(isc_task_t *task, isc_event_t *event) {
+ isc_result_t result;
+ dns_zone_t *zone;
+ dns_db_t *rawdb, *db = NULL;
+ dns_dbiterator_t *dbiterator = NULL;
+ dns_dbversion_t *version = NULL;
+ isc_time_t loadtime;
+ unsigned int oldserial = 0, *oldserialp = NULL;
+ nsec3paramlist_t nsec3list;
+ isc_event_t *setnsec3param_event;
+ dns_zone_t *dummy;
+
+ UNUSED(task);
+
+ ISC_LIST_INIT(nsec3list);
+
+ zone = event->ev_arg;
+ rawdb = ((struct secure_event *)event)->db;
+ isc_event_free(&event);
+
+ LOCK_ZONE(zone);
+ if (DNS_ZONE_FLAG(zone, DNS_ZONEFLG_EXITING) || !inline_secure(zone)) {
+ result = ISC_R_SHUTTINGDOWN;
+ goto failure;
+ }
+
+ TIME_NOW(&loadtime);
+ ZONEDB_LOCK(&zone->dblock, isc_rwlocktype_read);
+ if (zone->db != NULL) {
+ result = dns_db_getsoaserial(zone->db, NULL, &oldserial);
+ if (result == ISC_R_SUCCESS) {
+ oldserialp = &oldserial;
+ }
+
+ /*
+ * assemble nsec3parameters from the old zone, and set a flag
+ * if any are found
+ */
+ result = save_nsec3param(zone, &nsec3list);
+ if (result != ISC_R_SUCCESS) {
+ ZONEDB_UNLOCK(&zone->dblock, isc_rwlocktype_read);
+ goto failure;
+ }
+ }
+ ZONEDB_UNLOCK(&zone->dblock, isc_rwlocktype_read);
+
+ result = dns_db_create(zone->mctx, zone->db_argv[0], &zone->origin,
+ dns_dbtype_zone, zone->rdclass,
+ zone->db_argc - 1, zone->db_argv + 1, &db);
+ if (result != ISC_R_SUCCESS) {
+ goto failure;
+ }
+
+ result = dns_db_setgluecachestats(db, zone->gluecachestats);
+ if (result != ISC_R_SUCCESS && result != ISC_R_NOTIMPLEMENTED) {
+ goto failure;
+ }
+
+ result = dns_db_newversion(db, &version);
+ if (result != ISC_R_SUCCESS) {
+ goto failure;
+ }
+
+ result = dns_db_createiterator(rawdb, 0, &dbiterator);
+ if (result != ISC_R_SUCCESS) {
+ goto failure;
+ }
+
+ for (result = dns_dbiterator_first(dbiterator); result == ISC_R_SUCCESS;
+ result = dns_dbiterator_next(dbiterator))
+ {
+ result = copy_non_dnssec_records(db, version, rawdb, dbiterator,
+ oldserialp);
+ if (result != ISC_R_SUCCESS) {
+ goto failure;
+ }
+ }
+ dns_dbiterator_destroy(&dbiterator);
+ if (result != ISC_R_NOMORE) {
+ goto failure;
+ }
+
+ /*
+ * Call restore_nsec3param() to create private-type records from
+ * the old nsec3 parameters and insert them into db
+ */
+ if (!ISC_LIST_EMPTY(nsec3list)) {
+ result = restore_nsec3param(zone, db, version, &nsec3list);
+ if (result != ISC_R_SUCCESS) {
+ goto failure;
+ }
+ }
+
+ dns_db_closeversion(db, &version, true);
+
+ /*
+ * Lock hierarchy: zmgr, zone, raw.
+ */
+ INSIST(zone != zone->raw);
+ LOCK_ZONE(zone->raw);
+ DNS_ZONE_SETFLAG(zone, DNS_ZONEFLG_NEEDNOTIFY);
+ result = zone_postload(zone, db, loadtime, ISC_R_SUCCESS);
+ zone_needdump(zone, 0); /* XXXMPA */
+ UNLOCK_ZONE(zone->raw);
+
+ /*
+ * Process any queued NSEC3PARAM change requests.
+ */
+ while (!ISC_LIST_EMPTY(zone->setnsec3param_queue)) {
+ setnsec3param_event = ISC_LIST_HEAD(zone->setnsec3param_queue);
+ ISC_LIST_UNLINK(zone->setnsec3param_queue, setnsec3param_event,
+ ev_link);
+ dummy = NULL;
+ zone_iattach(zone, &dummy);
+ isc_task_send(zone->task, &setnsec3param_event);
+ }
+
+failure:
+ UNLOCK_ZONE(zone);
+ if (dbiterator != NULL) {
+ dns_dbiterator_destroy(&dbiterator);
+ }
+ if (result != ISC_R_SUCCESS) {
+ dns_zone_log(zone, ISC_LOG_ERROR, "receive_secure_db: %s",
+ isc_result_totext(result));
+ }
+
+ while (!ISC_LIST_EMPTY(nsec3list)) {
+ nsec3param_t *nsec3p;
+ nsec3p = ISC_LIST_HEAD(nsec3list);
+ ISC_LIST_UNLINK(nsec3list, nsec3p, link);
+ isc_mem_put(zone->mctx, nsec3p, sizeof(nsec3param_t));
+ }
+ if (db != NULL) {
+ if (version != NULL) {
+ dns_db_closeversion(db, &version, false);
+ }
+ dns_db_detach(&db);
+ }
+ dns_db_detach(&rawdb);
+ dns_zone_idetach(&zone);
+
+ INSIST(version == NULL);
+}
+
+static isc_result_t
+zone_send_securedb(dns_zone_t *zone, dns_db_t *db) {
+ isc_event_t *e;
+ dns_db_t *dummy = NULL;
+ dns_zone_t *secure = NULL;
+
+ e = isc_event_allocate(zone->secure->mctx, zone, DNS_EVENT_ZONESECUREDB,
+ receive_secure_db, zone->secure,
+ sizeof(struct secure_event));
+ dns_db_attach(db, &dummy);
+ ((struct secure_event *)e)->db = dummy;
+ INSIST(LOCKED_ZONE(zone->secure));
+ zone_iattach(zone->secure, &secure);
+ isc_task_send(zone->secure->task, &e);
+ DNS_ZONE_CLRFLAG(zone, DNS_ZONEFLG_SENDSECURE);
+ return (ISC_R_SUCCESS);
+}
+
+isc_result_t
+dns_zone_replacedb(dns_zone_t *zone, dns_db_t *db, bool dump) {
+ isc_result_t result;
+ dns_zone_t *secure = NULL;
+
+ REQUIRE(DNS_ZONE_VALID(zone));
+again:
+ LOCK_ZONE(zone);
+ if (inline_raw(zone)) {
+ secure = zone->secure;
+ INSIST(secure != zone);
+ TRYLOCK_ZONE(result, secure);
+ if (result != ISC_R_SUCCESS) {
+ UNLOCK_ZONE(zone);
+ secure = NULL;
+ isc_thread_yield();
+ goto again;
+ }
+ }
+ ZONEDB_LOCK(&zone->dblock, isc_rwlocktype_write);
+ result = zone_replacedb(zone, db, dump);
+ ZONEDB_UNLOCK(&zone->dblock, isc_rwlocktype_write);
+ if (secure != NULL) {
+ UNLOCK_ZONE(secure);
+ }
+ UNLOCK_ZONE(zone);
+ return (result);
+}
+
+static isc_result_t
+zone_replacedb(dns_zone_t *zone, dns_db_t *db, bool dump) {
+ dns_dbversion_t *ver;
+ isc_result_t result;
+ unsigned int soacount = 0;
+ unsigned int nscount = 0;
+
+ /*
+ * 'zone' and 'zone->db' locked by caller.
+ */
+ REQUIRE(DNS_ZONE_VALID(zone));
+ REQUIRE(LOCKED_ZONE(zone));
+ if (inline_raw(zone)) {
+ REQUIRE(LOCKED_ZONE(zone->secure));
+ }
+
+ result = zone_get_from_db(zone, db, &nscount, &soacount, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL);
+ if (result == ISC_R_SUCCESS) {
+ if (soacount != 1) {
+ dns_zone_log(zone, ISC_LOG_ERROR, "has %d SOA records",
+ soacount);
+ result = DNS_R_BADZONE;
+ }
+ if (nscount == 0 && zone->type != dns_zone_key) {
+ dns_zone_log(zone, ISC_LOG_ERROR, "has no NS records");
+ result = DNS_R_BADZONE;
+ }
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+ } else {
+ dns_zone_log(zone, ISC_LOG_ERROR,
+ "retrieving SOA and NS records failed: %s",
+ isc_result_totext(result));
+ return (result);
+ }
+
+ result = check_nsec3param(zone, db);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+
+ ver = NULL;
+ dns_db_currentversion(db, &ver);
+
+ /*
+ * The initial version of a secondary zone is always dumped;
+ * subsequent versions may be journaled instead if this
+ * is enabled in the configuration.
+ */
+ if (zone->db != NULL && zone->journal != NULL &&
+ DNS_ZONE_OPTION(zone, DNS_ZONEOPT_IXFRFROMDIFFS) &&
+ !DNS_ZONE_FLAG(zone, DNS_ZONEFLG_FORCEXFER))
+ {
+ uint32_t serial, oldserial;
+
+ dns_zone_log(zone, ISC_LOG_DEBUG(3), "generating diffs");
+
+ result = dns_db_getsoaserial(db, ver, &serial);
+ if (result != ISC_R_SUCCESS) {
+ dns_zone_log(zone, ISC_LOG_ERROR,
+ "ixfr-from-differences: unable to get "
+ "new serial");
+ goto fail;
+ }
+
+ /*
+ * This is checked in zone_postload() for primary zones.
+ */
+ result = zone_get_from_db(zone, zone->db, NULL, &soacount, NULL,
+ &oldserial, NULL, NULL, NULL, NULL,
+ NULL);
+ RUNTIME_CHECK(result == ISC_R_SUCCESS);
+ RUNTIME_CHECK(soacount > 0U);
+ if ((zone->type == dns_zone_secondary ||
+ (zone->type == dns_zone_redirect &&
+ zone->primaries != NULL)) &&
+ !isc_serial_gt(serial, oldserial))
+ {
+ uint32_t serialmin, serialmax;
+ serialmin = (oldserial + 1) & 0xffffffffU;
+ serialmax = (oldserial + 0x7fffffffU) & 0xffffffffU;
+ dns_zone_log(zone, ISC_LOG_ERROR,
+ "ixfr-from-differences: failed: "
+ "new serial (%u) out of range [%u - %u]",
+ serial, serialmin, serialmax);
+ result = ISC_R_RANGE;
+ goto fail;
+ }
+
+ result = dns_db_diff(zone->mctx, db, ver, zone->db, NULL,
+ zone->journal);
+ if (result != ISC_R_SUCCESS) {
+ char strbuf[ISC_STRERRORSIZE];
+ strerror_r(errno, strbuf, sizeof(strbuf));
+ dns_zone_log(zone, ISC_LOG_ERROR,
+ "ixfr-from-differences: failed: "
+ "%s",
+ strbuf);
+ goto fallback;
+ }
+ if (dump) {
+ zone_needdump(zone, DNS_DUMP_DELAY);
+ } else {
+ zone_journal_compact(zone, zone->db, serial);
+ }
+ if (zone->type == dns_zone_primary && inline_raw(zone)) {
+ zone_send_secureserial(zone, serial);
+ }
+ } else {
+ fallback:
+ if (dump && zone->masterfile != NULL) {
+ /*
+ * If DNS_ZONEFLG_FORCEXFER was set we don't want
+ * to keep the old masterfile.
+ */
+ if (DNS_ZONE_FLAG(zone, DNS_ZONEFLG_FORCEXFER) &&
+ remove(zone->masterfile) < 0 && errno != ENOENT)
+ {
+ char strbuf[ISC_STRERRORSIZE];
+ strerror_r(errno, strbuf, sizeof(strbuf));
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL,
+ DNS_LOGMODULE_ZONE,
+ ISC_LOG_WARNING,
+ "unable to remove masterfile "
+ "'%s': '%s'",
+ zone->masterfile, strbuf);
+ }
+ if (DNS_ZONE_FLAG(zone, DNS_ZONEFLG_LOADED) == 0) {
+ DNS_ZONE_SETFLAG(zone, DNS_ZONEFLG_NODELAY);
+ } else {
+ zone_needdump(zone, 0);
+ }
+ }
+ if (dump && zone->journal != NULL) {
+ /*
+ * The in-memory database just changed, and
+ * because 'dump' is set, it didn't change by
+ * being loaded from disk. Also, we have not
+ * journaled diffs for this change.
+ * Therefore, the on-disk journal is missing
+ * the deltas for this change. Since it can
+ * no longer be used to bring the zone
+ * up-to-date, it is useless and should be
+ * removed.
+ */
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL,
+ DNS_LOGMODULE_ZONE, ISC_LOG_DEBUG(3),
+ "removing journal file");
+ if (remove(zone->journal) < 0 && errno != ENOENT) {
+ char strbuf[ISC_STRERRORSIZE];
+ strerror_r(errno, strbuf, sizeof(strbuf));
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL,
+ DNS_LOGMODULE_ZONE,
+ ISC_LOG_WARNING,
+ "unable to remove journal "
+ "'%s': '%s'",
+ zone->journal, strbuf);
+ }
+ }
+
+ if (inline_raw(zone)) {
+ zone_send_securedb(zone, db);
+ }
+ }
+
+ dns_db_closeversion(db, &ver, false);
+
+ dns_zone_log(zone, ISC_LOG_DEBUG(3), "replacing zone database");
+
+ if (zone->db != NULL) {
+ zone_detachdb(zone);
+ }
+ zone_attachdb(zone, db);
+ dns_db_settask(zone->db, zone->task);
+ DNS_ZONE_SETFLAG(zone, DNS_ZONEFLG_LOADED | DNS_ZONEFLG_NEEDNOTIFY);
+ return (ISC_R_SUCCESS);
+
+fail:
+ dns_db_closeversion(db, &ver, false);
+ return (result);
+}
+
+/* The caller must hold the dblock as a writer. */
+static void
+zone_attachdb(dns_zone_t *zone, dns_db_t *db) {
+ REQUIRE(zone->db == NULL && db != NULL);
+
+ dns_db_attach(db, &zone->db);
+}
+
+/* The caller must hold the dblock as a writer. */
+static void
+zone_detachdb(dns_zone_t *zone) {
+ REQUIRE(zone->db != NULL);
+
+ dns_zone_rpz_disable_db(zone, zone->db);
+ dns_zone_catz_disable_db(zone, zone->db);
+ dns_db_detach(&zone->db);
+}
+
+static void
+zone_xfrdone(dns_zone_t *zone, isc_result_t result) {
+ isc_time_t now;
+ bool again = false;
+ unsigned int soacount;
+ unsigned int nscount;
+ uint32_t serial, refresh, retry, expire, minimum, soattl;
+ isc_result_t xfrresult = result;
+ bool free_needed;
+ dns_zone_t *secure = NULL;
+
+ REQUIRE(DNS_ZONE_VALID(zone));
+
+ dns_zone_logc(zone, DNS_LOGCATEGORY_XFER_IN, ISC_LOG_DEBUG(1),
+ "zone transfer finished: %s", isc_result_totext(result));
+
+ /*
+ * Obtaining a lock on the zone->secure (see zone_send_secureserial)
+ * could result in a deadlock due to a LOR so we will spin if we
+ * can't obtain both locks.
+ */
+again:
+ LOCK_ZONE(zone);
+ if (inline_raw(zone)) {
+ secure = zone->secure;
+ INSIST(secure != zone);
+ TRYLOCK_ZONE(result, secure);
+ if (result != ISC_R_SUCCESS) {
+ UNLOCK_ZONE(zone);
+ secure = NULL;
+ isc_thread_yield();
+ goto again;
+ }
+ }
+
+ INSIST(DNS_ZONE_FLAG(zone, DNS_ZONEFLG_REFRESH));
+ DNS_ZONE_CLRFLAG(zone, DNS_ZONEFLG_REFRESH);
+ DNS_ZONE_CLRFLAG(zone, DNS_ZONEFLG_SOABEFOREAXFR);
+
+ TIME_NOW(&now);
+ switch (xfrresult) {
+ case ISC_R_SUCCESS:
+ DNS_ZONE_SETFLAG(zone, DNS_ZONEFLG_NEEDNOTIFY);
+ FALLTHROUGH;
+ case DNS_R_UPTODATE:
+ DNS_ZONE_CLRFLAG(zone, DNS_ZONEFLG_FORCEXFER);
+ /*
+ * Has the zone expired underneath us?
+ */
+ ZONEDB_LOCK(&zone->dblock, isc_rwlocktype_read);
+ if (zone->db == NULL) {
+ ZONEDB_UNLOCK(&zone->dblock, isc_rwlocktype_read);
+ goto same_primary;
+ }
+
+ /*
+ * Update the zone structure's data from the actual
+ * SOA received.
+ */
+ nscount = 0;
+ soacount = 0;
+ INSIST(zone->db != NULL);
+ result = zone_get_from_db(zone, zone->db, &nscount, &soacount,
+ &soattl, &serial, &refresh, &retry,
+ &expire, &minimum, NULL);
+ ZONEDB_UNLOCK(&zone->dblock, isc_rwlocktype_read);
+ if (result == ISC_R_SUCCESS) {
+ if (soacount != 1) {
+ dns_zone_log(zone, ISC_LOG_ERROR,
+ "transferred zone "
+ "has %d SOA records",
+ soacount);
+ if (DNS_ZONE_FLAG(zone, DNS_ZONEFLG_HAVETIMERS))
+ {
+ zone->refresh = DNS_ZONE_DEFAULTREFRESH;
+ zone->retry = DNS_ZONE_DEFAULTRETRY;
+ }
+ DNS_ZONE_CLRFLAG(zone, DNS_ZONEFLG_HAVETIMERS);
+ zone_unload(zone);
+ goto next_primary;
+ }
+ if (nscount == 0) {
+ dns_zone_log(zone, ISC_LOG_ERROR,
+ "transferred zone "
+ "has no NS records");
+ if (DNS_ZONE_FLAG(zone, DNS_ZONEFLG_HAVETIMERS))
+ {
+ zone->refresh = DNS_ZONE_DEFAULTREFRESH;
+ zone->retry = DNS_ZONE_DEFAULTRETRY;
+ }
+ DNS_ZONE_CLRFLAG(zone, DNS_ZONEFLG_HAVETIMERS);
+ zone_unload(zone);
+ goto next_primary;
+ }
+ zone->refresh = RANGE(refresh, zone->minrefresh,
+ zone->maxrefresh);
+ zone->retry = RANGE(retry, zone->minretry,
+ zone->maxretry);
+ zone->expire = RANGE(expire,
+ zone->refresh + zone->retry,
+ DNS_MAX_EXPIRE);
+ zone->soattl = soattl;
+ zone->minimum = minimum;
+ DNS_ZONE_SETFLAG(zone, DNS_ZONEFLG_HAVETIMERS);
+ }
+
+ /*
+ * Set our next update/expire times.
+ */
+ if (DNS_ZONE_FLAG(zone, DNS_ZONEFLG_NEEDREFRESH)) {
+ DNS_ZONE_CLRFLAG(zone, DNS_ZONEFLG_NEEDREFRESH);
+ zone->refreshtime = now;
+ DNS_ZONE_TIME_ADD(&now, zone->expire,
+ &zone->expiretime);
+ } else {
+ DNS_ZONE_JITTER_ADD(&now, zone->refresh,
+ &zone->refreshtime);
+ DNS_ZONE_TIME_ADD(&now, zone->expire,
+ &zone->expiretime);
+ }
+ if (result == ISC_R_SUCCESS && xfrresult == ISC_R_SUCCESS) {
+ char buf[DNS_NAME_FORMATSIZE + sizeof(": TSIG ''")];
+ if (zone->tsigkey != NULL) {
+ char namebuf[DNS_NAME_FORMATSIZE];
+ dns_name_format(&zone->tsigkey->name, namebuf,
+ sizeof(namebuf));
+ snprintf(buf, sizeof(buf), ": TSIG '%s'",
+ namebuf);
+ } else {
+ buf[0] = '\0';
+ }
+ dns_zone_logc(zone, DNS_LOGCATEGORY_XFER_IN,
+ ISC_LOG_INFO, "transferred serial %u%s",
+ serial, buf);
+ if (inline_raw(zone)) {
+ zone_send_secureserial(zone, serial);
+ }
+ }
+
+ /*
+ * This is not necessary if we just performed a AXFR
+ * however it is necessary for an IXFR / UPTODATE and
+ * won't hurt with an AXFR.
+ */
+ if (zone->masterfile != NULL || zone->journal != NULL) {
+ unsigned int delay = DNS_DUMP_DELAY;
+
+ result = ISC_R_FAILURE;
+ if (zone->journal != NULL) {
+ result = isc_file_settime(zone->journal, &now);
+ }
+ if (result != ISC_R_SUCCESS && zone->masterfile != NULL)
+ {
+ result = isc_file_settime(zone->masterfile,
+ &now);
+ }
+
+ if ((DNS_ZONE_FLAG(zone, DNS_ZONEFLG_NODELAY) != 0) ||
+ result == ISC_R_FILENOTFOUND)
+ {
+ delay = 0;
+ }
+
+ if ((result == ISC_R_SUCCESS ||
+ result == ISC_R_FILENOTFOUND) &&
+ zone->masterfile != NULL)
+ {
+ zone_needdump(zone, delay);
+ } else if (result != ISC_R_SUCCESS) {
+ dns_zone_logc(zone, DNS_LOGCATEGORY_XFER_IN,
+ ISC_LOG_ERROR,
+ "transfer: could not set file "
+ "modification time of '%s': %s",
+ zone->masterfile,
+ isc_result_totext(result));
+ }
+ }
+ DNS_ZONE_CLRFLAG(zone, DNS_ZONEFLG_NODELAY);
+ inc_stats(zone, dns_zonestatscounter_xfrsuccess);
+ break;
+
+ case DNS_R_BADIXFR:
+ /* Force retry with AXFR. */
+ DNS_ZONE_SETFLAG(zone, DNS_ZONEFLG_NOIXFR);
+ goto same_primary;
+
+ case DNS_R_TOOMANYRECORDS:
+ case DNS_R_VERIFYFAILURE:
+ DNS_ZONE_JITTER_ADD(&now, zone->refresh, &zone->refreshtime);
+ inc_stats(zone, dns_zonestatscounter_xfrfail);
+ break;
+
+ default:
+ next_primary:
+ /*
+ * Skip to next failed / untried primary.
+ */
+ do {
+ zone->curprimary++;
+ } while (zone->curprimary < zone->primariescnt &&
+ zone->primariesok[zone->curprimary]);
+ same_primary:
+ if (zone->curprimary >= zone->primariescnt) {
+ zone->curprimary = 0;
+ if (DNS_ZONE_OPTION(zone, DNS_ZONEOPT_USEALTXFRSRC) &&
+ !DNS_ZONE_FLAG(zone, DNS_ZONEFLG_USEALTXFRSRC))
+ {
+ DNS_ZONE_SETFLAG(zone, DNS_ZONEFLG_REFRESH);
+ DNS_ZONE_SETFLAG(zone,
+ DNS_ZONEFLG_USEALTXFRSRC);
+ while (zone->curprimary < zone->primariescnt &&
+ zone->primariesok[zone->curprimary])
+ {
+ zone->curprimary++;
+ }
+ again = true;
+ } else {
+ DNS_ZONE_CLRFLAG(zone,
+ DNS_ZONEFLG_USEALTXFRSRC);
+ }
+ } else {
+ DNS_ZONE_SETFLAG(zone, DNS_ZONEFLG_REFRESH);
+ again = true;
+ }
+ inc_stats(zone, dns_zonestatscounter_xfrfail);
+ break;
+ }
+ zone_settimer(zone, &now);
+
+ /*
+ * If creating the transfer object failed, zone->xfr is NULL.
+ * Otherwise, we are called as the done callback of a zone
+ * transfer object that just entered its shutting-down
+ * state. Since we are no longer responsible for shutting
+ * it down, we can detach our reference.
+ */
+ if (zone->xfr != NULL) {
+ dns_xfrin_detach(&zone->xfr);
+ }
+
+ if (zone->tsigkey != NULL) {
+ dns_tsigkey_detach(&zone->tsigkey);
+ }
+
+ if (zone->transport != NULL) {
+ dns_transport_detach(&zone->transport);
+ }
+
+ /*
+ * Handle any deferred journal compaction.
+ */
+ if (DNS_ZONE_FLAG(zone, DNS_ZONEFLG_NEEDCOMPACT)) {
+ dns_db_t *db = NULL;
+ if (dns_zone_getdb(zone, &db) == ISC_R_SUCCESS) {
+ zone_journal_compact(zone, db, zone->compact_serial);
+ dns_db_detach(&db);
+ DNS_ZONE_CLRFLAG(zone, DNS_ZONEFLG_NEEDCOMPACT);
+ }
+ }
+
+ if (secure != NULL) {
+ UNLOCK_ZONE(secure);
+ }
+ /*
+ * This transfer finishing freed up a transfer quota slot.
+ * Let any other zones waiting for quota have it.
+ */
+ if (zone->zmgr != NULL &&
+ zone->statelist == &zone->zmgr->xfrin_in_progress)
+ {
+ UNLOCK_ZONE(zone);
+ RWLOCK(&zone->zmgr->rwlock, isc_rwlocktype_write);
+ ISC_LIST_UNLINK(zone->zmgr->xfrin_in_progress, zone, statelink);
+ zone->statelist = NULL;
+ zmgr_resume_xfrs(zone->zmgr, false);
+ RWUNLOCK(&zone->zmgr->rwlock, isc_rwlocktype_write);
+ LOCK_ZONE(zone);
+ }
+
+ /*
+ * Retry with a different server if necessary.
+ */
+ if (again && !DNS_ZONE_FLAG(zone, DNS_ZONEFLG_EXITING)) {
+ queue_soa_query(zone);
+ }
+
+ isc_refcount_decrement(&zone->irefs);
+ free_needed = exit_check(zone);
+ UNLOCK_ZONE(zone);
+ if (free_needed) {
+ zone_free(zone);
+ }
+}
+
+static void
+zone_loaddone(void *arg, isc_result_t result) {
+ static char me[] = "zone_loaddone";
+ dns_load_t *load = arg;
+ dns_zone_t *zone;
+ isc_result_t tresult;
+ dns_zone_t *secure = NULL;
+
+ REQUIRE(DNS_LOAD_VALID(load));
+ zone = load->zone;
+
+ ENTER;
+
+ /*
+ * If zone loading failed, remove the update db callbacks prior
+ * to calling the list of callbacks in the zone load structure.
+ */
+ if (result != ISC_R_SUCCESS) {
+ dns_zone_rpz_disable_db(zone, load->db);
+ dns_zone_catz_disable_db(zone, load->db);
+ }
+
+ tresult = dns_db_endload(load->db, &load->callbacks);
+ if (tresult != ISC_R_SUCCESS &&
+ (result == ISC_R_SUCCESS || result == DNS_R_SEENINCLUDE))
+ {
+ result = tresult;
+ }
+
+ /*
+ * Lock hierarchy: zmgr, zone, raw.
+ */
+again:
+ LOCK_ZONE(zone);
+ INSIST(zone != zone->raw);
+ if (inline_secure(zone)) {
+ LOCK_ZONE(zone->raw);
+ } else if (inline_raw(zone)) {
+ secure = zone->secure;
+ TRYLOCK_ZONE(tresult, secure);
+ if (tresult != ISC_R_SUCCESS) {
+ UNLOCK_ZONE(zone);
+ secure = NULL;
+ isc_thread_yield();
+ goto again;
+ }
+ }
+ (void)zone_postload(zone, load->db, load->loadtime, result);
+ zonemgr_putio(&zone->readio);
+ DNS_ZONE_CLRFLAG(zone, DNS_ZONEFLG_LOADING);
+ zone_idetach(&load->callbacks.zone);
+ /*
+ * Leave the zone frozen if the reload fails.
+ */
+ if ((result == ISC_R_SUCCESS || result == DNS_R_SEENINCLUDE) &&
+ DNS_ZONE_FLAG(zone, DNS_ZONEFLG_THAW))
+ {
+ zone->update_disabled = false;
+ }
+ DNS_ZONE_CLRFLAG(zone, DNS_ZONEFLG_THAW);
+ if (inline_secure(zone)) {
+ UNLOCK_ZONE(zone->raw);
+ } else if (secure != NULL) {
+ UNLOCK_ZONE(secure);
+ }
+ UNLOCK_ZONE(zone);
+
+ load->magic = 0;
+ dns_db_detach(&load->db);
+ if (load->zone->lctx != NULL) {
+ dns_loadctx_detach(&load->zone->lctx);
+ }
+ dns_zone_idetach(&load->zone);
+ isc_mem_putanddetach(&load->mctx, load, sizeof(*load));
+}
+
+void
+dns_zone_getssutable(dns_zone_t *zone, dns_ssutable_t **table) {
+ REQUIRE(DNS_ZONE_VALID(zone));
+ REQUIRE(table != NULL);
+ REQUIRE(*table == NULL);
+
+ LOCK_ZONE(zone);
+ if (zone->ssutable != NULL) {
+ dns_ssutable_attach(zone->ssutable, table);
+ }
+ UNLOCK_ZONE(zone);
+}
+
+void
+dns_zone_setssutable(dns_zone_t *zone, dns_ssutable_t *table) {
+ REQUIRE(DNS_ZONE_VALID(zone));
+
+ LOCK_ZONE(zone);
+ if (zone->ssutable != NULL) {
+ dns_ssutable_detach(&zone->ssutable);
+ }
+ if (table != NULL) {
+ dns_ssutable_attach(table, &zone->ssutable);
+ }
+ UNLOCK_ZONE(zone);
+}
+
+void
+dns_zone_setsigvalidityinterval(dns_zone_t *zone, uint32_t interval) {
+ REQUIRE(DNS_ZONE_VALID(zone));
+
+ zone->sigvalidityinterval = interval;
+}
+
+uint32_t
+dns_zone_getsigvalidityinterval(dns_zone_t *zone) {
+ REQUIRE(DNS_ZONE_VALID(zone));
+
+ return (zone->sigvalidityinterval);
+}
+
+void
+dns_zone_setkeyvalidityinterval(dns_zone_t *zone, uint32_t interval) {
+ REQUIRE(DNS_ZONE_VALID(zone));
+
+ zone->keyvalidityinterval = interval;
+}
+
+uint32_t
+dns_zone_getkeyvalidityinterval(dns_zone_t *zone) {
+ REQUIRE(DNS_ZONE_VALID(zone));
+
+ return (zone->keyvalidityinterval);
+}
+
+void
+dns_zone_setsigresigninginterval(dns_zone_t *zone, uint32_t interval) {
+ isc_time_t now;
+
+ REQUIRE(DNS_ZONE_VALID(zone));
+
+ LOCK_ZONE(zone);
+ zone->sigresigninginterval = interval;
+ set_resigntime(zone);
+ if (zone->task != NULL) {
+ TIME_NOW(&now);
+ zone_settimer(zone, &now);
+ }
+ UNLOCK_ZONE(zone);
+}
+
+uint32_t
+dns_zone_getsigresigninginterval(dns_zone_t *zone) {
+ REQUIRE(DNS_ZONE_VALID(zone));
+
+ return (zone->sigresigninginterval);
+}
+
+static void
+queue_xfrin(dns_zone_t *zone) {
+ const char me[] = "queue_xfrin";
+ isc_result_t result;
+ dns_zonemgr_t *zmgr = zone->zmgr;
+
+ ENTER;
+
+ INSIST(zone->statelist == NULL);
+
+ RWLOCK(&zmgr->rwlock, isc_rwlocktype_write);
+ ISC_LIST_APPEND(zmgr->waiting_for_xfrin, zone, statelink);
+ isc_refcount_increment0(&zone->irefs);
+ zone->statelist = &zmgr->waiting_for_xfrin;
+ result = zmgr_start_xfrin_ifquota(zmgr, zone);
+ RWUNLOCK(&zmgr->rwlock, isc_rwlocktype_write);
+
+ if (result == ISC_R_QUOTA) {
+ dns_zone_logc(zone, DNS_LOGCATEGORY_XFER_IN, ISC_LOG_INFO,
+ "zone transfer deferred due to quota");
+ } else if (result != ISC_R_SUCCESS) {
+ dns_zone_logc(zone, DNS_LOGCATEGORY_XFER_IN, ISC_LOG_ERROR,
+ "starting zone transfer: %s",
+ isc_result_totext(result));
+ }
+}
+
+/*
+ * This event callback is called when a zone has received
+ * any necessary zone transfer quota. This is the time
+ * to go ahead and start the transfer.
+ */
+static void
+got_transfer_quota(isc_task_t *task, isc_event_t *event) {
+ isc_result_t result = ISC_R_SUCCESS;
+ dns_peer_t *peer = NULL;
+ char primary[ISC_SOCKADDR_FORMATSIZE];
+ char source[ISC_SOCKADDR_FORMATSIZE];
+ dns_rdatatype_t xfrtype;
+ dns_zone_t *zone = event->ev_arg;
+ isc_netaddr_t primaryip;
+ isc_sockaddr_t sourceaddr;
+ isc_sockaddr_t primaryaddr;
+ isc_time_t now;
+ const char *soa_before = "";
+ bool loaded;
+ isc_tlsctx_cache_t *zmgr_tlsctx_cache = NULL;
+
+ UNUSED(task);
+
+ INSIST(task == zone->task);
+
+ isc_event_free(&event);
+
+ if (DNS_ZONE_FLAG(zone, DNS_ZONEFLG_EXITING)) {
+ zone_xfrdone(zone, ISC_R_CANCELED);
+ return;
+ }
+
+ TIME_NOW(&now);
+
+ isc_sockaddr_format(&zone->primaryaddr, primary, sizeof(primary));
+ if (dns_zonemgr_unreachable(zone->zmgr, &zone->primaryaddr,
+ &zone->sourceaddr, &now))
+ {
+ isc_sockaddr_format(&zone->sourceaddr, source, sizeof(source));
+ dns_zone_logc(zone, DNS_LOGCATEGORY_XFER_IN, ISC_LOG_INFO,
+ "got_transfer_quota: skipping zone transfer as "
+ "primary %s (source %s) is unreachable (cached)",
+ primary, source);
+ zone_xfrdone(zone, ISC_R_CANCELED);
+ return;
+ }
+
+ isc_netaddr_fromsockaddr(&primaryip, &zone->primaryaddr);
+ (void)dns_peerlist_peerbyaddr(zone->view->peers, &primaryip, &peer);
+
+ if (DNS_ZONE_FLAG(zone, DNS_ZONEFLG_SOABEFOREAXFR)) {
+ soa_before = "SOA before ";
+ }
+ /*
+ * Decide whether we should request IXFR or AXFR.
+ */
+ ZONEDB_LOCK(&zone->dblock, isc_rwlocktype_read);
+ loaded = (zone->db != NULL);
+ ZONEDB_UNLOCK(&zone->dblock, isc_rwlocktype_read);
+
+ if (!loaded) {
+ dns_zone_logc(zone, DNS_LOGCATEGORY_XFER_IN, ISC_LOG_DEBUG(1),
+ "no database exists yet, requesting AXFR of "
+ "initial version from %s",
+ primary);
+ xfrtype = dns_rdatatype_axfr;
+ } else if (DNS_ZONE_FLAG(zone, DNS_ZONEFLG_FORCEXFER)) {
+ dns_zone_logc(zone, DNS_LOGCATEGORY_XFER_IN, ISC_LOG_DEBUG(1),
+ "forced reload, requesting AXFR of "
+ "initial version from %s",
+ primary);
+ xfrtype = dns_rdatatype_axfr;
+ } else if (DNS_ZONE_FLAG(zone, DNS_ZONEFLG_NOIXFR)) {
+ dns_zone_logc(zone, DNS_LOGCATEGORY_XFER_IN, ISC_LOG_DEBUG(1),
+ "retrying with AXFR from %s due to "
+ "previous IXFR failure",
+ primary);
+ xfrtype = dns_rdatatype_axfr;
+ LOCK_ZONE(zone);
+ DNS_ZONE_CLRFLAG(zone, DNS_ZONEFLG_NOIXFR);
+ UNLOCK_ZONE(zone);
+ } else {
+ bool use_ixfr = true;
+ if (peer != NULL) {
+ result = dns_peer_getrequestixfr(peer, &use_ixfr);
+ }
+ if (peer == NULL || result != ISC_R_SUCCESS) {
+ use_ixfr = zone->requestixfr;
+ }
+ if (!use_ixfr) {
+ dns_zone_logc(zone, DNS_LOGCATEGORY_XFER_IN,
+ ISC_LOG_DEBUG(1),
+ "IXFR disabled, "
+ "requesting %sAXFR from %s",
+ soa_before, primary);
+ if (DNS_ZONE_FLAG(zone, DNS_ZONEFLG_SOABEFOREAXFR)) {
+ xfrtype = dns_rdatatype_soa;
+ } else {
+ xfrtype = dns_rdatatype_axfr;
+ }
+ } else {
+ dns_zone_logc(zone, DNS_LOGCATEGORY_XFER_IN,
+ ISC_LOG_DEBUG(1),
+ "requesting IXFR from %s", primary);
+ xfrtype = dns_rdatatype_ixfr;
+ }
+ }
+
+ /*
+ * Determine if we should attempt to sign the request with TSIG.
+ */
+ result = ISC_R_NOTFOUND;
+
+ /*
+ * First, look for a tsig key in the primaries statement, then
+ * try for a server key.
+ */
+ if ((zone->primarykeynames != NULL) &&
+ (zone->primarykeynames[zone->curprimary] != NULL))
+ {
+ dns_view_t *view = dns_zone_getview(zone);
+ dns_name_t *keyname = zone->primarykeynames[zone->curprimary];
+ result = dns_view_gettsig(view, keyname, &zone->tsigkey);
+ }
+ if (result != ISC_R_SUCCESS) {
+ INSIST(zone->tsigkey == NULL);
+ result = dns_view_getpeertsig(zone->view, &primaryip,
+ &zone->tsigkey);
+ }
+ if (result != ISC_R_SUCCESS && result != ISC_R_NOTFOUND) {
+ dns_zone_logc(zone, DNS_LOGCATEGORY_XFER_IN, ISC_LOG_ERROR,
+ "could not get TSIG key for zone transfer: %s",
+ isc_result_totext(result));
+ }
+
+ /*
+ * Get the TLS transport for the primary, if configured.
+ */
+ if ((zone->primarytlsnames != NULL) &&
+ (zone->primarytlsnames[zone->curprimary] != NULL))
+ {
+ dns_view_t *view = dns_zone_getview(zone);
+ dns_name_t *tlsname = zone->primarytlsnames[zone->curprimary];
+ result = dns_view_gettransport(view, DNS_TRANSPORT_TLS, tlsname,
+ &zone->transport);
+ if (result != ISC_R_SUCCESS && result != ISC_R_NOTFOUND) {
+ dns_zone_logc(zone, DNS_LOGCATEGORY_XFER_IN,
+ ISC_LOG_ERROR,
+ "could not get TLS configuration for "
+ "zone transfer: %s",
+ isc_result_totext(result));
+ }
+ }
+
+ LOCK_ZONE(zone);
+ primaryaddr = zone->primaryaddr;
+ sourceaddr = zone->sourceaddr;
+ UNLOCK_ZONE(zone);
+ INSIST(isc_sockaddr_pf(&primaryaddr) == isc_sockaddr_pf(&sourceaddr));
+
+ if (zone->xfr != NULL) {
+ dns_xfrin_detach(&zone->xfr);
+ }
+
+ zmgr_tlsctx_attach(zone->zmgr, &zmgr_tlsctx_cache);
+
+ result = dns_xfrin_create(zone, xfrtype, &primaryaddr, &sourceaddr,
+ zone->tsigkey, zone->transport,
+ zmgr_tlsctx_cache, zone->mctx,
+ zone->zmgr->netmgr, zone_xfrdone, &zone->xfr);
+
+ isc_tlsctx_cache_detach(&zmgr_tlsctx_cache);
+
+ /*
+ * Any failure in this function is handled like a failed
+ * zone transfer. This ensures that we get removed from
+ * zmgr->xfrin_in_progress.
+ */
+ if (result != ISC_R_SUCCESS) {
+ zone_xfrdone(zone, result);
+ return;
+ }
+
+ LOCK_ZONE(zone);
+ if (xfrtype == dns_rdatatype_axfr) {
+ if (isc_sockaddr_pf(&primaryaddr) == PF_INET) {
+ inc_stats(zone, dns_zonestatscounter_axfrreqv4);
+ } else {
+ inc_stats(zone, dns_zonestatscounter_axfrreqv6);
+ }
+ } else if (xfrtype == dns_rdatatype_ixfr) {
+ if (isc_sockaddr_pf(&primaryaddr) == PF_INET) {
+ inc_stats(zone, dns_zonestatscounter_ixfrreqv4);
+ } else {
+ inc_stats(zone, dns_zonestatscounter_ixfrreqv6);
+ }
+ }
+ UNLOCK_ZONE(zone);
+}
+
+/*
+ * Update forwarding support.
+ */
+
+static void
+forward_destroy(dns_forward_t *forward) {
+ forward->magic = 0;
+ if (forward->request != NULL) {
+ dns_request_destroy(&forward->request);
+ }
+ if (forward->msgbuf != NULL) {
+ isc_buffer_free(&forward->msgbuf);
+ }
+ if (forward->zone != NULL) {
+ LOCK(&forward->zone->lock);
+ if (ISC_LINK_LINKED(forward, link)) {
+ ISC_LIST_UNLINK(forward->zone->forwards, forward, link);
+ }
+ UNLOCK(&forward->zone->lock);
+ dns_zone_idetach(&forward->zone);
+ }
+ isc_mem_putanddetach(&forward->mctx, forward, sizeof(*forward));
+}
+
+static isc_result_t
+sendtoprimary(dns_forward_t *forward) {
+ isc_result_t result;
+ isc_sockaddr_t src;
+
+ LOCK_ZONE(forward->zone);
+
+ if (DNS_ZONE_FLAG(forward->zone, DNS_ZONEFLG_EXITING)) {
+ UNLOCK_ZONE(forward->zone);
+ return (ISC_R_CANCELED);
+ }
+
+ if (forward->which >= forward->zone->primariescnt) {
+ UNLOCK_ZONE(forward->zone);
+ return (ISC_R_NOMORE);
+ }
+
+ forward->addr = forward->zone->primaries[forward->which];
+ /*
+ * Always use TCP regardless of whether the original update
+ * used TCP.
+ * XXX The timeout may but a bit small if we are far down a
+ * transfer graph and have to try several primaries.
+ */
+ switch (isc_sockaddr_pf(&forward->addr)) {
+ case PF_INET:
+ src = forward->zone->xfrsource4;
+ break;
+ case PF_INET6:
+ src = forward->zone->xfrsource6;
+ break;
+ default:
+ result = ISC_R_NOTIMPLEMENTED;
+ goto unlock;
+ }
+ result = dns_request_createraw(forward->zone->view->requestmgr,
+ forward->msgbuf, &src, &forward->addr,
+ forward->options, 15 /* XXX */, 0, 0,
+ forward->zone->task, forward_callback,
+ forward, &forward->request);
+ if (result == ISC_R_SUCCESS) {
+ if (!ISC_LINK_LINKED(forward, link)) {
+ ISC_LIST_APPEND(forward->zone->forwards, forward, link);
+ }
+ }
+
+unlock:
+ UNLOCK_ZONE(forward->zone);
+ return (result);
+}
+
+static void
+forward_callback(isc_task_t *task, isc_event_t *event) {
+ const char me[] = "forward_callback";
+ dns_requestevent_t *revent = (dns_requestevent_t *)event;
+ dns_message_t *msg = NULL;
+ char primary[ISC_SOCKADDR_FORMATSIZE];
+ isc_result_t result;
+ dns_forward_t *forward;
+ dns_zone_t *zone;
+
+ UNUSED(task);
+
+ forward = revent->ev_arg;
+ INSIST(DNS_FORWARD_VALID(forward));
+ zone = forward->zone;
+ INSIST(DNS_ZONE_VALID(zone));
+
+ ENTER;
+
+ isc_sockaddr_format(&forward->addr, primary, sizeof(primary));
+
+ if (revent->result != ISC_R_SUCCESS) {
+ dns_zone_log(zone, ISC_LOG_INFO,
+ "could not forward dynamic update to %s: %s",
+ primary, isc_result_totext(revent->result));
+ goto next_primary;
+ }
+
+ dns_message_create(zone->mctx, DNS_MESSAGE_INTENTPARSE, &msg);
+
+ result = dns_request_getresponse(revent->request, msg,
+ DNS_MESSAGEPARSE_PRESERVEORDER |
+ DNS_MESSAGEPARSE_CLONEBUFFER);
+ if (result != ISC_R_SUCCESS) {
+ goto next_primary;
+ }
+
+ /*
+ * Unexpected opcode.
+ */
+ if (msg->opcode != dns_opcode_update) {
+ char opcode[128];
+ isc_buffer_t rb;
+
+ isc_buffer_init(&rb, opcode, sizeof(opcode));
+ (void)dns_opcode_totext(msg->opcode, &rb);
+
+ dns_zone_log(zone, ISC_LOG_INFO,
+ "forwarding dynamic update: "
+ "unexpected opcode (%.*s) from %s",
+ (int)rb.used, opcode, primary);
+ goto next_primary;
+ }
+
+ switch (msg->rcode) {
+ /*
+ * Pass these rcodes back to client.
+ */
+ case dns_rcode_noerror:
+ case dns_rcode_yxdomain:
+ case dns_rcode_yxrrset:
+ case dns_rcode_nxrrset:
+ case dns_rcode_refused:
+ case dns_rcode_nxdomain: {
+ char rcode[128];
+ isc_buffer_t rb;
+
+ isc_buffer_init(&rb, rcode, sizeof(rcode));
+ (void)dns_rcode_totext(msg->rcode, &rb);
+ dns_zone_log(zone, ISC_LOG_INFO,
+ "forwarded dynamic update: "
+ "primary %s returned: %.*s",
+ primary, (int)rb.used, rcode);
+ break;
+ }
+
+ /* These should not occur if the primaries/zone are valid. */
+ case dns_rcode_notzone:
+ case dns_rcode_notauth: {
+ char rcode[128];
+ isc_buffer_t rb;
+
+ isc_buffer_init(&rb, rcode, sizeof(rcode));
+ (void)dns_rcode_totext(msg->rcode, &rb);
+ dns_zone_log(zone, ISC_LOG_WARNING,
+ "forwarding dynamic update: "
+ "unexpected response: primary %s returned: %.*s",
+ primary, (int)rb.used, rcode);
+ goto next_primary;
+ }
+
+ /* Try another server for these rcodes. */
+ case dns_rcode_formerr:
+ case dns_rcode_servfail:
+ case dns_rcode_notimp:
+ case dns_rcode_badvers:
+ default:
+ goto next_primary;
+ }
+
+ /* call callback */
+ (forward->callback)(forward->callback_arg, ISC_R_SUCCESS, msg);
+ msg = NULL;
+ dns_request_destroy(&forward->request);
+ forward_destroy(forward);
+ isc_event_free(&event);
+ return;
+
+next_primary:
+ if (msg != NULL) {
+ dns_message_detach(&msg);
+ }
+ isc_event_free(&event);
+ forward->which++;
+ dns_request_destroy(&forward->request);
+ result = sendtoprimary(forward);
+ if (result != ISC_R_SUCCESS) {
+ /* call callback */
+ dns_zone_log(zone, ISC_LOG_DEBUG(3),
+ "exhausted dynamic update forwarder list");
+ (forward->callback)(forward->callback_arg, result, NULL);
+ forward_destroy(forward);
+ }
+}
+
+isc_result_t
+dns_zone_forwardupdate(dns_zone_t *zone, dns_message_t *msg,
+ dns_updatecallback_t callback, void *callback_arg) {
+ dns_forward_t *forward;
+ isc_result_t result;
+ isc_region_t *mr;
+
+ REQUIRE(DNS_ZONE_VALID(zone));
+ REQUIRE(msg != NULL);
+ REQUIRE(callback != NULL);
+
+ forward = isc_mem_get(zone->mctx, sizeof(*forward));
+
+ forward->request = NULL;
+ forward->zone = NULL;
+ forward->msgbuf = NULL;
+ forward->which = 0;
+ forward->mctx = 0;
+ forward->callback = callback;
+ forward->callback_arg = callback_arg;
+ ISC_LINK_INIT(forward, link);
+ forward->magic = FORWARD_MAGIC;
+ forward->options = DNS_REQUESTOPT_TCP;
+ /*
+ * If we have a SIG(0) signed message we need to preserve the
+ * query id as that is included in the SIG(0) computation.
+ */
+ if (msg->sig0 != NULL) {
+ forward->options |= DNS_REQUESTOPT_FIXEDID;
+ }
+
+ mr = dns_message_getrawmessage(msg);
+ if (mr == NULL) {
+ result = ISC_R_UNEXPECTEDEND;
+ goto cleanup;
+ }
+
+ isc_buffer_allocate(zone->mctx, &forward->msgbuf, mr->length);
+ result = isc_buffer_copyregion(forward->msgbuf, mr);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+
+ isc_mem_attach(zone->mctx, &forward->mctx);
+ dns_zone_iattach(zone, &forward->zone);
+ result = sendtoprimary(forward);
+
+cleanup:
+ if (result != ISC_R_SUCCESS) {
+ forward_destroy(forward);
+ }
+ return (result);
+}
+
+isc_result_t
+dns_zone_next(dns_zone_t *zone, dns_zone_t **next) {
+ REQUIRE(DNS_ZONE_VALID(zone));
+ REQUIRE(next != NULL && *next == NULL);
+
+ *next = ISC_LIST_NEXT(zone, link);
+ if (*next == NULL) {
+ return (ISC_R_NOMORE);
+ } else {
+ return (ISC_R_SUCCESS);
+ }
+}
+
+isc_result_t
+dns_zone_first(dns_zonemgr_t *zmgr, dns_zone_t **first) {
+ REQUIRE(DNS_ZONEMGR_VALID(zmgr));
+ REQUIRE(first != NULL && *first == NULL);
+
+ *first = ISC_LIST_HEAD(zmgr->zones);
+ if (*first == NULL) {
+ return (ISC_R_NOMORE);
+ } else {
+ return (ISC_R_SUCCESS);
+ }
+}
+
+/***
+ *** Zone manager.
+ ***/
+
+#define KEYMGMT_OVERCOMMIT 3
+#define KEYMGMT_BITS_MIN 2U
+#define KEYMGMT_BITS_MAX 32U
+
+/*
+ * WMM: Static hash functions copied from lib/dns/rbtdb.c. Should be moved to
+ * lib/isc/hash.c when we refactor the hash table code.
+ */
+#define GOLDEN_RATIO_32 0x61C88647
+#define HASHSIZE(bits) (UINT64_C(1) << (bits))
+
+static uint32_t
+hash_index(uint32_t val, uint32_t bits) {
+ return (val * GOLDEN_RATIO_32 >> (32 - bits));
+}
+
+static uint32_t
+hash_bits_grow(uint32_t bits, uint32_t count) {
+ uint32_t newbits = bits;
+ while (count >= HASHSIZE(newbits) && newbits < KEYMGMT_BITS_MAX) {
+ newbits++;
+ }
+ return (newbits);
+}
+
+static uint32_t
+hash_bits_shrink(uint32_t bits, uint32_t count) {
+ uint32_t newbits = bits;
+ while (count <= HASHSIZE(newbits) && newbits > KEYMGMT_BITS_MIN) {
+ newbits--;
+ }
+ return (newbits);
+}
+
+static void
+zonemgr_keymgmt_init(dns_zonemgr_t *zmgr) {
+ dns_keymgmt_t *mgmt = isc_mem_get(zmgr->mctx, sizeof(*mgmt));
+ uint32_t size;
+
+ *mgmt = (dns_keymgmt_t){
+ .bits = KEYMGMT_BITS_MIN,
+ };
+ isc_mem_attach(zmgr->mctx, &mgmt->mctx);
+ isc_rwlock_init(&mgmt->lock, 0, 0);
+
+ size = HASHSIZE(mgmt->bits);
+ mgmt->table = isc_mem_get(mgmt->mctx, sizeof(*mgmt->table) * size);
+ memset(mgmt->table, 0, size * sizeof(mgmt->table[0]));
+
+ atomic_init(&mgmt->count, 0);
+ mgmt->magic = KEYMGMT_MAGIC;
+
+ zmgr->keymgmt = mgmt;
+}
+
+static void
+zonemgr_keymgmt_destroy(dns_zonemgr_t *zmgr) {
+ dns_keymgmt_t *mgmt = zmgr->keymgmt;
+ uint32_t size;
+
+ REQUIRE(DNS_KEYMGMT_VALID(mgmt));
+
+ size = HASHSIZE(mgmt->bits);
+
+ RWLOCK(&mgmt->lock, isc_rwlocktype_write);
+ INSIST(mgmt->count == 0);
+ RWUNLOCK(&mgmt->lock, isc_rwlocktype_write);
+
+ mgmt->magic = 0;
+ isc_rwlock_destroy(&mgmt->lock);
+ isc_mem_put(mgmt->mctx, mgmt->table, size * sizeof(mgmt->table[0]));
+ isc_mem_putanddetach(&mgmt->mctx, mgmt, sizeof(dns_keymgmt_t));
+}
+
+static void
+zonemgr_keymgmt_resize(dns_zonemgr_t *zmgr) {
+ dns_keyfileio_t **newtable;
+ dns_keymgmt_t *mgmt = zmgr->keymgmt;
+ uint32_t bits, newbits, count, size, newsize;
+ bool grow;
+
+ REQUIRE(DNS_KEYMGMT_VALID(mgmt));
+
+ RWLOCK(&mgmt->lock, isc_rwlocktype_read);
+ count = atomic_load_relaxed(&mgmt->count);
+ bits = mgmt->bits;
+ RWUNLOCK(&mgmt->lock, isc_rwlocktype_read);
+
+ size = HASHSIZE(bits);
+ INSIST(size > 0);
+
+ if (count >= (size * KEYMGMT_OVERCOMMIT)) {
+ grow = true;
+ } else if (count < (size / 2)) {
+ grow = false;
+ } else {
+ /* No need to resize. */
+ return;
+ }
+
+ if (grow) {
+ newbits = hash_bits_grow(bits, count);
+ } else {
+ newbits = hash_bits_shrink(bits, count);
+ }
+
+ if (newbits == bits) {
+ /*
+ * Bit values may stay the same if maximum or minimum is
+ * reached.
+ */
+ return;
+ }
+
+ newsize = HASHSIZE(newbits);
+ INSIST(newsize > 0);
+
+ RWLOCK(&mgmt->lock, isc_rwlocktype_write);
+
+ newtable = isc_mem_get(mgmt->mctx, sizeof(dns_keyfileio_t *) * newsize);
+ memset(newtable, 0, sizeof(dns_keyfileio_t *) * newsize);
+
+ for (unsigned int i = 0; i < size; i++) {
+ dns_keyfileio_t *kfio, *next;
+ for (kfio = mgmt->table[i]; kfio != NULL; kfio = next) {
+ uint32_t hash = hash_index(kfio->hashval, newbits);
+ next = kfio->next;
+ kfio->next = newtable[hash];
+ newtable[hash] = kfio;
+ }
+ mgmt->table[i] = NULL;
+ }
+
+ isc_mem_put(mgmt->mctx, mgmt->table, sizeof(*mgmt->table) * size);
+ mgmt->bits = newbits;
+ mgmt->table = newtable;
+
+ RWUNLOCK(&mgmt->lock, isc_rwlocktype_write);
+}
+
+static void
+zonemgr_keymgmt_add(dns_zonemgr_t *zmgr, dns_zone_t *zone,
+ dns_keyfileio_t **added) {
+ dns_keymgmt_t *mgmt = zmgr->keymgmt;
+ uint32_t hashval, hash;
+ dns_keyfileio_t *kfio, *next;
+
+ REQUIRE(DNS_KEYMGMT_VALID(mgmt));
+ REQUIRE(added != NULL && *added == NULL);
+
+ RWLOCK(&mgmt->lock, isc_rwlocktype_write);
+
+ hashval = dns_name_hash(&zone->origin, false);
+ hash = hash_index(hashval, mgmt->bits);
+
+ for (kfio = mgmt->table[hash]; kfio != NULL; kfio = next) {
+ next = kfio->next;
+ if (dns_name_equal(kfio->name, &zone->origin)) {
+ /* Already in table, increment the counter. */
+ isc_refcount_increment(&kfio->references);
+ break;
+ }
+ }
+
+ if (kfio == NULL) {
+ /* No entry found, add it. */
+ kfio = isc_mem_get(mgmt->mctx, sizeof(*kfio));
+ *kfio = (dns_keyfileio_t){
+ .hashval = hashval,
+ .next = mgmt->table[hash],
+ .magic = KEYFILEIO_MAGIC,
+ };
+
+ isc_refcount_init(&kfio->references, 1);
+
+ kfio->name = dns_fixedname_initname(&kfio->fname);
+ dns_name_copy(&zone->origin, kfio->name);
+
+ isc_mutex_init(&kfio->lock);
+
+ mgmt->table[hash] = kfio;
+
+ atomic_fetch_add_relaxed(&mgmt->count, 1);
+ }
+
+ RWUNLOCK(&mgmt->lock, isc_rwlocktype_write);
+
+ *added = kfio;
+
+ /*
+ * Call resize, that function will also check if resize is necessary.
+ */
+ zonemgr_keymgmt_resize(zmgr);
+}
+
+static void
+zonemgr_keymgmt_delete(dns_zonemgr_t *zmgr, dns_zone_t *zone,
+ dns_keyfileio_t **deleted) {
+ dns_keymgmt_t *mgmt = zmgr->keymgmt;
+ uint32_t hashval, hash;
+ dns_keyfileio_t *kfio, *prev, *next;
+
+ REQUIRE(DNS_KEYMGMT_VALID(mgmt));
+ REQUIRE(deleted != NULL && DNS_KEYFILEIO_VALID(*deleted));
+
+ RWLOCK(&mgmt->lock, isc_rwlocktype_write);
+
+ hashval = dns_name_hash(&zone->origin, false);
+ hash = hash_index(hashval, mgmt->bits);
+
+ prev = NULL;
+ for (kfio = mgmt->table[hash]; kfio != NULL; kfio = next) {
+ next = kfio->next;
+ if (dns_name_equal(kfio->name, &zone->origin)) {
+ INSIST(kfio == *deleted);
+ *deleted = NULL;
+
+ if (isc_refcount_decrement(&kfio->references) == 1) {
+ if (prev == NULL) {
+ mgmt->table[hash] = kfio->next;
+ } else {
+ prev->next = kfio->next;
+ }
+
+ isc_refcount_destroy(&kfio->references);
+ isc_mutex_destroy(&kfio->lock);
+ isc_mem_put(mgmt->mctx, kfio, sizeof(*kfio));
+
+ atomic_fetch_sub_relaxed(&mgmt->count, 1);
+ }
+ break;
+ }
+
+ prev = kfio;
+ }
+
+ RWUNLOCK(&mgmt->lock, isc_rwlocktype_write);
+
+ /*
+ * Call resize, that function will also check if resize is necessary.
+ */
+ zonemgr_keymgmt_resize(zmgr);
+}
+
+isc_result_t
+dns_zonemgr_create(isc_mem_t *mctx, isc_taskmgr_t *taskmgr,
+ isc_timermgr_t *timermgr, isc_nm_t *netmgr,
+ dns_zonemgr_t **zmgrp) {
+ dns_zonemgr_t *zmgr;
+ isc_result_t result;
+
+ zmgr = isc_mem_get(mctx, sizeof(*zmgr));
+ zmgr->mctx = NULL;
+ isc_refcount_init(&zmgr->refs, 1);
+ isc_mem_attach(mctx, &zmgr->mctx);
+ zmgr->taskmgr = taskmgr;
+ zmgr->timermgr = timermgr;
+ zmgr->netmgr = netmgr;
+ zmgr->zonetasks = NULL;
+ zmgr->loadtasks = NULL;
+ zmgr->mctxpool = NULL;
+ zmgr->task = NULL;
+ zmgr->checkdsrl = NULL;
+ zmgr->notifyrl = NULL;
+ zmgr->refreshrl = NULL;
+ zmgr->startupnotifyrl = NULL;
+ zmgr->startuprefreshrl = NULL;
+ ISC_LIST_INIT(zmgr->zones);
+ ISC_LIST_INIT(zmgr->waiting_for_xfrin);
+ ISC_LIST_INIT(zmgr->xfrin_in_progress);
+ memset(zmgr->unreachable, 0, sizeof(zmgr->unreachable));
+ for (size_t i = 0; i < UNREACH_CACHE_SIZE; i++) {
+ atomic_init(&zmgr->unreachable[i].expire, 0);
+ }
+ isc_rwlock_init(&zmgr->rwlock, 0, 0);
+
+ zmgr->transfersin = 10;
+ zmgr->transfersperns = 2;
+
+ /* Unreachable lock. */
+ isc_rwlock_init(&zmgr->urlock, 0, 0);
+
+ /* Create a single task for queueing of SOA queries. */
+ result = isc_task_create(taskmgr, 1, &zmgr->task);
+ if (result != ISC_R_SUCCESS) {
+ goto free_urlock;
+ }
+
+ isc_task_setname(zmgr->task, "zmgr", zmgr);
+ result = isc_ratelimiter_create(mctx, timermgr, zmgr->task,
+ &zmgr->checkdsrl);
+ if (result != ISC_R_SUCCESS) {
+ goto free_task;
+ }
+
+ result = isc_ratelimiter_create(mctx, timermgr, zmgr->task,
+ &zmgr->notifyrl);
+ if (result != ISC_R_SUCCESS) {
+ goto free_checkdsrl;
+ }
+
+ result = isc_ratelimiter_create(mctx, timermgr, zmgr->task,
+ &zmgr->refreshrl);
+ if (result != ISC_R_SUCCESS) {
+ goto free_notifyrl;
+ }
+
+ result = isc_ratelimiter_create(mctx, timermgr, zmgr->task,
+ &zmgr->startupnotifyrl);
+ if (result != ISC_R_SUCCESS) {
+ goto free_refreshrl;
+ }
+
+ result = isc_ratelimiter_create(mctx, timermgr, zmgr->task,
+ &zmgr->startuprefreshrl);
+ if (result != ISC_R_SUCCESS) {
+ goto free_startupnotifyrl;
+ }
+
+ /* Key file I/O locks. */
+ zonemgr_keymgmt_init(zmgr);
+
+ /* Default to 20 refresh queries / notifies / checkds per second. */
+ setrl(zmgr->checkdsrl, &zmgr->checkdsrate, 20);
+ setrl(zmgr->notifyrl, &zmgr->notifyrate, 20);
+ setrl(zmgr->startupnotifyrl, &zmgr->startupnotifyrate, 20);
+ setrl(zmgr->refreshrl, &zmgr->serialqueryrate, 20);
+ setrl(zmgr->startuprefreshrl, &zmgr->startupserialqueryrate, 20);
+ isc_ratelimiter_setpushpop(zmgr->startupnotifyrl, true);
+ isc_ratelimiter_setpushpop(zmgr->startuprefreshrl, true);
+
+ zmgr->iolimit = 1;
+ zmgr->ioactive = 0;
+ ISC_LIST_INIT(zmgr->high);
+ ISC_LIST_INIT(zmgr->low);
+
+ isc_mutex_init(&zmgr->iolock);
+
+ zmgr->tlsctx_cache = NULL;
+ isc_rwlock_init(&zmgr->tlsctx_cache_rwlock, 0, 0);
+
+ zmgr->magic = ZONEMGR_MAGIC;
+
+ *zmgrp = zmgr;
+ return (ISC_R_SUCCESS);
+
+#if 0
+ free_iolock:
+ isc_mutex_destroy(&zmgr->iolock);
+#endif /* if 0 */
+free_startupnotifyrl:
+ isc_ratelimiter_detach(&zmgr->startupnotifyrl);
+free_refreshrl:
+ isc_ratelimiter_detach(&zmgr->refreshrl);
+free_notifyrl:
+ isc_ratelimiter_detach(&zmgr->notifyrl);
+free_checkdsrl:
+ isc_ratelimiter_detach(&zmgr->checkdsrl);
+free_task:
+ isc_task_detach(&zmgr->task);
+free_urlock:
+ isc_rwlock_destroy(&zmgr->urlock);
+ isc_rwlock_destroy(&zmgr->rwlock);
+ isc_mem_put(zmgr->mctx, zmgr, sizeof(*zmgr));
+ isc_mem_detach(&mctx);
+ return (result);
+}
+
+isc_result_t
+dns_zonemgr_createzone(dns_zonemgr_t *zmgr, dns_zone_t **zonep) {
+ isc_result_t result;
+ isc_mem_t *mctx = NULL;
+ dns_zone_t *zone = NULL;
+ void *item;
+
+ REQUIRE(DNS_ZONEMGR_VALID(zmgr));
+ REQUIRE(zonep != NULL && *zonep == NULL);
+
+ if (zmgr->mctxpool == NULL) {
+ return (ISC_R_FAILURE);
+ }
+
+ item = isc_pool_get(zmgr->mctxpool);
+ if (item == NULL) {
+ return (ISC_R_FAILURE);
+ }
+
+ isc_mem_attach((isc_mem_t *)item, &mctx);
+ result = dns_zone_create(&zone, mctx);
+ isc_mem_detach(&mctx);
+
+ if (result == ISC_R_SUCCESS) {
+ *zonep = zone;
+ }
+
+ return (result);
+}
+
+isc_result_t
+dns_zonemgr_managezone(dns_zonemgr_t *zmgr, dns_zone_t *zone) {
+ isc_result_t result;
+
+ REQUIRE(DNS_ZONE_VALID(zone));
+ REQUIRE(DNS_ZONEMGR_VALID(zmgr));
+
+ if (zmgr->zonetasks == NULL) {
+ return (ISC_R_FAILURE);
+ }
+
+ RWLOCK(&zmgr->rwlock, isc_rwlocktype_write);
+ LOCK_ZONE(zone);
+ REQUIRE(zone->task == NULL);
+ REQUIRE(zone->timer == NULL);
+ REQUIRE(zone->zmgr == NULL);
+
+ isc_taskpool_gettask(zmgr->zonetasks, &zone->task);
+ isc_taskpool_gettask(zmgr->loadtasks, &zone->loadtask);
+
+ /*
+ * Set the task name. The tag will arbitrarily point to one
+ * of the zones sharing the task (in practice, the one
+ * to be managed last).
+ */
+ isc_task_setname(zone->task, "zone", zone);
+ isc_task_setname(zone->loadtask, "loadzone", zone);
+
+ result = isc_timer_create(zmgr->timermgr, isc_timertype_inactive, NULL,
+ NULL, zone->task, zone_timer, zone,
+ &zone->timer);
+
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup_tasks;
+ }
+
+ /*
+ * The timer "holds" a iref.
+ */
+ isc_refcount_increment0(&zone->irefs);
+
+ zonemgr_keymgmt_add(zmgr, zone, &zone->kfio);
+ INSIST(zone->kfio != NULL);
+
+ ISC_LIST_APPEND(zmgr->zones, zone, link);
+ zone->zmgr = zmgr;
+ isc_refcount_increment(&zmgr->refs);
+
+ goto unlock;
+
+cleanup_tasks:
+ isc_task_detach(&zone->loadtask);
+ isc_task_detach(&zone->task);
+
+unlock:
+ UNLOCK_ZONE(zone);
+ RWUNLOCK(&zmgr->rwlock, isc_rwlocktype_write);
+ return (result);
+}
+
+void
+dns_zonemgr_releasezone(dns_zonemgr_t *zmgr, dns_zone_t *zone) {
+ REQUIRE(DNS_ZONE_VALID(zone));
+ REQUIRE(DNS_ZONEMGR_VALID(zmgr));
+ REQUIRE(zone->zmgr == zmgr);
+
+ RWLOCK(&zmgr->rwlock, isc_rwlocktype_write);
+ LOCK_ZONE(zone);
+
+ ISC_LIST_UNLINK(zmgr->zones, zone, link);
+
+ if (zone->kfio != NULL) {
+ zonemgr_keymgmt_delete(zmgr, zone, &zone->kfio);
+ ENSURE(zone->kfio == NULL);
+ }
+
+ /* Detach below, outside of the write lock. */
+ zone->zmgr = NULL;
+
+ UNLOCK_ZONE(zone);
+ RWUNLOCK(&zmgr->rwlock, isc_rwlocktype_write);
+
+ dns_zonemgr_detach(&zmgr);
+}
+
+void
+dns_zonemgr_attach(dns_zonemgr_t *source, dns_zonemgr_t **target) {
+ REQUIRE(DNS_ZONEMGR_VALID(source));
+ REQUIRE(target != NULL && *target == NULL);
+
+ isc_refcount_increment(&source->refs);
+
+ *target = source;
+}
+
+void
+dns_zonemgr_detach(dns_zonemgr_t **zmgrp) {
+ dns_zonemgr_t *zmgr;
+
+ REQUIRE(zmgrp != NULL);
+ zmgr = *zmgrp;
+ *zmgrp = NULL;
+ REQUIRE(DNS_ZONEMGR_VALID(zmgr));
+
+ if (isc_refcount_decrement(&zmgr->refs) == 1) {
+ zonemgr_free(zmgr);
+ }
+}
+
+isc_result_t
+dns_zonemgr_forcemaint(dns_zonemgr_t *zmgr) {
+ dns_zone_t *p;
+
+ REQUIRE(DNS_ZONEMGR_VALID(zmgr));
+
+ RWLOCK(&zmgr->rwlock, isc_rwlocktype_read);
+ for (p = ISC_LIST_HEAD(zmgr->zones); p != NULL;
+ p = ISC_LIST_NEXT(p, link))
+ {
+ dns_zone_maintenance(p);
+ }
+ RWUNLOCK(&zmgr->rwlock, isc_rwlocktype_read);
+
+ /*
+ * Recent configuration changes may have increased the
+ * amount of available transfers quota. Make sure any
+ * transfers currently blocked on quota get started if
+ * possible.
+ */
+ RWLOCK(&zmgr->rwlock, isc_rwlocktype_write);
+ zmgr_resume_xfrs(zmgr, true);
+ RWUNLOCK(&zmgr->rwlock, isc_rwlocktype_write);
+ return (ISC_R_SUCCESS);
+}
+
+void
+dns_zonemgr_resumexfrs(dns_zonemgr_t *zmgr) {
+ REQUIRE(DNS_ZONEMGR_VALID(zmgr));
+
+ RWLOCK(&zmgr->rwlock, isc_rwlocktype_write);
+ zmgr_resume_xfrs(zmgr, true);
+ RWUNLOCK(&zmgr->rwlock, isc_rwlocktype_write);
+}
+
+void
+dns_zonemgr_shutdown(dns_zonemgr_t *zmgr) {
+ dns_zone_t *zone;
+
+ REQUIRE(DNS_ZONEMGR_VALID(zmgr));
+
+ isc_ratelimiter_shutdown(zmgr->checkdsrl);
+ isc_ratelimiter_shutdown(zmgr->notifyrl);
+ isc_ratelimiter_shutdown(zmgr->refreshrl);
+ isc_ratelimiter_shutdown(zmgr->startupnotifyrl);
+ isc_ratelimiter_shutdown(zmgr->startuprefreshrl);
+
+ if (zmgr->task != NULL) {
+ isc_task_destroy(&zmgr->task);
+ }
+ if (zmgr->zonetasks != NULL) {
+ isc_taskpool_destroy(&zmgr->zonetasks);
+ }
+ if (zmgr->loadtasks != NULL) {
+ isc_taskpool_destroy(&zmgr->loadtasks);
+ }
+ if (zmgr->mctxpool != NULL) {
+ isc_pool_destroy(&zmgr->mctxpool);
+ }
+
+ RWLOCK(&zmgr->rwlock, isc_rwlocktype_read);
+ for (zone = ISC_LIST_HEAD(zmgr->zones); zone != NULL;
+ zone = ISC_LIST_NEXT(zone, link))
+ {
+ LOCK_ZONE(zone);
+ forward_cancel(zone);
+ UNLOCK_ZONE(zone);
+ }
+ RWUNLOCK(&zmgr->rwlock, isc_rwlocktype_read);
+}
+
+static isc_result_t
+mctxinit(void **target, void *arg) {
+ isc_mem_t *mctx = NULL;
+
+ UNUSED(arg);
+
+ REQUIRE(target != NULL && *target == NULL);
+
+ isc_mem_create(&mctx);
+ isc_mem_setname(mctx, "zonemgr-pool");
+
+ *target = mctx;
+ return (ISC_R_SUCCESS);
+}
+
+static void
+mctxfree(void **target) {
+ isc_mem_t *mctx = *(isc_mem_t **)target;
+ isc_mem_detach(&mctx);
+ *target = NULL;
+}
+
+#define ZONES_PER_TASK 100
+#define ZONES_PER_MCTX 1000
+
+isc_result_t
+dns_zonemgr_setsize(dns_zonemgr_t *zmgr, int num_zones) {
+ isc_result_t result;
+ int ntasks = num_zones / ZONES_PER_TASK;
+ int nmctx = num_zones / ZONES_PER_MCTX;
+ isc_taskpool_t *pool = NULL;
+ isc_pool_t *mctxpool = NULL;
+
+ REQUIRE(DNS_ZONEMGR_VALID(zmgr));
+
+ /*
+ * For anything fewer than 1000 zones we use 10 tasks in
+ * the task pools. More than that, and we'll scale at one
+ * task per 100 zones. Similarly, for anything smaller than
+ * 2000 zones we use 2 memory contexts, then scale at 1:1000.
+ */
+ if (ntasks < 10) {
+ ntasks = 10;
+ }
+ if (nmctx < 2) {
+ nmctx = 2;
+ }
+
+ /* Create or resize the zone task pools. */
+ if (zmgr->zonetasks == NULL) {
+ result = isc_taskpool_create(zmgr->taskmgr, zmgr->mctx, ntasks,
+ 2, false, &pool);
+ } else {
+ result = isc_taskpool_expand(&zmgr->zonetasks, ntasks, false,
+ &pool);
+ }
+
+ if (result == ISC_R_SUCCESS) {
+ zmgr->zonetasks = pool;
+ }
+
+ /*
+ * We always set all tasks in the zone-load task pool to
+ * privileged. This prevents other tasks in the system from
+ * running while the server task manager is in privileged
+ * mode.
+ */
+ pool = NULL;
+ if (zmgr->loadtasks == NULL) {
+ result = isc_taskpool_create(zmgr->taskmgr, zmgr->mctx, ntasks,
+ UINT_MAX, true, &pool);
+ } else {
+ result = isc_taskpool_expand(&zmgr->loadtasks, ntasks, true,
+ &pool);
+ }
+
+ if (result == ISC_R_SUCCESS) {
+ zmgr->loadtasks = pool;
+ }
+
+ /* Create or resize the zone memory context pool. */
+ if (zmgr->mctxpool == NULL) {
+ result = isc_pool_create(zmgr->mctx, nmctx, mctxfree, mctxinit,
+ NULL, &mctxpool);
+ } else {
+ result = isc_pool_expand(&zmgr->mctxpool, nmctx, &mctxpool);
+ }
+
+ if (result == ISC_R_SUCCESS) {
+ zmgr->mctxpool = mctxpool;
+ }
+
+ return (result);
+}
+
+static void
+zonemgr_free(dns_zonemgr_t *zmgr) {
+ isc_mem_t *mctx;
+
+ INSIST(ISC_LIST_EMPTY(zmgr->zones));
+
+ zmgr->magic = 0;
+
+ isc_refcount_destroy(&zmgr->refs);
+ isc_mutex_destroy(&zmgr->iolock);
+ isc_ratelimiter_detach(&zmgr->checkdsrl);
+ isc_ratelimiter_detach(&zmgr->notifyrl);
+ isc_ratelimiter_detach(&zmgr->refreshrl);
+ isc_ratelimiter_detach(&zmgr->startupnotifyrl);
+ isc_ratelimiter_detach(&zmgr->startuprefreshrl);
+
+ isc_rwlock_destroy(&zmgr->urlock);
+ isc_rwlock_destroy(&zmgr->rwlock);
+ isc_rwlock_destroy(&zmgr->tlsctx_cache_rwlock);
+
+ zonemgr_keymgmt_destroy(zmgr);
+
+ mctx = zmgr->mctx;
+ if (zmgr->tlsctx_cache != NULL) {
+ isc_tlsctx_cache_detach(&zmgr->tlsctx_cache);
+ }
+ isc_mem_put(zmgr->mctx, zmgr, sizeof(*zmgr));
+ isc_mem_detach(&mctx);
+}
+
+void
+dns_zonemgr_settransfersin(dns_zonemgr_t *zmgr, uint32_t value) {
+ REQUIRE(DNS_ZONEMGR_VALID(zmgr));
+
+ zmgr->transfersin = value;
+}
+
+uint32_t
+dns_zonemgr_gettransfersin(dns_zonemgr_t *zmgr) {
+ REQUIRE(DNS_ZONEMGR_VALID(zmgr));
+
+ return (zmgr->transfersin);
+}
+
+void
+dns_zonemgr_settransfersperns(dns_zonemgr_t *zmgr, uint32_t value) {
+ REQUIRE(DNS_ZONEMGR_VALID(zmgr));
+
+ zmgr->transfersperns = value;
+}
+
+uint32_t
+dns_zonemgr_gettransfersperns(dns_zonemgr_t *zmgr) {
+ REQUIRE(DNS_ZONEMGR_VALID(zmgr));
+
+ return (zmgr->transfersperns);
+}
+
+isc_taskmgr_t *
+dns_zonemgr_gettaskmgr(dns_zonemgr_t *zmgr) {
+ REQUIRE(DNS_ZONEMGR_VALID(zmgr));
+
+ return (zmgr->taskmgr);
+}
+
+isc_timermgr_t *
+dns_zonemgr_gettimermgr(dns_zonemgr_t *zmgr) {
+ REQUIRE(DNS_ZONEMGR_VALID(zmgr));
+
+ return (zmgr->timermgr);
+}
+
+/*
+ * Try to start a new incoming zone transfer to fill a quota
+ * slot that was just vacated.
+ *
+ * Requires:
+ * The zone manager is locked by the caller.
+ */
+static void
+zmgr_resume_xfrs(dns_zonemgr_t *zmgr, bool multi) {
+ dns_zone_t *zone;
+ dns_zone_t *next;
+
+ for (zone = ISC_LIST_HEAD(zmgr->waiting_for_xfrin); zone != NULL;
+ zone = next)
+ {
+ isc_result_t result;
+ next = ISC_LIST_NEXT(zone, statelink);
+ result = zmgr_start_xfrin_ifquota(zmgr, zone);
+ if (result == ISC_R_SUCCESS) {
+ if (multi) {
+ continue;
+ }
+ /*
+ * We successfully filled the slot. We're done.
+ */
+ break;
+ } else if (result == ISC_R_QUOTA) {
+ /*
+ * Not enough quota. This is probably the per-server
+ * quota, because we usually get called when a unit of
+ * global quota has just been freed. Try the next
+ * zone, it may succeed if it uses another primary.
+ */
+ continue;
+ } else {
+ dns_zone_logc(zone, DNS_LOGCATEGORY_XFER_IN,
+ ISC_LOG_DEBUG(1),
+ "starting zone transfer: %s",
+ isc_result_totext(result));
+ break;
+ }
+ }
+}
+
+/*
+ * Try to start an incoming zone transfer for 'zone', quota permitting.
+ *
+ * Requires:
+ * The zone manager is locked by the caller.
+ *
+ * Returns:
+ * ISC_R_SUCCESS There was enough quota and we attempted to
+ * start a transfer. zone_xfrdone() has been or will
+ * be called.
+ * ISC_R_QUOTA Not enough quota.
+ * Others Failure.
+ */
+static isc_result_t
+zmgr_start_xfrin_ifquota(dns_zonemgr_t *zmgr, dns_zone_t *zone) {
+ dns_peer_t *peer = NULL;
+ isc_netaddr_t primaryip;
+ uint32_t nxfrsin, nxfrsperns;
+ dns_zone_t *x;
+ uint32_t maxtransfersin, maxtransfersperns;
+ isc_event_t *e;
+
+ /*
+ * If we are exiting just pretend we got quota so the zone will
+ * be cleaned up in the zone's task context.
+ */
+ LOCK_ZONE(zone);
+ if (DNS_ZONE_FLAG(zone, DNS_ZONEFLG_EXITING)) {
+ UNLOCK_ZONE(zone);
+ goto gotquota;
+ }
+
+ /*
+ * Find any configured information about the server we'd
+ * like to transfer this zone from.
+ */
+ isc_netaddr_fromsockaddr(&primaryip, &zone->primaryaddr);
+ (void)dns_peerlist_peerbyaddr(zone->view->peers, &primaryip, &peer);
+ UNLOCK_ZONE(zone);
+
+ /*
+ * Determine the total maximum number of simultaneous
+ * transfers allowed, and the maximum for this specific
+ * primary.
+ */
+ maxtransfersin = zmgr->transfersin;
+ maxtransfersperns = zmgr->transfersperns;
+ if (peer != NULL) {
+ (void)dns_peer_gettransfers(peer, &maxtransfersperns);
+ }
+
+ /*
+ * Count the total number of transfers that are in progress,
+ * and the number of transfers in progress from this primary.
+ * We linearly scan a list of all transfers; if this turns
+ * out to be too slow, we could hash on the primary address.
+ */
+ nxfrsin = nxfrsperns = 0;
+ for (x = ISC_LIST_HEAD(zmgr->xfrin_in_progress); x != NULL;
+ x = ISC_LIST_NEXT(x, statelink))
+ {
+ isc_netaddr_t xip;
+
+ LOCK_ZONE(x);
+ isc_netaddr_fromsockaddr(&xip, &x->primaryaddr);
+ UNLOCK_ZONE(x);
+
+ nxfrsin++;
+ if (isc_netaddr_equal(&xip, &primaryip)) {
+ nxfrsperns++;
+ }
+ }
+
+ /* Enforce quota. */
+ if (nxfrsin >= maxtransfersin) {
+ return (ISC_R_QUOTA);
+ }
+
+ if (nxfrsperns >= maxtransfersperns) {
+ return (ISC_R_QUOTA);
+ }
+
+gotquota:
+ /*
+ * We have sufficient quota. Move the zone to the "xfrin_in_progress"
+ * list and send it an event to let it start the actual transfer in the
+ * context of its own task.
+ */
+ e = isc_event_allocate(zmgr->mctx, zmgr, DNS_EVENT_ZONESTARTXFRIN,
+ got_transfer_quota, zone, sizeof(isc_event_t));
+
+ LOCK_ZONE(zone);
+ INSIST(zone->statelist == &zmgr->waiting_for_xfrin);
+ ISC_LIST_UNLINK(zmgr->waiting_for_xfrin, zone, statelink);
+ ISC_LIST_APPEND(zmgr->xfrin_in_progress, zone, statelink);
+ zone->statelist = &zmgr->xfrin_in_progress;
+ isc_task_send(zone->task, &e);
+ dns_zone_logc(zone, DNS_LOGCATEGORY_XFER_IN, ISC_LOG_INFO,
+ "Transfer started.");
+ UNLOCK_ZONE(zone);
+
+ return (ISC_R_SUCCESS);
+}
+
+void
+dns_zonemgr_setiolimit(dns_zonemgr_t *zmgr, uint32_t iolimit) {
+ REQUIRE(DNS_ZONEMGR_VALID(zmgr));
+ REQUIRE(iolimit > 0);
+
+ zmgr->iolimit = iolimit;
+}
+
+uint32_t
+dns_zonemgr_getiolimit(dns_zonemgr_t *zmgr) {
+ REQUIRE(DNS_ZONEMGR_VALID(zmgr));
+
+ return (zmgr->iolimit);
+}
+
+/*
+ * Get permission to request a file handle from the OS.
+ * An event will be sent to action when one is available.
+ * There are two queues available (high and low), the high
+ * queue will be serviced before the low one.
+ *
+ * zonemgr_putio() must be called after the event is delivered to
+ * 'action'.
+ */
+
+static isc_result_t
+zonemgr_getio(dns_zonemgr_t *zmgr, bool high, isc_task_t *task,
+ isc_taskaction_t action, void *arg, dns_io_t **iop) {
+ dns_io_t *io;
+ bool queue;
+
+ REQUIRE(DNS_ZONEMGR_VALID(zmgr));
+ REQUIRE(iop != NULL && *iop == NULL);
+
+ io = isc_mem_get(zmgr->mctx, sizeof(*io));
+
+ io->event = isc_event_allocate(zmgr->mctx, task, DNS_EVENT_IOREADY,
+ action, arg, sizeof(*io->event));
+
+ io->zmgr = zmgr;
+ io->high = high;
+ io->task = NULL;
+ isc_task_attach(task, &io->task);
+ ISC_LINK_INIT(io, link);
+ io->magic = IO_MAGIC;
+
+ LOCK(&zmgr->iolock);
+ zmgr->ioactive++;
+ queue = (zmgr->ioactive > zmgr->iolimit);
+ if (queue) {
+ if (io->high) {
+ ISC_LIST_APPEND(zmgr->high, io, link);
+ } else {
+ ISC_LIST_APPEND(zmgr->low, io, link);
+ }
+ }
+ UNLOCK(&zmgr->iolock);
+ *iop = io;
+
+ if (!queue) {
+ isc_task_send(io->task, &io->event);
+ }
+ return (ISC_R_SUCCESS);
+}
+
+static void
+zonemgr_putio(dns_io_t **iop) {
+ dns_io_t *io;
+ dns_io_t *next;
+ dns_zonemgr_t *zmgr;
+
+ REQUIRE(iop != NULL);
+ io = *iop;
+ *iop = NULL;
+ REQUIRE(DNS_IO_VALID(io));
+
+ INSIST(!ISC_LINK_LINKED(io, link));
+ INSIST(io->event == NULL);
+
+ zmgr = io->zmgr;
+ isc_task_detach(&io->task);
+ io->magic = 0;
+ isc_mem_put(zmgr->mctx, io, sizeof(*io));
+
+ LOCK(&zmgr->iolock);
+ INSIST(zmgr->ioactive > 0);
+ zmgr->ioactive--;
+ next = HEAD(zmgr->high);
+ if (next == NULL) {
+ next = HEAD(zmgr->low);
+ }
+ if (next != NULL) {
+ if (next->high) {
+ ISC_LIST_UNLINK(zmgr->high, next, link);
+ } else {
+ ISC_LIST_UNLINK(zmgr->low, next, link);
+ }
+ INSIST(next->event != NULL);
+ }
+ UNLOCK(&zmgr->iolock);
+ if (next != NULL) {
+ isc_task_send(next->task, &next->event);
+ }
+}
+
+static void
+zonemgr_cancelio(dns_io_t *io) {
+ bool send_event = false;
+
+ REQUIRE(DNS_IO_VALID(io));
+
+ /*
+ * If we are queued to be run then dequeue.
+ */
+ LOCK(&io->zmgr->iolock);
+ if (ISC_LINK_LINKED(io, link)) {
+ if (io->high) {
+ ISC_LIST_UNLINK(io->zmgr->high, io, link);
+ } else {
+ ISC_LIST_UNLINK(io->zmgr->low, io, link);
+ }
+
+ send_event = true;
+ INSIST(io->event != NULL);
+ }
+ UNLOCK(&io->zmgr->iolock);
+ if (send_event) {
+ io->event->ev_attributes |= ISC_EVENTATTR_CANCELED;
+ isc_task_send(io->task, &io->event);
+ }
+}
+
+static void
+zone_saveunique(dns_zone_t *zone, const char *path, const char *templat) {
+ char *buf;
+ int buflen;
+ isc_result_t result;
+
+ buflen = strlen(path) + strlen(templat) + 2;
+
+ buf = isc_mem_get(zone->mctx, buflen);
+
+ result = isc_file_template(path, templat, buf, buflen);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+
+ result = isc_file_renameunique(path, buf);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+
+ dns_zone_log(zone, ISC_LOG_WARNING,
+ "unable to load from '%s'; "
+ "renaming file to '%s' for failure analysis and "
+ "retransferring.",
+ path, buf);
+
+cleanup:
+ isc_mem_put(zone->mctx, buf, buflen);
+}
+
+static void
+setrl(isc_ratelimiter_t *rl, unsigned int *rate, unsigned int value) {
+ isc_interval_t interval;
+ uint32_t s, ns;
+ uint32_t pertic;
+ isc_result_t result;
+
+ if (value == 0) {
+ value = 1;
+ }
+
+ if (value == 1) {
+ s = 1;
+ ns = 0;
+ pertic = 1;
+ } else if (value <= 10) {
+ s = 0;
+ ns = 1000000000 / value;
+ pertic = 1;
+ } else {
+ s = 0;
+ ns = (1000000000 / value) * 10;
+ pertic = 10;
+ }
+
+ isc_interval_set(&interval, s, ns);
+
+ result = isc_ratelimiter_setinterval(rl, &interval);
+ RUNTIME_CHECK(result == ISC_R_SUCCESS);
+ isc_ratelimiter_setpertic(rl, pertic);
+
+ *rate = value;
+}
+
+void
+dns_zonemgr_setcheckdsrate(dns_zonemgr_t *zmgr, unsigned int value) {
+ REQUIRE(DNS_ZONEMGR_VALID(zmgr));
+
+ setrl(zmgr->checkdsrl, &zmgr->checkdsrate, value);
+}
+
+void
+dns_zonemgr_setnotifyrate(dns_zonemgr_t *zmgr, unsigned int value) {
+ REQUIRE(DNS_ZONEMGR_VALID(zmgr));
+
+ setrl(zmgr->notifyrl, &zmgr->notifyrate, value);
+}
+
+void
+dns_zonemgr_setstartupnotifyrate(dns_zonemgr_t *zmgr, unsigned int value) {
+ REQUIRE(DNS_ZONEMGR_VALID(zmgr));
+
+ setrl(zmgr->startupnotifyrl, &zmgr->startupnotifyrate, value);
+}
+
+void
+dns_zonemgr_setserialqueryrate(dns_zonemgr_t *zmgr, unsigned int value) {
+ REQUIRE(DNS_ZONEMGR_VALID(zmgr));
+
+ setrl(zmgr->refreshrl, &zmgr->serialqueryrate, value);
+ /* XXXMPA separate out once we have the code to support this. */
+ setrl(zmgr->startuprefreshrl, &zmgr->startupserialqueryrate, value);
+}
+
+unsigned int
+dns_zonemgr_getnotifyrate(dns_zonemgr_t *zmgr) {
+ REQUIRE(DNS_ZONEMGR_VALID(zmgr));
+
+ return (zmgr->notifyrate);
+}
+
+unsigned int
+dns_zonemgr_getstartupnotifyrate(dns_zonemgr_t *zmgr) {
+ REQUIRE(DNS_ZONEMGR_VALID(zmgr));
+
+ return (zmgr->startupnotifyrate);
+}
+
+unsigned int
+dns_zonemgr_getserialqueryrate(dns_zonemgr_t *zmgr) {
+ REQUIRE(DNS_ZONEMGR_VALID(zmgr));
+
+ return (zmgr->serialqueryrate);
+}
+
+bool
+dns_zonemgr_unreachable(dns_zonemgr_t *zmgr, isc_sockaddr_t *remote,
+ isc_sockaddr_t *local, isc_time_t *now) {
+ unsigned int i;
+ uint32_t seconds = isc_time_seconds(now);
+ uint32_t count = 0;
+
+ REQUIRE(DNS_ZONEMGR_VALID(zmgr));
+
+ RWLOCK(&zmgr->urlock, isc_rwlocktype_read);
+ for (i = 0; i < UNREACH_CACHE_SIZE; i++) {
+ if (atomic_load(&zmgr->unreachable[i].expire) >= seconds &&
+ isc_sockaddr_equal(&zmgr->unreachable[i].remote, remote) &&
+ isc_sockaddr_equal(&zmgr->unreachable[i].local, local))
+ {
+ atomic_store_relaxed(&zmgr->unreachable[i].last,
+ seconds);
+ count = zmgr->unreachable[i].count;
+ break;
+ }
+ }
+ RWUNLOCK(&zmgr->urlock, isc_rwlocktype_read);
+ return (i < UNREACH_CACHE_SIZE && count > 1U);
+}
+
+void
+dns_zonemgr_unreachabledel(dns_zonemgr_t *zmgr, isc_sockaddr_t *remote,
+ isc_sockaddr_t *local) {
+ unsigned int i;
+ char primary[ISC_SOCKADDR_FORMATSIZE];
+ char source[ISC_SOCKADDR_FORMATSIZE];
+
+ isc_sockaddr_format(remote, primary, sizeof(primary));
+ isc_sockaddr_format(local, source, sizeof(source));
+
+ REQUIRE(DNS_ZONEMGR_VALID(zmgr));
+
+ RWLOCK(&zmgr->urlock, isc_rwlocktype_read);
+ for (i = 0; i < UNREACH_CACHE_SIZE; i++) {
+ if (isc_sockaddr_equal(&zmgr->unreachable[i].remote, remote) &&
+ isc_sockaddr_equal(&zmgr->unreachable[i].local, local))
+ {
+ atomic_store_relaxed(&zmgr->unreachable[i].expire, 0);
+ break;
+ }
+ }
+ RWUNLOCK(&zmgr->urlock, isc_rwlocktype_read);
+}
+
+void
+dns_zonemgr_unreachableadd(dns_zonemgr_t *zmgr, isc_sockaddr_t *remote,
+ isc_sockaddr_t *local, isc_time_t *now) {
+ uint32_t seconds = isc_time_seconds(now);
+ uint32_t expire = 0, last = seconds;
+ unsigned int slot = UNREACH_CACHE_SIZE, oldest = 0;
+ bool update_entry = true;
+ REQUIRE(DNS_ZONEMGR_VALID(zmgr));
+
+ RWLOCK(&zmgr->urlock, isc_rwlocktype_write);
+ for (unsigned int i = 0; i < UNREACH_CACHE_SIZE; i++) {
+ /* Existing entry? */
+ if (isc_sockaddr_equal(&zmgr->unreachable[i].remote, remote) &&
+ isc_sockaddr_equal(&zmgr->unreachable[i].local, local))
+ {
+ update_entry = false;
+ slot = i;
+ expire = atomic_load_relaxed(
+ &zmgr->unreachable[i].expire);
+ break;
+ }
+ /* Pick first empty slot? */
+ if (atomic_load_relaxed(&zmgr->unreachable[i].expire) < seconds)
+ {
+ slot = i;
+ break;
+ }
+ /* The worst case, least recently used slot? */
+ if (atomic_load_relaxed(&zmgr->unreachable[i].last) < last) {
+ last = atomic_load_relaxed(&zmgr->unreachable[i].last);
+ oldest = i;
+ }
+ }
+
+ /* We haven't found any existing or free slots, use the oldest */
+ if (slot == UNREACH_CACHE_SIZE) {
+ slot = oldest;
+ }
+
+ if (expire < seconds) {
+ /* Expired or new entry, reset count to 1 */
+ zmgr->unreachable[slot].count = 1;
+ } else {
+ zmgr->unreachable[slot].count++;
+ }
+ atomic_store_relaxed(&zmgr->unreachable[slot].expire,
+ seconds + UNREACH_HOLD_TIME);
+ atomic_store_relaxed(&zmgr->unreachable[slot].last, seconds);
+ if (update_entry) {
+ zmgr->unreachable[slot].remote = *remote;
+ zmgr->unreachable[slot].local = *local;
+ }
+
+ RWUNLOCK(&zmgr->urlock, isc_rwlocktype_write);
+}
+
+void
+dns_zone_forcereload(dns_zone_t *zone) {
+ REQUIRE(DNS_ZONE_VALID(zone));
+
+ if (zone->type == dns_zone_primary ||
+ (zone->type == dns_zone_redirect && zone->primaries == NULL))
+ {
+ return;
+ }
+
+ LOCK_ZONE(zone);
+ DNS_ZONE_SETFLAG(zone, DNS_ZONEFLG_FORCEXFER);
+ UNLOCK_ZONE(zone);
+ dns_zone_refresh(zone);
+}
+
+bool
+dns_zone_isforced(dns_zone_t *zone) {
+ REQUIRE(DNS_ZONE_VALID(zone));
+
+ return (DNS_ZONE_FLAG(zone, DNS_ZONEFLG_FORCEXFER));
+}
+
+isc_result_t
+dns_zone_setstatistics(dns_zone_t *zone, bool on) {
+ /*
+ * This function is obsoleted.
+ */
+ UNUSED(zone);
+ UNUSED(on);
+ return (ISC_R_NOTIMPLEMENTED);
+}
+
+uint64_t *
+dns_zone_getstatscounters(dns_zone_t *zone) {
+ /*
+ * This function is obsoleted.
+ */
+ UNUSED(zone);
+ return (NULL);
+}
+
+void
+dns_zone_setstats(dns_zone_t *zone, isc_stats_t *stats) {
+ REQUIRE(DNS_ZONE_VALID(zone));
+ REQUIRE(zone->stats == NULL);
+
+ LOCK_ZONE(zone);
+ zone->stats = NULL;
+ isc_stats_attach(stats, &zone->stats);
+ UNLOCK_ZONE(zone);
+}
+
+void
+dns_zone_setrequeststats(dns_zone_t *zone, isc_stats_t *stats) {
+ REQUIRE(DNS_ZONE_VALID(zone));
+
+ LOCK_ZONE(zone);
+ if (zone->requeststats_on && stats == NULL) {
+ zone->requeststats_on = false;
+ } else if (!zone->requeststats_on && stats != NULL) {
+ if (zone->requeststats == NULL) {
+ isc_stats_attach(stats, &zone->requeststats);
+ }
+ zone->requeststats_on = true;
+ }
+ UNLOCK_ZONE(zone);
+}
+
+void
+dns_zone_setrcvquerystats(dns_zone_t *zone, dns_stats_t *stats) {
+ REQUIRE(DNS_ZONE_VALID(zone));
+
+ LOCK_ZONE(zone);
+ if (zone->requeststats_on && stats != NULL) {
+ if (zone->rcvquerystats == NULL) {
+ dns_stats_attach(stats, &zone->rcvquerystats);
+ zone->requeststats_on = true;
+ }
+ }
+ UNLOCK_ZONE(zone);
+}
+
+void
+dns_zone_setdnssecsignstats(dns_zone_t *zone, dns_stats_t *stats) {
+ REQUIRE(DNS_ZONE_VALID(zone));
+
+ LOCK_ZONE(zone);
+ if (stats != NULL && zone->dnssecsignstats == NULL) {
+ dns_stats_attach(stats, &zone->dnssecsignstats);
+ }
+ UNLOCK_ZONE(zone);
+}
+
+dns_stats_t *
+dns_zone_getdnssecsignstats(dns_zone_t *zone) {
+ REQUIRE(DNS_ZONE_VALID(zone));
+
+ return (zone->dnssecsignstats);
+}
+
+isc_stats_t *
+dns_zone_getrequeststats(dns_zone_t *zone) {
+ /*
+ * We don't lock zone for efficiency reason. This is not catastrophic
+ * because requeststats must always be valid when requeststats_on is
+ * true.
+ * Some counters may be incremented while requeststats_on is becoming
+ * false, or some cannot be incremented just after the statistics are
+ * installed, but it shouldn't matter much in practice.
+ */
+ if (zone->requeststats_on) {
+ return (zone->requeststats);
+ } else {
+ return (NULL);
+ }
+}
+
+/*
+ * Return the received query stats bucket
+ * see note from dns_zone_getrequeststats()
+ */
+dns_stats_t *
+dns_zone_getrcvquerystats(dns_zone_t *zone) {
+ if (zone->requeststats_on) {
+ return (zone->rcvquerystats);
+ } else {
+ return (NULL);
+ }
+}
+
+void
+dns_zone_dialup(dns_zone_t *zone) {
+ REQUIRE(DNS_ZONE_VALID(zone));
+
+ zone_debuglog(zone, "dns_zone_dialup", 3, "notify = %d, refresh = %d",
+ DNS_ZONE_FLAG(zone, DNS_ZONEFLG_DIALNOTIFY),
+ DNS_ZONE_FLAG(zone, DNS_ZONEFLG_DIALREFRESH));
+
+ if (DNS_ZONE_FLAG(zone, DNS_ZONEFLG_DIALNOTIFY)) {
+ dns_zone_notify(zone);
+ }
+ if (zone->type != dns_zone_primary && zone->primaries != NULL &&
+ DNS_ZONE_FLAG(zone, DNS_ZONEFLG_DIALREFRESH))
+ {
+ dns_zone_refresh(zone);
+ }
+}
+
+void
+dns_zone_setdialup(dns_zone_t *zone, dns_dialuptype_t dialup) {
+ REQUIRE(DNS_ZONE_VALID(zone));
+
+ LOCK_ZONE(zone);
+ DNS_ZONE_CLRFLAG(zone, DNS_ZONEFLG_DIALNOTIFY |
+ DNS_ZONEFLG_DIALREFRESH |
+ DNS_ZONEFLG_NOREFRESH);
+ switch (dialup) {
+ case dns_dialuptype_no:
+ break;
+ case dns_dialuptype_yes:
+ DNS_ZONE_SETFLAG(zone, (DNS_ZONEFLG_DIALNOTIFY |
+ DNS_ZONEFLG_DIALREFRESH |
+ DNS_ZONEFLG_NOREFRESH));
+ break;
+ case dns_dialuptype_notify:
+ DNS_ZONE_SETFLAG(zone, DNS_ZONEFLG_DIALNOTIFY);
+ break;
+ case dns_dialuptype_notifypassive:
+ DNS_ZONE_SETFLAG(zone, DNS_ZONEFLG_DIALNOTIFY);
+ DNS_ZONE_SETFLAG(zone, DNS_ZONEFLG_NOREFRESH);
+ break;
+ case dns_dialuptype_refresh:
+ DNS_ZONE_SETFLAG(zone, DNS_ZONEFLG_DIALREFRESH);
+ DNS_ZONE_SETFLAG(zone, DNS_ZONEFLG_NOREFRESH);
+ break;
+ case dns_dialuptype_passive:
+ DNS_ZONE_SETFLAG(zone, DNS_ZONEFLG_NOREFRESH);
+ break;
+ default:
+ UNREACHABLE();
+ }
+ UNLOCK_ZONE(zone);
+}
+
+isc_result_t
+dns_zone_setkeydirectory(dns_zone_t *zone, const char *directory) {
+ isc_result_t result = ISC_R_SUCCESS;
+
+ REQUIRE(DNS_ZONE_VALID(zone));
+
+ LOCK_ZONE(zone);
+ result = dns_zone_setstring(zone, &zone->keydirectory, directory);
+ UNLOCK_ZONE(zone);
+
+ return (result);
+}
+
+const char *
+dns_zone_getkeydirectory(dns_zone_t *zone) {
+ REQUIRE(DNS_ZONE_VALID(zone));
+
+ return (zone->keydirectory);
+}
+
+unsigned int
+dns_zonemgr_getcount(dns_zonemgr_t *zmgr, int state) {
+ dns_zone_t *zone;
+ unsigned int count = 0;
+
+ REQUIRE(DNS_ZONEMGR_VALID(zmgr));
+
+ RWLOCK(&zmgr->rwlock, isc_rwlocktype_read);
+ switch (state) {
+ case DNS_ZONESTATE_XFERRUNNING:
+ for (zone = ISC_LIST_HEAD(zmgr->xfrin_in_progress);
+ zone != NULL; zone = ISC_LIST_NEXT(zone, statelink))
+ {
+ count++;
+ }
+ break;
+ case DNS_ZONESTATE_XFERDEFERRED:
+ for (zone = ISC_LIST_HEAD(zmgr->waiting_for_xfrin);
+ zone != NULL; zone = ISC_LIST_NEXT(zone, statelink))
+ {
+ count++;
+ }
+ break;
+ case DNS_ZONESTATE_SOAQUERY:
+ for (zone = ISC_LIST_HEAD(zmgr->zones); zone != NULL;
+ zone = ISC_LIST_NEXT(zone, link))
+ {
+ if (DNS_ZONE_FLAG(zone, DNS_ZONEFLG_REFRESH)) {
+ count++;
+ }
+ }
+ break;
+ case DNS_ZONESTATE_ANY:
+ for (zone = ISC_LIST_HEAD(zmgr->zones); zone != NULL;
+ zone = ISC_LIST_NEXT(zone, link))
+ {
+ dns_view_t *view = zone->view;
+ if (view != NULL && strcmp(view->name, "_bind") == 0) {
+ continue;
+ }
+ count++;
+ }
+ break;
+ case DNS_ZONESTATE_AUTOMATIC:
+ for (zone = ISC_LIST_HEAD(zmgr->zones); zone != NULL;
+ zone = ISC_LIST_NEXT(zone, link))
+ {
+ dns_view_t *view = zone->view;
+ if (view != NULL && strcmp(view->name, "_bind") == 0) {
+ continue;
+ }
+ if (zone->automatic) {
+ count++;
+ }
+ }
+ break;
+ default:
+ UNREACHABLE();
+ }
+
+ RWUNLOCK(&zmgr->rwlock, isc_rwlocktype_read);
+
+ return (count);
+}
+
+void
+dns_zone_lock_keyfiles(dns_zone_t *zone) {
+ REQUIRE(DNS_ZONE_VALID(zone));
+
+ if (zone->kasp == NULL) {
+ /* No need to lock, nothing is writing key files. */
+ return;
+ }
+
+ REQUIRE(DNS_KEYFILEIO_VALID(zone->kfio));
+ isc_mutex_lock(&zone->kfio->lock);
+}
+
+void
+dns_zone_unlock_keyfiles(dns_zone_t *zone) {
+ REQUIRE(DNS_ZONE_VALID(zone));
+
+ if (zone->kasp == NULL) {
+ /* No need to lock, nothing is writing key files. */
+ return;
+ }
+
+ REQUIRE(DNS_KEYFILEIO_VALID(zone->kfio));
+ isc_mutex_unlock(&zone->kfio->lock);
+}
+
+isc_result_t
+dns_zone_checknames(dns_zone_t *zone, const dns_name_t *name,
+ dns_rdata_t *rdata) {
+ bool ok = true;
+ bool fail = false;
+ char namebuf[DNS_NAME_FORMATSIZE];
+ char namebuf2[DNS_NAME_FORMATSIZE];
+ char typebuf[DNS_RDATATYPE_FORMATSIZE];
+ int level = ISC_LOG_WARNING;
+ dns_name_t bad;
+
+ REQUIRE(DNS_ZONE_VALID(zone));
+
+ if (!DNS_ZONE_OPTION(zone, DNS_ZONEOPT_CHECKNAMES) &&
+ rdata->type != dns_rdatatype_nsec3)
+ {
+ return (ISC_R_SUCCESS);
+ }
+
+ if (DNS_ZONE_OPTION(zone, DNS_ZONEOPT_CHECKNAMESFAIL) ||
+ rdata->type == dns_rdatatype_nsec3)
+ {
+ level = ISC_LOG_ERROR;
+ fail = true;
+ }
+
+ ok = dns_rdata_checkowner(name, rdata->rdclass, rdata->type, true);
+ if (!ok) {
+ dns_name_format(name, namebuf, sizeof(namebuf));
+ dns_rdatatype_format(rdata->type, typebuf, sizeof(typebuf));
+ dns_zone_log(zone, level, "%s/%s: %s", namebuf, typebuf,
+ isc_result_totext(DNS_R_BADOWNERNAME));
+ if (fail) {
+ return (DNS_R_BADOWNERNAME);
+ }
+ }
+
+ dns_name_init(&bad, NULL);
+ ok = dns_rdata_checknames(rdata, name, &bad);
+ if (!ok) {
+ dns_name_format(name, namebuf, sizeof(namebuf));
+ dns_name_format(&bad, namebuf2, sizeof(namebuf2));
+ dns_rdatatype_format(rdata->type, typebuf, sizeof(typebuf));
+ dns_zone_log(zone, level, "%s/%s: %s: %s ", namebuf, typebuf,
+ namebuf2, isc_result_totext(DNS_R_BADNAME));
+ if (fail) {
+ return (DNS_R_BADNAME);
+ }
+ }
+
+ return (ISC_R_SUCCESS);
+}
+
+void
+dns_zone_setcheckmx(dns_zone_t *zone, dns_checkmxfunc_t checkmx) {
+ REQUIRE(DNS_ZONE_VALID(zone));
+ zone->checkmx = checkmx;
+}
+
+void
+dns_zone_setchecksrv(dns_zone_t *zone, dns_checksrvfunc_t checksrv) {
+ REQUIRE(DNS_ZONE_VALID(zone));
+ zone->checksrv = checksrv;
+}
+
+void
+dns_zone_setcheckns(dns_zone_t *zone, dns_checknsfunc_t checkns) {
+ REQUIRE(DNS_ZONE_VALID(zone));
+ zone->checkns = checkns;
+}
+
+void
+dns_zone_setisself(dns_zone_t *zone, dns_isselffunc_t isself, void *arg) {
+ REQUIRE(DNS_ZONE_VALID(zone));
+
+ LOCK_ZONE(zone);
+ zone->isself = isself;
+ zone->isselfarg = arg;
+ UNLOCK_ZONE(zone);
+}
+
+void
+dns_zone_setnotifydelay(dns_zone_t *zone, uint32_t delay) {
+ REQUIRE(DNS_ZONE_VALID(zone));
+
+ LOCK_ZONE(zone);
+ zone->notifydelay = delay;
+ UNLOCK_ZONE(zone);
+}
+
+uint32_t
+dns_zone_getnotifydelay(dns_zone_t *zone) {
+ REQUIRE(DNS_ZONE_VALID(zone));
+
+ return (zone->notifydelay);
+}
+
+isc_result_t
+dns_zone_signwithkey(dns_zone_t *zone, dns_secalg_t algorithm, uint16_t keyid,
+ bool deleteit) {
+ isc_result_t result;
+ REQUIRE(DNS_ZONE_VALID(zone));
+
+ dnssec_log(zone, ISC_LOG_NOTICE,
+ "dns_zone_signwithkey(algorithm=%u, keyid=%u)", algorithm,
+ keyid);
+ LOCK_ZONE(zone);
+ result = zone_signwithkey(zone, algorithm, keyid, deleteit);
+ UNLOCK_ZONE(zone);
+
+ return (result);
+}
+
+/*
+ * Called when a dynamic update for an NSEC3PARAM record is received.
+ *
+ * If set, transform the NSEC3 salt into human-readable form so that it can be
+ * logged. Then call zone_addnsec3chain(), passing NSEC3PARAM RDATA to it.
+ */
+isc_result_t
+dns_zone_addnsec3chain(dns_zone_t *zone, dns_rdata_nsec3param_t *nsec3param) {
+ isc_result_t result;
+ char salt[255 * 2 + 1];
+
+ REQUIRE(DNS_ZONE_VALID(zone));
+
+ result = dns_nsec3param_salttotext(nsec3param, salt, sizeof(salt));
+ RUNTIME_CHECK(result == ISC_R_SUCCESS);
+ dnssec_log(zone, ISC_LOG_NOTICE,
+ "dns_zone_addnsec3chain(hash=%u, iterations=%u, salt=%s)",
+ nsec3param->hash, nsec3param->iterations, salt);
+ LOCK_ZONE(zone);
+ result = zone_addnsec3chain(zone, nsec3param);
+ UNLOCK_ZONE(zone);
+
+ return (result);
+}
+
+void
+dns_zone_setnodes(dns_zone_t *zone, uint32_t nodes) {
+ REQUIRE(DNS_ZONE_VALID(zone));
+
+ if (nodes == 0) {
+ nodes = 1;
+ }
+ zone->nodes = nodes;
+}
+
+void
+dns_zone_setsignatures(dns_zone_t *zone, uint32_t signatures) {
+ REQUIRE(DNS_ZONE_VALID(zone));
+
+ /*
+ * We treat signatures as a signed value so explicitly
+ * limit its range here.
+ */
+ if (signatures > INT32_MAX) {
+ signatures = INT32_MAX;
+ } else if (signatures == 0) {
+ signatures = 1;
+ }
+ zone->signatures = signatures;
+}
+
+uint32_t
+dns_zone_getsignatures(dns_zone_t *zone) {
+ REQUIRE(DNS_ZONE_VALID(zone));
+ return (zone->signatures);
+}
+
+void
+dns_zone_setprivatetype(dns_zone_t *zone, dns_rdatatype_t type) {
+ REQUIRE(DNS_ZONE_VALID(zone));
+ zone->privatetype = type;
+}
+
+dns_rdatatype_t
+dns_zone_getprivatetype(dns_zone_t *zone) {
+ REQUIRE(DNS_ZONE_VALID(zone));
+ return (zone->privatetype);
+}
+
+static isc_result_t
+zone_signwithkey(dns_zone_t *zone, dns_secalg_t algorithm, uint16_t keyid,
+ bool deleteit) {
+ dns_signing_t *signing;
+ dns_signing_t *current;
+ isc_result_t result = ISC_R_SUCCESS;
+ isc_time_t now;
+ dns_db_t *db = NULL;
+
+ signing = isc_mem_get(zone->mctx, sizeof *signing);
+
+ signing->magic = 0;
+ signing->db = NULL;
+ signing->dbiterator = NULL;
+ signing->algorithm = algorithm;
+ signing->keyid = keyid;
+ signing->deleteit = deleteit;
+ signing->done = false;
+
+ TIME_NOW(&now);
+
+ ZONEDB_LOCK(&zone->dblock, isc_rwlocktype_read);
+ if (zone->db != NULL) {
+ dns_db_attach(zone->db, &db);
+ }
+ ZONEDB_UNLOCK(&zone->dblock, isc_rwlocktype_read);
+
+ if (db == NULL) {
+ result = ISC_R_NOTFOUND;
+ goto cleanup;
+ }
+
+ dns_db_attach(db, &signing->db);
+
+ for (current = ISC_LIST_HEAD(zone->signing); current != NULL;
+ current = ISC_LIST_NEXT(current, link))
+ {
+ if (current->db == signing->db &&
+ current->algorithm == signing->algorithm &&
+ current->keyid == signing->keyid)
+ {
+ if (current->deleteit != signing->deleteit) {
+ current->done = true;
+ } else {
+ goto cleanup;
+ }
+ }
+ }
+
+ result = dns_db_createiterator(signing->db, 0, &signing->dbiterator);
+
+ if (result == ISC_R_SUCCESS) {
+ result = dns_dbiterator_first(signing->dbiterator);
+ }
+ if (result == ISC_R_SUCCESS) {
+ dns_dbiterator_pause(signing->dbiterator);
+ ISC_LIST_INITANDAPPEND(zone->signing, signing, link);
+ signing = NULL;
+ if (isc_time_isepoch(&zone->signingtime)) {
+ zone->signingtime = now;
+ if (zone->task != NULL) {
+ zone_settimer(zone, &now);
+ }
+ }
+ }
+
+cleanup:
+ if (signing != NULL) {
+ if (signing->db != NULL) {
+ dns_db_detach(&signing->db);
+ }
+ if (signing->dbiterator != NULL) {
+ dns_dbiterator_destroy(&signing->dbiterator);
+ }
+ isc_mem_put(zone->mctx, signing, sizeof *signing);
+ }
+ if (db != NULL) {
+ dns_db_detach(&db);
+ }
+ return (result);
+}
+
+/* Called once; *timep should be set to the current time. */
+static isc_result_t
+next_keyevent(dst_key_t *key, isc_stdtime_t *timep) {
+ isc_result_t result;
+ isc_stdtime_t now, then = 0, event;
+ int i;
+
+ now = *timep;
+
+ for (i = 0; i <= DST_MAX_TIMES; i++) {
+ result = dst_key_gettime(key, i, &event);
+ if (result == ISC_R_SUCCESS && event > now &&
+ (then == 0 || event < then))
+ {
+ then = event;
+ }
+ }
+
+ if (then != 0) {
+ *timep = then;
+ return (ISC_R_SUCCESS);
+ }
+
+ return (ISC_R_NOTFOUND);
+}
+
+static isc_result_t
+rr_exists(dns_db_t *db, dns_dbversion_t *ver, dns_name_t *name,
+ const dns_rdata_t *rdata, bool *flag) {
+ dns_rdataset_t rdataset;
+ dns_dbnode_t *node = NULL;
+ isc_result_t result;
+
+ dns_rdataset_init(&rdataset);
+ if (rdata->type == dns_rdatatype_nsec3) {
+ CHECK(dns_db_findnsec3node(db, name, false, &node));
+ } else {
+ CHECK(dns_db_findnode(db, name, false, &node));
+ }
+ result = dns_db_findrdataset(db, node, ver, rdata->type, 0,
+ (isc_stdtime_t)0, &rdataset, NULL);
+ if (result == ISC_R_NOTFOUND) {
+ *flag = false;
+ result = ISC_R_SUCCESS;
+ goto failure;
+ }
+
+ for (result = dns_rdataset_first(&rdataset); result == ISC_R_SUCCESS;
+ result = dns_rdataset_next(&rdataset))
+ {
+ dns_rdata_t myrdata = DNS_RDATA_INIT;
+ dns_rdataset_current(&rdataset, &myrdata);
+ if (!dns_rdata_compare(&myrdata, rdata)) {
+ break;
+ }
+ }
+ dns_rdataset_disassociate(&rdataset);
+ if (result == ISC_R_SUCCESS) {
+ *flag = true;
+ } else if (result == ISC_R_NOMORE) {
+ *flag = false;
+ result = ISC_R_SUCCESS;
+ }
+
+failure:
+ if (node != NULL) {
+ dns_db_detachnode(db, &node);
+ }
+ return (result);
+}
+
+/*
+ * Add records to signal the state of signing or of key removal.
+ */
+static isc_result_t
+add_signing_records(dns_db_t *db, dns_rdatatype_t privatetype,
+ dns_dbversion_t *ver, dns_diff_t *diff, bool sign_all) {
+ dns_difftuple_t *tuple, *newtuple = NULL;
+ dns_rdata_dnskey_t dnskey;
+ dns_rdata_t rdata = DNS_RDATA_INIT;
+ bool flag;
+ isc_region_t r;
+ isc_result_t result = ISC_R_SUCCESS;
+ uint16_t keyid;
+ unsigned char buf[5];
+ dns_name_t *name = dns_db_origin(db);
+
+ for (tuple = ISC_LIST_HEAD(diff->tuples); tuple != NULL;
+ tuple = ISC_LIST_NEXT(tuple, link))
+ {
+ if (tuple->rdata.type != dns_rdatatype_dnskey) {
+ continue;
+ }
+
+ result = dns_rdata_tostruct(&tuple->rdata, &dnskey, NULL);
+ RUNTIME_CHECK(result == ISC_R_SUCCESS);
+ if ((dnskey.flags & (DNS_KEYFLAG_OWNERMASK |
+ DNS_KEYTYPE_NOAUTH)) != DNS_KEYOWNER_ZONE)
+ {
+ continue;
+ }
+
+ dns_rdata_toregion(&tuple->rdata, &r);
+
+ keyid = dst_region_computeid(&r);
+
+ buf[0] = dnskey.algorithm;
+ buf[1] = (keyid & 0xff00) >> 8;
+ buf[2] = (keyid & 0xff);
+ buf[3] = (tuple->op == DNS_DIFFOP_ADD) ? 0 : 1;
+ buf[4] = 0;
+ rdata.data = buf;
+ rdata.length = sizeof(buf);
+ rdata.type = privatetype;
+ rdata.rdclass = tuple->rdata.rdclass;
+
+ if (sign_all || tuple->op == DNS_DIFFOP_DEL) {
+ CHECK(rr_exists(db, ver, name, &rdata, &flag));
+ if (flag) {
+ continue;
+ }
+
+ CHECK(dns_difftuple_create(diff->mctx, DNS_DIFFOP_ADD,
+ name, 0, &rdata, &newtuple));
+ CHECK(do_one_tuple(&newtuple, db, ver, diff));
+ INSIST(newtuple == NULL);
+ }
+
+ /*
+ * Remove any record which says this operation has already
+ * completed.
+ */
+ buf[4] = 1;
+ CHECK(rr_exists(db, ver, name, &rdata, &flag));
+ if (flag) {
+ CHECK(dns_difftuple_create(diff->mctx, DNS_DIFFOP_DEL,
+ name, 0, &rdata, &newtuple));
+ CHECK(do_one_tuple(&newtuple, db, ver, diff));
+ INSIST(newtuple == NULL);
+ }
+ }
+failure:
+ return (result);
+}
+
+/*
+ * See if dns__zone_updatesigs() will update signature for RRset 'rrtype' at
+ * the apex, and if not tickle them and cause to sign so that newly activated
+ * keys are used.
+ */
+static isc_result_t
+tickle_apex_rrset(dns_rdatatype_t rrtype, dns_zone_t *zone, dns_db_t *db,
+ dns_dbversion_t *ver, isc_stdtime_t now, dns_diff_t *diff,
+ dns__zonediff_t *zonediff, dst_key_t **keys,
+ unsigned int nkeys, isc_stdtime_t inception,
+ isc_stdtime_t keyexpire, bool check_ksk,
+ bool keyset_kskonly) {
+ dns_difftuple_t *tuple;
+ isc_result_t result;
+
+ for (tuple = ISC_LIST_HEAD(diff->tuples); tuple != NULL;
+ tuple = ISC_LIST_NEXT(tuple, link))
+ {
+ if (tuple->rdata.type == rrtype &&
+ dns_name_equal(&tuple->name, &zone->origin))
+ {
+ break;
+ }
+ }
+
+ if (tuple == NULL) {
+ result = del_sigs(zone, db, ver, &zone->origin, rrtype,
+ zonediff, keys, nkeys, now, false);
+ if (result != ISC_R_SUCCESS) {
+ dnssec_log(zone, ISC_LOG_ERROR,
+ "sign_apex:del_sigs -> %s",
+ isc_result_totext(result));
+ return (result);
+ }
+ result = add_sigs(db, ver, &zone->origin, zone, rrtype,
+ zonediff->diff, keys, nkeys, zone->mctx,
+ inception, keyexpire, check_ksk,
+ keyset_kskonly);
+ if (result != ISC_R_SUCCESS) {
+ dnssec_log(zone, ISC_LOG_ERROR,
+ "sign_apex:add_sigs -> %s",
+ isc_result_totext(result));
+ return (result);
+ }
+ }
+
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+sign_apex(dns_zone_t *zone, dns_db_t *db, dns_dbversion_t *ver,
+ isc_stdtime_t now, dns_diff_t *diff, dns__zonediff_t *zonediff) {
+ isc_result_t result;
+ isc_stdtime_t inception, soaexpire, keyexpire;
+ bool check_ksk, keyset_kskonly;
+ dst_key_t *zone_keys[DNS_MAXZONEKEYS];
+ unsigned int nkeys = 0, i;
+
+ result = dns__zone_findkeys(zone, db, ver, now, zone->mctx,
+ DNS_MAXZONEKEYS, zone_keys, &nkeys);
+ if (result != ISC_R_SUCCESS) {
+ dnssec_log(zone, ISC_LOG_ERROR,
+ "sign_apex:dns__zone_findkeys -> %s",
+ isc_result_totext(result));
+ return (result);
+ }
+
+ inception = now - 3600; /* Allow for clock skew. */
+ soaexpire = now + dns_zone_getsigvalidityinterval(zone);
+
+ keyexpire = dns_zone_getkeyvalidityinterval(zone);
+ if (keyexpire == 0) {
+ keyexpire = soaexpire - 1;
+ } else {
+ keyexpire += now;
+ }
+
+ check_ksk = DNS_ZONE_OPTION(zone, DNS_ZONEOPT_UPDATECHECKKSK);
+ keyset_kskonly = DNS_ZONE_OPTION(zone, DNS_ZONEOPT_DNSKEYKSKONLY);
+
+ /*
+ * See if dns__zone_updatesigs() will update DNSKEY/CDS/CDNSKEY
+ * signature and if not cause them to sign so that newly activated
+ * keys are used.
+ */
+ result = tickle_apex_rrset(dns_rdatatype_dnskey, zone, db, ver, now,
+ diff, zonediff, zone_keys, nkeys, inception,
+ keyexpire, check_ksk, keyset_kskonly);
+ if (result != ISC_R_SUCCESS) {
+ goto failure;
+ }
+ result = tickle_apex_rrset(dns_rdatatype_cds, zone, db, ver, now, diff,
+ zonediff, zone_keys, nkeys, inception,
+ keyexpire, check_ksk, keyset_kskonly);
+ if (result != ISC_R_SUCCESS) {
+ goto failure;
+ }
+ result = tickle_apex_rrset(dns_rdatatype_cdnskey, zone, db, ver, now,
+ diff, zonediff, zone_keys, nkeys, inception,
+ keyexpire, check_ksk, keyset_kskonly);
+ if (result != ISC_R_SUCCESS) {
+ goto failure;
+ }
+
+ result = dns__zone_updatesigs(diff, db, ver, zone_keys, nkeys, zone,
+ inception, soaexpire, keyexpire, now,
+ check_ksk, keyset_kskonly, zonediff);
+
+ if (result != ISC_R_SUCCESS) {
+ dnssec_log(zone, ISC_LOG_ERROR,
+ "sign_apex:dns__zone_updatesigs -> %s",
+ isc_result_totext(result));
+ goto failure;
+ }
+
+failure:
+ for (i = 0; i < nkeys; i++) {
+ dst_key_free(&zone_keys[i]);
+ }
+ return (result);
+}
+
+static isc_result_t
+clean_nsec3param(dns_zone_t *zone, dns_db_t *db, dns_dbversion_t *ver,
+ dns_diff_t *diff) {
+ isc_result_t result;
+ dns_dbnode_t *node = NULL;
+ dns_rdataset_t rdataset;
+
+ dns_rdataset_init(&rdataset);
+ CHECK(dns_db_getoriginnode(db, &node));
+
+ result = dns_db_findrdataset(db, node, ver, dns_rdatatype_dnskey,
+ dns_rdatatype_none, 0, &rdataset, NULL);
+ if (dns_rdataset_isassociated(&rdataset)) {
+ dns_rdataset_disassociate(&rdataset);
+ }
+ if (result != ISC_R_NOTFOUND) {
+ goto failure;
+ }
+
+ result = dns_nsec3param_deletechains(db, ver, zone, true, diff);
+
+failure:
+ if (node != NULL) {
+ dns_db_detachnode(db, &node);
+ }
+ return (result);
+}
+
+/*
+ * Given an RRSIG rdataset and an algorithm, determine whether there
+ * are any signatures using that algorithm.
+ */
+static bool
+signed_with_alg(dns_rdataset_t *rdataset, dns_secalg_t alg) {
+ dns_rdata_t rdata = DNS_RDATA_INIT;
+ dns_rdata_rrsig_t rrsig;
+ isc_result_t result;
+
+ REQUIRE(rdataset == NULL || rdataset->type == dns_rdatatype_rrsig);
+ if (rdataset == NULL || !dns_rdataset_isassociated(rdataset)) {
+ return (false);
+ }
+
+ for (result = dns_rdataset_first(rdataset); result == ISC_R_SUCCESS;
+ result = dns_rdataset_next(rdataset))
+ {
+ dns_rdataset_current(rdataset, &rdata);
+ result = dns_rdata_tostruct(&rdata, &rrsig, NULL);
+ RUNTIME_CHECK(result == ISC_R_SUCCESS);
+ dns_rdata_reset(&rdata);
+ if (rrsig.algorithm == alg) {
+ return (true);
+ }
+ }
+
+ return (false);
+}
+
+static isc_result_t
+add_chains(dns_zone_t *zone, dns_db_t *db, dns_dbversion_t *ver,
+ dns_diff_t *diff) {
+ dns_name_t *origin;
+ bool build_nsec3;
+ isc_result_t result;
+
+ origin = dns_db_origin(db);
+ CHECK(dns_private_chains(db, ver, zone->privatetype, NULL,
+ &build_nsec3));
+ if (build_nsec3) {
+ CHECK(dns_nsec3_addnsec3sx(db, ver, origin, zone_nsecttl(zone),
+ false, zone->privatetype, diff));
+ }
+ CHECK(updatesecure(db, ver, origin, zone_nsecttl(zone), true, diff));
+
+failure:
+ return (result);
+}
+
+static void
+dnssec_report(const char *format, ...) {
+ va_list args;
+ va_start(args, format);
+ isc_log_vwrite(dns_lctx, DNS_LOGCATEGORY_DNSSEC, DNS_LOGMODULE_ZONE,
+ ISC_LOG_INFO, format, args);
+ va_end(args);
+}
+
+static void
+checkds_destroy(dns_checkds_t *checkds, bool locked) {
+ isc_mem_t *mctx;
+
+ REQUIRE(DNS_CHECKDS_VALID(checkds));
+
+ dns_zone_log(checkds->zone, ISC_LOG_DEBUG(3),
+ "checkds: destroy DS query");
+
+ if (checkds->zone != NULL) {
+ if (!locked) {
+ LOCK_ZONE(checkds->zone);
+ }
+ REQUIRE(LOCKED_ZONE(checkds->zone));
+ if (ISC_LINK_LINKED(checkds, link)) {
+ ISC_LIST_UNLINK(checkds->zone->checkds_requests,
+ checkds, link);
+ }
+ if (!locked) {
+ UNLOCK_ZONE(checkds->zone);
+ }
+ if (locked) {
+ zone_idetach(&checkds->zone);
+ } else {
+ dns_zone_idetach(&checkds->zone);
+ }
+ }
+ if (checkds->request != NULL) {
+ dns_request_destroy(&checkds->request);
+ }
+ if (checkds->key != NULL) {
+ dns_tsigkey_detach(&checkds->key);
+ }
+ if (checkds->transport != NULL) {
+ dns_transport_detach(&checkds->transport);
+ }
+ mctx = checkds->mctx;
+ isc_mem_put(checkds->mctx, checkds, sizeof(*checkds));
+ isc_mem_detach(&mctx);
+}
+
+static isc_result_t
+make_dnskey(dst_key_t *key, unsigned char *buf, int bufsize,
+ dns_rdata_t *target) {
+ isc_result_t result;
+ isc_buffer_t b;
+ isc_region_t r;
+
+ isc_buffer_init(&b, buf, bufsize);
+ result = dst_key_todns(key, &b);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+
+ dns_rdata_reset(target);
+ isc_buffer_usedregion(&b, &r);
+ dns_rdata_fromregion(target, dst_key_class(key), dns_rdatatype_dnskey,
+ &r);
+ return (ISC_R_SUCCESS);
+}
+
+static bool
+do_checkds(dns_zone_t *zone, dst_key_t *key, isc_stdtime_t now,
+ bool dspublish) {
+ dns_kasp_t *kasp = dns_zone_getkasp(zone);
+ const char *dir = dns_zone_getkeydirectory(zone);
+ isc_result_t result;
+ uint32_t count = 0;
+
+ if (dspublish) {
+ (void)dst_key_getnum(key, DST_NUM_DSPUBCOUNT, &count);
+ count += 1;
+ dst_key_setnum(key, DST_NUM_DSPUBCOUNT, count);
+ dns_zone_log(zone, ISC_LOG_DEBUG(3),
+ "checkds: %u DS published "
+ "for key %u",
+ count, dst_key_id(key));
+
+ if (count != zone->parentalscnt) {
+ return false;
+ }
+ } else {
+ (void)dst_key_getnum(key, DST_NUM_DSDELCOUNT, &count);
+ count += 1;
+ dst_key_setnum(key, DST_NUM_DSDELCOUNT, count);
+ dns_zone_log(zone, ISC_LOG_DEBUG(3),
+ "checkds: %u DS withdrawn "
+ "for key %u",
+ count, dst_key_id(key));
+
+ if (count != zone->parentalscnt) {
+ return false;
+ }
+ }
+
+ dns_zone_log(zone, ISC_LOG_DEBUG(3),
+ "checkds: checkds %s for key "
+ "%u",
+ dspublish ? "published" : "withdrawn", dst_key_id(key));
+
+ dns_zone_lock_keyfiles(zone);
+ result = dns_keymgr_checkds_id(kasp, &zone->checkds_ok, dir, now, now,
+ dspublish, dst_key_id(key),
+ dst_key_alg(key));
+ dns_zone_unlock_keyfiles(zone);
+
+ if (result != ISC_R_SUCCESS) {
+ dns_zone_log(zone, ISC_LOG_WARNING,
+ "checkds: checkds for key %u failed: %s",
+ dst_key_id(key), isc_result_totext(result));
+ return false;
+ }
+
+ return true;
+}
+
+static isc_result_t
+validate_ds(dns_zone_t *zone, dns_message_t *message) {
+ UNUSED(zone);
+ UNUSED(message);
+
+ /* Get closest trust anchor */
+
+ /* Check that trust anchor is (grand)parent of zone. */
+
+ /* Find the DNSKEY signing the message. */
+
+ /* Check that DNSKEY is in chain of trust. */
+
+ /* Validate DS RRset. */
+
+ return (ISC_R_SUCCESS);
+}
+
+static void
+checkds_done(isc_task_t *task, isc_event_t *event) {
+ char addrbuf[ISC_SOCKADDR_FORMATSIZE];
+ char rcode[128];
+ dns_checkds_t *checkds;
+ dns_zone_t *zone;
+ dns_db_t *db = NULL;
+ dns_dbversion_t *version = NULL;
+ dns_dnsseckey_t *key;
+ dns_dnsseckeylist_t keys;
+ dns_kasp_t *kasp = NULL;
+ dns_message_t *message = NULL;
+ dns_rdataset_t *ds_rrset = NULL;
+ dns_requestevent_t *revent = (dns_requestevent_t *)event;
+ isc_buffer_t buf;
+ isc_result_t result;
+ isc_stdtime_t now;
+ isc_time_t timenow;
+ bool rekey = false;
+ bool empty = false;
+
+ UNUSED(task);
+
+ checkds = event->ev_arg;
+ REQUIRE(DNS_CHECKDS_VALID(checkds));
+
+ zone = checkds->zone;
+ INSIST(task == zone->task);
+
+ ISC_LIST_INIT(keys);
+
+ kasp = zone->kasp;
+ INSIST(kasp != NULL);
+
+ isc_buffer_init(&buf, rcode, sizeof(rcode));
+ isc_sockaddr_format(&checkds->dst, addrbuf, sizeof(addrbuf));
+
+ dns_zone_log(zone, ISC_LOG_DEBUG(1), "checkds: DS query to %s: done",
+ addrbuf);
+
+ dns_message_create(zone->mctx, DNS_MESSAGE_INTENTPARSE, &message);
+ INSIST(message != NULL);
+
+ CHECK(revent->result);
+ CHECK(dns_request_getresponse(revent->request, message,
+ DNS_MESSAGEPARSE_PRESERVEORDER));
+ CHECK(dns_rcode_totext(message->rcode, &buf));
+
+ dns_zone_log(zone, ISC_LOG_DEBUG(3),
+ "checkds: DS response from %s: %.*s", addrbuf,
+ (int)buf.used, rcode);
+
+ /* Validate response. */
+ CHECK(validate_ds(zone, message));
+
+ /* Check RCODE. */
+ if (message->rcode != dns_rcode_noerror) {
+ dns_zone_log(zone, ISC_LOG_NOTICE,
+ "checkds: bad DS response from %s: %.*s", addrbuf,
+ (int)buf.used, rcode);
+ goto failure;
+ }
+
+ /* Make sure that either AA or RA bit is set. */
+ if ((message->flags & DNS_MESSAGEFLAG_AA) == 0 &&
+ (message->flags & DNS_MESSAGEFLAG_RA) == 0)
+ {
+ dns_zone_log(zone, ISC_LOG_NOTICE,
+ "checkds: bad DS response from %s: expected AA or "
+ "RA bit set",
+ addrbuf);
+ goto failure;
+ }
+
+ /* Lookup DS RRset. */
+ result = dns_message_firstname(message, DNS_SECTION_ANSWER);
+ while (result == ISC_R_SUCCESS) {
+ dns_name_t *name = NULL;
+ dns_rdataset_t *rdataset;
+
+ dns_message_currentname(message, DNS_SECTION_ANSWER, &name);
+ if (dns_name_compare(&zone->origin, name) != 0) {
+ goto next;
+ }
+
+ for (rdataset = ISC_LIST_HEAD(name->list); rdataset != NULL;
+ rdataset = ISC_LIST_NEXT(rdataset, link))
+ {
+ if (rdataset->type != dns_rdatatype_ds) {
+ goto next;
+ }
+
+ ds_rrset = rdataset;
+ break;
+ }
+
+ if (ds_rrset != NULL) {
+ break;
+ }
+
+ next:
+ result = dns_message_nextname(message, DNS_SECTION_ANSWER);
+ }
+
+ if (ds_rrset == NULL) {
+ empty = true;
+ dns_zone_log(zone, ISC_LOG_NOTICE,
+ "checkds: empty DS response from %s", addrbuf);
+ }
+
+ TIME_NOW(&timenow);
+ now = isc_time_seconds(&timenow);
+
+ CHECK(dns_zone_getdb(zone, &db));
+ dns_db_currentversion(db, &version);
+
+ KASP_LOCK(kasp);
+ LOCK_ZONE(zone);
+ for (key = ISC_LIST_HEAD(zone->checkds_ok); key != NULL;
+ key = ISC_LIST_NEXT(key, link))
+ {
+ bool alldone = false, found = false;
+ bool checkdspub = false, checkdsdel = false, ksk = false;
+ dst_key_state_t ds_state = DST_KEY_STATE_NA;
+ isc_stdtime_t published = 0, withdrawn = 0;
+ isc_result_t ret = ISC_R_SUCCESS;
+
+ /* Is this key have the KSK role? */
+ (void)dst_key_role(key->key, &ksk, NULL);
+ if (!ksk) {
+ continue;
+ }
+
+ /* Do we need to check the DS RRset for this key? */
+ (void)dst_key_getstate(key->key, DST_KEY_DS, &ds_state);
+ (void)dst_key_gettime(key->key, DST_TIME_DSPUBLISH, &published);
+ (void)dst_key_gettime(key->key, DST_TIME_DSDELETE, &withdrawn);
+
+ if (ds_state == DST_KEY_STATE_RUMOURED && published == 0) {
+ checkdspub = true;
+ } else if (ds_state == DST_KEY_STATE_UNRETENTIVE &&
+ withdrawn == 0)
+ {
+ checkdsdel = true;
+ }
+ if (!checkdspub && !checkdsdel) {
+ continue;
+ }
+
+ if (empty) {
+ goto dswithdrawn;
+ }
+
+ /* Find the appropriate DS record. */
+ ret = dns_rdataset_first(ds_rrset);
+ while (ret == ISC_R_SUCCESS) {
+ dns_rdata_ds_t ds;
+ dns_rdata_t dnskey = DNS_RDATA_INIT;
+ dns_rdata_t dsrdata = DNS_RDATA_INIT;
+ dns_rdata_t rdata = DNS_RDATA_INIT;
+ isc_result_t r;
+ unsigned char dsbuf[DNS_DS_BUFFERSIZE];
+ unsigned char keybuf[DST_KEY_MAXSIZE];
+
+ dns_rdataset_current(ds_rrset, &rdata);
+ r = dns_rdata_tostruct(&rdata, &ds, NULL);
+ if (r != ISC_R_SUCCESS) {
+ goto nextds;
+ }
+ /* Check key tag and algorithm. */
+ if (dst_key_id(key->key) != ds.key_tag) {
+ goto nextds;
+ }
+ if (dst_key_alg(key->key) != ds.algorithm) {
+ goto nextds;
+ }
+ /* Derive DS from DNSKEY, see if the rdata is equal. */
+ make_dnskey(key->key, keybuf, sizeof(keybuf), &dnskey);
+ r = dns_ds_buildrdata(&zone->origin, &dnskey,
+ ds.digest_type, dsbuf, &dsrdata);
+ if (r != ISC_R_SUCCESS) {
+ goto nextds;
+ }
+ if (dns_rdata_compare(&rdata, &dsrdata) == 0) {
+ found = true;
+ if (checkdspub) {
+ /* DS Published. */
+ alldone = do_checkds(zone, key->key,
+ now, true);
+ if (alldone) {
+ rekey = true;
+ }
+ }
+ }
+
+ nextds:
+ ret = dns_rdataset_next(ds_rrset);
+ }
+
+ dswithdrawn:
+ /* DS withdrawn. */
+ if (checkdsdel && !found) {
+ alldone = do_checkds(zone, key->key, now, false);
+ if (alldone) {
+ rekey = true;
+ }
+ }
+ }
+ UNLOCK_ZONE(zone);
+ KASP_UNLOCK(kasp);
+
+ /* Rekey after checkds. */
+ if (rekey) {
+ dns_zone_rekey(zone, false);
+ }
+
+failure:
+ if (result != ISC_R_SUCCESS) {
+ dns_zone_log(zone, ISC_LOG_DEBUG(3),
+ "checkds: DS request failed: %s",
+ isc_result_totext(result));
+ }
+
+ if (version != NULL) {
+ dns_db_closeversion(db, &version, false);
+ }
+ if (db != NULL) {
+ dns_db_detach(&db);
+ }
+
+ while (!ISC_LIST_EMPTY(keys)) {
+ key = ISC_LIST_HEAD(keys);
+ ISC_LIST_UNLINK(keys, key, link);
+ dns_dnsseckey_destroy(dns_zone_getmctx(zone), &key);
+ }
+
+ isc_event_free(&event);
+ checkds_destroy(checkds, false);
+ dns_message_detach(&message);
+}
+
+static bool
+checkds_isqueued(dns_zone_t *zone, isc_sockaddr_t *addr, dns_tsigkey_t *key,
+ dns_transport_t *transport) {
+ dns_checkds_t *checkds;
+
+ for (checkds = ISC_LIST_HEAD(zone->checkds_requests); checkds != NULL;
+ checkds = ISC_LIST_NEXT(checkds, link))
+ {
+ if (checkds->request != NULL) {
+ continue;
+ }
+ if (addr != NULL && isc_sockaddr_equal(addr, &checkds->dst) &&
+ checkds->key == key && checkds->transport == transport)
+ {
+ return (true);
+ }
+ }
+ return (false);
+}
+
+static isc_result_t
+checkds_create(isc_mem_t *mctx, unsigned int flags, dns_checkds_t **checkdsp) {
+ dns_checkds_t *checkds;
+
+ REQUIRE(checkdsp != NULL && *checkdsp == NULL);
+
+ checkds = isc_mem_get(mctx, sizeof(*checkds));
+ *checkds = (dns_checkds_t){
+ .flags = flags,
+ };
+
+ isc_mem_attach(mctx, &checkds->mctx);
+ isc_sockaddr_any(&checkds->dst);
+ ISC_LINK_INIT(checkds, link);
+ checkds->magic = CHECKDS_MAGIC;
+ *checkdsp = checkds;
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+checkds_createmessage(dns_zone_t *zone, dns_message_t **messagep) {
+ dns_message_t *message = NULL;
+
+ dns_name_t *tempname = NULL;
+ dns_rdataset_t *temprdataset = NULL;
+
+ isc_result_t result;
+
+ REQUIRE(DNS_ZONE_VALID(zone));
+ REQUIRE(messagep != NULL && *messagep == NULL);
+
+ dns_message_create(zone->mctx, DNS_MESSAGE_INTENTRENDER, &message);
+
+ message->opcode = dns_opcode_query;
+ message->rdclass = zone->rdclass;
+ message->flags |= DNS_MESSAGEFLAG_RD;
+
+ result = dns_message_gettempname(message, &tempname);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+
+ result = dns_message_gettemprdataset(message, &temprdataset);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+
+ /*
+ * Make question.
+ */
+ dns_name_init(tempname, NULL);
+ dns_name_clone(&zone->origin, tempname);
+ dns_rdataset_makequestion(temprdataset, zone->rdclass,
+ dns_rdatatype_ds);
+ ISC_LIST_APPEND(tempname->list, temprdataset, link);
+ dns_message_addname(message, tempname, DNS_SECTION_QUESTION);
+ tempname = NULL;
+ temprdataset = NULL;
+
+ *messagep = message;
+ return (ISC_R_SUCCESS);
+
+cleanup:
+ if (tempname != NULL) {
+ dns_message_puttempname(message, &tempname);
+ }
+ if (temprdataset != NULL) {
+ dns_message_puttemprdataset(message, &temprdataset);
+ }
+ dns_message_detach(&message);
+ return (result);
+}
+
+static void
+checkds_send_toaddr(isc_task_t *task, isc_event_t *event) {
+ dns_checkds_t *checkds;
+ isc_result_t result;
+ dns_message_t *message = NULL;
+ isc_netaddr_t dstip;
+ dns_tsigkey_t *key = NULL;
+ char addrbuf[ISC_SOCKADDR_FORMATSIZE];
+ isc_sockaddr_t src;
+ unsigned int options, timeout;
+ bool have_checkdssource = false;
+
+ checkds = event->ev_arg;
+ REQUIRE(DNS_CHECKDS_VALID(checkds));
+
+ UNUSED(task);
+
+ LOCK_ZONE(checkds->zone);
+
+ checkds->event = NULL;
+
+ if (DNS_ZONE_FLAG(checkds->zone, DNS_ZONEFLG_LOADED) == 0) {
+ result = ISC_R_CANCELED;
+ goto cleanup;
+ }
+
+ if ((event->ev_attributes & ISC_EVENTATTR_CANCELED) != 0 ||
+ DNS_ZONE_FLAG(checkds->zone, DNS_ZONEFLG_EXITING) ||
+ checkds->zone->view->requestmgr == NULL ||
+ checkds->zone->db == NULL)
+ {
+ result = ISC_R_CANCELED;
+ goto cleanup;
+ }
+
+ /*
+ * The raw IPv4 address should also exist. Don't send to the
+ * mapped form.
+ */
+ if (isc_sockaddr_pf(&checkds->dst) == PF_INET6 &&
+ IN6_IS_ADDR_V4MAPPED(&checkds->dst.type.sin6.sin6_addr))
+ {
+ isc_sockaddr_format(&checkds->dst, addrbuf, sizeof(addrbuf));
+ dns_zone_log(checkds->zone, ISC_LOG_DEBUG(3),
+ "checkds: ignoring IPv6 mapped IPV4 address: %s",
+ addrbuf);
+ result = ISC_R_CANCELED;
+ goto cleanup;
+ }
+
+ result = checkds_createmessage(checkds->zone, &message);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+
+ isc_sockaddr_format(&checkds->dst, addrbuf, sizeof(addrbuf));
+ if (checkds->key != NULL) {
+ /* Transfer ownership of key */
+ key = checkds->key;
+ checkds->key = NULL;
+ } else {
+ isc_netaddr_fromsockaddr(&dstip, &checkds->dst);
+ result = dns_view_getpeertsig(checkds->zone->view, &dstip,
+ &key);
+ if (result != ISC_R_SUCCESS && result != ISC_R_NOTFOUND) {
+ dns_zone_log(checkds->zone, ISC_LOG_ERROR,
+ "checkds: DS query to %s not sent. "
+ "Peer TSIG key lookup failure.",
+ addrbuf);
+ goto cleanup_message;
+ }
+ }
+
+ if (key != NULL) {
+ char namebuf[DNS_NAME_FORMATSIZE];
+
+ dns_name_format(&key->name, namebuf, sizeof(namebuf));
+ dns_zone_log(checkds->zone, ISC_LOG_DEBUG(3),
+ "checkds: sending DS query to %s : TSIG (%s)",
+ addrbuf, namebuf);
+ } else {
+ dns_zone_log(checkds->zone, ISC_LOG_DEBUG(3),
+ "checkds: sending DS query to %s", addrbuf);
+ }
+ options = 0;
+ if (checkds->zone->view->peers != NULL) {
+ dns_peer_t *peer = NULL;
+ bool usetcp = false;
+ result = dns_peerlist_peerbyaddr(checkds->zone->view->peers,
+ &dstip, &peer);
+ if (result == ISC_R_SUCCESS) {
+ result = dns_peer_getquerysource(peer, &src);
+ if (result == ISC_R_SUCCESS) {
+ have_checkdssource = true;
+ }
+ result = dns_peer_getforcetcp(peer, &usetcp);
+ if (result == ISC_R_SUCCESS && usetcp) {
+ options |= DNS_FETCHOPT_TCP;
+ }
+ }
+ }
+ switch (isc_sockaddr_pf(&checkds->dst)) {
+ case PF_INET:
+ if (!have_checkdssource) {
+ src = checkds->zone->parentalsrc4;
+ }
+ break;
+ case PF_INET6:
+ if (!have_checkdssource) {
+ src = checkds->zone->parentalsrc6;
+ }
+ break;
+ default:
+ result = ISC_R_NOTIMPLEMENTED;
+ goto cleanup_key;
+ }
+
+ dns_zone_log(checkds->zone, ISC_LOG_DEBUG(3),
+ "checkds: create request for DS query to %s", addrbuf);
+
+ timeout = 15;
+ options |= DNS_REQUESTOPT_TCP;
+ result = dns_request_create(
+ checkds->zone->view->requestmgr, message, &src, &checkds->dst,
+ options, key, timeout * 3, timeout, 2, checkds->zone->task,
+ checkds_done, checkds, &checkds->request);
+ if (result != ISC_R_SUCCESS) {
+ dns_zone_log(checkds->zone, ISC_LOG_DEBUG(3),
+ "checkds: dns_request_create() to %s failed: %s",
+ addrbuf, isc_result_totext(result));
+ }
+
+cleanup_key:
+ if (key != NULL) {
+ dns_tsigkey_detach(&key);
+ }
+cleanup_message:
+ dns_message_detach(&message);
+cleanup:
+ UNLOCK_ZONE(checkds->zone);
+ isc_event_free(&event);
+ if (result != ISC_R_SUCCESS) {
+ checkds_destroy(checkds, false);
+ }
+}
+
+static isc_result_t
+checkds_send_queue(dns_checkds_t *checkds) {
+ isc_event_t *e;
+ isc_result_t result;
+
+ INSIST(checkds->event == NULL);
+ e = isc_event_allocate(checkds->mctx, NULL, DNS_EVENT_CHECKDSSENDTOADDR,
+ checkds_send_toaddr, checkds,
+ sizeof(isc_event_t));
+ e->ev_arg = checkds;
+ e->ev_sender = NULL;
+ result = isc_ratelimiter_enqueue(checkds->zone->zmgr->checkdsrl,
+ checkds->zone->task, &e);
+ if (result != ISC_R_SUCCESS) {
+ isc_event_free(&e);
+ checkds->event = NULL;
+ }
+ return (result);
+}
+
+static void
+checkds_send(dns_zone_t *zone) {
+ dns_view_t *view = dns_zone_getview(zone);
+ isc_result_t result;
+ unsigned int flags = 0;
+
+ /*
+ * Zone lock held by caller.
+ */
+ REQUIRE(LOCKED_ZONE(zone));
+
+ dns_zone_log(zone, ISC_LOG_DEBUG(3),
+ "checkds: start sending DS queries to %u parentals",
+ zone->parentalscnt);
+
+ if (DNS_ZONE_FLAG(zone, DNS_ZONEFLG_EXITING)) {
+ dns_zone_log(zone, ISC_LOG_DEBUG(3),
+ "checkds: abort, named exiting");
+ return;
+ }
+
+ for (unsigned int i = 0; i < zone->parentalscnt; i++) {
+ dns_tsigkey_t *key = NULL;
+ dns_transport_t *transport = NULL;
+ isc_sockaddr_t dst;
+ dns_checkds_t *checkds = NULL;
+
+ if ((zone->parentalkeynames != NULL) &&
+ (zone->parentalkeynames[i] != NULL))
+ {
+ dns_name_t *keyname = zone->parentalkeynames[i];
+ (void)dns_view_gettsig(view, keyname, &key);
+ }
+
+ if ((zone->parentaltlsnames != NULL) &&
+ (zone->parentaltlsnames[i] != NULL))
+ {
+ dns_name_t *tlsname = zone->parentaltlsnames[i];
+ (void)dns_view_gettransport(view, DNS_TRANSPORT_TLS,
+ tlsname, &transport);
+ dns_zone_logc(
+ zone, DNS_LOGCATEGORY_XFER_IN, ISC_LOG_INFO,
+ "got TLS configuration for zone transfer");
+ }
+
+ dst = zone->parentals[i];
+
+ /* TODO: glue the transport to the checkds request */
+
+ if (checkds_isqueued(zone, &dst, key, transport)) {
+ dns_zone_log(zone, ISC_LOG_DEBUG(3),
+ "checkds: DS query to parent "
+ "%d is queued",
+ i);
+ if (key != NULL) {
+ dns_tsigkey_detach(&key);
+ }
+ if (transport != NULL) {
+ dns_transport_detach(&transport);
+ }
+ continue;
+ }
+
+ dns_zone_log(zone, ISC_LOG_DEBUG(3),
+ "checkds: create DS query for "
+ "parent %d",
+ i);
+
+ result = checkds_create(zone->mctx, flags, &checkds);
+ if (result != ISC_R_SUCCESS) {
+ dns_zone_log(zone, ISC_LOG_DEBUG(3),
+ "checkds: create DS query for "
+ "parent %d failed",
+ i);
+ continue;
+ }
+ zone_iattach(zone, &checkds->zone);
+ checkds->dst = dst;
+
+ INSIST(checkds->key == NULL);
+ if (key != NULL) {
+ checkds->key = key;
+ key = NULL;
+ }
+
+ INSIST(checkds->transport == NULL);
+ if (transport != NULL) {
+ checkds->transport = transport;
+ transport = NULL;
+ }
+
+ ISC_LIST_APPEND(zone->checkds_requests, checkds, link);
+ result = checkds_send_queue(checkds);
+ if (result != ISC_R_SUCCESS) {
+ dns_zone_log(zone, ISC_LOG_DEBUG(3),
+ "checkds: send DS query to "
+ "parent %d failed",
+ i);
+ checkds_destroy(checkds, true);
+ }
+ }
+}
+
+static void
+zone_checkds(dns_zone_t *zone) {
+ bool cdscheck = false;
+
+ for (dns_dnsseckey_t *key = ISC_LIST_HEAD(zone->checkds_ok);
+ key != NULL; key = ISC_LIST_NEXT(key, link))
+ {
+ dst_key_state_t ds_state = DST_KEY_STATE_NA;
+ bool ksk = false;
+ isc_stdtime_t published = 0, withdrawn = 0;
+
+ /* Is this key have the KSK role? */
+ (void)dst_key_role(key->key, &ksk, NULL);
+ if (!ksk) {
+ continue;
+ }
+
+ /* Do we need to check the DS RRset? */
+ (void)dst_key_getstate(key->key, DST_KEY_DS, &ds_state);
+ (void)dst_key_gettime(key->key, DST_TIME_DSPUBLISH, &published);
+ (void)dst_key_gettime(key->key, DST_TIME_DSDELETE, &withdrawn);
+
+ if (ds_state == DST_KEY_STATE_RUMOURED && published == 0) {
+ dst_key_setnum(key->key, DST_NUM_DSPUBCOUNT, 0);
+ cdscheck = true;
+ } else if (ds_state == DST_KEY_STATE_UNRETENTIVE &&
+ withdrawn == 0)
+ {
+ dst_key_setnum(key->key, DST_NUM_DSDELCOUNT, 0);
+ cdscheck = true;
+ }
+ }
+
+ if (cdscheck) {
+ /* Request the DS RRset. */
+ LOCK_ZONE(zone);
+ checkds_send(zone);
+ UNLOCK_ZONE(zone);
+ }
+}
+
+static void
+zone_rekey(dns_zone_t *zone) {
+ isc_result_t result;
+ dns_db_t *db = NULL;
+ dns_dbnode_t *node = NULL;
+ dns_dbversion_t *ver = NULL;
+ dns_rdataset_t cdsset, soaset, soasigs, keyset, keysigs, cdnskeyset;
+ dns_dnsseckeylist_t dnskeys, keys, rmkeys;
+ dns_dnsseckey_t *key = NULL;
+ dns_diff_t diff, _sig_diff;
+ dns_kasp_t *kasp;
+ dns__zonediff_t zonediff;
+ bool commit = false, newactive = false;
+ bool newalg = false;
+ bool fullsign;
+ dns_ttl_t ttl = 3600;
+ const char *dir = NULL;
+ isc_mem_t *mctx = NULL;
+ isc_stdtime_t now, nexttime = 0;
+ isc_time_t timenow;
+ isc_interval_t ival;
+ char timebuf[80];
+
+ REQUIRE(DNS_ZONE_VALID(zone));
+
+ ISC_LIST_INIT(dnskeys);
+ ISC_LIST_INIT(keys);
+ ISC_LIST_INIT(rmkeys);
+ dns_rdataset_init(&soaset);
+ dns_rdataset_init(&soasigs);
+ dns_rdataset_init(&keyset);
+ dns_rdataset_init(&keysigs);
+ dns_rdataset_init(&cdsset);
+ dns_rdataset_init(&cdnskeyset);
+ dir = dns_zone_getkeydirectory(zone);
+ mctx = zone->mctx;
+ dns_diff_init(mctx, &diff);
+ dns_diff_init(mctx, &_sig_diff);
+ zonediff_init(&zonediff, &_sig_diff);
+
+ CHECK(dns_zone_getdb(zone, &db));
+ CHECK(dns_db_newversion(db, &ver));
+ CHECK(dns_db_getoriginnode(db, &node));
+
+ TIME_NOW(&timenow);
+ now = isc_time_seconds(&timenow);
+
+ kasp = dns_zone_getkasp(zone);
+
+ dnssec_log(zone, ISC_LOG_INFO, "reconfiguring zone keys");
+
+ /* Get the SOA record's TTL */
+ CHECK(dns_db_findrdataset(db, node, ver, dns_rdatatype_soa,
+ dns_rdatatype_none, 0, &soaset, &soasigs));
+ ttl = soaset.ttl;
+ dns_rdataset_disassociate(&soaset);
+
+ /* Get the DNSKEY rdataset */
+ result = dns_db_findrdataset(db, node, ver, dns_rdatatype_dnskey,
+ dns_rdatatype_none, 0, &keyset, &keysigs);
+ if (result == ISC_R_SUCCESS) {
+ ttl = keyset.ttl;
+
+ dns_zone_lock_keyfiles(zone);
+
+ result = dns_dnssec_keylistfromrdataset(
+ &zone->origin, dir, mctx, &keyset, &keysigs, &soasigs,
+ false, false, &dnskeys);
+
+ dns_zone_unlock_keyfiles(zone);
+
+ if (result != ISC_R_SUCCESS) {
+ goto failure;
+ }
+ } else if (result != ISC_R_NOTFOUND) {
+ goto failure;
+ }
+
+ /* Get the CDS rdataset */
+ result = dns_db_findrdataset(db, node, ver, dns_rdatatype_cds,
+ dns_rdatatype_none, 0, &cdsset, NULL);
+ if (result != ISC_R_SUCCESS && dns_rdataset_isassociated(&cdsset)) {
+ dns_rdataset_disassociate(&cdsset);
+ }
+
+ /* Get the CDNSKEY rdataset */
+ result = dns_db_findrdataset(db, node, ver, dns_rdatatype_cdnskey,
+ dns_rdatatype_none, 0, &cdnskeyset, NULL);
+ if (result != ISC_R_SUCCESS && dns_rdataset_isassociated(&cdnskeyset)) {
+ dns_rdataset_disassociate(&cdnskeyset);
+ }
+
+ /*
+ * True when called from "rndc sign". Indicates the zone should be
+ * fully signed now.
+ */
+ fullsign = DNS_ZONEKEY_OPTION(zone, DNS_ZONEKEY_FULLSIGN);
+
+ KASP_LOCK(kasp);
+
+ dns_zone_lock_keyfiles(zone);
+ result = dns_dnssec_findmatchingkeys(&zone->origin, dir, now, mctx,
+ &keys);
+ dns_zone_unlock_keyfiles(zone);
+
+ if (result != ISC_R_SUCCESS) {
+ dnssec_log(zone, ISC_LOG_DEBUG(1),
+ "zone_rekey:dns_dnssec_findmatchingkeys failed: %s",
+ isc_result_totext(result));
+ }
+
+ if (kasp != NULL) {
+ /*
+ * Check DS at parental agents. Clear ongoing checks.
+ */
+ LOCK_ZONE(zone);
+ checkds_cancel(zone);
+ clear_keylist(&zone->checkds_ok, zone->mctx);
+ ISC_LIST_INIT(zone->checkds_ok);
+ UNLOCK_ZONE(zone);
+
+ result = dns_zone_getdnsseckeys(zone, db, ver, now,
+ &zone->checkds_ok);
+
+ if (result == ISC_R_SUCCESS) {
+ zone_checkds(zone);
+ } else {
+ dnssec_log(zone,
+ (result == ISC_R_NOTFOUND) ? ISC_LOG_DEBUG(1)
+ : ISC_LOG_ERROR,
+ "zone_rekey:dns_zone_getdnsseckeys failed: "
+ "%s",
+ isc_result_totext(result));
+ }
+
+ if (result == ISC_R_SUCCESS || result == ISC_R_NOTFOUND) {
+ dns_zone_lock_keyfiles(zone);
+ result = dns_keymgr_run(&zone->origin, zone->rdclass,
+ dir, mctx, &keys, &dnskeys,
+ kasp, now, &nexttime);
+ dns_zone_unlock_keyfiles(zone);
+
+ if (result != ISC_R_SUCCESS) {
+ dnssec_log(zone, ISC_LOG_ERROR,
+ "zone_rekey:dns_dnssec_keymgr "
+ "failed: %s",
+ isc_result_totext(result));
+ KASP_UNLOCK(kasp);
+ goto failure;
+ }
+ }
+ }
+
+ KASP_UNLOCK(kasp);
+
+ if (result == ISC_R_SUCCESS) {
+ bool cdsdel = false;
+ bool cdnskeydel = false;
+ bool sane_diff, sane_dnskey;
+ isc_stdtime_t when;
+
+ /*
+ * Publish CDS/CDNSKEY DELETE records if the zone is
+ * transitioning from secure to insecure.
+ */
+ if (kasp != NULL) {
+ if (strcmp(dns_kasp_getname(kasp), "insecure") == 0) {
+ cdsdel = true;
+ cdnskeydel = true;
+ }
+ } else {
+ /* Check if there is a CDS DELETE record. */
+ if (dns_rdataset_isassociated(&cdsset)) {
+ for (result = dns_rdataset_first(&cdsset);
+ result == ISC_R_SUCCESS;
+ result = dns_rdataset_next(&cdsset))
+ {
+ dns_rdata_t crdata = DNS_RDATA_INIT;
+ dns_rdataset_current(&cdsset, &crdata);
+ /*
+ * CDS deletion record has this form
+ * "0 0 0 00" which is 5 zero octets.
+ */
+ if (crdata.length == 5U &&
+ memcmp(crdata.data,
+ (unsigned char[5]){ 0, 0, 0,
+ 0, 0 },
+ 5) == 0)
+ {
+ cdsdel = true;
+ break;
+ }
+ }
+ }
+
+ /* Check if there is a CDNSKEY DELETE record. */
+ if (dns_rdataset_isassociated(&cdnskeyset)) {
+ for (result = dns_rdataset_first(&cdnskeyset);
+ result == ISC_R_SUCCESS;
+ result = dns_rdataset_next(&cdnskeyset))
+ {
+ dns_rdata_t crdata = DNS_RDATA_INIT;
+ dns_rdataset_current(&cdnskeyset,
+ &crdata);
+ /*
+ * CDNSKEY deletion record has this form
+ * "0 3 0 AA==" which is 2 zero octets,
+ * a 3, and 2 zero octets.
+ */
+ if (crdata.length == 5U &&
+ memcmp(crdata.data,
+ (unsigned char[5]){ 0, 0, 3,
+ 0, 0 },
+ 5) == 0)
+ {
+ cdnskeydel = true;
+ break;
+ }
+ }
+ }
+ }
+
+ /*
+ * Only update DNSKEY TTL if we have a policy.
+ */
+ if (kasp != NULL) {
+ ttl = dns_kasp_dnskeyttl(kasp);
+ }
+
+ result = dns_dnssec_updatekeys(&dnskeys, &keys, &rmkeys,
+ &zone->origin, ttl, &diff, mctx,
+ dnssec_report);
+ /*
+ * Keys couldn't be updated for some reason;
+ * try again later.
+ */
+ if (result != ISC_R_SUCCESS) {
+ dnssec_log(zone, ISC_LOG_ERROR,
+ "zone_rekey:couldn't update zone keys: %s",
+ isc_result_totext(result));
+ goto failure;
+ }
+
+ /*
+ * Update CDS / CDNSKEY records.
+ */
+ result = dns_dnssec_syncupdate(&dnskeys, &rmkeys, &cdsset,
+ &cdnskeyset, now, ttl, &diff,
+ mctx);
+ if (result != ISC_R_SUCCESS) {
+ dnssec_log(zone, ISC_LOG_ERROR,
+ "zone_rekey:couldn't update CDS/CDNSKEY: %s",
+ isc_result_totext(result));
+ goto failure;
+ }
+
+ if (cdsdel || cdnskeydel) {
+ /*
+ * Only publish CDS/CDNSKEY DELETE records if there is
+ * a KSK that can be used to verify the RRset. This
+ * means there must be a key with the KSK role that is
+ * published and is used for signing.
+ */
+ bool allow = false;
+ for (key = ISC_LIST_HEAD(dnskeys); key != NULL;
+ key = ISC_LIST_NEXT(key, link))
+ {
+ dst_key_t *dstk = key->key;
+
+ if (dst_key_is_published(dstk, now, &when) &&
+ dst_key_is_signing(dstk, DST_BOOL_KSK, now,
+ &when))
+ {
+ allow = true;
+ break;
+ }
+ }
+ if (cdsdel) {
+ cdsdel = allow;
+ }
+ if (cdnskeydel) {
+ cdnskeydel = allow;
+ }
+ }
+ result = dns_dnssec_syncdelete(
+ &cdsset, &cdnskeyset, &zone->origin, zone->rdclass, ttl,
+ &diff, mctx, cdsdel, cdnskeydel);
+ if (result != ISC_R_SUCCESS) {
+ dnssec_log(zone, ISC_LOG_ERROR,
+ "zone_rekey:couldn't update CDS/CDNSKEY "
+ "DELETE records: %s",
+ isc_result_totext(result));
+ goto failure;
+ }
+
+ /*
+ * See if any pre-existing keys have newly become active;
+ * also, see if any new key is for a new algorithm, as in that
+ * event, we need to sign the zone fully. (If there's a new
+ * key, but it's for an already-existing algorithm, then
+ * the zone signing can be handled incrementally.)
+ */
+ for (key = ISC_LIST_HEAD(dnskeys); key != NULL;
+ key = ISC_LIST_NEXT(key, link))
+ {
+ if (!key->first_sign) {
+ continue;
+ }
+
+ newactive = true;
+
+ if (!dns_rdataset_isassociated(&keysigs)) {
+ newalg = true;
+ break;
+ }
+
+ if (signed_with_alg(&keysigs, dst_key_alg(key->key))) {
+ /*
+ * This isn't a new algorithm; clear
+ * first_sign so we won't sign the
+ * whole zone with this key later.
+ */
+ key->first_sign = false;
+ } else {
+ newalg = true;
+ break;
+ }
+ }
+
+ /*
+ * A sane diff is one that is not empty, and that does not
+ * introduce a zone with NSEC only DNSKEYs along with NSEC3
+ * chains.
+ */
+ sane_dnskey = dns_zone_check_dnskey_nsec3(zone, db, ver, &diff,
+ NULL, 0);
+ sane_diff = !ISC_LIST_EMPTY(diff.tuples) && sane_dnskey;
+ if (!sane_dnskey) {
+ dnssec_log(zone, ISC_LOG_ERROR,
+ "NSEC only DNSKEYs and NSEC3 chains not "
+ "allowed");
+ }
+
+ if (newactive || fullsign || sane_diff) {
+ CHECK(dns_diff_apply(&diff, db, ver));
+ CHECK(clean_nsec3param(zone, db, ver, &diff));
+ CHECK(add_signing_records(db, zone->privatetype, ver,
+ &diff, (newalg || fullsign)));
+ CHECK(update_soa_serial(zone, db, ver, &diff, mctx,
+ zone->updatemethod));
+ CHECK(add_chains(zone, db, ver, &diff));
+ CHECK(sign_apex(zone, db, ver, now, &diff, &zonediff));
+ CHECK(zone_journal(zone, zonediff.diff, NULL,
+ "zone_rekey"));
+ commit = true;
+ }
+ }
+
+ dns_db_closeversion(db, &ver, true);
+
+ LOCK_ZONE(zone);
+
+ if (commit) {
+ dns_difftuple_t *tuple;
+ dns_stats_t *dnssecsignstats =
+ dns_zone_getdnssecsignstats(zone);
+
+ DNS_ZONE_SETFLAG(zone, DNS_ZONEFLG_NEEDNOTIFY);
+
+ zone_needdump(zone, DNS_DUMP_DELAY);
+
+ zone_settimer(zone, &timenow);
+
+ /* Remove any signatures from removed keys. */
+ if (!ISC_LIST_EMPTY(rmkeys)) {
+ for (key = ISC_LIST_HEAD(rmkeys); key != NULL;
+ key = ISC_LIST_NEXT(key, link))
+ {
+ result = zone_signwithkey(
+ zone, dst_key_alg(key->key),
+ dst_key_id(key->key), true);
+ if (result != ISC_R_SUCCESS) {
+ dnssec_log(zone, ISC_LOG_ERROR,
+ "zone_signwithkey failed: "
+ "%s",
+ isc_result_totext(result));
+ }
+
+ /* Clear DNSSEC sign statistics. */
+ if (dnssecsignstats != NULL) {
+ dns_dnssecsignstats_clear(
+ dnssecsignstats,
+ dst_key_id(key->key),
+ dst_key_alg(key->key));
+ /*
+ * Also clear the dnssec-sign
+ * statistics of the revoked key id.
+ */
+ dns_dnssecsignstats_clear(
+ dnssecsignstats,
+ dst_key_rid(key->key),
+ dst_key_alg(key->key));
+ }
+ }
+ }
+
+ if (fullsign) {
+ /*
+ * "rndc sign" was called, so we now sign the zone
+ * with all active keys, whether they're new or not.
+ */
+ for (key = ISC_LIST_HEAD(dnskeys); key != NULL;
+ key = ISC_LIST_NEXT(key, link))
+ {
+ if (!key->force_sign && !key->hint_sign) {
+ continue;
+ }
+
+ result = zone_signwithkey(
+ zone, dst_key_alg(key->key),
+ dst_key_id(key->key), false);
+ if (result != ISC_R_SUCCESS) {
+ dnssec_log(zone, ISC_LOG_ERROR,
+ "zone_signwithkey failed: "
+ "%s",
+ isc_result_totext(result));
+ }
+ }
+ } else if (newalg) {
+ /*
+ * We haven't been told to sign fully, but a new
+ * algorithm was added to the DNSKEY. We sign
+ * the full zone, but only with newly active
+ * keys.
+ */
+ for (key = ISC_LIST_HEAD(dnskeys); key != NULL;
+ key = ISC_LIST_NEXT(key, link))
+ {
+ if (!key->first_sign) {
+ continue;
+ }
+
+ result = zone_signwithkey(
+ zone, dst_key_alg(key->key),
+ dst_key_id(key->key), false);
+ if (result != ISC_R_SUCCESS) {
+ dnssec_log(zone, ISC_LOG_ERROR,
+ "zone_signwithkey failed: "
+ "%s",
+ isc_result_totext(result));
+ }
+ }
+ }
+
+ /*
+ * Clear fullsign flag, if it was set, so we don't do
+ * another full signing next time.
+ */
+ DNS_ZONEKEY_CLROPTION(zone, DNS_ZONEKEY_FULLSIGN);
+
+ /*
+ * Cause the zone to add/delete NSEC3 chains for the
+ * deferred NSEC3PARAM changes.
+ */
+ for (tuple = ISC_LIST_HEAD(zonediff.diff->tuples);
+ tuple != NULL; tuple = ISC_LIST_NEXT(tuple, link))
+ {
+ unsigned char buf[DNS_NSEC3PARAM_BUFFERSIZE];
+ dns_rdata_t rdata = DNS_RDATA_INIT;
+ dns_rdata_nsec3param_t nsec3param;
+
+ if (tuple->rdata.type != zone->privatetype ||
+ tuple->op != DNS_DIFFOP_ADD)
+ {
+ continue;
+ }
+
+ if (!dns_nsec3param_fromprivate(&tuple->rdata, &rdata,
+ buf, sizeof(buf)))
+ {
+ continue;
+ }
+
+ result = dns_rdata_tostruct(&rdata, &nsec3param, NULL);
+ RUNTIME_CHECK(result == ISC_R_SUCCESS);
+ if (nsec3param.flags == 0) {
+ continue;
+ }
+
+ result = zone_addnsec3chain(zone, &nsec3param);
+ if (result != ISC_R_SUCCESS) {
+ dnssec_log(zone, ISC_LOG_ERROR,
+ "zone_addnsec3chain failed: %s",
+ isc_result_totext(result));
+ }
+ }
+
+ /*
+ * Activate any NSEC3 chain updates that may have
+ * been scheduled before this rekey.
+ */
+ if (fullsign || newalg) {
+ resume_addnsec3chain(zone);
+ }
+
+ /*
+ * Schedule the next resigning event
+ */
+ set_resigntime(zone);
+ }
+
+ isc_time_settoepoch(&zone->refreshkeytime);
+
+ /*
+ * If keymgr provided a next time, use the calculated next rekey time.
+ */
+ if (kasp != NULL) {
+ isc_time_t timenext;
+ uint32_t nexttime_seconds;
+
+ /*
+ * Set the key refresh timer to the next scheduled key event
+ * or to 'dnssec-loadkeys-interval' seconds in the future
+ * if no next key event is scheduled (nexttime == 0).
+ */
+ if (nexttime > 0) {
+ nexttime_seconds = nexttime - now;
+ } else {
+ nexttime_seconds = zone->refreshkeyinterval;
+ }
+
+ DNS_ZONE_TIME_ADD(&timenow, nexttime_seconds, &timenext);
+ zone->refreshkeytime = timenext;
+ zone_settimer(zone, &timenow);
+ isc_time_formattimestamp(&zone->refreshkeytime, timebuf, 80);
+
+ dnssec_log(zone, ISC_LOG_DEBUG(3),
+ "next key event in %u seconds", nexttime_seconds);
+ dnssec_log(zone, ISC_LOG_INFO, "next key event: %s", timebuf);
+ }
+ /*
+ * If we're doing key maintenance, set the key refresh timer to
+ * the next scheduled key event or to 'dnssec-loadkeys-interval'
+ * seconds in the future, whichever is sooner.
+ */
+ else if (DNS_ZONEKEY_OPTION(zone, DNS_ZONEKEY_MAINTAIN))
+ {
+ isc_time_t timethen;
+ isc_stdtime_t then;
+
+ DNS_ZONE_TIME_ADD(&timenow, zone->refreshkeyinterval,
+ &timethen);
+ zone->refreshkeytime = timethen;
+
+ for (key = ISC_LIST_HEAD(dnskeys); key != NULL;
+ key = ISC_LIST_NEXT(key, link))
+ {
+ then = now;
+ result = next_keyevent(key->key, &then);
+ if (result != ISC_R_SUCCESS) {
+ continue;
+ }
+
+ DNS_ZONE_TIME_ADD(&timenow, then - now, &timethen);
+ if (isc_time_compare(&timethen, &zone->refreshkeytime) <
+ 0)
+ {
+ zone->refreshkeytime = timethen;
+ }
+ }
+
+ zone_settimer(zone, &timenow);
+
+ isc_time_formattimestamp(&zone->refreshkeytime, timebuf, 80);
+ dnssec_log(zone, ISC_LOG_INFO, "next key event: %s", timebuf);
+ }
+ UNLOCK_ZONE(zone);
+
+ if (isc_log_wouldlog(dns_lctx, ISC_LOG_DEBUG(3))) {
+ for (key = ISC_LIST_HEAD(dnskeys); key != NULL;
+ key = ISC_LIST_NEXT(key, link))
+ {
+ /* This debug log is used in the kasp system test */
+ char algbuf[DNS_SECALG_FORMATSIZE];
+ dns_secalg_format(dst_key_alg(key->key), algbuf,
+ sizeof(algbuf));
+ dnssec_log(zone, ISC_LOG_DEBUG(3),
+ "zone_rekey done: key %d/%s",
+ dst_key_id(key->key), algbuf);
+ }
+ }
+
+ result = ISC_R_SUCCESS;
+
+failure:
+ LOCK_ZONE(zone);
+ if (result != ISC_R_SUCCESS) {
+ /*
+ * Something went wrong; try again in ten minutes or
+ * after a key refresh interval, whichever is shorter.
+ */
+ dnssec_log(zone, ISC_LOG_DEBUG(3),
+ "zone_rekey failure: %s (retry in %u seconds)",
+ isc_result_totext(result),
+ ISC_MIN(zone->refreshkeyinterval, 600));
+ isc_interval_set(&ival, ISC_MIN(zone->refreshkeyinterval, 600),
+ 0);
+ isc_time_nowplusinterval(&zone->refreshkeytime, &ival);
+ }
+ UNLOCK_ZONE(zone);
+
+ dns_diff_clear(&diff);
+ dns_diff_clear(&_sig_diff);
+
+ clear_keylist(&dnskeys, mctx);
+ clear_keylist(&keys, mctx);
+ clear_keylist(&rmkeys, mctx);
+
+ if (ver != NULL) {
+ dns_db_closeversion(db, &ver, false);
+ }
+ if (dns_rdataset_isassociated(&cdsset)) {
+ dns_rdataset_disassociate(&cdsset);
+ }
+ if (dns_rdataset_isassociated(&keyset)) {
+ dns_rdataset_disassociate(&keyset);
+ }
+ if (dns_rdataset_isassociated(&keysigs)) {
+ dns_rdataset_disassociate(&keysigs);
+ }
+ if (dns_rdataset_isassociated(&soasigs)) {
+ dns_rdataset_disassociate(&soasigs);
+ }
+ if (dns_rdataset_isassociated(&cdnskeyset)) {
+ dns_rdataset_disassociate(&cdnskeyset);
+ }
+ if (node != NULL) {
+ dns_db_detachnode(db, &node);
+ }
+ if (db != NULL) {
+ dns_db_detach(&db);
+ }
+
+ INSIST(ver == NULL);
+}
+
+void
+dns_zone_rekey(dns_zone_t *zone, bool fullsign) {
+ isc_time_t now;
+
+ if (zone->type == dns_zone_primary && zone->task != NULL) {
+ LOCK_ZONE(zone);
+
+ if (fullsign) {
+ DNS_ZONEKEY_SETOPTION(zone, DNS_ZONEKEY_FULLSIGN);
+ }
+
+ TIME_NOW(&now);
+ zone->refreshkeytime = now;
+ zone_settimer(zone, &now);
+
+ UNLOCK_ZONE(zone);
+ }
+}
+
+isc_result_t
+dns_zone_nscheck(dns_zone_t *zone, dns_db_t *db, dns_dbversion_t *version,
+ unsigned int *errors) {
+ isc_result_t result;
+ dns_dbnode_t *node = NULL;
+
+ REQUIRE(DNS_ZONE_VALID(zone));
+ REQUIRE(errors != NULL);
+
+ result = dns_db_getoriginnode(db, &node);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+ result = zone_count_ns_rr(zone, db, node, version, NULL, errors, false);
+ dns_db_detachnode(db, &node);
+ return (result);
+}
+
+isc_result_t
+dns_zone_cdscheck(dns_zone_t *zone, dns_db_t *db, dns_dbversion_t *version) {
+ isc_result_t result;
+ dns_dbnode_t *node = NULL;
+ dns_rdataset_t dnskey, cds, cdnskey;
+ unsigned char algorithms[256];
+ unsigned int i;
+ bool empty = false;
+
+ enum { notexpected = 0, expected = 1, found = 2 };
+
+ REQUIRE(DNS_ZONE_VALID(zone));
+
+ result = dns_db_getoriginnode(db, &node);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+
+ dns_rdataset_init(&cds);
+ dns_rdataset_init(&dnskey);
+ dns_rdataset_init(&cdnskey);
+
+ result = dns_db_findrdataset(db, node, version, dns_rdatatype_cds,
+ dns_rdatatype_none, 0, &cds, NULL);
+ if (result != ISC_R_SUCCESS && result != ISC_R_NOTFOUND) {
+ goto failure;
+ }
+
+ result = dns_db_findrdataset(db, node, version, dns_rdatatype_cdnskey,
+ dns_rdatatype_none, 0, &cdnskey, NULL);
+ if (result != ISC_R_SUCCESS && result != ISC_R_NOTFOUND) {
+ goto failure;
+ }
+
+ if (!dns_rdataset_isassociated(&cds) &&
+ !dns_rdataset_isassociated(&cdnskey))
+ {
+ result = ISC_R_SUCCESS;
+ goto failure;
+ }
+
+ result = dns_db_findrdataset(db, node, version, dns_rdatatype_dnskey,
+ dns_rdatatype_none, 0, &dnskey, NULL);
+ if (result == ISC_R_NOTFOUND) {
+ empty = true;
+ } else if (result != ISC_R_SUCCESS) {
+ goto failure;
+ }
+
+ /*
+ * For each DNSSEC algorithm in the CDS RRset there must be
+ * a matching DNSKEY record with the exception of a CDS deletion
+ * record which must be by itself.
+ */
+ if (dns_rdataset_isassociated(&cds)) {
+ bool delete = false;
+ memset(algorithms, notexpected, sizeof(algorithms));
+ for (result = dns_rdataset_first(&cds); result == ISC_R_SUCCESS;
+ result = dns_rdataset_next(&cds))
+ {
+ dns_rdata_t crdata = DNS_RDATA_INIT;
+ dns_rdata_cds_t structcds;
+
+ dns_rdataset_current(&cds, &crdata);
+ /*
+ * CDS deletion record has this form "0 0 0 00" which
+ * is 5 zero octets.
+ */
+ if (crdata.length == 5U &&
+ memcmp(crdata.data,
+ (unsigned char[5]){ 0, 0, 0, 0, 0 }, 5) == 0)
+ {
+ delete = true;
+ continue;
+ }
+
+ if (empty) {
+ result = DNS_R_BADCDS;
+ goto failure;
+ }
+
+ CHECK(dns_rdata_tostruct(&crdata, &structcds, NULL));
+ if (algorithms[structcds.algorithm] == 0) {
+ algorithms[structcds.algorithm] = expected;
+ }
+ for (result = dns_rdataset_first(&dnskey);
+ result == ISC_R_SUCCESS;
+ result = dns_rdataset_next(&dnskey))
+ {
+ dns_rdata_t rdata = DNS_RDATA_INIT;
+ dns_rdata_dnskey_t structdnskey;
+
+ dns_rdataset_current(&dnskey, &rdata);
+ CHECK(dns_rdata_tostruct(&rdata, &structdnskey,
+ NULL));
+
+ if (structdnskey.algorithm ==
+ structcds.algorithm)
+ {
+ algorithms[structcds.algorithm] = found;
+ }
+ }
+ if (result != ISC_R_NOMORE) {
+ goto failure;
+ }
+ }
+ for (i = 0; i < sizeof(algorithms); i++) {
+ if (delete) {
+ if (algorithms[i] != notexpected) {
+ result = DNS_R_BADCDS;
+ goto failure;
+ }
+ } else if (algorithms[i] == expected) {
+ result = DNS_R_BADCDS;
+ goto failure;
+ }
+ }
+ }
+
+ /*
+ * For each DNSSEC algorithm in the CDNSKEY RRset there must be
+ * a matching DNSKEY record with the exception of a CDNSKEY deletion
+ * record which must be by itself.
+ */
+ if (dns_rdataset_isassociated(&cdnskey)) {
+ bool delete = false;
+ memset(algorithms, notexpected, sizeof(algorithms));
+ for (result = dns_rdataset_first(&cdnskey);
+ result == ISC_R_SUCCESS;
+ result = dns_rdataset_next(&cdnskey))
+ {
+ dns_rdata_t crdata = DNS_RDATA_INIT;
+ dns_rdata_cdnskey_t structcdnskey;
+
+ dns_rdataset_current(&cdnskey, &crdata);
+ /*
+ * CDNSKEY deletion record has this form
+ * "0 3 0 AA==" which is 2 zero octets, a 3,
+ * and 2 zero octets.
+ */
+ if (crdata.length == 5U &&
+ memcmp(crdata.data,
+ (unsigned char[5]){ 0, 0, 3, 0, 0 }, 5) == 0)
+ {
+ delete = true;
+ continue;
+ }
+
+ if (empty) {
+ result = DNS_R_BADCDNSKEY;
+ goto failure;
+ }
+
+ CHECK(dns_rdata_tostruct(&crdata, &structcdnskey,
+ NULL));
+ if (algorithms[structcdnskey.algorithm] == 0) {
+ algorithms[structcdnskey.algorithm] = expected;
+ }
+ for (result = dns_rdataset_first(&dnskey);
+ result == ISC_R_SUCCESS;
+ result = dns_rdataset_next(&dnskey))
+ {
+ dns_rdata_t rdata = DNS_RDATA_INIT;
+ dns_rdata_dnskey_t structdnskey;
+
+ dns_rdataset_current(&dnskey, &rdata);
+ CHECK(dns_rdata_tostruct(&rdata, &structdnskey,
+ NULL));
+
+ if (structdnskey.algorithm ==
+ structcdnskey.algorithm)
+ {
+ algorithms[structcdnskey.algorithm] =
+ found;
+ }
+ }
+ if (result != ISC_R_NOMORE) {
+ goto failure;
+ }
+ }
+ for (i = 0; i < sizeof(algorithms); i++) {
+ if (delete) {
+ if (algorithms[i] != notexpected) {
+ result = DNS_R_BADCDNSKEY;
+ goto failure;
+ }
+ } else if (algorithms[i] == expected) {
+ result = DNS_R_BADCDNSKEY;
+ goto failure;
+ }
+ }
+ }
+ result = ISC_R_SUCCESS;
+
+failure:
+ if (dns_rdataset_isassociated(&cds)) {
+ dns_rdataset_disassociate(&cds);
+ }
+ if (dns_rdataset_isassociated(&dnskey)) {
+ dns_rdataset_disassociate(&dnskey);
+ }
+ if (dns_rdataset_isassociated(&cdnskey)) {
+ dns_rdataset_disassociate(&cdnskey);
+ }
+ dns_db_detachnode(db, &node);
+ return (result);
+}
+
+void
+dns_zone_setautomatic(dns_zone_t *zone, bool automatic) {
+ REQUIRE(DNS_ZONE_VALID(zone));
+
+ LOCK_ZONE(zone);
+ zone->automatic = automatic;
+ UNLOCK_ZONE(zone);
+}
+
+bool
+dns_zone_getautomatic(dns_zone_t *zone) {
+ REQUIRE(DNS_ZONE_VALID(zone));
+ return (zone->automatic);
+}
+
+void
+dns_zone_setadded(dns_zone_t *zone, bool added) {
+ REQUIRE(DNS_ZONE_VALID(zone));
+
+ LOCK_ZONE(zone);
+ zone->added = added;
+ UNLOCK_ZONE(zone);
+}
+
+bool
+dns_zone_getadded(dns_zone_t *zone) {
+ REQUIRE(DNS_ZONE_VALID(zone));
+ return (zone->added);
+}
+
+isc_result_t
+dns_zone_dlzpostload(dns_zone_t *zone, dns_db_t *db) {
+ isc_time_t loadtime;
+ isc_result_t result;
+ dns_zone_t *secure = NULL;
+
+ TIME_NOW(&loadtime);
+
+ /*
+ * Lock hierarchy: zmgr, zone, raw.
+ */
+again:
+ LOCK_ZONE(zone);
+ INSIST(zone != zone->raw);
+ if (inline_secure(zone)) {
+ LOCK_ZONE(zone->raw);
+ } else if (inline_raw(zone)) {
+ secure = zone->secure;
+ TRYLOCK_ZONE(result, secure);
+ if (result != ISC_R_SUCCESS) {
+ UNLOCK_ZONE(zone);
+ secure = NULL;
+ isc_thread_yield();
+ goto again;
+ }
+ }
+ result = zone_postload(zone, db, loadtime, ISC_R_SUCCESS);
+ if (inline_secure(zone)) {
+ UNLOCK_ZONE(zone->raw);
+ } else if (secure != NULL) {
+ UNLOCK_ZONE(secure);
+ }
+ UNLOCK_ZONE(zone);
+ return (result);
+}
+
+isc_result_t
+dns_zone_setrefreshkeyinterval(dns_zone_t *zone, uint32_t interval) {
+ REQUIRE(DNS_ZONE_VALID(zone));
+ if (interval == 0) {
+ return (ISC_R_RANGE);
+ }
+ /* Maximum value: 24 hours (3600 minutes) */
+ if (interval > (24 * 60)) {
+ interval = (24 * 60);
+ }
+ /* Multiply by 60 for seconds */
+ zone->refreshkeyinterval = interval * 60;
+ return (ISC_R_SUCCESS);
+}
+
+void
+dns_zone_setrequestixfr(dns_zone_t *zone, bool flag) {
+ REQUIRE(DNS_ZONE_VALID(zone));
+ zone->requestixfr = flag;
+}
+
+bool
+dns_zone_getrequestixfr(dns_zone_t *zone) {
+ REQUIRE(DNS_ZONE_VALID(zone));
+ return (zone->requestixfr);
+}
+
+void
+dns_zone_setixfrratio(dns_zone_t *zone, uint32_t ratio) {
+ REQUIRE(DNS_ZONE_VALID(zone));
+ zone->ixfr_ratio = ratio;
+}
+
+uint32_t
+dns_zone_getixfrratio(dns_zone_t *zone) {
+ REQUIRE(DNS_ZONE_VALID(zone));
+ return (zone->ixfr_ratio);
+}
+
+void
+dns_zone_setrequestexpire(dns_zone_t *zone, bool flag) {
+ REQUIRE(DNS_ZONE_VALID(zone));
+ zone->requestexpire = flag;
+}
+
+bool
+dns_zone_getrequestexpire(dns_zone_t *zone) {
+ REQUIRE(DNS_ZONE_VALID(zone));
+ return (zone->requestexpire);
+}
+
+void
+dns_zone_setserialupdatemethod(dns_zone_t *zone, dns_updatemethod_t method) {
+ REQUIRE(DNS_ZONE_VALID(zone));
+ zone->updatemethod = method;
+}
+
+dns_updatemethod_t
+dns_zone_getserialupdatemethod(dns_zone_t *zone) {
+ REQUIRE(DNS_ZONE_VALID(zone));
+ return (zone->updatemethod);
+}
+
+/*
+ * Lock hierarchy: zmgr, zone, raw.
+ */
+isc_result_t
+dns_zone_link(dns_zone_t *zone, dns_zone_t *raw) {
+ isc_result_t result;
+ dns_zonemgr_t *zmgr;
+
+ REQUIRE(DNS_ZONE_VALID(zone));
+ REQUIRE(zone->zmgr != NULL);
+ REQUIRE(zone->task != NULL);
+ REQUIRE(zone->loadtask != NULL);
+ REQUIRE(zone->raw == NULL);
+
+ REQUIRE(DNS_ZONE_VALID(raw));
+ REQUIRE(raw->zmgr == NULL);
+ REQUIRE(raw->task == NULL);
+ REQUIRE(raw->loadtask == NULL);
+ REQUIRE(raw->secure == NULL);
+
+ REQUIRE(zone != raw);
+
+ /*
+ * Lock hierarchy: zmgr, zone, raw.
+ */
+ zmgr = zone->zmgr;
+ RWLOCK(&zmgr->rwlock, isc_rwlocktype_write);
+ LOCK_ZONE(zone);
+ LOCK_ZONE(raw);
+
+ result = isc_timer_create(zmgr->timermgr, isc_timertype_inactive, NULL,
+ NULL, zone->task, zone_timer, raw,
+ &raw->timer);
+ if (result != ISC_R_SUCCESS) {
+ goto unlock;
+ }
+
+ /*
+ * The timer "holds" a iref.
+ */
+ isc_refcount_increment0(&raw->irefs);
+
+ /* dns_zone_attach(raw, &zone->raw); */
+ isc_refcount_increment(&raw->erefs);
+ zone->raw = raw;
+
+ /* dns_zone_iattach(zone, &raw->secure); */
+ zone_iattach(zone, &raw->secure);
+
+ isc_task_attach(zone->task, &raw->task);
+ isc_task_attach(zone->loadtask, &raw->loadtask);
+
+ ISC_LIST_APPEND(zmgr->zones, raw, link);
+ raw->zmgr = zmgr;
+ isc_refcount_increment(&zmgr->refs);
+
+unlock:
+ UNLOCK_ZONE(raw);
+ UNLOCK_ZONE(zone);
+ RWUNLOCK(&zmgr->rwlock, isc_rwlocktype_write);
+ return (result);
+}
+
+void
+dns_zone_getraw(dns_zone_t *zone, dns_zone_t **raw) {
+ REQUIRE(DNS_ZONE_VALID(zone));
+ REQUIRE(raw != NULL && *raw == NULL);
+
+ LOCK(&zone->lock);
+ INSIST(zone != zone->raw);
+ if (zone->raw != NULL) {
+ dns_zone_attach(zone->raw, raw);
+ }
+ UNLOCK(&zone->lock);
+}
+
+struct keydone {
+ isc_event_t event;
+ bool all;
+ unsigned char data[5];
+};
+
+#define PENDINGFLAGS (DNS_NSEC3FLAG_CREATE | DNS_NSEC3FLAG_INITIAL)
+
+static void
+keydone(isc_task_t *task, isc_event_t *event) {
+ const char *me = "keydone";
+ bool commit = false;
+ isc_result_t result;
+ dns_rdata_t rdata = DNS_RDATA_INIT;
+ dns_dbversion_t *oldver = NULL, *newver = NULL;
+ dns_zone_t *zone;
+ dns_db_t *db = NULL;
+ dns_dbnode_t *node = NULL;
+ dns_rdataset_t rdataset;
+ dns_diff_t diff;
+ struct keydone *kd = (struct keydone *)event;
+ dns_update_log_t log = { update_log_cb, NULL };
+ bool clear_pending = false;
+
+ UNUSED(task);
+
+ zone = event->ev_arg;
+ INSIST(DNS_ZONE_VALID(zone));
+
+ ENTER;
+
+ dns_rdataset_init(&rdataset);
+ dns_diff_init(zone->mctx, &diff);
+
+ ZONEDB_LOCK(&zone->dblock, isc_rwlocktype_read);
+ if (zone->db != NULL) {
+ dns_db_attach(zone->db, &db);
+ }
+ ZONEDB_UNLOCK(&zone->dblock, isc_rwlocktype_read);
+ if (db == NULL) {
+ goto failure;
+ }
+
+ dns_db_currentversion(db, &oldver);
+ result = dns_db_newversion(db, &newver);
+ if (result != ISC_R_SUCCESS) {
+ dnssec_log(zone, ISC_LOG_ERROR,
+ "keydone:dns_db_newversion -> %s",
+ isc_result_totext(result));
+ goto failure;
+ }
+
+ result = dns_db_getoriginnode(db, &node);
+ if (result != ISC_R_SUCCESS) {
+ goto failure;
+ }
+
+ result = dns_db_findrdataset(db, node, newver, zone->privatetype,
+ dns_rdatatype_none, 0, &rdataset, NULL);
+ if (result == ISC_R_NOTFOUND) {
+ INSIST(!dns_rdataset_isassociated(&rdataset));
+ goto failure;
+ }
+ if (result != ISC_R_SUCCESS) {
+ INSIST(!dns_rdataset_isassociated(&rdataset));
+ goto failure;
+ }
+
+ for (result = dns_rdataset_first(&rdataset); result == ISC_R_SUCCESS;
+ result = dns_rdataset_next(&rdataset))
+ {
+ bool found = false;
+
+ dns_rdataset_current(&rdataset, &rdata);
+
+ if (kd->all) {
+ if (rdata.length == 5 && rdata.data[0] != 0 &&
+ rdata.data[3] == 0 && rdata.data[4] == 1)
+ {
+ found = true;
+ } else if (rdata.data[0] == 0 &&
+ (rdata.data[2] & PENDINGFLAGS) != 0)
+ {
+ found = true;
+ clear_pending = true;
+ }
+ } else if (rdata.length == 5 &&
+ memcmp(rdata.data, kd->data, 5) == 0)
+ {
+ found = true;
+ }
+
+ if (found) {
+ CHECK(update_one_rr(db, newver, &diff, DNS_DIFFOP_DEL,
+ &zone->origin, rdataset.ttl,
+ &rdata));
+ }
+ dns_rdata_reset(&rdata);
+ }
+
+ if (!ISC_LIST_EMPTY(diff.tuples)) {
+ /* Write changes to journal file. */
+ CHECK(update_soa_serial(zone, db, newver, &diff, zone->mctx,
+ zone->updatemethod));
+
+ result = dns_update_signatures(&log, zone, db, oldver, newver,
+ &diff,
+ zone->sigvalidityinterval);
+ if (!clear_pending) {
+ CHECK(result);
+ }
+
+ CHECK(zone_journal(zone, &diff, NULL, "keydone"));
+ commit = true;
+
+ LOCK_ZONE(zone);
+ DNS_ZONE_SETFLAG(zone,
+ DNS_ZONEFLG_LOADED | DNS_ZONEFLG_NEEDNOTIFY);
+ zone_needdump(zone, 30);
+ UNLOCK_ZONE(zone);
+ }
+
+failure:
+ if (dns_rdataset_isassociated(&rdataset)) {
+ dns_rdataset_disassociate(&rdataset);
+ }
+ if (db != NULL) {
+ if (node != NULL) {
+ dns_db_detachnode(db, &node);
+ }
+ if (oldver != NULL) {
+ dns_db_closeversion(db, &oldver, false);
+ }
+ if (newver != NULL) {
+ dns_db_closeversion(db, &newver, commit);
+ }
+ dns_db_detach(&db);
+ }
+ dns_diff_clear(&diff);
+ isc_event_free(&event);
+ dns_zone_idetach(&zone);
+
+ INSIST(oldver == NULL);
+ INSIST(newver == NULL);
+}
+
+isc_result_t
+dns_zone_keydone(dns_zone_t *zone, const char *keystr) {
+ isc_result_t result = ISC_R_SUCCESS;
+ isc_event_t *e;
+ isc_buffer_t b;
+ dns_zone_t *dummy = NULL;
+ struct keydone *kd;
+
+ REQUIRE(DNS_ZONE_VALID(zone));
+
+ LOCK_ZONE(zone);
+
+ e = isc_event_allocate(zone->mctx, zone, DNS_EVENT_KEYDONE, keydone,
+ zone, sizeof(struct keydone));
+
+ kd = (struct keydone *)e;
+ if (strcasecmp(keystr, "all") == 0) {
+ kd->all = true;
+ } else {
+ isc_textregion_t r;
+ const char *algstr;
+ dns_keytag_t keyid;
+ dns_secalg_t alg;
+ size_t n;
+
+ kd->all = false;
+
+ n = sscanf(keystr, "%hu/", &keyid);
+ if (n == 0U) {
+ CHECK(ISC_R_FAILURE);
+ }
+
+ algstr = strchr(keystr, '/');
+ if (algstr != NULL) {
+ algstr++;
+ } else {
+ CHECK(ISC_R_FAILURE);
+ }
+
+ n = sscanf(algstr, "%hhu", &alg);
+ if (n == 0U) {
+ DE_CONST(algstr, r.base);
+ r.length = strlen(algstr);
+ CHECK(dns_secalg_fromtext(&alg, &r));
+ }
+
+ /* construct a private-type rdata */
+ isc_buffer_init(&b, kd->data, sizeof(kd->data));
+ isc_buffer_putuint8(&b, alg);
+ isc_buffer_putuint8(&b, (keyid & 0xff00) >> 8);
+ isc_buffer_putuint8(&b, (keyid & 0xff));
+ isc_buffer_putuint8(&b, 0);
+ isc_buffer_putuint8(&b, 1);
+ }
+
+ zone_iattach(zone, &dummy);
+ isc_task_send(zone->task, &e);
+
+failure:
+ if (e != NULL) {
+ isc_event_free(&e);
+ }
+ UNLOCK_ZONE(zone);
+ return (result);
+}
+
+/*
+ * Called from the zone task's queue after the relevant event is posted by
+ * dns_zone_setnsec3param().
+ */
+static void
+setnsec3param(isc_task_t *task, isc_event_t *event) {
+ const char *me = "setnsec3param";
+ dns_zone_t *zone = event->ev_arg;
+ bool loadpending;
+
+ INSIST(DNS_ZONE_VALID(zone));
+
+ UNUSED(task);
+
+ ENTER;
+
+ LOCK_ZONE(zone);
+ loadpending = DNS_ZONE_FLAG(zone, DNS_ZONEFLG_LOADPENDING);
+ UNLOCK_ZONE(zone);
+
+ /*
+ * If receive_secure_serial is still processing or we have a
+ * queued event append rss_post queue.
+ */
+ if (zone->rss_newver != NULL || ISC_LIST_HEAD(zone->rss_post) != NULL) {
+ /*
+ * Wait for receive_secure_serial() to finish processing.
+ */
+ ISC_LIST_APPEND(zone->rss_post, event, ev_link);
+ } else {
+ bool rescheduled = false;
+ ZONEDB_LOCK(&zone->dblock, isc_rwlocktype_read);
+ /*
+ * The zone is not yet fully loaded. Reschedule the event to
+ * be picked up later. This turns this function into a busy
+ * wait, but it only happens at startup.
+ */
+ if (zone->db == NULL && loadpending) {
+ rescheduled = true;
+ isc_task_send(task, &event);
+ }
+ ZONEDB_UNLOCK(&zone->dblock, isc_rwlocktype_read);
+ if (rescheduled) {
+ return;
+ }
+
+ rss_post(zone, event);
+ }
+ dns_zone_idetach(&zone);
+}
+
+static void
+salt2text(unsigned char *salt, uint8_t saltlen, unsigned char *text,
+ unsigned int textlen) {
+ isc_region_t r;
+ isc_buffer_t buf;
+ isc_result_t result;
+
+ r.base = salt;
+ r.length = (unsigned int)saltlen;
+
+ isc_buffer_init(&buf, text, textlen);
+ result = isc_hex_totext(&r, 2, "", &buf);
+ if (result == ISC_R_SUCCESS) {
+ text[saltlen * 2] = 0;
+ } else {
+ text[0] = 0;
+ }
+}
+
+/*
+ * Check whether NSEC3 chain addition or removal specified by the private-type
+ * record passed with the event was already queued (or even fully performed).
+ * If not, modify the relevant private-type records at the zone apex and call
+ * resume_addnsec3chain().
+ */
+static void
+rss_post(dns_zone_t *zone, isc_event_t *event) {
+ const char *me = "rss_post";
+ bool commit = false;
+ isc_result_t result;
+ dns_dbversion_t *oldver = NULL, *newver = NULL;
+ dns_db_t *db = NULL;
+ dns_dbnode_t *node = NULL;
+ dns_rdataset_t prdataset, nrdataset;
+ dns_diff_t diff;
+ struct np3event *npe = (struct np3event *)event;
+ nsec3param_t *np;
+ dns_update_log_t log = { update_log_cb, NULL };
+ dns_rdata_t rdata;
+ bool nseconly;
+ bool exists = false;
+
+ ENTER;
+
+ np = &npe->params;
+
+ dns_rdataset_init(&prdataset);
+ dns_rdataset_init(&nrdataset);
+ dns_diff_init(zone->mctx, &diff);
+
+ ZONEDB_LOCK(&zone->dblock, isc_rwlocktype_read);
+ if (zone->db != NULL) {
+ dns_db_attach(zone->db, &db);
+ }
+ ZONEDB_UNLOCK(&zone->dblock, isc_rwlocktype_read);
+ if (db == NULL) {
+ goto failure;
+ }
+
+ dns_db_currentversion(db, &oldver);
+ result = dns_db_newversion(db, &newver);
+ if (result != ISC_R_SUCCESS) {
+ dnssec_log(zone, ISC_LOG_ERROR,
+ "setnsec3param:dns_db_newversion -> %s",
+ isc_result_totext(result));
+ goto failure;
+ }
+
+ CHECK(dns_db_getoriginnode(db, &node));
+
+ /*
+ * Do we need to look up the NSEC3 parameters?
+ */
+ if (np->lookup) {
+ dns_rdata_nsec3param_t param;
+ dns_rdata_t nrdata = DNS_RDATA_INIT;
+ dns_rdata_t prdata = DNS_RDATA_INIT;
+ unsigned char nbuf[DNS_NSEC3PARAM_BUFFERSIZE];
+ unsigned char saltbuf[255];
+ isc_buffer_t b;
+
+ param.salt = NULL;
+ result = dns__zone_lookup_nsec3param(zone, &np->rdata, &param,
+ saltbuf, np->resalt);
+ if (result == ISC_R_SUCCESS) {
+ /*
+ * Success because the NSEC3PARAM already exists, but
+ * function returns void, so goto failure to clean up.
+ */
+ goto failure;
+ }
+ if (result != DNS_R_NSEC3RESALT && result != ISC_R_NOTFOUND) {
+ dnssec_log(zone, ISC_LOG_DEBUG(3),
+ "setnsec3param:lookup nsec3param -> %s",
+ isc_result_totext(result));
+ goto failure;
+ }
+
+ INSIST(param.salt != NULL);
+
+ /* Update NSEC3 parameters. */
+ np->rdata.hash = param.hash;
+ np->rdata.flags = param.flags;
+ np->rdata.iterations = param.iterations;
+ np->rdata.salt_length = param.salt_length;
+ np->rdata.salt = param.salt;
+
+ isc_buffer_init(&b, nbuf, sizeof(nbuf));
+ CHECK(dns_rdata_fromstruct(&nrdata, zone->rdclass,
+ dns_rdatatype_nsec3param, &np->rdata,
+ &b));
+ dns_nsec3param_toprivate(&nrdata, &prdata, zone->privatetype,
+ np->data, sizeof(np->data));
+ np->length = prdata.length;
+ np->nsec = false;
+ }
+
+ /*
+ * Does a private-type record already exist for this chain?
+ */
+ result = dns_db_findrdataset(db, node, newver, zone->privatetype,
+ dns_rdatatype_none, 0, &prdataset, NULL);
+ if (result == ISC_R_SUCCESS) {
+ for (result = dns_rdataset_first(&prdataset);
+ result == ISC_R_SUCCESS;
+ result = dns_rdataset_next(&prdataset))
+ {
+ dns_rdata_init(&rdata);
+ dns_rdataset_current(&prdataset, &rdata);
+
+ if (np->length == rdata.length &&
+ memcmp(rdata.data, np->data, np->length) == 0)
+ {
+ exists = true;
+ break;
+ }
+ }
+ } else if (result != ISC_R_NOTFOUND) {
+ INSIST(!dns_rdataset_isassociated(&prdataset));
+ goto failure;
+ }
+
+ /*
+ * Does the chain already exist?
+ */
+ result = dns_db_findrdataset(db, node, newver, dns_rdatatype_nsec3param,
+ dns_rdatatype_none, 0, &nrdataset, NULL);
+ if (result == ISC_R_SUCCESS) {
+ for (result = dns_rdataset_first(&nrdataset);
+ result == ISC_R_SUCCESS;
+ result = dns_rdataset_next(&nrdataset))
+ {
+ dns_rdata_init(&rdata);
+ dns_rdataset_current(&nrdataset, &rdata);
+
+ if (np->length == (rdata.length + 1) &&
+ memcmp(rdata.data, np->data + 1, np->length - 1) ==
+ 0)
+ {
+ exists = true;
+ break;
+ }
+ }
+ } else if (result != ISC_R_NOTFOUND) {
+ INSIST(!dns_rdataset_isassociated(&nrdataset));
+ goto failure;
+ }
+
+ /*
+ * We need to remove any existing NSEC3 chains if the supplied NSEC3
+ * parameters are supposed to replace the current ones or if we are
+ * switching to NSEC.
+ */
+ if (!exists && np->replace && (np->length != 0 || np->nsec)) {
+ CHECK(dns_nsec3param_deletechains(db, newver, zone, !np->nsec,
+ &diff));
+ }
+
+ if (!exists && np->length != 0) {
+ /*
+ * We're creating an NSEC3 chain. Add the private-type record
+ * passed in the event handler's argument to the zone apex.
+ *
+ * If the zone is not currently capable of supporting an NSEC3
+ * chain (due to the DNSKEY RRset at the zone apex not existing
+ * or containing at least one key using an NSEC-only
+ * algorithm), add the INITIAL flag, so these parameters can be
+ * used later when NSEC3 becomes available.
+ */
+ dns_rdata_init(&rdata);
+
+ np->data[2] |= DNS_NSEC3FLAG_CREATE;
+ result = dns_nsec_nseconly(db, newver, NULL, &nseconly);
+ if (result == ISC_R_NOTFOUND || nseconly) {
+ np->data[2] |= DNS_NSEC3FLAG_INITIAL;
+ }
+
+ rdata.length = np->length;
+ rdata.data = np->data;
+ rdata.type = zone->privatetype;
+ rdata.rdclass = zone->rdclass;
+ CHECK(update_one_rr(db, newver, &diff, DNS_DIFFOP_ADD,
+ &zone->origin, 0, &rdata));
+ }
+
+ /*
+ * If we changed anything in the zone, write changes to journal file
+ * and set commit to true so that resume_addnsec3chain() will be
+ * called below in order to kick off adding/removing relevant NSEC3
+ * records.
+ */
+ if (!ISC_LIST_EMPTY(diff.tuples)) {
+ CHECK(update_soa_serial(zone, db, newver, &diff, zone->mctx,
+ zone->updatemethod));
+ result = dns_update_signatures(&log, zone, db, oldver, newver,
+ &diff,
+ zone->sigvalidityinterval);
+ if (result != ISC_R_NOTFOUND) {
+ CHECK(result);
+ }
+ CHECK(zone_journal(zone, &diff, NULL, "setnsec3param"));
+ commit = true;
+
+ LOCK_ZONE(zone);
+ DNS_ZONE_SETFLAG(zone, DNS_ZONEFLG_LOADED);
+ zone_needdump(zone, 30);
+ UNLOCK_ZONE(zone);
+ }
+
+failure:
+ if (dns_rdataset_isassociated(&prdataset)) {
+ dns_rdataset_disassociate(&prdataset);
+ }
+ if (dns_rdataset_isassociated(&nrdataset)) {
+ dns_rdataset_disassociate(&nrdataset);
+ }
+ if (node != NULL) {
+ dns_db_detachnode(db, &node);
+ }
+ if (oldver != NULL) {
+ dns_db_closeversion(db, &oldver, false);
+ }
+ if (newver != NULL) {
+ dns_db_closeversion(db, &newver, commit);
+ }
+ if (db != NULL) {
+ dns_db_detach(&db);
+ }
+ if (commit) {
+ LOCK_ZONE(zone);
+ resume_addnsec3chain(zone);
+ UNLOCK_ZONE(zone);
+ }
+ dns_diff_clear(&diff);
+ isc_event_free(&event);
+
+ INSIST(oldver == NULL);
+ INSIST(newver == NULL);
+}
+
+/*
+ * Check if zone has NSEC3PARAM (and thus a chain) with the right parameters.
+ *
+ * If 'salt' is NULL, a match is found if the salt has the requested length,
+ * otherwise the NSEC3 salt must match the requested salt value too.
+ *
+ * Returns ISC_R_SUCCESS, if a match is found, or an error if no match is
+ * found, or if the db lookup failed.
+ */
+isc_result_t
+dns__zone_lookup_nsec3param(dns_zone_t *zone, dns_rdata_nsec3param_t *lookup,
+ dns_rdata_nsec3param_t *param,
+ unsigned char saltbuf[255], bool resalt) {
+ isc_result_t result = ISC_R_UNEXPECTED;
+ dns_dbnode_t *node = NULL;
+ dns_db_t *db = NULL;
+ dns_dbversion_t *version = NULL;
+ dns_rdataset_t rdataset;
+ dns_rdata_nsec3param_t nsec3param;
+ dns_rdata_t rdata = DNS_RDATA_INIT;
+
+ REQUIRE(DNS_ZONE_VALID(zone));
+
+ dns_rdataset_init(&rdataset);
+
+ ZONEDB_LOCK(&zone->dblock, isc_rwlocktype_read);
+ if (zone->db != NULL) {
+ dns_db_attach(zone->db, &db);
+ }
+ ZONEDB_UNLOCK(&zone->dblock, isc_rwlocktype_read);
+ if (db == NULL) {
+ result = ISC_R_FAILURE;
+ goto setparam;
+ }
+
+ result = dns_db_findnode(db, &zone->origin, false, &node);
+ if (result != ISC_R_SUCCESS) {
+ dns_zone_log(zone, ISC_LOG_ERROR,
+ "dns__zone_lookup_nsec3param:"
+ "dns_db_findnode -> %s",
+ isc_result_totext(result));
+ result = ISC_R_FAILURE;
+ goto setparam;
+ }
+ dns_db_currentversion(db, &version);
+
+ result = dns_db_findrdataset(db, node, version,
+ dns_rdatatype_nsec3param,
+ dns_rdatatype_none, 0, &rdataset, NULL);
+ if (result != ISC_R_SUCCESS) {
+ INSIST(!dns_rdataset_isassociated(&rdataset));
+ if (result != ISC_R_NOTFOUND) {
+ dns_zone_log(zone, ISC_LOG_ERROR,
+ "dns__zone_lookup_nsec3param:"
+ "dns_db_findrdataset -> %s",
+ isc_result_totext(result));
+ }
+ goto setparam;
+ }
+
+ for (result = dns_rdataset_first(&rdataset); result == ISC_R_SUCCESS;
+ result = dns_rdataset_next(&rdataset))
+ {
+ dns_rdataset_current(&rdataset, &rdata);
+ result = dns_rdata_tostruct(&rdata, &nsec3param, NULL);
+ INSIST(result == ISC_R_SUCCESS);
+ dns_rdata_reset(&rdata);
+
+ /* Check parameters. */
+ if (nsec3param.hash != lookup->hash) {
+ continue;
+ }
+ if (nsec3param.iterations != lookup->iterations) {
+ continue;
+ }
+ if (nsec3param.salt_length != lookup->salt_length) {
+ continue;
+ }
+ if (lookup->salt != NULL) {
+ if (memcmp(nsec3param.salt, lookup->salt,
+ lookup->salt_length) != 0)
+ {
+ continue;
+ }
+ }
+ /* Found a match. */
+ result = ISC_R_SUCCESS;
+ param->hash = nsec3param.hash;
+ param->flags = nsec3param.flags;
+ param->iterations = nsec3param.iterations;
+ param->salt_length = nsec3param.salt_length;
+ param->salt = nsec3param.salt;
+ break;
+ }
+
+ if (result == ISC_R_NOMORE) {
+ result = ISC_R_NOTFOUND;
+ }
+
+setparam:
+ if (result != ISC_R_SUCCESS) {
+ /* Found no match. */
+ param->hash = lookup->hash;
+ param->flags = lookup->flags;
+ param->iterations = lookup->iterations;
+ param->salt_length = lookup->salt_length;
+ param->salt = lookup->salt;
+ }
+
+ if (result != ISC_R_NOTFOUND && result != ISC_R_SUCCESS) {
+ goto failure;
+ }
+
+ if (param->salt_length == 0) {
+ DE_CONST("-", param->salt);
+ } else if (resalt || param->salt == NULL) {
+ unsigned char *newsalt;
+ unsigned char salttext[255 * 2 + 1];
+ do {
+ /* Generate a new salt. */
+ result = dns_nsec3_generate_salt(saltbuf,
+ param->salt_length);
+ if (result != ISC_R_SUCCESS) {
+ break;
+ }
+ newsalt = saltbuf;
+ salt2text(newsalt, param->salt_length, salttext,
+ sizeof(salttext));
+ dnssec_log(zone, ISC_LOG_INFO, "generated salt: %s",
+ salttext);
+ /* Check for salt conflict. */
+ if (param->salt != NULL &&
+ memcmp(newsalt, param->salt, param->salt_length) ==
+ 0)
+ {
+ result = ISC_R_SUCCESS;
+ } else {
+ param->salt = newsalt;
+ result = DNS_R_NSEC3RESALT;
+ }
+ } while (result == ISC_R_SUCCESS);
+
+ INSIST(result != ISC_R_SUCCESS);
+ }
+
+failure:
+ if (dns_rdataset_isassociated(&rdataset)) {
+ dns_rdataset_disassociate(&rdataset);
+ }
+ if (node != NULL) {
+ dns_db_detachnode(db, &node);
+ }
+ if (version != NULL) {
+ dns_db_closeversion(db, &version, false);
+ }
+ if (db != NULL) {
+ dns_db_detach(&db);
+ }
+
+ return (result);
+}
+
+/*
+ * Called when an "rndc signing -nsec3param ..." command is received, or the
+ * 'dnssec-policy' has changed.
+ *
+ * Allocate and prepare an nsec3param_t structure which holds information about
+ * the NSEC3 changes requested for the zone:
+ *
+ * - if NSEC3 is to be disabled ("-nsec3param none"), only set the "nsec"
+ * field of the structure to true and the "replace" field to the value
+ * of the "replace" argument, leaving other fields initialized to zeros, to
+ * signal that the zone should be signed using NSEC instead of NSEC3,
+ *
+ * - otherwise, prepare NSEC3PARAM RDATA that will eventually be inserted at
+ * the zone apex, convert it to a private-type record and store the latter
+ * in the "data" field of the nsec3param_t structure.
+ *
+ * Once the nsec3param_t structure is prepared, post an event to the zone's
+ * task which will cause setnsec3param() to be called with the prepared
+ * structure passed as an argument.
+ */
+isc_result_t
+dns_zone_setnsec3param(dns_zone_t *zone, uint8_t hash, uint8_t flags,
+ uint16_t iter, uint8_t saltlen, unsigned char *salt,
+ bool replace, bool resalt) {
+ isc_result_t result = ISC_R_SUCCESS;
+ dns_rdata_nsec3param_t param, lookup;
+ dns_rdata_t nrdata = DNS_RDATA_INIT;
+ dns_rdata_t prdata = DNS_RDATA_INIT;
+ unsigned char nbuf[DNS_NSEC3PARAM_BUFFERSIZE];
+ unsigned char saltbuf[255];
+ struct np3event *npe;
+ nsec3param_t *np;
+ dns_zone_t *dummy = NULL;
+ isc_buffer_t b;
+ isc_event_t *e = NULL;
+ bool do_lookup = false;
+
+ REQUIRE(DNS_ZONE_VALID(zone));
+
+ LOCK_ZONE(zone);
+
+ /*
+ * First check if the requested NSEC3 parameters are already set,
+ * if so, no need to set again.
+ */
+ if (hash != 0) {
+ lookup.hash = hash;
+ lookup.flags = flags;
+ lookup.iterations = iter;
+ lookup.salt_length = saltlen;
+ lookup.salt = salt;
+ param.salt = NULL;
+ result = dns__zone_lookup_nsec3param(zone, &lookup, &param,
+ saltbuf, resalt);
+ if (result == ISC_R_SUCCESS) {
+ UNLOCK_ZONE(zone);
+ return (ISC_R_SUCCESS);
+ }
+ /*
+ * Schedule lookup if lookup above failed (may happen if zone
+ * db is NULL for example).
+ */
+ do_lookup = (param.salt == NULL) ? true : false;
+ }
+
+ e = isc_event_allocate(zone->mctx, zone, DNS_EVENT_SETNSEC3PARAM,
+ setnsec3param, zone, sizeof(struct np3event));
+
+ npe = (struct np3event *)e;
+ np = &npe->params;
+ np->replace = replace;
+ np->resalt = resalt;
+ np->lookup = do_lookup;
+ if (hash == 0) {
+ np->length = 0;
+ np->nsec = true;
+ dnssec_log(zone, ISC_LOG_DEBUG(3), "setnsec3param:nsec");
+ } else {
+ param.common.rdclass = zone->rdclass;
+ param.common.rdtype = dns_rdatatype_nsec3param;
+ ISC_LINK_INIT(&param.common, link);
+ param.mctx = NULL;
+ /* nsec3 specific param set in dns__zone_lookup_nsec3param() */
+ isc_buffer_init(&b, nbuf, sizeof(nbuf));
+
+ if (param.salt != NULL) {
+ CHECK(dns_rdata_fromstruct(&nrdata, zone->rdclass,
+ dns_rdatatype_nsec3param,
+ &param, &b));
+ dns_nsec3param_toprivate(&nrdata, &prdata,
+ zone->privatetype, np->data,
+ sizeof(np->data));
+ np->length = prdata.length;
+ }
+
+ np->rdata = param;
+ np->nsec = false;
+
+ if (isc_log_wouldlog(dns_lctx, ISC_LOG_DEBUG(3))) {
+ unsigned char salttext[255 * 2 + 1];
+ if (param.salt != NULL) {
+ salt2text(param.salt, param.salt_length,
+ salttext, sizeof(salttext));
+ }
+ dnssec_log(zone, ISC_LOG_DEBUG(3),
+ "setnsec3param:nsec3 %u %u %u %u:%s",
+ param.hash, param.flags, param.iterations,
+ param.salt_length,
+ param.salt == NULL ? "unknown"
+ : (char *)salttext);
+ }
+ }
+
+ /*
+ * setnsec3param() will silently return early if the zone does not yet
+ * have a database. Prevent that by queueing the event up if zone->db
+ * is NULL. All events queued here are subsequently processed by
+ * receive_secure_db() if it ever gets called or simply freed by
+ * zone_free() otherwise.
+ */
+
+ ZONEDB_LOCK(&zone->dblock, isc_rwlocktype_read);
+ if (zone->db != NULL) {
+ zone_iattach(zone, &dummy);
+ isc_task_send(zone->task, &e);
+ } else {
+ ISC_LIST_APPEND(zone->setnsec3param_queue, e, ev_link);
+ e = NULL;
+ }
+ ZONEDB_UNLOCK(&zone->dblock, isc_rwlocktype_read);
+
+ result = ISC_R_SUCCESS;
+
+failure:
+ if (e != NULL) {
+ isc_event_free(&e);
+ }
+ UNLOCK_ZONE(zone);
+ return (result);
+}
+
+isc_result_t
+dns_zone_getloadtime(dns_zone_t *zone, isc_time_t *loadtime) {
+ REQUIRE(DNS_ZONE_VALID(zone));
+ REQUIRE(loadtime != NULL);
+
+ LOCK_ZONE(zone);
+ *loadtime = zone->loadtime;
+ UNLOCK_ZONE(zone);
+ return (ISC_R_SUCCESS);
+}
+
+isc_result_t
+dns_zone_getexpiretime(dns_zone_t *zone, isc_time_t *expiretime) {
+ REQUIRE(DNS_ZONE_VALID(zone));
+ REQUIRE(expiretime != NULL);
+
+ LOCK_ZONE(zone);
+ *expiretime = zone->expiretime;
+ UNLOCK_ZONE(zone);
+ return (ISC_R_SUCCESS);
+}
+
+isc_result_t
+dns_zone_getrefreshtime(dns_zone_t *zone, isc_time_t *refreshtime) {
+ REQUIRE(DNS_ZONE_VALID(zone));
+ REQUIRE(refreshtime != NULL);
+
+ LOCK_ZONE(zone);
+ *refreshtime = zone->refreshtime;
+ UNLOCK_ZONE(zone);
+ return (ISC_R_SUCCESS);
+}
+
+isc_result_t
+dns_zone_getrefreshkeytime(dns_zone_t *zone, isc_time_t *refreshkeytime) {
+ REQUIRE(DNS_ZONE_VALID(zone));
+ REQUIRE(refreshkeytime != NULL);
+
+ LOCK_ZONE(zone);
+ *refreshkeytime = zone->refreshkeytime;
+ UNLOCK_ZONE(zone);
+ return (ISC_R_SUCCESS);
+}
+
+unsigned int
+dns_zone_getincludes(dns_zone_t *zone, char ***includesp) {
+ dns_include_t *include;
+ char **array = NULL;
+ unsigned int n = 0;
+
+ REQUIRE(DNS_ZONE_VALID(zone));
+ REQUIRE(includesp != NULL && *includesp == NULL);
+
+ LOCK_ZONE(zone);
+ if (zone->nincludes == 0) {
+ goto done;
+ }
+
+ array = isc_mem_allocate(zone->mctx, sizeof(char *) * zone->nincludes);
+ for (include = ISC_LIST_HEAD(zone->includes); include != NULL;
+ include = ISC_LIST_NEXT(include, link))
+ {
+ INSIST(n < zone->nincludes);
+ array[n++] = isc_mem_strdup(zone->mctx, include->name);
+ }
+ INSIST(n == zone->nincludes);
+ *includesp = array;
+
+done:
+ UNLOCK_ZONE(zone);
+ return (n);
+}
+
+void
+dns_zone_setstatlevel(dns_zone_t *zone, dns_zonestat_level_t level) {
+ REQUIRE(DNS_ZONE_VALID(zone));
+
+ zone->statlevel = level;
+}
+
+dns_zonestat_level_t
+dns_zone_getstatlevel(dns_zone_t *zone) {
+ REQUIRE(DNS_ZONE_VALID(zone));
+
+ return (zone->statlevel);
+}
+
+static void
+setserial(isc_task_t *task, isc_event_t *event) {
+ uint32_t oldserial, desired;
+ const char *me = "setserial";
+ bool commit = false;
+ isc_result_t result;
+ dns_dbversion_t *oldver = NULL, *newver = NULL;
+ dns_zone_t *zone;
+ dns_db_t *db = NULL;
+ dns_diff_t diff;
+ struct ssevent *sse = (struct ssevent *)event;
+ dns_update_log_t log = { update_log_cb, NULL };
+ dns_difftuple_t *oldtuple = NULL, *newtuple = NULL;
+
+ UNUSED(task);
+
+ zone = event->ev_arg;
+ INSIST(DNS_ZONE_VALID(zone));
+
+ ENTER;
+
+ if (zone->update_disabled) {
+ goto disabled;
+ }
+
+ desired = sse->serial;
+
+ dns_diff_init(zone->mctx, &diff);
+
+ ZONEDB_LOCK(&zone->dblock, isc_rwlocktype_read);
+ if (zone->db != NULL) {
+ dns_db_attach(zone->db, &db);
+ }
+ ZONEDB_UNLOCK(&zone->dblock, isc_rwlocktype_read);
+ if (db == NULL) {
+ goto failure;
+ }
+
+ dns_db_currentversion(db, &oldver);
+ result = dns_db_newversion(db, &newver);
+ if (result != ISC_R_SUCCESS) {
+ dns_zone_log(zone, ISC_LOG_ERROR,
+ "setserial:dns_db_newversion -> %s",
+ isc_result_totext(result));
+ goto failure;
+ }
+
+ CHECK(dns_db_createsoatuple(db, oldver, diff.mctx, DNS_DIFFOP_DEL,
+ &oldtuple));
+ CHECK(dns_difftuple_copy(oldtuple, &newtuple));
+ newtuple->op = DNS_DIFFOP_ADD;
+
+ oldserial = dns_soa_getserial(&oldtuple->rdata);
+ if (desired == 0U) {
+ desired = 1;
+ }
+ if (!isc_serial_gt(desired, oldserial)) {
+ if (desired != oldserial) {
+ dns_zone_log(zone, ISC_LOG_INFO,
+ "setserial: desired serial (%u) "
+ "out of range (%u-%u)",
+ desired, oldserial + 1,
+ (oldserial + 0x7fffffff));
+ }
+ goto failure;
+ }
+
+ dns_soa_setserial(desired, &newtuple->rdata);
+ CHECK(do_one_tuple(&oldtuple, db, newver, &diff));
+ CHECK(do_one_tuple(&newtuple, db, newver, &diff));
+ result = dns_update_signatures(&log, zone, db, oldver, newver, &diff,
+ zone->sigvalidityinterval);
+ if (result != ISC_R_NOTFOUND) {
+ CHECK(result);
+ }
+
+ /* Write changes to journal file. */
+ CHECK(zone_journal(zone, &diff, NULL, "setserial"));
+ commit = true;
+
+ LOCK_ZONE(zone);
+ zone_needdump(zone, 30);
+ UNLOCK_ZONE(zone);
+
+failure:
+ if (oldtuple != NULL) {
+ dns_difftuple_free(&oldtuple);
+ }
+ if (newtuple != NULL) {
+ dns_difftuple_free(&newtuple);
+ }
+ if (oldver != NULL) {
+ dns_db_closeversion(db, &oldver, false);
+ }
+ if (newver != NULL) {
+ dns_db_closeversion(db, &newver, commit);
+ }
+ if (db != NULL) {
+ dns_db_detach(&db);
+ }
+ dns_diff_clear(&diff);
+
+disabled:
+ isc_event_free(&event);
+ dns_zone_idetach(&zone);
+
+ INSIST(oldver == NULL);
+ INSIST(newver == NULL);
+}
+
+isc_result_t
+dns_zone_setserial(dns_zone_t *zone, uint32_t serial) {
+ isc_result_t result = ISC_R_SUCCESS;
+ dns_zone_t *dummy = NULL;
+ isc_event_t *e = NULL;
+ struct ssevent *sse;
+
+ REQUIRE(DNS_ZONE_VALID(zone));
+
+ LOCK_ZONE(zone);
+
+ if (!inline_secure(zone)) {
+ if (!dns_zone_isdynamic(zone, true)) {
+ result = DNS_R_NOTDYNAMIC;
+ goto failure;
+ }
+ }
+
+ if (zone->update_disabled) {
+ result = DNS_R_FROZEN;
+ goto failure;
+ }
+
+ e = isc_event_allocate(zone->mctx, zone, DNS_EVENT_SETSERIAL, setserial,
+ zone, sizeof(struct ssevent));
+
+ sse = (struct ssevent *)e;
+ sse->serial = serial;
+
+ zone_iattach(zone, &dummy);
+ isc_task_send(zone->task, &e);
+
+failure:
+ if (e != NULL) {
+ isc_event_free(&e);
+ }
+ UNLOCK_ZONE(zone);
+ return (result);
+}
+
+isc_stats_t *
+dns_zone_getgluecachestats(dns_zone_t *zone) {
+ REQUIRE(DNS_ZONE_VALID(zone));
+
+ return (zone->gluecachestats);
+}
+
+bool
+dns_zone_isloaded(dns_zone_t *zone) {
+ REQUIRE(DNS_ZONE_VALID(zone));
+
+ return (DNS_ZONE_FLAG(zone, DNS_ZONEFLG_LOADED));
+}
+
+isc_result_t
+dns_zone_verifydb(dns_zone_t *zone, dns_db_t *db, dns_dbversion_t *ver) {
+ dns_dbversion_t *version = NULL;
+ dns_keytable_t *secroots = NULL;
+ isc_result_t result;
+ dns_name_t *origin;
+
+ const char me[] = "dns_zone_verifydb";
+
+ REQUIRE(DNS_ZONE_VALID(zone));
+ REQUIRE(db != NULL);
+
+ ENTER;
+
+ if (dns_zone_gettype(zone) != dns_zone_mirror) {
+ return (ISC_R_SUCCESS);
+ }
+
+ if (ver == NULL) {
+ dns_db_currentversion(db, &version);
+ } else {
+ version = ver;
+ }
+
+ if (zone->view != NULL) {
+ result = dns_view_getsecroots(zone->view, &secroots);
+ if (result != ISC_R_SUCCESS) {
+ goto done;
+ }
+ }
+
+ origin = dns_db_origin(db);
+ result = dns_zoneverify_dnssec(zone, db, version, origin, secroots,
+ zone->mctx, true, false, dnssec_report);
+
+done:
+ if (secroots != NULL) {
+ dns_keytable_detach(&secroots);
+ }
+
+ if (ver == NULL) {
+ dns_db_closeversion(db, &version, false);
+ }
+
+ if (result != ISC_R_SUCCESS) {
+ dnssec_log(zone, ISC_LOG_ERROR, "zone verification failed: %s",
+ isc_result_totext(result));
+ result = DNS_R_VERIFYFAILURE;
+ }
+
+ return (result);
+}
+
+static dns_ttl_t
+zone_nsecttl(dns_zone_t *zone) {
+ REQUIRE(DNS_ZONE_VALID(zone));
+
+ return (ISC_MIN(zone->minimum, zone->soattl));
+}
+
+void
+dns_zonemgr_set_tlsctx_cache(dns_zonemgr_t *zmgr,
+ isc_tlsctx_cache_t *tlsctx_cache) {
+ REQUIRE(DNS_ZONEMGR_VALID(zmgr));
+ REQUIRE(tlsctx_cache != NULL);
+
+ RWLOCK(&zmgr->tlsctx_cache_rwlock, isc_rwlocktype_write);
+
+ if (zmgr->tlsctx_cache != NULL) {
+ isc_tlsctx_cache_detach(&zmgr->tlsctx_cache);
+ }
+
+ isc_tlsctx_cache_attach(tlsctx_cache, &zmgr->tlsctx_cache);
+
+ RWUNLOCK(&zmgr->tlsctx_cache_rwlock, isc_rwlocktype_write);
+}
+
+static void
+zmgr_tlsctx_attach(dns_zonemgr_t *zmgr, isc_tlsctx_cache_t **ptlsctx_cache) {
+ REQUIRE(DNS_ZONEMGR_VALID(zmgr));
+ REQUIRE(ptlsctx_cache != NULL && *ptlsctx_cache == NULL);
+
+ RWLOCK(&zmgr->tlsctx_cache_rwlock, isc_rwlocktype_read);
+
+ INSIST(zmgr->tlsctx_cache != NULL);
+ isc_tlsctx_cache_attach(zmgr->tlsctx_cache, ptlsctx_cache);
+
+ RWUNLOCK(&zmgr->tlsctx_cache_rwlock, isc_rwlocktype_read);
+}
diff --git a/lib/dns/zone_p.h b/lib/dns/zone_p.h
new file mode 100644
index 0000000..8eeeab4
--- /dev/null
+++ b/lib/dns/zone_p.h
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#pragma once
+
+#include <stdbool.h>
+
+/*! \file */
+
+/*%
+ * Types and functions below not be used outside this module and its
+ * associated unit tests.
+ */
+
+ISC_LANG_BEGINDECLS
+
+typedef struct {
+ dns_diff_t *diff;
+ bool offline;
+} dns__zonediff_t;
+
+isc_result_t
+dns__zone_findkeys(dns_zone_t *zone, dns_db_t *db, dns_dbversion_t *ver,
+ isc_stdtime_t now, isc_mem_t *mctx, unsigned int maxkeys,
+ dst_key_t **keys, unsigned int *nkeys);
+
+isc_result_t
+dns__zone_updatesigs(dns_diff_t *diff, dns_db_t *db, dns_dbversion_t *version,
+ dst_key_t *zone_keys[], unsigned int nkeys,
+ dns_zone_t *zone, isc_stdtime_t inception,
+ isc_stdtime_t expire, isc_stdtime_t keyxpire,
+ isc_stdtime_t now, bool check_ksk, bool keyset_kskonly,
+ dns__zonediff_t *zonediff);
+
+isc_result_t
+dns__zone_lookup_nsec3param(dns_zone_t *zone, dns_rdata_nsec3param_t *lookup,
+ dns_rdata_nsec3param_t *param,
+ unsigned char saltbuf[255], bool resalt);
+
+ISC_LANG_ENDDECLS
diff --git a/lib/dns/zonekey.c b/lib/dns/zonekey.c
new file mode 100644
index 0000000..f86c2ca
--- /dev/null
+++ b/lib/dns/zonekey.c
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/*! \file */
+
+#include <stdbool.h>
+
+#include <isc/result.h>
+#include <isc/types.h>
+#include <isc/util.h>
+
+#include <dns/keyvalues.h>
+#include <dns/rdata.h>
+#include <dns/rdatastruct.h>
+#include <dns/types.h>
+#include <dns/zonekey.h>
+
+bool
+dns_zonekey_iszonekey(dns_rdata_t *keyrdata) {
+ isc_result_t result;
+ dns_rdata_dnskey_t key;
+ bool iszonekey = true;
+
+ REQUIRE(keyrdata != NULL);
+
+ result = dns_rdata_tostruct(keyrdata, &key, NULL);
+ if (result != ISC_R_SUCCESS) {
+ return (false);
+ }
+
+ if ((key.flags & DNS_KEYTYPE_NOAUTH) != 0) {
+ iszonekey = false;
+ }
+ if ((key.flags & DNS_KEYFLAG_OWNERMASK) != DNS_KEYOWNER_ZONE) {
+ iszonekey = false;
+ }
+ if (key.protocol != DNS_KEYPROTO_DNSSEC &&
+ key.protocol != DNS_KEYPROTO_ANY)
+ {
+ iszonekey = false;
+ }
+
+ return (iszonekey);
+}
diff --git a/lib/dns/zoneverify.c b/lib/dns/zoneverify.c
new file mode 100644
index 0000000..d5c1537
--- /dev/null
+++ b/lib/dns/zoneverify.c
@@ -0,0 +1,2037 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/*! \file */
+
+#include <inttypes.h>
+#include <stdarg.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <string.h>
+
+#include <isc/base32.h>
+#include <isc/buffer.h>
+#include <isc/heap.h>
+#include <isc/iterated_hash.h>
+#include <isc/log.h>
+#include <isc/mem.h>
+#include <isc/region.h>
+#include <isc/result.h>
+#include <isc/types.h>
+#include <isc/util.h>
+
+#include <dns/db.h>
+#include <dns/dbiterator.h>
+#include <dns/dnssec.h>
+#include <dns/fixedname.h>
+#include <dns/keytable.h>
+#include <dns/keyvalues.h>
+#include <dns/log.h>
+#include <dns/name.h>
+#include <dns/nsec.h>
+#include <dns/nsec3.h>
+#include <dns/rdata.h>
+#include <dns/rdataset.h>
+#include <dns/rdatasetiter.h>
+#include <dns/rdatastruct.h>
+#include <dns/rdatatype.h>
+#include <dns/secalg.h>
+#include <dns/types.h>
+#include <dns/zone.h>
+#include <dns/zoneverify.h>
+
+#include <dst/dst.h>
+
+typedef struct vctx {
+ isc_mem_t *mctx;
+ dns_zone_t *zone;
+ dns_db_t *db;
+ dns_dbversion_t *ver;
+ dns_name_t *origin;
+ dns_keytable_t *secroots;
+ bool goodksk;
+ bool goodzsk;
+ dns_rdataset_t keyset;
+ dns_rdataset_t keysigs;
+ dns_rdataset_t soaset;
+ dns_rdataset_t soasigs;
+ dns_rdataset_t nsecset;
+ dns_rdataset_t nsecsigs;
+ dns_rdataset_t nsec3paramset;
+ dns_rdataset_t nsec3paramsigs;
+ unsigned char revoked_ksk[256];
+ unsigned char revoked_zsk[256];
+ unsigned char standby_ksk[256];
+ unsigned char standby_zsk[256];
+ unsigned char ksk_algorithms[256];
+ unsigned char zsk_algorithms[256];
+ unsigned char bad_algorithms[256];
+ unsigned char act_algorithms[256];
+ isc_heap_t *expected_chains;
+ isc_heap_t *found_chains;
+} vctx_t;
+
+struct nsec3_chain_fixed {
+ uint8_t hash;
+ uint8_t salt_length;
+ uint8_t next_length;
+ uint16_t iterations;
+ /*
+ * The following non-fixed-length data is stored in memory after the
+ * fields declared above for each NSEC3 chain element:
+ *
+ * unsigned char salt[salt_length];
+ * unsigned char owner[next_length];
+ * unsigned char next[next_length];
+ */
+};
+
+/*
+ * Helper function used to calculate length of variable-length
+ * data section in object pointed to by 'chain'.
+ */
+static size_t
+chain_length(struct nsec3_chain_fixed *chain) {
+ return (chain->salt_length + 2 * chain->next_length);
+}
+
+/*%
+ * Log a zone verification error described by 'fmt' and the variable arguments
+ * following it. Either use dns_zone_logv() or print to stderr, depending on
+ * whether the function was invoked from within named or by a standalone tool,
+ * respectively.
+ */
+static void
+zoneverify_log_error(const vctx_t *vctx, const char *fmt, ...) {
+ va_list ap;
+
+ va_start(ap, fmt);
+ if (vctx->zone != NULL) {
+ dns_zone_logv(vctx->zone, DNS_LOGCATEGORY_GENERAL,
+ ISC_LOG_ERROR, NULL, fmt, ap);
+ } else {
+ vfprintf(stderr, fmt, ap);
+ fprintf(stderr, "\n");
+ }
+ va_end(ap);
+}
+
+static bool
+is_delegation(const vctx_t *vctx, const dns_name_t *name, dns_dbnode_t *node,
+ uint32_t *ttlp) {
+ dns_rdataset_t nsset;
+ isc_result_t result;
+
+ if (dns_name_equal(name, vctx->origin)) {
+ return (false);
+ }
+
+ dns_rdataset_init(&nsset);
+ result = dns_db_findrdataset(vctx->db, node, vctx->ver,
+ dns_rdatatype_ns, 0, 0, &nsset, NULL);
+ if (dns_rdataset_isassociated(&nsset)) {
+ if (ttlp != NULL) {
+ *ttlp = nsset.ttl;
+ }
+ dns_rdataset_disassociate(&nsset);
+ }
+
+ return ((result == ISC_R_SUCCESS));
+}
+
+/*%
+ * Return true if version 'ver' of database 'db' contains a DNAME RRset at
+ * 'node'; return false otherwise.
+ */
+static bool
+has_dname(const vctx_t *vctx, dns_dbnode_t *node) {
+ dns_rdataset_t dnameset;
+ isc_result_t result;
+
+ dns_rdataset_init(&dnameset);
+ result = dns_db_findrdataset(vctx->db, node, vctx->ver,
+ dns_rdatatype_dname, 0, 0, &dnameset,
+ NULL);
+ if (dns_rdataset_isassociated(&dnameset)) {
+ dns_rdataset_disassociate(&dnameset);
+ }
+
+ return ((result == ISC_R_SUCCESS));
+}
+
+static bool
+goodsig(const vctx_t *vctx, dns_rdata_t *sigrdata, const dns_name_t *name,
+ dst_key_t **dstkeys, size_t nkeys, dns_rdataset_t *rdataset) {
+ dns_rdata_rrsig_t sig;
+ isc_result_t result;
+
+ result = dns_rdata_tostruct(sigrdata, &sig, NULL);
+ RUNTIME_CHECK(result == ISC_R_SUCCESS);
+
+ for (size_t key = 0; key < nkeys; key++) {
+ if (sig.algorithm != dst_key_alg(dstkeys[key]) ||
+ sig.keyid != dst_key_id(dstkeys[key]) ||
+ !dns_name_equal(&sig.signer, vctx->origin))
+ {
+ continue;
+ }
+ result = dns_dnssec_verify(name, rdataset, dstkeys[key], false,
+ 0, vctx->mctx, sigrdata, NULL);
+ if (result == ISC_R_SUCCESS || result == DNS_R_FROMWILDCARD) {
+ return (true);
+ }
+ }
+ return (false);
+}
+
+static bool
+nsec_bitmap_equal(dns_rdata_nsec_t *nsec, dns_rdata_t *rdata) {
+ isc_result_t result;
+ dns_rdata_nsec_t tmpnsec;
+
+ result = dns_rdata_tostruct(rdata, &tmpnsec, NULL);
+ RUNTIME_CHECK(result == ISC_R_SUCCESS);
+
+ if (nsec->len != tmpnsec.len ||
+ memcmp(nsec->typebits, tmpnsec.typebits, nsec->len) != 0)
+ {
+ return (false);
+ }
+ return (true);
+}
+
+static isc_result_t
+verifynsec(const vctx_t *vctx, const dns_name_t *name, dns_dbnode_t *node,
+ const dns_name_t *nextname, isc_result_t *vresult) {
+ unsigned char buffer[DNS_NSEC_BUFFERSIZE];
+ char namebuf[DNS_NAME_FORMATSIZE];
+ char nextbuf[DNS_NAME_FORMATSIZE];
+ char found[DNS_NAME_FORMATSIZE];
+ dns_rdataset_t rdataset;
+ dns_rdata_t rdata = DNS_RDATA_INIT;
+ dns_rdata_t tmprdata = DNS_RDATA_INIT;
+ dns_rdata_nsec_t nsec;
+ isc_result_t result;
+
+ dns_rdataset_init(&rdataset);
+ result = dns_db_findrdataset(vctx->db, node, vctx->ver,
+ dns_rdatatype_nsec, 0, 0, &rdataset, NULL);
+ if (result != ISC_R_SUCCESS) {
+ dns_name_format(name, namebuf, sizeof(namebuf));
+ zoneverify_log_error(vctx, "Missing NSEC record for %s",
+ namebuf);
+ *vresult = ISC_R_FAILURE;
+ result = ISC_R_SUCCESS;
+ goto done;
+ }
+
+ result = dns_rdataset_first(&rdataset);
+ if (result != ISC_R_SUCCESS) {
+ zoneverify_log_error(vctx, "dns_rdataset_first(): %s",
+ isc_result_totext(result));
+ goto done;
+ }
+
+ dns_rdataset_current(&rdataset, &rdata);
+ result = dns_rdata_tostruct(&rdata, &nsec, NULL);
+ RUNTIME_CHECK(result == ISC_R_SUCCESS);
+
+ /* Check next name is consistent */
+ if (!dns_name_equal(&nsec.next, nextname)) {
+ dns_name_format(name, namebuf, sizeof(namebuf));
+ dns_name_format(nextname, nextbuf, sizeof(nextbuf));
+ dns_name_format(&nsec.next, found, sizeof(found));
+ zoneverify_log_error(vctx,
+ "Bad NSEC record for %s, next name "
+ "mismatch (expected:%s, found:%s)",
+ namebuf, nextbuf, found);
+ *vresult = ISC_R_FAILURE;
+ goto done;
+ }
+
+ /* Check bit map is consistent */
+ result = dns_nsec_buildrdata(vctx->db, vctx->ver, node, nextname,
+ buffer, &tmprdata);
+ if (result != ISC_R_SUCCESS) {
+ zoneverify_log_error(vctx, "dns_nsec_buildrdata(): %s",
+ isc_result_totext(result));
+ goto done;
+ }
+ if (!nsec_bitmap_equal(&nsec, &tmprdata)) {
+ dns_name_format(name, namebuf, sizeof(namebuf));
+ zoneverify_log_error(vctx,
+ "Bad NSEC record for %s, bit map "
+ "mismatch",
+ namebuf);
+ *vresult = ISC_R_FAILURE;
+ goto done;
+ }
+
+ result = dns_rdataset_next(&rdataset);
+ if (result != ISC_R_NOMORE) {
+ dns_name_format(name, namebuf, sizeof(namebuf));
+ zoneverify_log_error(vctx, "Multiple NSEC records for %s",
+ namebuf);
+ *vresult = ISC_R_FAILURE;
+ goto done;
+ }
+
+ *vresult = ISC_R_SUCCESS;
+ result = ISC_R_SUCCESS;
+
+done:
+ if (dns_rdataset_isassociated(&rdataset)) {
+ dns_rdataset_disassociate(&rdataset);
+ }
+
+ return (result);
+}
+
+static isc_result_t
+check_no_rrsig(const vctx_t *vctx, const dns_rdataset_t *rdataset,
+ const dns_name_t *name, dns_dbnode_t *node) {
+ char namebuf[DNS_NAME_FORMATSIZE];
+ char typebuf[DNS_RDATATYPE_FORMATSIZE];
+ dns_rdataset_t sigrdataset;
+ dns_rdatasetiter_t *rdsiter = NULL;
+ isc_result_t result;
+
+ dns_rdataset_init(&sigrdataset);
+ result = dns_db_allrdatasets(vctx->db, node, vctx->ver, 0, 0, &rdsiter);
+ if (result != ISC_R_SUCCESS) {
+ zoneverify_log_error(vctx, "dns_db_allrdatasets(): %s",
+ isc_result_totext(result));
+ return (result);
+ }
+ for (result = dns_rdatasetiter_first(rdsiter); result == ISC_R_SUCCESS;
+ result = dns_rdatasetiter_next(rdsiter))
+ {
+ dns_rdatasetiter_current(rdsiter, &sigrdataset);
+ if (sigrdataset.type == dns_rdatatype_rrsig &&
+ sigrdataset.covers == rdataset->type)
+ {
+ dns_name_format(name, namebuf, sizeof(namebuf));
+ dns_rdatatype_format(rdataset->type, typebuf,
+ sizeof(typebuf));
+ zoneverify_log_error(
+ vctx,
+ "Warning: Found unexpected signatures "
+ "for %s/%s",
+ namebuf, typebuf);
+ break;
+ }
+ dns_rdataset_disassociate(&sigrdataset);
+ }
+ if (dns_rdataset_isassociated(&sigrdataset)) {
+ dns_rdataset_disassociate(&sigrdataset);
+ }
+ dns_rdatasetiter_destroy(&rdsiter);
+
+ return (ISC_R_SUCCESS);
+}
+
+static bool
+chain_compare(void *arg1, void *arg2) {
+ struct nsec3_chain_fixed *e1 = arg1, *e2 = arg2;
+ /*
+ * Do each element in turn to get a stable sort.
+ */
+ if (e1->hash < e2->hash) {
+ return (true);
+ }
+ if (e1->hash > e2->hash) {
+ return (false);
+ }
+ if (e1->iterations < e2->iterations) {
+ return (true);
+ }
+ if (e1->iterations > e2->iterations) {
+ return (false);
+ }
+ if (e1->salt_length < e2->salt_length) {
+ return (true);
+ }
+ if (e1->salt_length > e2->salt_length) {
+ return (false);
+ }
+ if (e1->next_length < e2->next_length) {
+ return (true);
+ }
+ if (e1->next_length > e2->next_length) {
+ return (false);
+ }
+ if (memcmp(e1 + 1, e2 + 1, chain_length(e1)) < 0) {
+ return (true);
+ }
+ return (false);
+}
+
+static bool
+chain_equal(const struct nsec3_chain_fixed *e1,
+ const struct nsec3_chain_fixed *e2, size_t data_length) {
+ if (e1->hash != e2->hash) {
+ return (false);
+ }
+ if (e1->iterations != e2->iterations) {
+ return (false);
+ }
+ if (e1->salt_length != e2->salt_length) {
+ return (false);
+ }
+ if (e1->next_length != e2->next_length) {
+ return (false);
+ }
+
+ return (memcmp(e1 + 1, e2 + 1, data_length) == 0);
+}
+
+static void
+record_nsec3(const vctx_t *vctx, const unsigned char *rawhash,
+ const dns_rdata_nsec3_t *nsec3, isc_heap_t *chains) {
+ struct nsec3_chain_fixed *element = NULL;
+ unsigned char *cp = NULL;
+ size_t len;
+
+ len = sizeof(*element) + nsec3->next_length * 2 + nsec3->salt_length;
+
+ element = isc_mem_get(vctx->mctx, len);
+ memset(element, 0, len);
+ element->hash = nsec3->hash;
+ element->salt_length = nsec3->salt_length;
+ element->next_length = nsec3->next_length;
+ element->iterations = nsec3->iterations;
+ cp = (unsigned char *)(element + 1);
+ memmove(cp, nsec3->salt, nsec3->salt_length);
+ cp += nsec3->salt_length;
+ memmove(cp, rawhash, nsec3->next_length);
+ cp += nsec3->next_length;
+ memmove(cp, nsec3->next, nsec3->next_length);
+ isc_heap_insert(chains, element);
+}
+
+/*
+ * Check whether any NSEC3 within 'rdataset' matches the parameters in
+ * 'nsec3param'.
+ */
+static isc_result_t
+find_nsec3_match(const dns_rdata_nsec3param_t *nsec3param,
+ dns_rdataset_t *rdataset, size_t rhsize,
+ dns_rdata_nsec3_t *nsec3_match) {
+ isc_result_t result;
+
+ /*
+ * Find matching NSEC3 record.
+ */
+ for (result = dns_rdataset_first(rdataset); result == ISC_R_SUCCESS;
+ result = dns_rdataset_next(rdataset))
+ {
+ dns_rdata_t rdata = DNS_RDATA_INIT;
+ dns_rdataset_current(rdataset, &rdata);
+ result = dns_rdata_tostruct(&rdata, nsec3_match, NULL);
+ RUNTIME_CHECK(result == ISC_R_SUCCESS);
+ if (nsec3_match->hash == nsec3param->hash &&
+ nsec3_match->next_length == rhsize &&
+ nsec3_match->iterations == nsec3param->iterations &&
+ nsec3_match->salt_length == nsec3param->salt_length &&
+ memcmp(nsec3_match->salt, nsec3param->salt,
+ nsec3param->salt_length) == 0)
+ {
+ return (ISC_R_SUCCESS);
+ }
+ }
+
+ return (result);
+}
+
+static isc_result_t
+match_nsec3(const vctx_t *vctx, const dns_name_t *name,
+ const dns_rdata_nsec3param_t *nsec3param, dns_rdataset_t *rdataset,
+ const unsigned char types[8192], unsigned int maxtype,
+ const unsigned char *rawhash, size_t rhsize,
+ isc_result_t *vresult) {
+ unsigned char cbm[8244];
+ char namebuf[DNS_NAME_FORMATSIZE];
+ dns_rdata_nsec3_t nsec3;
+ isc_result_t result;
+ unsigned int len;
+
+ result = find_nsec3_match(nsec3param, rdataset, rhsize, &nsec3);
+ if (result != ISC_R_SUCCESS) {
+ dns_name_format(name, namebuf, sizeof(namebuf));
+ zoneverify_log_error(vctx, "Missing NSEC3 record for %s",
+ namebuf);
+ *vresult = result;
+ return (ISC_R_SUCCESS);
+ }
+
+ /*
+ * Check the type list.
+ */
+ len = dns_nsec_compressbitmap(cbm, types, maxtype);
+ if (nsec3.len != len || memcmp(cbm, nsec3.typebits, len) != 0) {
+ dns_name_format(name, namebuf, sizeof(namebuf));
+ zoneverify_log_error(vctx,
+ "Bad NSEC3 record for %s, bit map "
+ "mismatch",
+ namebuf);
+ *vresult = ISC_R_FAILURE;
+ return (ISC_R_SUCCESS);
+ }
+
+ /*
+ * Record chain.
+ */
+ record_nsec3(vctx, rawhash, &nsec3, vctx->expected_chains);
+
+ /*
+ * Make sure there is only one NSEC3 record with this set of
+ * parameters.
+ */
+ for (result = dns_rdataset_next(rdataset); result == ISC_R_SUCCESS;
+ result = dns_rdataset_next(rdataset))
+ {
+ dns_rdata_t rdata = DNS_RDATA_INIT;
+ dns_rdataset_current(rdataset, &rdata);
+ result = dns_rdata_tostruct(&rdata, &nsec3, NULL);
+ RUNTIME_CHECK(result == ISC_R_SUCCESS);
+ if (nsec3.hash == nsec3param->hash &&
+ nsec3.iterations == nsec3param->iterations &&
+ nsec3.salt_length == nsec3param->salt_length &&
+ memcmp(nsec3.salt, nsec3param->salt, nsec3.salt_length) ==
+ 0)
+ {
+ dns_name_format(name, namebuf, sizeof(namebuf));
+ zoneverify_log_error(vctx,
+ "Multiple NSEC3 records with the "
+ "same parameter set for %s",
+ namebuf);
+ *vresult = DNS_R_DUPLICATE;
+ return (ISC_R_SUCCESS);
+ }
+ }
+ if (result != ISC_R_NOMORE) {
+ return (result);
+ }
+
+ *vresult = ISC_R_SUCCESS;
+
+ return (ISC_R_SUCCESS);
+}
+
+static bool
+innsec3params(const dns_rdata_nsec3_t *nsec3, dns_rdataset_t *nsec3paramset) {
+ dns_rdata_nsec3param_t nsec3param;
+ isc_result_t result;
+
+ for (result = dns_rdataset_first(nsec3paramset);
+ result == ISC_R_SUCCESS; result = dns_rdataset_next(nsec3paramset))
+ {
+ dns_rdata_t rdata = DNS_RDATA_INIT;
+
+ dns_rdataset_current(nsec3paramset, &rdata);
+ result = dns_rdata_tostruct(&rdata, &nsec3param, NULL);
+ RUNTIME_CHECK(result == ISC_R_SUCCESS);
+ if (nsec3param.flags == 0 && nsec3param.hash == nsec3->hash &&
+ nsec3param.iterations == nsec3->iterations &&
+ nsec3param.salt_length == nsec3->salt_length &&
+ memcmp(nsec3param.salt, nsec3->salt, nsec3->salt_length) ==
+ 0)
+ {
+ return (true);
+ }
+ }
+ return (false);
+}
+
+static isc_result_t
+record_found(const vctx_t *vctx, const dns_name_t *name, dns_dbnode_t *node,
+ dns_rdataset_t *nsec3paramset) {
+ unsigned char owner[NSEC3_MAX_HASH_LENGTH];
+ dns_rdata_nsec3_t nsec3;
+ dns_rdataset_t rdataset;
+ dns_label_t hashlabel;
+ isc_buffer_t b;
+ isc_result_t result;
+
+ if (nsec3paramset == NULL || !dns_rdataset_isassociated(nsec3paramset))
+ {
+ return (ISC_R_SUCCESS);
+ }
+
+ dns_rdataset_init(&rdataset);
+ result = dns_db_findrdataset(vctx->db, node, vctx->ver,
+ dns_rdatatype_nsec3, 0, 0, &rdataset,
+ NULL);
+ if (result != ISC_R_SUCCESS) {
+ return (ISC_R_SUCCESS);
+ }
+
+ dns_name_getlabel(name, 0, &hashlabel);
+ isc_region_consume(&hashlabel, 1);
+ isc_buffer_init(&b, owner, sizeof(owner));
+ result = isc_base32hex_decoderegion(&hashlabel, &b);
+ if (result != ISC_R_SUCCESS) {
+ result = ISC_R_SUCCESS;
+ goto cleanup;
+ }
+
+ for (result = dns_rdataset_first(&rdataset); result == ISC_R_SUCCESS;
+ result = dns_rdataset_next(&rdataset))
+ {
+ dns_rdata_t rdata = DNS_RDATA_INIT;
+ dns_rdataset_current(&rdataset, &rdata);
+ result = dns_rdata_tostruct(&rdata, &nsec3, NULL);
+ RUNTIME_CHECK(result == ISC_R_SUCCESS);
+ if (nsec3.next_length != isc_buffer_usedlength(&b)) {
+ continue;
+ }
+
+ /*
+ * We only care about NSEC3 records that match a NSEC3PARAM
+ * record.
+ */
+ if (!innsec3params(&nsec3, nsec3paramset)) {
+ continue;
+ }
+
+ /*
+ * Record chain.
+ */
+ record_nsec3(vctx, owner, &nsec3, vctx->found_chains);
+ }
+ result = ISC_R_SUCCESS;
+
+cleanup:
+ dns_rdataset_disassociate(&rdataset);
+ return (result);
+}
+
+static isc_result_t
+isoptout(const vctx_t *vctx, const dns_rdata_nsec3param_t *nsec3param,
+ bool *optout) {
+ dns_rdataset_t rdataset;
+ dns_rdata_t rdata = DNS_RDATA_INIT;
+ dns_rdata_nsec3_t nsec3;
+ dns_fixedname_t fixed;
+ dns_name_t *hashname;
+ isc_result_t result;
+ dns_dbnode_t *node = NULL;
+ unsigned char rawhash[NSEC3_MAX_HASH_LENGTH];
+ size_t rhsize = sizeof(rawhash);
+
+ dns_fixedname_init(&fixed);
+ result = dns_nsec3_hashname(&fixed, rawhash, &rhsize, vctx->origin,
+ vctx->origin, nsec3param->hash,
+ nsec3param->iterations, nsec3param->salt,
+ nsec3param->salt_length);
+ if (result != ISC_R_SUCCESS) {
+ zoneverify_log_error(vctx, "dns_nsec3_hashname(): %s",
+ isc_result_totext(result));
+ return (result);
+ }
+
+ dns_rdataset_init(&rdataset);
+ hashname = dns_fixedname_name(&fixed);
+ result = dns_db_findnsec3node(vctx->db, hashname, false, &node);
+ if (result == ISC_R_SUCCESS) {
+ result = dns_db_findrdataset(vctx->db, node, vctx->ver,
+ dns_rdatatype_nsec3, 0, 0,
+ &rdataset, NULL);
+ }
+ if (result != ISC_R_SUCCESS) {
+ *optout = false;
+ result = ISC_R_SUCCESS;
+ goto done;
+ }
+
+ result = dns_rdataset_first(&rdataset);
+ if (result != ISC_R_SUCCESS) {
+ zoneverify_log_error(vctx, "dns_rdataset_first(): %s",
+ isc_result_totext(result));
+ goto done;
+ }
+
+ dns_rdataset_current(&rdataset, &rdata);
+
+ result = dns_rdata_tostruct(&rdata, &nsec3, NULL);
+ RUNTIME_CHECK(result == ISC_R_SUCCESS);
+ *optout = ((nsec3.flags & DNS_NSEC3FLAG_OPTOUT) != 0);
+
+done:
+ if (dns_rdataset_isassociated(&rdataset)) {
+ dns_rdataset_disassociate(&rdataset);
+ }
+ if (node != NULL) {
+ dns_db_detachnode(vctx->db, &node);
+ }
+
+ return (result);
+}
+
+static isc_result_t
+verifynsec3(const vctx_t *vctx, const dns_name_t *name,
+ const dns_rdata_t *rdata, bool delegation, bool empty,
+ const unsigned char types[8192], unsigned int maxtype,
+ isc_result_t *vresult) {
+ char namebuf[DNS_NAME_FORMATSIZE];
+ char hashbuf[DNS_NAME_FORMATSIZE];
+ dns_rdataset_t rdataset;
+ dns_rdata_nsec3param_t nsec3param;
+ dns_fixedname_t fixed;
+ dns_name_t *hashname;
+ isc_result_t result, tvresult = ISC_R_UNSET;
+ dns_dbnode_t *node = NULL;
+ unsigned char rawhash[NSEC3_MAX_HASH_LENGTH];
+ size_t rhsize = sizeof(rawhash);
+ bool optout = false;
+
+ result = dns_rdata_tostruct(rdata, &nsec3param, NULL);
+ RUNTIME_CHECK(result == ISC_R_SUCCESS);
+
+ if (nsec3param.flags != 0) {
+ return (ISC_R_SUCCESS);
+ }
+
+ if (!dns_nsec3_supportedhash(nsec3param.hash)) {
+ return (ISC_R_SUCCESS);
+ }
+
+ if (nsec3param.iterations > DNS_NSEC3_MAXITERATIONS) {
+ result = DNS_R_NSEC3ITERRANGE;
+ zoneverify_log_error(vctx, "verifynsec3: %s",
+ isc_result_totext(result));
+ return (result);
+ }
+
+ result = isoptout(vctx, &nsec3param, &optout);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+
+ dns_fixedname_init(&fixed);
+ result = dns_nsec3_hashname(
+ &fixed, rawhash, &rhsize, name, vctx->origin, nsec3param.hash,
+ nsec3param.iterations, nsec3param.salt, nsec3param.salt_length);
+ if (result != ISC_R_SUCCESS) {
+ zoneverify_log_error(vctx, "dns_nsec3_hashname(): %s",
+ isc_result_totext(result));
+ return (result);
+ }
+
+ /*
+ * We don't use dns_db_find() here as it works with the chosen
+ * nsec3 chain and we may also be called with uncommitted data
+ * from dnssec-signzone so the secure status of the zone may not
+ * be up to date.
+ */
+ dns_rdataset_init(&rdataset);
+ hashname = dns_fixedname_name(&fixed);
+ result = dns_db_findnsec3node(vctx->db, hashname, false, &node);
+ if (result == ISC_R_SUCCESS) {
+ result = dns_db_findrdataset(vctx->db, node, vctx->ver,
+ dns_rdatatype_nsec3, 0, 0,
+ &rdataset, NULL);
+ }
+ if (result != ISC_R_SUCCESS &&
+ (!delegation || (empty && !optout) ||
+ (!empty && dns_nsec_isset(types, dns_rdatatype_ds))))
+ {
+ dns_name_format(name, namebuf, sizeof(namebuf));
+ dns_name_format(hashname, hashbuf, sizeof(hashbuf));
+ zoneverify_log_error(vctx, "Missing NSEC3 record for %s (%s)",
+ namebuf, hashbuf);
+ } else if (result == ISC_R_NOTFOUND && delegation && (!empty || optout))
+ {
+ result = ISC_R_SUCCESS;
+ } else if (result == ISC_R_SUCCESS) {
+ result = match_nsec3(vctx, name, &nsec3param, &rdataset, types,
+ maxtype, rawhash, rhsize, &tvresult);
+ if (result != ISC_R_SUCCESS) {
+ goto done;
+ }
+ result = tvresult;
+ }
+
+ *vresult = result;
+ result = ISC_R_SUCCESS;
+
+done:
+ if (dns_rdataset_isassociated(&rdataset)) {
+ dns_rdataset_disassociate(&rdataset);
+ }
+ if (node != NULL) {
+ dns_db_detachnode(vctx->db, &node);
+ }
+
+ return (result);
+}
+
+static isc_result_t
+verifynsec3s(const vctx_t *vctx, const dns_name_t *name,
+ dns_rdataset_t *nsec3paramset, bool delegation, bool empty,
+ const unsigned char types[8192], unsigned int maxtype,
+ isc_result_t *vresult) {
+ isc_result_t result;
+
+ for (result = dns_rdataset_first(nsec3paramset);
+ result == ISC_R_SUCCESS; result = dns_rdataset_next(nsec3paramset))
+ {
+ dns_rdata_t rdata = DNS_RDATA_INIT;
+
+ dns_rdataset_current(nsec3paramset, &rdata);
+ result = verifynsec3(vctx, name, &rdata, delegation, empty,
+ types, maxtype, vresult);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+ if (*vresult != ISC_R_SUCCESS) {
+ break;
+ }
+ }
+ if (result == ISC_R_NOMORE) {
+ result = ISC_R_SUCCESS;
+ }
+ return (result);
+}
+
+static isc_result_t
+verifyset(vctx_t *vctx, dns_rdataset_t *rdataset, const dns_name_t *name,
+ dns_dbnode_t *node, dst_key_t **dstkeys, size_t nkeys) {
+ unsigned char set_algorithms[256] = { 0 };
+ char namebuf[DNS_NAME_FORMATSIZE];
+ char algbuf[DNS_SECALG_FORMATSIZE];
+ char typebuf[DNS_RDATATYPE_FORMATSIZE];
+ dns_rdataset_t sigrdataset;
+ dns_rdatasetiter_t *rdsiter = NULL;
+ isc_result_t result;
+
+ dns_rdataset_init(&sigrdataset);
+ result = dns_db_allrdatasets(vctx->db, node, vctx->ver, 0, 0, &rdsiter);
+ if (result != ISC_R_SUCCESS) {
+ zoneverify_log_error(vctx, "dns_db_allrdatasets(): %s",
+ isc_result_totext(result));
+ return (result);
+ }
+ for (result = dns_rdatasetiter_first(rdsiter); result == ISC_R_SUCCESS;
+ result = dns_rdatasetiter_next(rdsiter))
+ {
+ dns_rdatasetiter_current(rdsiter, &sigrdataset);
+ if (sigrdataset.type == dns_rdatatype_rrsig &&
+ sigrdataset.covers == rdataset->type)
+ {
+ break;
+ }
+ dns_rdataset_disassociate(&sigrdataset);
+ }
+ if (result != ISC_R_SUCCESS) {
+ dns_name_format(name, namebuf, sizeof(namebuf));
+ dns_rdatatype_format(rdataset->type, typebuf, sizeof(typebuf));
+ zoneverify_log_error(vctx, "No signatures for %s/%s", namebuf,
+ typebuf);
+ for (size_t i = 0; i < ARRAY_SIZE(set_algorithms); i++) {
+ if (vctx->act_algorithms[i] != 0) {
+ vctx->bad_algorithms[i] = 1;
+ }
+ }
+ result = ISC_R_SUCCESS;
+ goto done;
+ }
+
+ for (result = dns_rdataset_first(&sigrdataset); result == ISC_R_SUCCESS;
+ result = dns_rdataset_next(&sigrdataset))
+ {
+ dns_rdata_t rdata = DNS_RDATA_INIT;
+ dns_rdata_rrsig_t sig;
+
+ dns_rdataset_current(&sigrdataset, &rdata);
+ result = dns_rdata_tostruct(&rdata, &sig, NULL);
+ RUNTIME_CHECK(result == ISC_R_SUCCESS);
+ if (rdataset->ttl != sig.originalttl) {
+ dns_name_format(name, namebuf, sizeof(namebuf));
+ dns_rdatatype_format(rdataset->type, typebuf,
+ sizeof(typebuf));
+ zoneverify_log_error(vctx,
+ "TTL mismatch for "
+ "%s %s keytag %u",
+ namebuf, typebuf, sig.keyid);
+ continue;
+ }
+ if ((set_algorithms[sig.algorithm] != 0) ||
+ (vctx->act_algorithms[sig.algorithm] == 0))
+ {
+ continue;
+ }
+ if (goodsig(vctx, &rdata, name, dstkeys, nkeys, rdataset)) {
+ dns_rdataset_settrust(rdataset, dns_trust_secure);
+ dns_rdataset_settrust(&sigrdataset, dns_trust_secure);
+ set_algorithms[sig.algorithm] = 1;
+ }
+ }
+ result = ISC_R_SUCCESS;
+
+ if (memcmp(set_algorithms, vctx->act_algorithms,
+ sizeof(set_algorithms)) != 0)
+ {
+ dns_name_format(name, namebuf, sizeof(namebuf));
+ dns_rdatatype_format(rdataset->type, typebuf, sizeof(typebuf));
+ for (size_t i = 0; i < ARRAY_SIZE(set_algorithms); i++) {
+ if ((vctx->act_algorithms[i] != 0) &&
+ (set_algorithms[i] == 0))
+ {
+ dns_secalg_format(i, algbuf, sizeof(algbuf));
+ zoneverify_log_error(vctx,
+ "No correct %s signature "
+ "for %s %s",
+ algbuf, namebuf, typebuf);
+ vctx->bad_algorithms[i] = 1;
+ }
+ }
+ }
+
+done:
+ if (dns_rdataset_isassociated(&sigrdataset)) {
+ dns_rdataset_disassociate(&sigrdataset);
+ }
+ dns_rdatasetiter_destroy(&rdsiter);
+
+ return (result);
+}
+
+static isc_result_t
+verifynode(vctx_t *vctx, const dns_name_t *name, dns_dbnode_t *node,
+ bool delegation, dst_key_t **dstkeys, size_t nkeys,
+ dns_rdataset_t *nsecset, dns_rdataset_t *nsec3paramset,
+ const dns_name_t *nextname, isc_result_t *vresult) {
+ unsigned char types[8192] = { 0 };
+ unsigned int maxtype = 0;
+ dns_rdataset_t rdataset;
+ dns_rdatasetiter_t *rdsiter = NULL;
+ isc_result_t result, tvresult = ISC_R_UNSET;
+
+ REQUIRE(vresult != NULL || (nsecset == NULL && nsec3paramset == NULL));
+
+ result = dns_db_allrdatasets(vctx->db, node, vctx->ver, 0, 0, &rdsiter);
+ if (result != ISC_R_SUCCESS) {
+ zoneverify_log_error(vctx, "dns_db_allrdatasets(): %s",
+ isc_result_totext(result));
+ return (result);
+ }
+
+ result = dns_rdatasetiter_first(rdsiter);
+ dns_rdataset_init(&rdataset);
+ while (result == ISC_R_SUCCESS) {
+ dns_rdatasetiter_current(rdsiter, &rdataset);
+ /*
+ * If we are not at a delegation then everything should be
+ * signed. If we are at a delegation then only the DS set
+ * is signed. The NS set is not signed at a delegation but
+ * its existence is recorded in the bit map. Anything else
+ * other than NSEC and DS is not signed at a delegation.
+ */
+ if (rdataset.type != dns_rdatatype_rrsig &&
+ rdataset.type != dns_rdatatype_dnskey &&
+ (!delegation || rdataset.type == dns_rdatatype_ds ||
+ rdataset.type == dns_rdatatype_nsec))
+ {
+ result = verifyset(vctx, &rdataset, name, node, dstkeys,
+ nkeys);
+ if (result != ISC_R_SUCCESS) {
+ dns_rdataset_disassociate(&rdataset);
+ dns_rdatasetiter_destroy(&rdsiter);
+ return (result);
+ }
+ dns_nsec_setbit(types, rdataset.type, 1);
+ if (rdataset.type > maxtype) {
+ maxtype = rdataset.type;
+ }
+ } else if (rdataset.type != dns_rdatatype_rrsig &&
+ rdataset.type != dns_rdatatype_dnskey)
+ {
+ if (rdataset.type == dns_rdatatype_ns) {
+ dns_nsec_setbit(types, rdataset.type, 1);
+ }
+ result = check_no_rrsig(vctx, &rdataset, name, node);
+ if (result != ISC_R_SUCCESS) {
+ dns_rdataset_disassociate(&rdataset);
+ dns_rdatasetiter_destroy(&rdsiter);
+ return (result);
+ }
+ } else {
+ dns_nsec_setbit(types, rdataset.type, 1);
+ }
+ dns_rdataset_disassociate(&rdataset);
+ result = dns_rdatasetiter_next(rdsiter);
+ }
+ dns_rdatasetiter_destroy(&rdsiter);
+ if (result != ISC_R_NOMORE) {
+ zoneverify_log_error(vctx, "rdataset iteration failed: %s",
+ isc_result_totext(result));
+ return (result);
+ }
+
+ if (vresult == NULL) {
+ return (ISC_R_SUCCESS);
+ }
+
+ *vresult = ISC_R_SUCCESS;
+
+ if (nsecset != NULL && dns_rdataset_isassociated(nsecset)) {
+ result = verifynsec(vctx, name, node, nextname, &tvresult);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+ *vresult = tvresult;
+ }
+
+ if (nsec3paramset != NULL && dns_rdataset_isassociated(nsec3paramset)) {
+ result = verifynsec3s(vctx, name, nsec3paramset, delegation,
+ false, types, maxtype, &tvresult);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+ if (*vresult == ISC_R_SUCCESS) {
+ *vresult = tvresult;
+ }
+ }
+
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+is_empty(const vctx_t *vctx, dns_dbnode_t *node, bool *empty) {
+ dns_rdatasetiter_t *rdsiter = NULL;
+ isc_result_t result;
+
+ result = dns_db_allrdatasets(vctx->db, node, vctx->ver, 0, 0, &rdsiter);
+ if (result != ISC_R_SUCCESS) {
+ zoneverify_log_error(vctx, "dns_db_allrdatasets(): %s",
+ isc_result_totext(result));
+ return (result);
+ }
+ result = dns_rdatasetiter_first(rdsiter);
+ dns_rdatasetiter_destroy(&rdsiter);
+
+ *empty = (result == ISC_R_NOMORE);
+
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+check_no_nsec(const vctx_t *vctx, const dns_name_t *name, dns_dbnode_t *node) {
+ bool nsec_exists = false;
+ dns_rdataset_t rdataset;
+ isc_result_t result;
+
+ dns_rdataset_init(&rdataset);
+ result = dns_db_findrdataset(vctx->db, node, vctx->ver,
+ dns_rdatatype_nsec, 0, 0, &rdataset, NULL);
+ if (result != ISC_R_NOTFOUND) {
+ char namebuf[DNS_NAME_FORMATSIZE];
+ dns_name_format(name, namebuf, sizeof(namebuf));
+ zoneverify_log_error(vctx, "unexpected NSEC RRset at %s",
+ namebuf);
+ nsec_exists = true;
+ }
+
+ if (dns_rdataset_isassociated(&rdataset)) {
+ dns_rdataset_disassociate(&rdataset);
+ }
+
+ return (nsec_exists ? ISC_R_FAILURE : ISC_R_SUCCESS);
+}
+
+static void
+free_element(isc_mem_t *mctx, struct nsec3_chain_fixed *e) {
+ size_t len;
+
+ len = sizeof(*e) + e->salt_length + 2 * e->next_length;
+ isc_mem_put(mctx, e, len);
+}
+
+static void
+free_element_heap(void *element, void *uap) {
+ struct nsec3_chain_fixed *e = (struct nsec3_chain_fixed *)element;
+ isc_mem_t *mctx = (isc_mem_t *)uap;
+
+ free_element(mctx, e);
+}
+
+static bool
+_checknext(const vctx_t *vctx, const struct nsec3_chain_fixed *first,
+ const struct nsec3_chain_fixed *e) {
+ char buf[512];
+ const unsigned char *d1 = (const unsigned char *)(first + 1);
+ const unsigned char *d2 = (const unsigned char *)(e + 1);
+ isc_buffer_t b;
+ isc_region_t sr;
+
+ d1 += first->salt_length + first->next_length;
+ d2 += e->salt_length;
+
+ if (memcmp(d1, d2, first->next_length) == 0) {
+ return (true);
+ }
+
+ DE_CONST(d1 - first->next_length, sr.base);
+ sr.length = first->next_length;
+ isc_buffer_init(&b, buf, sizeof(buf));
+ isc_base32hex_totext(&sr, 1, "", &b);
+ zoneverify_log_error(vctx, "Break in NSEC3 chain at: %.*s",
+ (int)isc_buffer_usedlength(&b), buf);
+
+ DE_CONST(d1, sr.base);
+ sr.length = first->next_length;
+ isc_buffer_init(&b, buf, sizeof(buf));
+ isc_base32hex_totext(&sr, 1, "", &b);
+ zoneverify_log_error(vctx, "Expected: %.*s",
+ (int)isc_buffer_usedlength(&b), buf);
+
+ DE_CONST(d2, sr.base);
+ sr.length = first->next_length;
+ isc_buffer_init(&b, buf, sizeof(buf));
+ isc_base32hex_totext(&sr, 1, "", &b);
+ zoneverify_log_error(vctx, "Found: %.*s",
+ (int)isc_buffer_usedlength(&b), buf);
+
+ return (false);
+}
+
+static bool
+checknext(isc_mem_t *mctx, const vctx_t *vctx,
+ const struct nsec3_chain_fixed *first, struct nsec3_chain_fixed *prev,
+ const struct nsec3_chain_fixed *cur) {
+ bool result = _checknext(vctx, prev, cur);
+
+ if (prev != first) {
+ free_element(mctx, prev);
+ }
+
+ return (result);
+}
+
+static bool
+checklast(isc_mem_t *mctx, const vctx_t *vctx, struct nsec3_chain_fixed *first,
+ struct nsec3_chain_fixed *prev) {
+ bool result = _checknext(vctx, prev, first);
+ if (prev != first) {
+ free_element(mctx, prev);
+ }
+ free_element(mctx, first);
+
+ return (result);
+}
+
+static isc_result_t
+verify_nsec3_chains(const vctx_t *vctx, isc_mem_t *mctx) {
+ isc_result_t result = ISC_R_SUCCESS;
+ struct nsec3_chain_fixed *e, *f = NULL;
+ struct nsec3_chain_fixed *first = NULL, *prev = NULL;
+
+ while ((e = isc_heap_element(vctx->expected_chains, 1)) != NULL) {
+ isc_heap_delete(vctx->expected_chains, 1);
+ if (f == NULL) {
+ f = isc_heap_element(vctx->found_chains, 1);
+ }
+ if (f != NULL) {
+ isc_heap_delete(vctx->found_chains, 1);
+
+ /*
+ * Check that they match.
+ */
+ if (chain_equal(e, f, chain_length(e))) {
+ free_element(mctx, f);
+ f = NULL;
+ } else {
+ if (result == ISC_R_SUCCESS) {
+ zoneverify_log_error(vctx, "Expected "
+ "and found "
+ "NSEC3 "
+ "chains not "
+ "equal");
+ }
+ result = ISC_R_FAILURE;
+ /*
+ * Attempt to resync found_chain.
+ */
+ while (f != NULL && !chain_compare(e, f)) {
+ free_element(mctx, f);
+ f = isc_heap_element(vctx->found_chains,
+ 1);
+ if (f != NULL) {
+ isc_heap_delete(
+ vctx->found_chains, 1);
+ }
+ if (f != NULL &&
+ chain_equal(e, f, chain_length(e)))
+ {
+ free_element(mctx, f);
+ f = NULL;
+ break;
+ }
+ }
+ }
+ } else if (result == ISC_R_SUCCESS) {
+ zoneverify_log_error(vctx, "Expected and found NSEC3 "
+ "chains "
+ "not equal");
+ result = ISC_R_FAILURE;
+ }
+
+ if (first == NULL) {
+ prev = first = e;
+ } else if (!chain_equal(first, e, first->salt_length)) {
+ if (!checklast(mctx, vctx, first, prev)) {
+ result = ISC_R_FAILURE;
+ }
+
+ prev = first = e;
+ } else {
+ if (!checknext(mctx, vctx, first, prev, e)) {
+ result = ISC_R_FAILURE;
+ }
+
+ prev = e;
+ }
+ }
+ if (prev != NULL) {
+ if (!checklast(mctx, vctx, first, prev)) {
+ result = ISC_R_FAILURE;
+ }
+ }
+ do {
+ if (f != NULL) {
+ if (result == ISC_R_SUCCESS) {
+ zoneverify_log_error(vctx, "Expected and found "
+ "NSEC3 chains not "
+ "equal");
+ result = ISC_R_FAILURE;
+ }
+ free_element(mctx, f);
+ }
+ f = isc_heap_element(vctx->found_chains, 1);
+ if (f != NULL) {
+ isc_heap_delete(vctx->found_chains, 1);
+ }
+ } while (f != NULL);
+
+ return (result);
+}
+
+static isc_result_t
+verifyemptynodes(const vctx_t *vctx, const dns_name_t *name,
+ const dns_name_t *prevname, bool isdelegation,
+ dns_rdataset_t *nsec3paramset, isc_result_t *vresult) {
+ dns_namereln_t reln;
+ int order;
+ unsigned int labels, nlabels, i;
+ dns_name_t suffix;
+ isc_result_t result, tvresult = ISC_R_UNSET;
+
+ *vresult = ISC_R_SUCCESS;
+
+ reln = dns_name_fullcompare(prevname, name, &order, &labels);
+ if (order >= 0) {
+ return (ISC_R_SUCCESS);
+ }
+
+ nlabels = dns_name_countlabels(name);
+
+ if (reln == dns_namereln_commonancestor ||
+ reln == dns_namereln_contains)
+ {
+ dns_name_init(&suffix, NULL);
+ for (i = labels + 1; i < nlabels; i++) {
+ dns_name_getlabelsequence(name, nlabels - i, i,
+ &suffix);
+ if (nsec3paramset != NULL &&
+ dns_rdataset_isassociated(nsec3paramset))
+ {
+ result = verifynsec3s(
+ vctx, &suffix, nsec3paramset,
+ isdelegation, true, NULL, 0, &tvresult);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+ if (*vresult == ISC_R_SUCCESS) {
+ *vresult = tvresult;
+ }
+ }
+ }
+ }
+
+ return (ISC_R_SUCCESS);
+}
+
+static void
+vctx_init(vctx_t *vctx, isc_mem_t *mctx, dns_zone_t *zone, dns_db_t *db,
+ dns_dbversion_t *ver, dns_name_t *origin, dns_keytable_t *secroots) {
+ memset(vctx, 0, sizeof(*vctx));
+
+ vctx->mctx = mctx;
+ vctx->zone = zone;
+ vctx->db = db;
+ vctx->ver = ver;
+ vctx->origin = origin;
+ vctx->secroots = secroots;
+ vctx->goodksk = false;
+ vctx->goodzsk = false;
+
+ dns_rdataset_init(&vctx->keyset);
+ dns_rdataset_init(&vctx->keysigs);
+ dns_rdataset_init(&vctx->soaset);
+ dns_rdataset_init(&vctx->soasigs);
+ dns_rdataset_init(&vctx->nsecset);
+ dns_rdataset_init(&vctx->nsecsigs);
+ dns_rdataset_init(&vctx->nsec3paramset);
+ dns_rdataset_init(&vctx->nsec3paramsigs);
+
+ vctx->expected_chains = NULL;
+ isc_heap_create(mctx, chain_compare, NULL, 1024,
+ &vctx->expected_chains);
+
+ vctx->found_chains = NULL;
+ isc_heap_create(mctx, chain_compare, NULL, 1024, &vctx->found_chains);
+}
+
+static void
+vctx_destroy(vctx_t *vctx) {
+ if (dns_rdataset_isassociated(&vctx->keyset)) {
+ dns_rdataset_disassociate(&vctx->keyset);
+ }
+ if (dns_rdataset_isassociated(&vctx->keysigs)) {
+ dns_rdataset_disassociate(&vctx->keysigs);
+ }
+ if (dns_rdataset_isassociated(&vctx->soaset)) {
+ dns_rdataset_disassociate(&vctx->soaset);
+ }
+ if (dns_rdataset_isassociated(&vctx->soasigs)) {
+ dns_rdataset_disassociate(&vctx->soasigs);
+ }
+ if (dns_rdataset_isassociated(&vctx->nsecset)) {
+ dns_rdataset_disassociate(&vctx->nsecset);
+ }
+ if (dns_rdataset_isassociated(&vctx->nsecsigs)) {
+ dns_rdataset_disassociate(&vctx->nsecsigs);
+ }
+ if (dns_rdataset_isassociated(&vctx->nsec3paramset)) {
+ dns_rdataset_disassociate(&vctx->nsec3paramset);
+ }
+ if (dns_rdataset_isassociated(&vctx->nsec3paramsigs)) {
+ dns_rdataset_disassociate(&vctx->nsec3paramsigs);
+ }
+ isc_heap_foreach(vctx->expected_chains, free_element_heap, vctx->mctx);
+ isc_heap_destroy(&vctx->expected_chains);
+ isc_heap_foreach(vctx->found_chains, free_element_heap, vctx->mctx);
+ isc_heap_destroy(&vctx->found_chains);
+}
+
+static isc_result_t
+check_apex_rrsets(vctx_t *vctx) {
+ dns_dbnode_t *node = NULL;
+ isc_result_t result;
+
+ result = dns_db_findnode(vctx->db, vctx->origin, false, &node);
+ if (result != ISC_R_SUCCESS) {
+ zoneverify_log_error(vctx,
+ "failed to find the zone's origin: %s",
+ isc_result_totext(result));
+ return (result);
+ }
+
+ result = dns_db_findrdataset(vctx->db, node, vctx->ver,
+ dns_rdatatype_dnskey, 0, 0, &vctx->keyset,
+ &vctx->keysigs);
+ if (result != ISC_R_SUCCESS) {
+ zoneverify_log_error(vctx, "Zone contains no DNSSEC keys");
+ goto done;
+ }
+
+ result = dns_db_findrdataset(vctx->db, node, vctx->ver,
+ dns_rdatatype_soa, 0, 0, &vctx->soaset,
+ &vctx->soasigs);
+ if (result != ISC_R_SUCCESS) {
+ zoneverify_log_error(vctx, "Zone contains no SOA record");
+ goto done;
+ }
+
+ result = dns_db_findrdataset(vctx->db, node, vctx->ver,
+ dns_rdatatype_nsec, 0, 0, &vctx->nsecset,
+ &vctx->nsecsigs);
+ if (result != ISC_R_SUCCESS && result != ISC_R_NOTFOUND) {
+ zoneverify_log_error(vctx, "NSEC lookup failed");
+ goto done;
+ }
+
+ result = dns_db_findrdataset(
+ vctx->db, node, vctx->ver, dns_rdatatype_nsec3param, 0, 0,
+ &vctx->nsec3paramset, &vctx->nsec3paramsigs);
+ if (result != ISC_R_SUCCESS && result != ISC_R_NOTFOUND) {
+ zoneverify_log_error(vctx, "NSEC3PARAM lookup failed");
+ goto done;
+ }
+
+ if (!dns_rdataset_isassociated(&vctx->keysigs)) {
+ zoneverify_log_error(vctx, "DNSKEY is not signed "
+ "(keys offline or inactive?)");
+ result = ISC_R_FAILURE;
+ goto done;
+ }
+
+ if (!dns_rdataset_isassociated(&vctx->soasigs)) {
+ zoneverify_log_error(vctx, "SOA is not signed "
+ "(keys offline or inactive?)");
+ result = ISC_R_FAILURE;
+ goto done;
+ }
+
+ if (dns_rdataset_isassociated(&vctx->nsecset) &&
+ !dns_rdataset_isassociated(&vctx->nsecsigs))
+ {
+ zoneverify_log_error(vctx, "NSEC is not signed "
+ "(keys offline or inactive?)");
+ result = ISC_R_FAILURE;
+ goto done;
+ }
+
+ if (dns_rdataset_isassociated(&vctx->nsec3paramset) &&
+ !dns_rdataset_isassociated(&vctx->nsec3paramsigs))
+ {
+ zoneverify_log_error(vctx, "NSEC3PARAM is not signed "
+ "(keys offline or inactive?)");
+ result = ISC_R_FAILURE;
+ goto done;
+ }
+
+ if (!dns_rdataset_isassociated(&vctx->nsecset) &&
+ !dns_rdataset_isassociated(&vctx->nsec3paramset))
+ {
+ zoneverify_log_error(vctx, "No valid NSEC/NSEC3 chain for "
+ "testing");
+ result = ISC_R_FAILURE;
+ goto done;
+ }
+
+ result = ISC_R_SUCCESS;
+
+done:
+ dns_db_detachnode(vctx->db, &node);
+
+ return (result);
+}
+
+/*%
+ * Update 'vctx' tables tracking active and standby key algorithms used in the
+ * verified zone based on the signatures made using 'dnskey' (prepared from
+ * 'rdata') found at zone apex. Set 'vctx->goodksk' or 'vctx->goodzsk' to true
+ * if 'dnskey' correctly signs the DNSKEY RRset at zone apex and either
+ * 'vctx->secroots' is NULL or 'dnskey' is present in 'vctx->secroots'.
+ *
+ * The variables to update are chosen based on 'is_ksk', which is true when
+ * 'dnskey' is a KSK and false otherwise.
+ */
+static void
+check_dnskey_sigs(vctx_t *vctx, const dns_rdata_dnskey_t *dnskey,
+ dns_rdata_t *keyrdata, bool is_ksk) {
+ unsigned char *active_keys = NULL, *standby_keys = NULL;
+ dns_keynode_t *keynode = NULL;
+ bool *goodkey = NULL;
+ dst_key_t *key = NULL;
+ isc_result_t result;
+ dns_rdataset_t dsset;
+
+ active_keys = (is_ksk ? vctx->ksk_algorithms : vctx->zsk_algorithms);
+ standby_keys = (is_ksk ? vctx->standby_ksk : vctx->standby_zsk);
+ goodkey = (is_ksk ? &vctx->goodksk : &vctx->goodzsk);
+
+ /*
+ * First, does this key sign the DNSKEY rrset?
+ */
+ if (!dns_dnssec_selfsigns(keyrdata, vctx->origin, &vctx->keyset,
+ &vctx->keysigs, false, vctx->mctx))
+ {
+ if (!is_ksk &&
+ dns_dnssec_signs(keyrdata, vctx->origin, &vctx->soaset,
+ &vctx->soasigs, false, vctx->mctx))
+ {
+ if (active_keys[dnskey->algorithm] != DNS_KEYALG_MAX) {
+ active_keys[dnskey->algorithm]++;
+ }
+ } else {
+ if (standby_keys[dnskey->algorithm] != DNS_KEYALG_MAX) {
+ standby_keys[dnskey->algorithm]++;
+ }
+ }
+ return;
+ }
+
+ if (active_keys[dnskey->algorithm] != DNS_KEYALG_MAX) {
+ active_keys[dnskey->algorithm]++;
+ }
+
+ /*
+ * If a trust anchor table was not supplied, a correctly self-signed
+ * DNSKEY RRset is good enough.
+ */
+ if (vctx->secroots == NULL) {
+ *goodkey = true;
+ return;
+ }
+
+ /*
+ * Convert the supplied key rdata to dst_key_t. (If this
+ * fails we can't go further.)
+ */
+ result = dns_dnssec_keyfromrdata(vctx->origin, keyrdata, vctx->mctx,
+ &key);
+ RUNTIME_CHECK(result == ISC_R_SUCCESS);
+
+ /*
+ * Look up the supplied key in the trust anchor table.
+ * If we don't find an exact match, or if the keynode data
+ * is NULL, then we have neither a DNSKEY nor a DS format
+ * trust anchor, and can give up.
+ */
+ result = dns_keytable_find(vctx->secroots, vctx->origin, &keynode);
+ if (result != ISC_R_SUCCESS) {
+ /* No such trust anchor */
+ goto cleanup;
+ }
+
+ /*
+ * If the keynode has any DS format trust anchors, that means
+ * it doesn't have any DNSKEY ones. So, we can check for a DS
+ * match and then stop.
+ */
+ dns_rdataset_init(&dsset);
+ if (dns_keynode_dsset(keynode, &dsset)) {
+ for (result = dns_rdataset_first(&dsset);
+ result == ISC_R_SUCCESS;
+ result = dns_rdataset_next(&dsset))
+ {
+ dns_rdata_t dsrdata = DNS_RDATA_INIT;
+ dns_rdata_t newdsrdata = DNS_RDATA_INIT;
+ unsigned char buf[DNS_DS_BUFFERSIZE];
+ dns_rdata_ds_t ds;
+
+ dns_rdata_reset(&dsrdata);
+ dns_rdataset_current(&dsset, &dsrdata);
+ result = dns_rdata_tostruct(&dsrdata, &ds, NULL);
+ RUNTIME_CHECK(result == ISC_R_SUCCESS);
+
+ if (ds.key_tag != dst_key_id(key) ||
+ ds.algorithm != dst_key_alg(key))
+ {
+ continue;
+ }
+
+ result = dns_ds_buildrdata(vctx->origin, keyrdata,
+ ds.digest_type, buf,
+ &newdsrdata);
+ if (result != ISC_R_SUCCESS) {
+ continue;
+ }
+
+ if (dns_rdata_compare(&dsrdata, &newdsrdata) == 0) {
+ dns_rdataset_settrust(&vctx->keyset,
+ dns_trust_secure);
+ dns_rdataset_settrust(&vctx->keysigs,
+ dns_trust_secure);
+ *goodkey = true;
+ break;
+ }
+ }
+ dns_rdataset_disassociate(&dsset);
+
+ goto cleanup;
+ }
+
+cleanup:
+ if (keynode != NULL) {
+ dns_keytable_detachkeynode(vctx->secroots, &keynode);
+ }
+ if (key != NULL) {
+ dst_key_free(&key);
+ }
+}
+
+/*%
+ * Check that the DNSKEY RR has at least one self signing KSK and one ZSK per
+ * algorithm in it (or, if -x was used, one self-signing KSK).
+ */
+static isc_result_t
+check_dnskey(vctx_t *vctx) {
+ dns_rdata_t rdata = DNS_RDATA_INIT;
+ dns_rdata_dnskey_t dnskey;
+ isc_result_t result;
+ bool is_ksk;
+
+ for (result = dns_rdataset_first(&vctx->keyset);
+ result == ISC_R_SUCCESS; result = dns_rdataset_next(&vctx->keyset))
+ {
+ dns_rdataset_current(&vctx->keyset, &rdata);
+ result = dns_rdata_tostruct(&rdata, &dnskey, NULL);
+ RUNTIME_CHECK(result == ISC_R_SUCCESS);
+ is_ksk = ((dnskey.flags & DNS_KEYFLAG_KSK) != 0);
+
+ if ((dnskey.flags & DNS_KEYOWNER_ZONE) != 0 &&
+ (dnskey.flags & DNS_KEYFLAG_REVOKE) != 0)
+ {
+ if ((dnskey.flags & DNS_KEYFLAG_KSK) != 0 &&
+ !dns_dnssec_selfsigns(&rdata, vctx->origin,
+ &vctx->keyset, &vctx->keysigs,
+ false, vctx->mctx))
+ {
+ char namebuf[DNS_NAME_FORMATSIZE];
+ char buffer[1024];
+ isc_buffer_t buf;
+
+ dns_name_format(vctx->origin, namebuf,
+ sizeof(namebuf));
+ isc_buffer_init(&buf, buffer, sizeof(buffer));
+ result = dns_rdata_totext(&rdata, NULL, &buf);
+ if (result != ISC_R_SUCCESS) {
+ zoneverify_log_error(
+ vctx, "dns_rdata_totext: %s",
+ isc_result_totext(result));
+ return (ISC_R_FAILURE);
+ }
+ zoneverify_log_error(
+ vctx,
+ "revoked KSK is not self signed:\n"
+ "%s DNSKEY %.*s",
+ namebuf,
+ (int)isc_buffer_usedlength(&buf),
+ buffer);
+ return (ISC_R_FAILURE);
+ }
+ if ((dnskey.flags & DNS_KEYFLAG_KSK) != 0 &&
+ vctx->revoked_ksk[dnskey.algorithm] !=
+ DNS_KEYALG_MAX)
+ {
+ vctx->revoked_ksk[dnskey.algorithm]++;
+ } else if ((dnskey.flags & DNS_KEYFLAG_KSK) == 0 &&
+ vctx->revoked_zsk[dnskey.algorithm] !=
+ DNS_KEYALG_MAX)
+ {
+ vctx->revoked_zsk[dnskey.algorithm]++;
+ }
+ } else {
+ check_dnskey_sigs(vctx, &dnskey, &rdata, is_ksk);
+ }
+ dns_rdata_freestruct(&dnskey);
+ dns_rdata_reset(&rdata);
+ }
+
+ return (ISC_R_SUCCESS);
+}
+
+static void
+determine_active_algorithms(vctx_t *vctx, bool ignore_kskflag,
+ bool keyset_kskonly,
+ void (*report)(const char *, ...)) {
+ char algbuf[DNS_SECALG_FORMATSIZE];
+
+ report("Verifying the zone using the following algorithms:");
+
+ for (size_t i = 0; i < ARRAY_SIZE(vctx->act_algorithms); i++) {
+ if (ignore_kskflag) {
+ vctx->act_algorithms[i] = (vctx->ksk_algorithms[i] !=
+ 0 ||
+ vctx->zsk_algorithms[i] != 0)
+ ? 1
+ : 0;
+ } else {
+ vctx->act_algorithms[i] = vctx->ksk_algorithms[i] != 0
+ ? 1
+ : 0;
+ }
+ if (vctx->act_algorithms[i] != 0) {
+ dns_secalg_format(i, algbuf, sizeof(algbuf));
+ report("- %s", algbuf);
+ }
+ }
+
+ if (ignore_kskflag || keyset_kskonly) {
+ return;
+ }
+
+ for (size_t i = 0; i < ARRAY_SIZE(vctx->ksk_algorithms); i++) {
+ /*
+ * The counts should both be zero or both be non-zero. Mark
+ * the algorithm as bad if this is not met.
+ */
+ if ((vctx->ksk_algorithms[i] != 0) ==
+ (vctx->zsk_algorithms[i] != 0))
+ {
+ continue;
+ }
+ dns_secalg_format(i, algbuf, sizeof(algbuf));
+ zoneverify_log_error(vctx, "Missing %s for algorithm %s",
+ (vctx->ksk_algorithms[i] != 0) ? "ZSK"
+ : "self-"
+ "signed "
+ "KSK",
+ algbuf);
+ vctx->bad_algorithms[i] = 1;
+ }
+}
+
+/*%
+ * Check that all the records not yet verified were signed by keys that are
+ * present in the DNSKEY RRset.
+ */
+static isc_result_t
+verify_nodes(vctx_t *vctx, isc_result_t *vresult) {
+ dns_fixedname_t fname, fnextname, fprevname, fzonecut;
+ dns_name_t *name, *nextname, *prevname, *zonecut;
+ dns_dbnode_t *node = NULL, *nextnode;
+ dns_dbiterator_t *dbiter = NULL;
+ dst_key_t **dstkeys;
+ size_t count, nkeys = 0;
+ bool done = false;
+ isc_result_t tvresult = ISC_R_UNSET;
+ isc_result_t result;
+
+ name = dns_fixedname_initname(&fname);
+ nextname = dns_fixedname_initname(&fnextname);
+ dns_fixedname_init(&fprevname);
+ prevname = NULL;
+ dns_fixedname_init(&fzonecut);
+ zonecut = NULL;
+
+ count = dns_rdataset_count(&vctx->keyset);
+ dstkeys = isc_mem_get(vctx->mctx, sizeof(*dstkeys) * count);
+
+ for (result = dns_rdataset_first(&vctx->keyset);
+ result == ISC_R_SUCCESS; result = dns_rdataset_next(&vctx->keyset))
+ {
+ dns_rdata_t rdata = DNS_RDATA_INIT;
+ dns_rdataset_current(&vctx->keyset, &rdata);
+ dstkeys[nkeys] = NULL;
+ result = dns_dnssec_keyfromrdata(vctx->origin, &rdata,
+ vctx->mctx, &dstkeys[nkeys]);
+ if (result == ISC_R_SUCCESS) {
+ nkeys++;
+ }
+ }
+
+ result = dns_db_createiterator(vctx->db, DNS_DB_NONSEC3, &dbiter);
+ if (result != ISC_R_SUCCESS) {
+ zoneverify_log_error(vctx, "dns_db_createiterator(): %s",
+ isc_result_totext(result));
+ goto done;
+ }
+
+ result = dns_dbiterator_first(dbiter);
+ if (result != ISC_R_SUCCESS) {
+ zoneverify_log_error(vctx, "dns_dbiterator_first(): %s",
+ isc_result_totext(result));
+ goto done;
+ }
+
+ while (!done) {
+ bool isdelegation = false;
+
+ result = dns_dbiterator_current(dbiter, &node, name);
+ if (result != ISC_R_SUCCESS && result != DNS_R_NEWORIGIN) {
+ zoneverify_log_error(vctx,
+ "dns_dbiterator_current(): %s",
+ isc_result_totext(result));
+ goto done;
+ }
+ if (!dns_name_issubdomain(name, vctx->origin)) {
+ result = check_no_nsec(vctx, name, node);
+ if (result != ISC_R_SUCCESS) {
+ dns_db_detachnode(vctx->db, &node);
+ goto done;
+ }
+ dns_db_detachnode(vctx->db, &node);
+ result = dns_dbiterator_next(dbiter);
+ if (result == ISC_R_NOMORE) {
+ done = true;
+ } else if (result != ISC_R_SUCCESS) {
+ zoneverify_log_error(vctx,
+ "dns_dbiterator_next(): "
+ "%s",
+ isc_result_totext(result));
+ goto done;
+ }
+ continue;
+ }
+ if (is_delegation(vctx, name, node, NULL)) {
+ zonecut = dns_fixedname_name(&fzonecut);
+ dns_name_copy(name, zonecut);
+ isdelegation = true;
+ } else if (has_dname(vctx, node)) {
+ zonecut = dns_fixedname_name(&fzonecut);
+ dns_name_copy(name, zonecut);
+ }
+ nextnode = NULL;
+ result = dns_dbiterator_next(dbiter);
+ while (result == ISC_R_SUCCESS) {
+ bool empty;
+ result = dns_dbiterator_current(dbiter, &nextnode,
+ nextname);
+ if (result != ISC_R_SUCCESS &&
+ result != DNS_R_NEWORIGIN)
+ {
+ zoneverify_log_error(vctx,
+ "dns_dbiterator_current():"
+ " %s",
+ isc_result_totext(result));
+ dns_db_detachnode(vctx->db, &node);
+ goto done;
+ }
+ if (!dns_name_issubdomain(nextname, vctx->origin) ||
+ (zonecut != NULL &&
+ dns_name_issubdomain(nextname, zonecut)))
+ {
+ result = check_no_nsec(vctx, nextname,
+ nextnode);
+ if (result != ISC_R_SUCCESS) {
+ dns_db_detachnode(vctx->db, &node);
+ dns_db_detachnode(vctx->db, &nextnode);
+ goto done;
+ }
+ dns_db_detachnode(vctx->db, &nextnode);
+ result = dns_dbiterator_next(dbiter);
+ continue;
+ }
+ result = is_empty(vctx, nextnode, &empty);
+ dns_db_detachnode(vctx->db, &nextnode);
+ if (result != ISC_R_SUCCESS) {
+ dns_db_detachnode(vctx->db, &node);
+ goto done;
+ }
+ if (empty) {
+ result = dns_dbiterator_next(dbiter);
+ continue;
+ }
+ break;
+ }
+ if (result == ISC_R_NOMORE) {
+ done = true;
+ nextname = vctx->origin;
+ } else if (result != ISC_R_SUCCESS) {
+ zoneverify_log_error(vctx,
+ "iterating through the database "
+ "failed: %s",
+ isc_result_totext(result));
+ dns_db_detachnode(vctx->db, &node);
+ goto done;
+ }
+ result = verifynode(vctx, name, node, isdelegation, dstkeys,
+ nkeys, &vctx->nsecset, &vctx->nsec3paramset,
+ nextname, &tvresult);
+ if (result != ISC_R_SUCCESS) {
+ dns_db_detachnode(vctx->db, &node);
+ goto done;
+ }
+ if (*vresult == ISC_R_UNSET) {
+ *vresult = ISC_R_SUCCESS;
+ }
+ if (*vresult == ISC_R_SUCCESS) {
+ *vresult = tvresult;
+ }
+ if (prevname != NULL) {
+ result = verifyemptynodes(
+ vctx, name, prevname, isdelegation,
+ &vctx->nsec3paramset, &tvresult);
+ if (result != ISC_R_SUCCESS) {
+ dns_db_detachnode(vctx->db, &node);
+ goto done;
+ }
+ } else {
+ prevname = dns_fixedname_name(&fprevname);
+ }
+ dns_name_copy(name, prevname);
+ if (*vresult == ISC_R_SUCCESS) {
+ *vresult = tvresult;
+ }
+ dns_db_detachnode(vctx->db, &node);
+ }
+
+ dns_dbiterator_destroy(&dbiter);
+
+ result = dns_db_createiterator(vctx->db, DNS_DB_NSEC3ONLY, &dbiter);
+ if (result != ISC_R_SUCCESS) {
+ zoneverify_log_error(vctx, "dns_db_createiterator(): %s",
+ isc_result_totext(result));
+ return (result);
+ }
+
+ for (result = dns_dbiterator_first(dbiter); result == ISC_R_SUCCESS;
+ result = dns_dbiterator_next(dbiter))
+ {
+ result = dns_dbiterator_current(dbiter, &node, name);
+ if (result != ISC_R_SUCCESS && result != DNS_R_NEWORIGIN) {
+ zoneverify_log_error(vctx,
+ "dns_dbiterator_current(): %s",
+ isc_result_totext(result));
+ goto done;
+ }
+ result = verifynode(vctx, name, node, false, dstkeys, nkeys,
+ NULL, NULL, NULL, NULL);
+ if (result != ISC_R_SUCCESS) {
+ zoneverify_log_error(vctx, "verifynode: %s",
+ isc_result_totext(result));
+ dns_db_detachnode(vctx->db, &node);
+ goto done;
+ }
+ result = record_found(vctx, name, node, &vctx->nsec3paramset);
+ dns_db_detachnode(vctx->db, &node);
+ if (result != ISC_R_SUCCESS) {
+ goto done;
+ }
+ }
+
+ result = ISC_R_SUCCESS;
+
+done:
+ while (nkeys-- > 0U) {
+ dst_key_free(&dstkeys[nkeys]);
+ }
+ isc_mem_put(vctx->mctx, dstkeys, sizeof(*dstkeys) * count);
+ if (dbiter != NULL) {
+ dns_dbiterator_destroy(&dbiter);
+ }
+
+ return (result);
+}
+
+static isc_result_t
+check_bad_algorithms(const vctx_t *vctx, void (*report)(const char *, ...)) {
+ char algbuf[DNS_SECALG_FORMATSIZE];
+ bool first = true;
+
+ for (size_t i = 0; i < ARRAY_SIZE(vctx->bad_algorithms); i++) {
+ if (vctx->bad_algorithms[i] == 0) {
+ continue;
+ }
+ if (first) {
+ report("The zone is not fully signed "
+ "for the following algorithms:");
+ }
+ dns_secalg_format(i, algbuf, sizeof(algbuf));
+ report(" %s", algbuf);
+ first = false;
+ }
+
+ if (!first) {
+ report(".");
+ }
+
+ return (first ? ISC_R_SUCCESS : ISC_R_FAILURE);
+}
+
+static void
+print_summary(const vctx_t *vctx, bool keyset_kskonly,
+ void (*report)(const char *, ...)) {
+ char algbuf[DNS_SECALG_FORMATSIZE];
+
+ report("Zone fully signed:");
+ for (size_t i = 0; i < ARRAY_SIZE(vctx->ksk_algorithms); i++) {
+ if ((vctx->ksk_algorithms[i] == 0) &&
+ (vctx->standby_ksk[i] == 0) &&
+ (vctx->revoked_ksk[i] == 0) &&
+ (vctx->zsk_algorithms[i] == 0) &&
+ (vctx->standby_zsk[i] == 0) && (vctx->revoked_zsk[i] == 0))
+ {
+ continue;
+ }
+ dns_secalg_format(i, algbuf, sizeof(algbuf));
+ report("Algorithm: %s: KSKs: "
+ "%u active, %u stand-by, %u revoked",
+ algbuf, vctx->ksk_algorithms[i], vctx->standby_ksk[i],
+ vctx->revoked_ksk[i]);
+ report("%*sZSKs: "
+ "%u active, %u %s, %u revoked",
+ (int)strlen(algbuf) + 13, "", vctx->zsk_algorithms[i],
+ vctx->standby_zsk[i],
+ keyset_kskonly ? "present" : "stand-by",
+ vctx->revoked_zsk[i]);
+ }
+}
+
+isc_result_t
+dns_zoneverify_dnssec(dns_zone_t *zone, dns_db_t *db, dns_dbversion_t *ver,
+ dns_name_t *origin, dns_keytable_t *secroots,
+ isc_mem_t *mctx, bool ignore_kskflag, bool keyset_kskonly,
+ void (*report)(const char *, ...)) {
+ const char *keydesc = (secroots == NULL ? "self-signed" : "trusted");
+ isc_result_t result, vresult = ISC_R_UNSET;
+ vctx_t vctx;
+
+ vctx_init(&vctx, mctx, zone, db, ver, origin, secroots);
+
+ result = check_apex_rrsets(&vctx);
+ if (result != ISC_R_SUCCESS) {
+ goto done;
+ }
+
+ result = check_dnskey(&vctx);
+ if (result != ISC_R_SUCCESS) {
+ goto done;
+ }
+
+ if (ignore_kskflag) {
+ if (!vctx.goodksk && !vctx.goodzsk) {
+ zoneverify_log_error(&vctx, "No %s DNSKEY found",
+ keydesc);
+ result = ISC_R_FAILURE;
+ goto done;
+ }
+ } else if (!vctx.goodksk) {
+ zoneverify_log_error(&vctx, "No %s KSK DNSKEY found", keydesc);
+ result = ISC_R_FAILURE;
+ goto done;
+ }
+
+ determine_active_algorithms(&vctx, ignore_kskflag, keyset_kskonly,
+ report);
+
+ result = verify_nodes(&vctx, &vresult);
+ if (result != ISC_R_SUCCESS) {
+ goto done;
+ }
+
+ result = verify_nsec3_chains(&vctx, mctx);
+ if (vresult == ISC_R_UNSET) {
+ vresult = ISC_R_SUCCESS;
+ }
+ if (result != ISC_R_SUCCESS && vresult == ISC_R_SUCCESS) {
+ vresult = result;
+ }
+
+ result = check_bad_algorithms(&vctx, report);
+ if (result != ISC_R_SUCCESS) {
+ report("DNSSEC completeness test failed.");
+ goto done;
+ }
+
+ result = vresult;
+ if (result != ISC_R_SUCCESS) {
+ report("DNSSEC completeness test failed (%s).",
+ isc_result_totext(result));
+ goto done;
+ }
+
+ if (vctx.goodksk || ignore_kskflag) {
+ print_summary(&vctx, keyset_kskonly, report);
+ }
+
+done:
+ vctx_destroy(&vctx);
+
+ return (result);
+}
diff --git a/lib/dns/zt.c b/lib/dns/zt.c
new file mode 100644
index 0000000..a0c300a
--- /dev/null
+++ b/lib/dns/zt.c
@@ -0,0 +1,615 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/*! \file */
+
+#include <inttypes.h>
+#include <stdbool.h>
+
+#include <isc/atomic.h>
+#include <isc/file.h>
+#include <isc/magic.h>
+#include <isc/mem.h>
+#include <isc/result.h>
+#include <isc/string.h>
+#include <isc/task.h>
+#include <isc/util.h>
+
+#include <dns/log.h>
+#include <dns/name.h>
+#include <dns/rbt.h>
+#include <dns/rdataclass.h>
+#include <dns/view.h>
+#include <dns/zone.h>
+#include <dns/zt.h>
+
+struct zt_load_params {
+ dns_zt_zoneloaded_t dl;
+ bool newonly;
+};
+
+struct dns_zt {
+ /* Unlocked. */
+ unsigned int magic;
+ isc_mem_t *mctx;
+ dns_rdataclass_t rdclass;
+ isc_rwlock_t rwlock;
+ dns_zt_allloaded_t loaddone;
+ void *loaddone_arg;
+ struct zt_load_params *loadparams;
+
+ /* Atomic */
+ atomic_bool flush;
+ isc_refcount_t references;
+ isc_refcount_t loads_pending;
+
+ /* Locked by lock. */
+ dns_rbt_t *table;
+};
+
+struct zt_freeze_params {
+ dns_view_t *view;
+ bool freeze;
+};
+
+#define ZTMAGIC ISC_MAGIC('Z', 'T', 'b', 'l')
+#define VALID_ZT(zt) ISC_MAGIC_VALID(zt, ZTMAGIC)
+
+static void
+auto_detach(void *, void *);
+
+static isc_result_t
+load(dns_zone_t *zone, void *uap);
+
+static isc_result_t
+asyncload(dns_zone_t *zone, void *callback);
+
+static isc_result_t
+freezezones(dns_zone_t *zone, void *uap);
+
+static isc_result_t
+doneloading(dns_zt_t *zt, dns_zone_t *zone, isc_task_t *task);
+
+isc_result_t
+dns_zt_create(isc_mem_t *mctx, dns_rdataclass_t rdclass, dns_zt_t **ztp) {
+ dns_zt_t *zt;
+ isc_result_t result;
+
+ REQUIRE(ztp != NULL && *ztp == NULL);
+
+ zt = isc_mem_get(mctx, sizeof(*zt));
+
+ zt->table = NULL;
+ result = dns_rbt_create(mctx, auto_detach, zt, &zt->table);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup_zt;
+ }
+
+ isc_rwlock_init(&zt->rwlock, 0, 0);
+ zt->mctx = NULL;
+ isc_mem_attach(mctx, &zt->mctx);
+ isc_refcount_init(&zt->references, 1);
+ atomic_init(&zt->flush, false);
+ zt->rdclass = rdclass;
+ zt->magic = ZTMAGIC;
+ zt->loaddone = NULL;
+ zt->loaddone_arg = NULL;
+ zt->loadparams = NULL;
+ isc_refcount_init(&zt->loads_pending, 0);
+ *ztp = zt;
+
+ return (ISC_R_SUCCESS);
+
+cleanup_zt:
+ isc_mem_put(mctx, zt, sizeof(*zt));
+
+ return (result);
+}
+
+isc_result_t
+dns_zt_mount(dns_zt_t *zt, dns_zone_t *zone) {
+ isc_result_t result;
+ dns_zone_t *dummy = NULL;
+ dns_name_t *name;
+
+ REQUIRE(VALID_ZT(zt));
+
+ name = dns_zone_getorigin(zone);
+
+ RWLOCK(&zt->rwlock, isc_rwlocktype_write);
+
+ result = dns_rbt_addname(zt->table, name, zone);
+ if (result == ISC_R_SUCCESS) {
+ dns_zone_attach(zone, &dummy);
+ }
+
+ RWUNLOCK(&zt->rwlock, isc_rwlocktype_write);
+
+ return (result);
+}
+
+isc_result_t
+dns_zt_unmount(dns_zt_t *zt, dns_zone_t *zone) {
+ isc_result_t result;
+ dns_name_t *name;
+
+ REQUIRE(VALID_ZT(zt));
+
+ name = dns_zone_getorigin(zone);
+
+ RWLOCK(&zt->rwlock, isc_rwlocktype_write);
+
+ result = dns_rbt_deletename(zt->table, name, false);
+
+ RWUNLOCK(&zt->rwlock, isc_rwlocktype_write);
+
+ return (result);
+}
+
+isc_result_t
+dns_zt_find(dns_zt_t *zt, const dns_name_t *name, unsigned int options,
+ dns_name_t *foundname, dns_zone_t **zonep) {
+ isc_result_t result;
+ dns_zone_t *dummy = NULL;
+ unsigned int rbtoptions = 0;
+
+ REQUIRE(VALID_ZT(zt));
+
+ if ((options & DNS_ZTFIND_NOEXACT) != 0) {
+ rbtoptions |= DNS_RBTFIND_NOEXACT;
+ }
+
+ RWLOCK(&zt->rwlock, isc_rwlocktype_read);
+
+ result = dns_rbt_findname(zt->table, name, rbtoptions, foundname,
+ (void **)(void *)&dummy);
+ if (result == ISC_R_SUCCESS || result == DNS_R_PARTIALMATCH) {
+ /*
+ * If DNS_ZTFIND_MIRROR is set and the zone which was
+ * determined to be the deepest match for the supplied name is
+ * a mirror zone which is expired or not yet loaded, treat it
+ * as non-existent. This will trigger a fallback to recursion
+ * instead of returning a SERVFAIL.
+ *
+ * Note that currently only the deepest match in the zone table
+ * is checked. Consider a server configured with two mirror
+ * zones: "bar" and its child, "foo.bar". If zone data is
+ * available for "bar" but not for "foo.bar", a query with
+ * QNAME equal to or below "foo.bar" will cause ISC_R_NOTFOUND
+ * to be returned, not DNS_R_PARTIALMATCH, despite zone data
+ * being available for "bar". This is considered to be an edge
+ * case, handling which more appropriately is possible, but
+ * arguably not worth the added complexity.
+ */
+ if ((options & DNS_ZTFIND_MIRROR) != 0 &&
+ dns_zone_gettype(dummy) == dns_zone_mirror &&
+ !dns_zone_isloaded(dummy))
+ {
+ result = ISC_R_NOTFOUND;
+ } else {
+ dns_zone_attach(dummy, zonep);
+ }
+ }
+
+ RWUNLOCK(&zt->rwlock, isc_rwlocktype_read);
+
+ return (result);
+}
+
+void
+dns_zt_attach(dns_zt_t *zt, dns_zt_t **ztp) {
+ REQUIRE(VALID_ZT(zt));
+ REQUIRE(ztp != NULL && *ztp == NULL);
+
+ isc_refcount_increment(&zt->references);
+
+ *ztp = zt;
+}
+
+static isc_result_t
+flush(dns_zone_t *zone, void *uap) {
+ UNUSED(uap);
+ return (dns_zone_flush(zone));
+}
+
+static void
+zt_destroy(dns_zt_t *zt) {
+ isc_refcount_destroy(&zt->references);
+ isc_refcount_destroy(&zt->loads_pending);
+
+ if (atomic_load_acquire(&zt->flush)) {
+ (void)dns_zt_apply(zt, isc_rwlocktype_none, false, NULL, flush,
+ NULL);
+ }
+
+ dns_rbt_destroy(&zt->table);
+ isc_rwlock_destroy(&zt->rwlock);
+ zt->magic = 0;
+ isc_mem_putanddetach(&zt->mctx, zt, sizeof(*zt));
+}
+
+void
+dns_zt_detach(dns_zt_t **ztp) {
+ dns_zt_t *zt;
+
+ REQUIRE(ztp != NULL && VALID_ZT(*ztp));
+
+ zt = *ztp;
+ *ztp = NULL;
+
+ if (isc_refcount_decrement(&zt->references) == 1) {
+ zt_destroy(zt);
+ }
+}
+
+void
+dns_zt_flush(dns_zt_t *zt) {
+ REQUIRE(VALID_ZT(zt));
+ atomic_store_release(&zt->flush, true);
+}
+
+isc_result_t
+dns_zt_load(dns_zt_t *zt, bool stop, bool newonly) {
+ isc_result_t result;
+ struct zt_load_params params;
+ REQUIRE(VALID_ZT(zt));
+ params.newonly = newonly;
+ result = dns_zt_apply(zt, isc_rwlocktype_read, stop, NULL, load,
+ &params);
+ return (result);
+}
+
+static isc_result_t
+load(dns_zone_t *zone, void *paramsv) {
+ isc_result_t result;
+ struct zt_load_params *params = (struct zt_load_params *)paramsv;
+ result = dns_zone_load(zone, params->newonly);
+ if (result == DNS_R_CONTINUE || result == DNS_R_UPTODATE ||
+ result == DNS_R_DYNAMIC)
+ {
+ result = ISC_R_SUCCESS;
+ }
+ return (result);
+}
+
+static void
+call_loaddone(dns_zt_t *zt) {
+ dns_zt_allloaded_t loaddone = zt->loaddone;
+ void *loaddone_arg = zt->loaddone_arg;
+
+ /*
+ * Set zt->loaddone, zt->loaddone_arg and zt->loadparams to NULL
+ * before calling loaddone.
+ */
+ zt->loaddone = NULL;
+ zt->loaddone_arg = NULL;
+
+ isc_mem_put(zt->mctx, zt->loadparams, sizeof(struct zt_load_params));
+ zt->loadparams = NULL;
+
+ /*
+ * Call the callback last.
+ */
+ if (loaddone != NULL) {
+ loaddone(loaddone_arg);
+ }
+}
+
+isc_result_t
+dns_zt_asyncload(dns_zt_t *zt, bool newonly, dns_zt_allloaded_t alldone,
+ void *arg) {
+ isc_result_t result;
+ uint_fast32_t loads_pending;
+
+ REQUIRE(VALID_ZT(zt));
+
+ /*
+ * Obtain a reference to zt->loads_pending so that asyncload can
+ * safely decrement both zt->references and zt->loads_pending
+ * without going to zero.
+ */
+ loads_pending = isc_refcount_increment0(&zt->loads_pending);
+ INSIST(loads_pending == 0);
+
+ /*
+ * Only one dns_zt_asyncload call at a time should be active so
+ * these pointers should be NULL. They are set back to NULL
+ * before the zt->loaddone (alldone) is called in call_loaddone.
+ */
+ INSIST(zt->loadparams == NULL);
+ INSIST(zt->loaddone == NULL);
+ INSIST(zt->loaddone_arg == NULL);
+
+ zt->loadparams = isc_mem_get(zt->mctx, sizeof(struct zt_load_params));
+ zt->loadparams->dl = doneloading;
+ zt->loadparams->newonly = newonly;
+ zt->loaddone = alldone;
+ zt->loaddone_arg = arg;
+
+ result = dns_zt_apply(zt, isc_rwlocktype_read, false, NULL, asyncload,
+ zt);
+
+ /*
+ * Have all the loads completed?
+ */
+ if (isc_refcount_decrement(&zt->loads_pending) == 1) {
+ call_loaddone(zt);
+ }
+
+ return (result);
+}
+
+/*
+ * Initiates asynchronous loading of zone 'zone'. 'callback' is a
+ * pointer to a function which will be used to inform the caller when
+ * the zone loading is complete.
+ */
+static isc_result_t
+asyncload(dns_zone_t *zone, void *zt_) {
+ isc_result_t result;
+ struct dns_zt *zt = (dns_zt_t *)zt_;
+ REQUIRE(zone != NULL);
+
+ isc_refcount_increment(&zt->references);
+ isc_refcount_increment(&zt->loads_pending);
+
+ result = dns_zone_asyncload(zone, zt->loadparams->newonly,
+ *zt->loadparams->dl, zt);
+ if (result != ISC_R_SUCCESS) {
+ /*
+ * Caller is holding a reference to zt->loads_pending
+ * and zt->references so these can't decrement to zero.
+ */
+ isc_refcount_decrement1(&zt->references);
+ isc_refcount_decrement1(&zt->loads_pending);
+ }
+ return (ISC_R_SUCCESS);
+}
+
+isc_result_t
+dns_zt_freezezones(dns_zt_t *zt, dns_view_t *view, bool freeze) {
+ isc_result_t result, tresult;
+ struct zt_freeze_params params = { view, freeze };
+
+ REQUIRE(VALID_ZT(zt));
+
+ result = dns_zt_apply(zt, isc_rwlocktype_read, false, &tresult,
+ freezezones, &params);
+ if (tresult == ISC_R_NOTFOUND) {
+ tresult = ISC_R_SUCCESS;
+ }
+ return ((result == ISC_R_SUCCESS) ? tresult : result);
+}
+
+static isc_result_t
+freezezones(dns_zone_t *zone, void *uap) {
+ struct zt_freeze_params *params = uap;
+ bool frozen;
+ isc_result_t result = ISC_R_SUCCESS;
+ char classstr[DNS_RDATACLASS_FORMATSIZE];
+ char zonename[DNS_NAME_FORMATSIZE];
+ dns_zone_t *raw = NULL;
+ dns_view_t *view;
+ const char *vname;
+ const char *sep;
+ int level;
+
+ dns_zone_getraw(zone, &raw);
+ if (raw != NULL) {
+ zone = raw;
+ }
+ if (params->view != dns_zone_getview(zone)) {
+ if (raw != NULL) {
+ dns_zone_detach(&raw);
+ }
+ return (ISC_R_SUCCESS);
+ }
+ if (dns_zone_gettype(zone) != dns_zone_primary) {
+ if (raw != NULL) {
+ dns_zone_detach(&raw);
+ }
+ return (ISC_R_SUCCESS);
+ }
+ if (!dns_zone_isdynamic(zone, true)) {
+ if (raw != NULL) {
+ dns_zone_detach(&raw);
+ }
+ return (ISC_R_SUCCESS);
+ }
+
+ frozen = dns_zone_getupdatedisabled(zone);
+ if (params->freeze) {
+ if (frozen) {
+ result = DNS_R_FROZEN;
+ }
+ if (result == ISC_R_SUCCESS) {
+ result = dns_zone_flush(zone);
+ }
+ if (result == ISC_R_SUCCESS) {
+ dns_zone_setupdatedisabled(zone, params->freeze);
+ }
+ } else {
+ if (frozen) {
+ result = dns_zone_loadandthaw(zone);
+ if (result == DNS_R_CONTINUE ||
+ result == DNS_R_UPTODATE)
+ {
+ result = ISC_R_SUCCESS;
+ }
+ }
+ }
+ view = dns_zone_getview(zone);
+ if (strcmp(view->name, "_bind") == 0 || strcmp(view->name, "_defaul"
+ "t") == 0)
+ {
+ vname = "";
+ sep = "";
+ } else {
+ vname = view->name;
+ sep = " ";
+ }
+ dns_rdataclass_format(dns_zone_getclass(zone), classstr,
+ sizeof(classstr));
+ dns_name_format(dns_zone_getorigin(zone), zonename, sizeof(zonename));
+ level = (result != ISC_R_SUCCESS) ? ISC_LOG_ERROR : ISC_LOG_DEBUG(1);
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL, DNS_LOGMODULE_ZONE,
+ level, "%s zone '%s/%s'%s%s: %s",
+ params->freeze ? "freezing" : "thawing", zonename,
+ classstr, sep, vname, isc_result_totext(result));
+ if (raw != NULL) {
+ dns_zone_detach(&raw);
+ }
+ return (result);
+}
+
+void
+dns_zt_setviewcommit(dns_zt_t *zt) {
+ dns_rbtnode_t *node;
+ dns_rbtnodechain_t chain;
+ isc_result_t result;
+
+ REQUIRE(VALID_ZT(zt));
+
+ RWLOCK(&zt->rwlock, isc_rwlocktype_read);
+ dns_rbtnodechain_init(&chain);
+
+ result = dns_rbtnodechain_first(&chain, zt->table, NULL, NULL);
+ while (result == DNS_R_NEWORIGIN || result == ISC_R_SUCCESS) {
+ result = dns_rbtnodechain_current(&chain, NULL, NULL, &node);
+ if (result == ISC_R_SUCCESS && node->data != NULL) {
+ dns_zone_setviewcommit(node->data);
+ }
+
+ result = dns_rbtnodechain_next(&chain, NULL, NULL);
+ }
+
+ dns_rbtnodechain_invalidate(&chain);
+ RWUNLOCK(&zt->rwlock, isc_rwlocktype_read);
+}
+
+void
+dns_zt_setviewrevert(dns_zt_t *zt) {
+ dns_rbtnode_t *node;
+ dns_rbtnodechain_t chain;
+ isc_result_t result;
+
+ REQUIRE(VALID_ZT(zt));
+
+ dns_rbtnodechain_init(&chain);
+
+ result = dns_rbtnodechain_first(&chain, zt->table, NULL, NULL);
+ while (result == DNS_R_NEWORIGIN || result == ISC_R_SUCCESS) {
+ result = dns_rbtnodechain_current(&chain, NULL, NULL, &node);
+ if (result == ISC_R_SUCCESS && node->data != NULL) {
+ dns_zone_setviewrevert(node->data);
+ }
+
+ result = dns_rbtnodechain_next(&chain, NULL, NULL);
+ }
+
+ dns_rbtnodechain_invalidate(&chain);
+}
+
+isc_result_t
+dns_zt_apply(dns_zt_t *zt, isc_rwlocktype_t lock, bool stop, isc_result_t *sub,
+ isc_result_t (*action)(dns_zone_t *, void *), void *uap) {
+ dns_rbtnode_t *node;
+ dns_rbtnodechain_t chain;
+ isc_result_t result, tresult = ISC_R_SUCCESS;
+ dns_zone_t *zone;
+
+ REQUIRE(VALID_ZT(zt));
+ REQUIRE(action != NULL);
+
+ if (lock != isc_rwlocktype_none) {
+ RWLOCK(&zt->rwlock, lock);
+ }
+
+ dns_rbtnodechain_init(&chain);
+ result = dns_rbtnodechain_first(&chain, zt->table, NULL, NULL);
+ if (result == ISC_R_NOTFOUND) {
+ /*
+ * The tree is empty.
+ */
+ tresult = result;
+ result = ISC_R_NOMORE;
+ }
+ while (result == DNS_R_NEWORIGIN || result == ISC_R_SUCCESS) {
+ result = dns_rbtnodechain_current(&chain, NULL, NULL, &node);
+ if (result == ISC_R_SUCCESS) {
+ zone = node->data;
+ if (zone != NULL) {
+ result = (action)(zone, uap);
+ }
+ if (result != ISC_R_SUCCESS && stop) {
+ tresult = result;
+ goto cleanup; /* don't break */
+ } else if (result != ISC_R_SUCCESS &&
+ tresult == ISC_R_SUCCESS)
+ {
+ tresult = result;
+ }
+ }
+ result = dns_rbtnodechain_next(&chain, NULL, NULL);
+ }
+ if (result == ISC_R_NOMORE) {
+ result = ISC_R_SUCCESS;
+ }
+
+cleanup:
+ dns_rbtnodechain_invalidate(&chain);
+ if (sub != NULL) {
+ *sub = tresult;
+ }
+
+ if (lock != isc_rwlocktype_none) {
+ RWUNLOCK(&zt->rwlock, lock);
+ }
+
+ return (result);
+}
+
+/*
+ * Decrement the loads_pending counter; when counter reaches
+ * zero, call the loaddone callback that was initially set by
+ * dns_zt_asyncload().
+ */
+static isc_result_t
+doneloading(dns_zt_t *zt, dns_zone_t *zone, isc_task_t *task) {
+ UNUSED(zone);
+ UNUSED(task);
+
+ REQUIRE(VALID_ZT(zt));
+
+ if (isc_refcount_decrement(&zt->loads_pending) == 1) {
+ call_loaddone(zt);
+ }
+
+ if (isc_refcount_decrement(&zt->references) == 1) {
+ zt_destroy(zt);
+ }
+
+ return (ISC_R_SUCCESS);
+}
+
+/***
+ *** Private
+ ***/
+
+static void
+auto_detach(void *data, void *arg) {
+ dns_zone_t *zone = data;
+
+ UNUSED(arg);
+ dns_zone_detach(&zone);
+}